| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812 | from django.db import modelsfrom bound.models import BoundBatchModel,BoundDetailModel,OutBatchModel,BoundListModel,BoundListModel,MaterialStatisticsfrom django.utils import timezonefrom django.db.models.signals import pre_save, post_savefrom django.dispatch import receiverfrom decimal import Decimalfrom django.db.models import Sumfrom datetime import timedelta from django.db.models import Qfrom django.db.models import Sum, Fimport logginglogger = logging.getLogger(__name__)import threadingfrom django.db import close_old_connectionsfrom concurrent.futures import ThreadPoolExecutorimport queue# 创建线程池执行器和任务队列log_processing_executor = ThreadPoolExecutor(max_workers=4)log_processing_queue = queue.Queue()def async_worker():    """后台线程处理日志任务"""    while True:        try:            # 从队列获取任务            logs, created = log_processing_queue.get(timeout=30)            close_old_connections()  # 确保每次处理前关闭旧连接                        # 处理批量日志            processed_logs = []            for log in logs:                try:                    aggregate_to_batch_log(log)                    count_day_in_out(log)                    processed_logs.append(log.id)                except Exception as e:                    logger.error(f"处理日志时出错: {e}", exc_info=True)                        # 如果有批次日志需要触发material history更新            if created:                from bound.models import MaterialStatistics                # 这里可以添加批量更新MaterialStatistics的逻辑                # ...                            logger.info(f"已处理 {len(processed_logs)} 条日志")            log_processing_queue.task_done()                    except queue.Empty:            # 30秒无任务则进入休眠            continue        except Exception as e:            logger.error(f"后台线程异常: {e}", exc_info=True)# 启动后台线程处理日志threading.Thread(target=async_worker, daemon=True).start()# Create your models here.# 主表:托盘数据class ContainerListModel(models.Model):    CONTAINER_STATUS = (        (0, '空置'),        (1, '入库中'),        (2, '在库'),        (3, '出库中'),        (4, '已出库'),        (5, '空托盘组')    )    # 新增托盘分类字段    CONTAINER_CATEGORY_CHOICES = (        (0, '整盘'),        (1, '散盘'),        (2, '空盘在库'),        (3, '空盘不在库'),    )       container_code = models.IntegerField( verbose_name='托盘编号',unique=True)    current_location = models.CharField(max_length=50, verbose_name='当前库位', default='N/A')    target_location = models.CharField(max_length=50, verbose_name='目标库位', default='N/A')    available= models.BooleanField(default=True, verbose_name='可用')    status = models.IntegerField(choices=CONTAINER_STATUS, default=0, verbose_name='托盘状态')    last_operation = models.DateTimeField(auto_now=True, verbose_name='最后操作时间')    category = models.IntegerField(        choices=CONTAINER_CATEGORY_CHOICES,         default=3,  # 默认空盘不在库        verbose_name='托盘分类'    )    # 新增批次数组字段(缓存)    batch_info = models.JSONField(        default=list,        verbose_name='批次信息缓存',        help_text='格式: [{"batch_id":1,"check_status":"合格","bound_number":"B001","qty":100}]'    )    class Meta:        db_table = 'container_list'        verbose_name = 'ContainerList'        verbose_name_plural = "ContainerList"        ordering = ['-container_code']class ContainerDetailModel(models.Model):    BATCH_STATUS=(        (0, '空盘'),        (1, '组盘'),        (2, '在库'),        (3, '已出库')    )    BATCH_CLASS = (        (1, '成品'),        (2, '空盘'),        (3, '散盘'),    )        month = models.IntegerField(verbose_name='月份')    container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='details')    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='批次',blank=True, null=True)    goods_class = models.IntegerField(verbose_name='货品类别',choices=BATCH_CLASS, default=1)    goods_code = models.CharField(max_length=50, verbose_name='货品编码')    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')    goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'), verbose_name='数量')    goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='出库数量')    goods_weight = models.DecimalField(max_digits=10, decimal_places=3, verbose_name='重量')    status = models.IntegerField(choices=BATCH_STATUS,default=0, verbose_name='状态')              creater = models.CharField(max_length=50, verbose_name='创建人')    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')    is_delete = models.BooleanField(default=False, verbose_name='是否删除')    class Meta:        db_table = 'container_detail'        verbose_name = 'ContainerDetail'        verbose_name_plural = "ContainerDetail"        ordering = ['-id']        indexes = [            models.Index(fields=['container']),            models.Index(fields=['batch']),            models.Index(fields=['goods_code']),            models.Index(fields=['status']),        ]    def __str__(self):        return f"{self.container.container_code} - {self.batch.bound_number if self.batch else 'N/A'} - {self.goods_code}"        def save(self, *args, **kwargs):        """        更新托盘上的物料数量,更新批次上的        goods_in_qty(组盘数目)        ,goods_in_location_qty(在库数目)        ,goods_out_qty(出库数目)        """        # 在保存前调用信号处理器        if self.pk:            # 获取状态变化前的状态            original = ContainerDetailModel.objects.get(pk=self.pk)            original_status = original.status                    super().save(*args, **kwargs)                # 更新批次数据        if self.batch:            # 避免循环更新 - 仅在数量相关字段变更时才更新            if 'update_fields' not in kwargs or any(field in kwargs['update_fields'] for field in ['goods_qty', 'goods_out_qty','is_delete']):                self.update_batch_stats()                        # # 根据出库数量更新状态            # if self.goods_out_qty > self.goods_qty:            #     # 出库数量不能大于总数量            #     self.goods_out_qty = self.goods_qty            #     self.save(update_fields=['goods_out_qty'])                            # 根据当前数量状态更新状态            if self.goods_out_qty >= self.goods_qty:                self.status = 3  # 已出库                super().save(*args, **kwargs)                            elif self.goods_qty - self.goods_out_qty > 0 and self.goods_out_qty > 0:                self.status = 2  # 在库                super().save(*args, **kwargs)                          if self.status == 3 and self.goods_qty - self.goods_out_qty > 0 :                self.status = 2  # 在库                super().save(*args, **kwargs)             # 更新货物分类(关键修改点)            if not kwargs.get('skip_class_update', False):                # 获取托盘上所有物料(排除已删除和已出库的)                details = ContainerDetailModel.objects.filter(                    container=self.container.id,                    is_delete=False                ).exclude(status=3)                                # 统计不同批次数目                batch_count = details.values('batch').distinct().count()                new_class = 1 if batch_count == 1 else 3                                # 批量更新所有相关物料的goods_class                details.exclude(goods_class=new_class).update(goods_class=new_class)                                # 如果当前物料的class需要更新                           if self.goods_class != new_class:                    self.goods_class = new_class                    super().save(update_fields=['goods_class'])        def update_batch_stats(self):        """更新批次的统计数据"""        if not self.batch:            return                    # 聚合托盘明细数据        stats = ContainerDetailModel.objects.filter(            batch=self.batch,            is_delete=False        ).aggregate(            total_qty=models.Sum('goods_qty'),            total_out_qty=models.Sum('goods_out_qty')        )                # 更新批次数据        self.batch.goods_in_qty = stats['total_qty'] or 0        self.batch.goods_in_location_qty = (stats['total_qty'] or 0) - (stats['total_out_qty'] or 0)        self.batch.goods_out_qty = stats['total_out_qty'] or 0        self.batch.save()    def get_container_code(self):        return self.container.container_code                   class ContainerDetailLogModel(models.Model):    """托盘明细变更日志模型"""    LOG_TYPES = (        ('create', '创建'),        ('update', '更新'),        ('delete', '删除'),        ('out', '出库'),        ('cancel_out', '取消出库'),        ('status_change', '状态变更'),    )        # 关联的托盘明细    container_detail = models.ForeignKey(        ContainerDetailModel,         on_delete=models.CASCADE,        related_name='logs'    )        # 日志类型    log_type = models.CharField(        max_length=20,        choices=LOG_TYPES,        verbose_name='日志类型'    )        # 原值    old_goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='原数量', null=True, blank=True)    old_goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='原出库数量', null=True, blank=True)    old_status = models.IntegerField(        choices=ContainerDetailModel.BATCH_STATUS,        null=True,        blank=True,        verbose_name='原状态'    )        # 新值    new_goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新数量', null=True, blank=True)    new_goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新出库数量', null=True, blank=True)    new_status = models.IntegerField(        choices=ContainerDetailModel.BATCH_STATUS,        null=True,        blank=True,        verbose_name='新状态'    )        # 元信息    creater = models.CharField(max_length=50, verbose_name='操作人')    create_time = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')    tobatchlog = models.BooleanField(default=False, verbose_name='是否转移到批次日志')        class Meta:        db_table = 'container_detail_log'        verbose_name = '托盘明细变更日志'        verbose_name_plural = "托盘明细变更日志"        ordering = ['-create_time']        def __str__(self):        return f"{self.container_detail} - {self.get_log_type_display()} - {self.create_time.strftime('%Y-%m-%d %H:%M:%S')}"  class batchLogModel(models.Model):    """批次日志模型"""    LOG_TYPES = (        ('create', '创建'),        ('update', '更新'),        ('delete', '删除'),        ('out', '出库'),        ('cancel_out', '取消出库'),        ('status_change', '状态变更'),    )    bound = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, related_name='logs', null=True, blank=True)    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, related_name='logs')    log_type = models.CharField(max_length=20, choices=LOG_TYPES, verbose_name='日志类型')    goods_code = models.CharField(max_length=50, verbose_name='货品编码')    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')    goods_std = models.CharField(max_length=50, verbose_name='货品规格', null=True, blank=True)    goods_unit = models.CharField(max_length=50, verbose_name='货品单位', null=True, blank=True)    goods_in_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新入数量', null=True, blank=True)    goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新出数量', null=True, blank=True)    detail_logs = models.ManyToManyField(        ContainerDetailLogModel,        related_name='batch_logs',        verbose_name='关联托盘日志'    )    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')    class Meta:        db_table = 'batch_log_from_container_log'        verbose_name = '批次日志'        verbose_name_plural = "批次日志"        ordering = ['-create_time']        def __str__(self):        return f"{self.batch} - {self.get_log_type_display()} - {self.create_time.strftime('%Y-%m-%d %H:%M:%S')}"          def update_from_details(self):        """从关联的托盘日志更新批次日志数据"""        # 获取所有关联的托盘日志        detail_logs = self.detail_logs.all()                # 计算聚合值        total_old_qty = detail_logs.aggregate(total=Sum('old_goods_qty'))['total'] or 0        total_new_qty = detail_logs.aggregate(total=Sum('new_goods_qty'))['total'] or 0        total_old_out = detail_logs.aggregate(total=Sum('old_goods_out_qty'))['total'] or 0        total_new_out = detail_logs.aggregate(total=Sum('new_goods_out_qty'))['total'] or 0                # 更新批次日志        self.goods_in_qty = total_new_qty - total_old_qty        self.goods_out_qty = total_new_out - total_old_out        self.save()                        # 同时更新批次统计数据        self.update_batch_stats()        def update_batch_stats(self):        """更新批次的统计数据"""        # 聚合托盘明细数据        stats = ContainerDetailModel.objects.filter(            batch=self.batch,            is_delete=False        ).aggregate(            total_qty=Sum('goods_qty'),            total_out_qty=Sum('goods_out_qty')        )                # 更新批次数据        self.batch.goods_in_qty = stats['total_qty'] or 0        self.batch.goods_in_location_qty = (stats['total_qty'] or 0) - (stats['total_out_qty'] or 0)        self.batch.goods_out_qty = stats['total_out_qty'] or 0        self.batch.save()# 批次日志聚合处理器def aggregate_to_batch_log(container_log):    """将托盘日志聚合到批次日志"""    logger.info(f"开始聚合托盘日志: {container_log.id}")        # 检查日志是否已处理    if container_log.tobatchlog:        logger.info(f"托盘日志 {container_log.id} 已处理过,跳过")        return        try:        # 确定批次        detail = container_log.container_detail        if not detail:            logger.warning(f"托盘日志 {container_log.id} 缺少关联的托盘明细")            return                    batch = detail.batch        if not batch:            logger.warning(f"托盘明细 {detail.id} 缺少关联的批次")            return                # 确定操作类型        log_type = container_log.log_type                # 创建或获取批次日志(按类型和时间窗口分组)        # 设置时间窗口(5分钟)        time_window_start = container_log.create_time - timedelta(minutes=2)        time_window_end = container_log.create_time + timedelta(minutes=2)                # 查找相同批次、相同类型、在时间窗口内的批次日志        existing_logs = batchLogModel.objects.filter(            batch=batch,            log_type=log_type,            create_time__gte=time_window_start,            create_time__lte=time_window_end        ).order_by('create_time')                if existing_logs.exists():            # 使用时间窗口内最早的批次日志            batch_log = existing_logs.first()            logger.info(f"找到已有批次日志 {batch_log.id} 用于聚合")        else:            # 创建新的批次日志            batch_log = batchLogModel.objects.create(                batch=batch,                log_type=log_type,                goods_code=batch.goods_code,                goods_desc=batch.goods_desc,                goods_std=batch.goods_std,                goods_unit=batch.goods_unit,                create_time=container_log.create_time            )            logger.info(f"创建新批次日志 {batch_log.id}")            # create_material_history(batch_log, update=True)                # 将托盘日志关联到批次日志        batch_log.detail_logs.add(container_log)                # 从关联日志更新批次日志数据        batch_log.update_from_details()        # 根据日志类型添加关联单据信息        if log_type == 'out' and not batch_log.bound:            from bound.models import OutBoundDetailModel            bound = OutBoundDetailModel.objects.filter(                bound_batch_number=batch            ).order_by('-create_time').first()            if bound:                batch_log.bound = bound.bound_list                batch_log.save()                logger.info(f"为批次日志 {batch_log.id} 添加出库单关联")        if log_type == 'create' and not batch_log.bound:            from bound.models import BoundDetailModel            bound = BoundDetailModel.objects.filter(                bound_batch=batch            ).order_by('-create_time').first()            if bound:                batch_log.bound = bound.bound_list                logger.info(f"为批次日志 {batch_log.id} 添加入库单关联")                batch_log.save()                    # 标记日志已处理        container_log.tobatchlog = True        container_log.save()                logger.info(f"成功聚合托盘日志 {container_log.id} 到批次日志 {batch_log.id}")        return batch_log        except Exception as e:        logger.error(f"聚合托盘日志时出错: {e}", exc_info=True)        raise    # # 批次日志的信号处理器@receiver(post_save, sender=batchLogModel)def update_material_history(sender, instance, created, **kwargs):    """批次日志保存后更新物料统计"""    if created:        # 创建物料变动历史记录        create_material_history(instance, update=True)def create_material_history( instance, update):    """为每条批次日志创建物料变动历史记录"""    from bound.models import MaterialStatistics     if update:        # 获取或创建物料统计        stats, _ = MaterialStatistics.objects.get_or_create(            goods_code=instance.goods_code,            defaults={                'goods_desc': instance.goods_desc,                'goods_std': instance.goods_std or '待填写',                'goods_unit': instance.goods_unit or '待填写',            }        )                # 计算期初数量(变动前的库存)        opening_quantity = stats.total_quantity                # 计算期末数量        closing_quantity = opening_quantity        if instance.goods_in_qty:            closing_quantity += instance.goods_in_qty        if instance.goods_out_qty:            closing_quantity -= instance.goods_out_qty                MaterialChangeHistory.objects.create(            material_stats=stats,            batch_log=instance,            goods_code=instance.goods_code,            goods_desc=instance.goods_desc,            goods_std=instance.goods_std,            goods_unit=instance.goods_unit,            change_time=instance.create_time,            in_quantity=instance.goods_in_qty or Decimal('0'),            out_quantity=instance.goods_out_qty or Decimal('0'),            change_type=instance.log_type,            opening_quantity=opening_quantity,            closing_quantity=closing_quantity        )                # 更新物料统计的实时库存        stats.total_quantity = closing_quantity        stats.save()      def count_day_in_out(ContainerDetailLogModelobj):    """统计某批次的每日入库和出库数量"""    from reportcenter.models import bigScreenModel    from datetime import datetime, timedelta    count_today, _ = bigScreenModel.objects.get_or_create(        day = datetime.now().date(),        defaults={            'day_in': 0,            'day_out': 0,        }        )    count_today.day_in += ContainerDetailLogModelobj.new_goods_qty - ContainerDetailLogModelobj.old_goods_qty or 0    count_today.day_out += ContainerDetailLogModelobj.new_goods_out_qty - ContainerDetailLogModelobj.old_goods_out_qty or 0    count_today.save()    # 获取批次的创建时间@receiver(pre_save, sender=ContainerDetailModel)def container_detail_pre_save(sender, instance, **kwargs):    """在托盘明细保存前记录变更"""    if instance.pk:        try:            old_instance = ContainerDetailModel.objects.get(pk=instance.pk)            logs = []                        # 数量变化日志            if old_instance.goods_qty != instance.goods_qty:                logs.append(ContainerDetailLogModel(                    container_detail=instance,                    log_type='update',                    old_goods_qty=old_instance.goods_qty,                    new_goods_qty=instance.goods_qty,                    creater=instance.creater                ))                        # 出库数量变化日志            if old_instance.goods_out_qty != instance.goods_out_qty:                if old_instance.goods_out_qty < instance.goods_out_qty:                    log_type = 'out'                else:                    log_type = 'cancel_out'                                logs.append(ContainerDetailLogModel(                    container_detail=instance,                    log_type=log_type,                    old_goods_out_qty=old_instance.goods_out_qty,                    new_goods_out_qty=instance.goods_out_qty,                    creater=instance.creater                ))                        # 删除日志            if old_instance.is_delete != instance.is_delete and instance.is_delete:                logs.append(ContainerDetailLogModel(                    container_detail=instance,                    log_type='delete',                    old_goods_qty=old_instance.goods_qty,                    old_goods_out_qty=old_instance.goods_out_qty,                    new_goods_qty = old_instance.goods_qty,                    new_goods_out_qty = old_instance.goods_qty,                    old_status=old_instance.status,                    creater=instance.creater                ))                        # 批量创建日志            if logs:                created_logs = ContainerDetailLogModel.objects.bulk_create(logs)                # print(f"创建{len(created_logs)}条ContainerDetailLogModel日志")                for log in created_logs:                # 由于bulk_create不会触发信号,我们手动调用信号处理函数                    handle_container_detail_log(ContainerDetailLogModel, log, created=True)                        except ContainerDetailModel.DoesNotExist:            pass        @receiver(post_save, sender=ContainerDetailLogModel)def handle_container_detail_log(sender, instance, created, **kwargs):    """将日志放入队列进行后台处理"""    if created:        # 检查实例是否已处理过(防止多次入队)        if hasattr(instance, '_is_enqueued'):            return        instance._is_enqueued = True        try:            log_processing_queue.put(([instance], created), block=False)            logger.info(f"已放入队列处理: {instance.id}")        except queue.Full:            print("日志处理队列已满,将同步处理")            logger.warning("日志处理队列已满,将同步处理")            # 队列满时同步处理当前日志            aggregate_to_batch_log(instance)            count_day_in_out(instance)            # if created:            #     # 创建物料变动历史记录            #     create_material_history(instance, update=True)@receiver(post_save, sender=ContainerDetailModel)def container_detail_post_save(sender, instance, created, **kwargs):    """在托盘明细新建时创建日志"""    if created:        ContainerDetailLogModel.objects.create(            container_detail=instance,            log_type='create',            creater=instance.creater,            new_goods_qty=instance.goods_qty,            new_status=instance.status        )class MaterialChangeHistory(models.Model):    """物料库存变动历史记录(与批次日志多对一)"""    material_stats = models.ForeignKey(        MaterialStatistics,        on_delete=models.CASCADE,        related_name='history_records',        verbose_name="关联物料"    )        # 与批次日志建立多对一关系    batch_log = models.ForeignKey(batchLogModel,on_delete=models.CASCADE, related_name='material_history', verbose_name="关联批次日志",primary_key=False)    count_time = models.IntegerField(verbose_name='统计次数',default=0,null=True, blank=True)    goods_code = models.CharField(max_length=50, verbose_name='货品编码')    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')    goods_std = models.CharField(max_length=50, verbose_name='货品规格', null=True, blank=True)    goods_unit = models.CharField(max_length=50, verbose_name='货品单位', null=True, blank=True)    # 时间戳记录(使用批次日志的时间)    change_time = models.DateTimeField(        verbose_name="变动时间"    )        # 库存变动情况    in_quantity = models.DecimalField(        max_digits=10, decimal_places=3,        default=Decimal('0'),        verbose_name="入库数量"    )        out_quantity = models.DecimalField(        max_digits=10, decimal_places=3,        default=Decimal('0'),        verbose_name="出库数量"    )        # 变更类型(从批次日志获取)    change_type = models.CharField(        max_length=20,        verbose_name="变动类型"    )        # 变更时的库存快照    opening_quantity = models.DecimalField(        max_digits=10, decimal_places=3,        default=Decimal('0'),        verbose_name="期初数量"    )        closing_quantity = models.DecimalField(        max_digits=10, decimal_places=3,        default=Decimal('0'),        verbose_name="期末数量"    )        class Meta:        db_table = 'material_change_history'        verbose_name = '物料变动历史'        verbose_name_plural = "物料变动历史"        ordering = ['-change_time']        indexes = [            models.Index(fields=['material_stats', 'change_time']),            models.Index(fields=['change_time']),        ]   # 明细表:操作记录 记录每次出入库的记录,使用goods来进行盘点,使用托盘码来进行托盘的操作记录class ContainerOperationModel(models.Model):    OPERATION_TYPES = (        ('container','组盘'),        ('inbound', '入库'),        ('outbound', '出库'),         ('adjust', '调整'),    )    month = models.IntegerField(verbose_name='月份')    container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='operations')    operation_type = models.CharField(max_length=20, choices=OPERATION_TYPES, verbose_name='操作类型')    bound_id = models.IntegerField(verbose_name='出库申请', null=True, blank=True)    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='批次',null=True, blank=True)    goods_code = models.CharField(max_length=50, verbose_name='货品编码')    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')    goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='数量')    goods_weight = models.DecimalField(max_digits=10, decimal_places=3, verbose_name='重量')    operator = models.CharField(max_length=50, verbose_name='操作人')    timestamp = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')        from_location = models.CharField(max_length=50, null=True, verbose_name='原库位')    to_location = models.CharField(max_length=50, null=True, verbose_name='目标库位')    memo = models.TextField(null=True, verbose_name='备注')    is_delete = models.BooleanField(default=False, verbose_name='是否删除')    class Meta:        db_table = 'container_operation'            verbose_name = 'ContainerOperation'        verbose_name_plural = "ContainerOperation"        ordering = ['-timestamp']class ContainerWCSModel(models.Model):    TASK_STATUS = (        (100, '等待中'),        (101, '处理中'),        (102, '已暂停'),        (103, '入库中'),        (200, '已发送'),        (300, '已完成'),        (400, '失败'),    )    taskid = models.CharField(max_length=50, verbose_name='任务ID')    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='关联批次',null=True, blank=True )    batch_out = models.ForeignKey(OutBatchModel, on_delete=models.CASCADE, verbose_name='出库批次' , null=True, blank=True )    bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name='关联出库单',null=True, blank=True )    batch_number = models.CharField(max_length=50, verbose_name='批次号',null=True, blank=True )    sequence = models.BigIntegerField(verbose_name='任务顺序')    priority = models.IntegerField(default=100, verbose_name='优先级')    month = models.IntegerField(verbose_name='月份')    tasktype = models.CharField(max_length=50, verbose_name='任务类型')    tasknumber = models.BigIntegerField(verbose_name='任务号',unique=True)    order_number = models.IntegerField(verbose_name='c_number')    container = models.CharField(max_length=50, verbose_name='托盘号')    current_location = models.CharField(max_length=50, verbose_name='当前库位')    target_location = models.CharField(max_length=50, verbose_name='目标库位')    message = models.TextField(verbose_name='消息')    working = models.IntegerField(default = 1,verbose_name='工作状态')    status = models.IntegerField(choices=TASK_STATUS, default=100, verbose_name='状态')    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')    is_delete = models.BooleanField(default=False, verbose_name='是否删除')    class Meta:        db_table = 'container_wcs'        verbose_name = 'ContainerWCS'        verbose_name_plural = "ContainerWCS"        ordering = ['-create_time']        def to_dict(self):        return {            'container': self.container,            'current_location': self.current_location,            'month' : self.month,            'target_location': self.target_location,            'tasktype': self.tasktype,            'taskid': self.taskid,            'taskNumber': self.tasknumber-20000000000,            'message': self.message,            'container': self.container,            'status': self.status        }    def __str__(self):        return f"{self.taskid} - {self.get_status_display()}"# 这里的批次详情是主入库申请单下的子批次class TaskModel(models.Model):        task_wcs = models.ForeignKey(ContainerWCSModel, on_delete=models.CASCADE, related_name='tasks')    batch_detail = models.ForeignKey(BoundDetailModel, on_delete=models.CASCADE, verbose_name='批次详情')    container_detail = models.ForeignKey(ContainerDetailModel, on_delete=models.CASCADE, verbose_name='托盘明细')    class Meta:        db_table = 'task'        verbose_name = 'Task'        verbose_name_plural = "Task"        ordering = ['-id']class out_batch_detail(models.Model):    out_bound = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, related_name='out_batch_details')    container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='out_batch_details')    container_detail = models.ForeignKey(ContainerDetailModel, on_delete=models.CASCADE, verbose_name='托盘明细')    out_goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='数量')    last_out_goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='上次出库数量')    working = models.IntegerField(default = 1,verbose_name='工作状态')    is_delete = models.BooleanField(default=False, verbose_name='是否删除')    class Meta:        db_table = 'out_batch_detail'        verbose_name = 'OutBatchDetail'        verbose_name_plural = "OutBatchDetail"        ordering = ['container']
 |