from decimal import Decimal from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db import models from flower.common import ModelBase from basic_info import models as basic_models from stateflow import models as stateflow_models class PlateOrder(ModelBase): """开版管理订单""" # 自动编号相关 design_code = models.CharField(max_length=50, blank=True, null=True, verbose_name='设计编号') # 版相关信息 plate_type = models.CharField(max_length=20, blank=True, null=True, verbose_name='起版情况') # 首版/复版等 plate_date = models.DateTimeField(null=True, blank=True, verbose_name='下版时间') plate_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='开版方式') plate_image = models.JSONField( default=list, blank=True, verbose_name='开版图', help_text='存储多个开版图片引用信息,例如 [{"file_id": 1, "name": "封面", "path": "/media/xxx"}]' ) image_name = models.TextField(blank=True, null=True, verbose_name='图片名称') plate_notes = models.TextField(blank=True, null=True, verbose_name='打版注意事项') reprint_reason = models.TextField(blank=True, null=True, verbose_name='复版原因') # 状态和进程 # development_status = models.CharField(max_length=20, blank=True, null=True, verbose_name='开发进程') # 未打印/待画图等 urgency_level = models.CharField(max_length=20, default='正常', verbose_name='紧急程度') # 正常/加急 # 人员关联 salesperson = models.ForeignKey( basic_models.Employee, on_delete=models.PROTECT, null=True, blank=True, related_name='plate_orders_as_salesperson', verbose_name='销售员' ) merchandiser = models.ForeignKey( basic_models.Employee, on_delete=models.PROTECT, null=True, blank=True, related_name='plate_orders_as_merchandiser', verbose_name='跟单员' ) designer = models.ForeignKey( basic_models.Employee, on_delete=models.PROTECT, null=True, blank=True, related_name='plate_orders_as_designer', verbose_name='设计师' ) # 客户和地址信息 customer = models.ForeignKey( basic_models.Customer, on_delete=models.PROTECT, related_name='plate_orders', verbose_name='客户', ) area = models.CharField(max_length=255, blank=True, null=True, verbose_name='区域') default_address = models.CharField(max_length=500, blank=True, null=True, verbose_name='默认地址') # 质量评级 is_mark_frame = models.BooleanField(default=False, verbose_name='是否套唛架') drawing_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='画图评级') color_matching_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='调色评级') sample_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='套样评级') difficulty_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='难度评级') # 时间相关 required_completion_date = models.DateTimeField(null=True, blank=True, verbose_name='要求完成时间') completion_date = models.DateTimeField(null=True, blank=True, verbose_name='完成时间') # 产品信息 fabric = models.CharField(max_length=100, blank=True, null=True, verbose_name='布料') fabric_source = models.CharField(max_length=100, blank=True, null=True, verbose_name='布料来源') width = models.CharField(max_length=50, blank=True, null=True, verbose_name='幅宽') style_name = models.CharField(max_length=100, blank=True, null=True, verbose_name='款号名称') # 生产方式 production_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='做货方式') # 样品相关 sample_meter = models.CharField(max_length=100, blank=True, null=True, verbose_name='米样') required_sample_meters = models.DecimalField( max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='客户要求米样米数' ) # 审批和订单状态 approval_result = models.CharField(max_length=50, blank=True, null=True, verbose_name='审批结果') is_ordered = models.BooleanField(default=False, verbose_name='是否已下单') is_invalid = models.BooleanField(default=False, verbose_name='是否作废') customer_feedback = models.TextField(blank=True, null=True, verbose_name='客户修改意见') print_count = models.PositiveIntegerField(default=0, verbose_name='打印次数') # 流程管理 process = models.IntegerField(default=settings.PLATE_ORDER_DEFAULT_PROCESS_ID, verbose_name='关联流程') created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_plate_orders', verbose_name='创建人', ) business_object = models.OneToOneField( stateflow_models.BusinessObject, on_delete=models.SET_NULL, null=True, blank=True, related_name='plate_order', verbose_name='流程实例', ) def __str__(self): return f'PlateOrder-{self.id}' def save(self, *args, **kwargs): """保存时自动创建 BusinessObject""" # 先保存以获取 ID(如果是新建) is_new = self.pk is None update_fields = kwargs.get('update_fields') super().save(*args, **kwargs) should_auto_create = ( self.process and not self.business_object and (is_new or update_fields is None or 'process' in (update_fields or [])) ) # 如果有 process 且没有 business_object,则自动创建 if should_auto_create: try: from stateflow.models import Process process_obj = Process.objects.get(id=self.process) # 创建 BusinessObject business_object = stateflow_models.BusinessObject.objects.create( name=f"PlateOrder-{self.id}", process=process_obj, description=f"开版订单 {self.id} 的流程实例", content_type=ContentType.objects.get_for_model(PlateOrder), object_id=self.id, ) self.business_object = business_object # 再次保存以更新 business_object 字段 super().save(update_fields=['business_object']) except Process.DoesNotExist: # 如果流程不存在,忽略错误 pass class Meta: verbose_name = '开版订单' verbose_name_plural = '开版订单' ordering = ['-created_at'] permissions = [ ('can_invalidate_plateorder', '可以作废开版订单'), ('can_activate_plateorder', '可以恢复开版订单'), ] @property def status(self) -> str: """ 返回当前状态名称 规则: - current_state 为 None:返回"已完成" - 否则:返回 current_state 的名称 注意:废除了"未开始"的概念,直接显示节点名称 """ from stateflow.services import get_display_status return get_display_status(self.business_object) @property def last_completed_state(self) -> str: """ 返回最后一个已完成节点的名称,用于详情展示 """ if not self.business_object: return '' from stateflow.services import get_last_completed_state last_state = get_last_completed_state(self.business_object) return last_state.name if last_state else '' @property def status_id(self): """返回当前状态ID(下一个待执行的状态ID)""" if not self.business_object: return None current_state = self.business_object.get_current_state() return current_state.id if current_state else None @property def is_completed(self) -> bool: """判断是否完成""" if not self.business_object: return False # 检查流程是否完成 from stateflow.services import get_overall_status return get_overall_status(self.business_object) == 'completed' @property def has_started(self) -> bool: """判断是否已开始(有未撤销的状态记录)""" if not self.business_object: return False # 只检查未撤销的记录 from stateflow.models import StateFlowRecord has_records = StateFlowRecord.objects.filter( business_object=self.business_object, is_cancelled=False ).exists() return has_records @property def progress_percentage(self) -> float: """计算进度百分比""" if not self.business_object: return 0.0 return self.business_object.get_progress_percentage() class PrintingOrder(ModelBase): """PrintingOrder model representing a printing order.""" customer = models.ForeignKey( basic_models.Customer, on_delete=models.PROTECT, related_name='printing_orders', verbose_name='客户', ) fabric = models.CharField(max_length=100, verbose_name='面料') width = models.CharField(max_length=100, verbose_name='幅宽') is_urgent = models.BooleanField(default=False, verbose_name='紧急') area = models.CharField(max_length=100, null=True, blank=True, verbose_name='地区') address = models.CharField(max_length=200, null=True, blank=True, verbose_name='地址') fabric_source = models.CharField(max_length=100, null=True, blank=True, verbose_name='布料来源') is_fabric_received = models.BooleanField(default=False, verbose_name='布料是否已收') craft = models.CharField(max_length=100, null=True, blank=True, verbose_name='工艺') description = models.TextField(blank=True, null=True, verbose_name='订单描述') outgoing_date = models.DateField(null=True, blank=True, verbose_name='出货日期') curve = models.CharField(max_length=100, blank=True, null=True, verbose_name='曲线') new_curve = models.CharField(max_length=100, blank=True, null=True, verbose_name='新加曲线') position = models.TextField(blank=True, null=True, verbose_name='位置') printing_warn = models.TextField(blank=True, null=True, verbose_name='打印注意事项') rolling_warn = models.TextField(blank=True, null=True, verbose_name='滚筒注意事项') production_warn = models.TextField(blank=True, null=True, verbose_name='生产注意事项') print_count = models.PositiveIntegerField(default=0, verbose_name='打印次数') is_invalid = models.BooleanField(default=False, verbose_name='是否作废') process = models.ForeignKey( stateflow_models.Process, on_delete=models.PROTECT, null=True, blank=True, related_name='printing_orders', verbose_name='印染流程', ) def __str__(self): return self.human_id class Meta: verbose_name = '印染订单' verbose_name_plural = '印染订单' permissions = [ ('can_invalidate_printingorder', '可以作废印染订单'), ('can_activate_printingorder', '可以恢复印染订单'), ] @property def human_id(self) -> str: if self.id is None or self.created_at is None: return "" return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" def can_change_process(self) -> bool: """检查是否可以修改流程(所有job都未开始)""" return not any(job.has_started for job in self.printing_jobs.all()) @property def progress(self) -> int: """计算订单完成进度百分比(已完成的任务数 / 总任务数)""" jobs = self.printing_jobs.all() if not jobs.exists(): return 0 completed_count = sum(1 for job in jobs if job.is_completed) return int((completed_count / jobs.count()) * 100) class PrintingJobWorkStateEnum(models.IntegerChoices): PRODUCING = 0, '生产中' WAITING_FOR_DELIVERY = 1, '待送货' WAITING_FOR_INVOICE = 2, '待开单' FINISHED = 3, '已完结' class PrintingJob(ModelBase): """PrintingJob model representing a printing job associated with an order.""" printing_order = models.ForeignKey( PrintingOrder, on_delete=models.CASCADE, related_name='printing_jobs', verbose_name='主订单', ) product = models.ForeignKey( basic_models.Product, on_delete=models.PROTECT, related_name='printing_jobs', verbose_name='产品', ) work_state = models.IntegerField( choices=PrintingJobWorkStateEnum.choices, default=PrintingJobWorkStateEnum.PRODUCING, verbose_name='业务进展', ) quantity = models.PositiveIntegerField(verbose_name='数量') unit = models.CharField(max_length=50, verbose_name='单位') size = models.CharField(max_length=100, null=True, blank=True, verbose_name='一段尺寸') pieces = models.PositiveIntegerField(null=True, blank=True, verbose_name='件数') description = models.TextField(blank=True, null=True, verbose_name='备注') business_object = models.OneToOneField( stateflow_models.BusinessObject, on_delete=models.SET_NULL, null=True, blank=True, related_name='printing_job', verbose_name='流程实例', ) def __str__(self): return self.printing_order.human_id @property def billed_quantity(self) -> Decimal: """ 开单数量:所有关联销售明细的数量之和 """ from business.models import SalesOrderItem total = self.sales_order_items.aggregate(total=models.Sum('quantity')).get('total') return total or Decimal('0') @property def status(self) -> str: """ 返回当前状态名称 规则: - current_state 为 None:返回"已完成" - 否则:返回 current_state 的名称 注意:废除了"未开始"的概念,直接显示节点名称 """ from stateflow.services import get_display_status return get_display_status(self.business_object) @property def status_id(self): """返回当前状态ID(下一个待执行的状态ID)""" if not self.business_object: return None current_state = self.business_object.get_current_state() return current_state.id if current_state else None @property def is_completed(self) -> bool: """判断是否完成(所有流程节点都已完成)""" if not self.business_object or not self.printing_order.process: return False # 获取流程的所有节点 process_nodes = self.printing_order.process.process_nodes.all() total_nodes = process_nodes.count() if total_nodes == 0: return False # 检查已完成的节点数 completed_count = self.business_object.state_logs.filter(is_cancelled=False).count() # 所有节点都完成了才算完成 return completed_count >= total_nodes @property def has_started(self) -> bool: """判断是否已开始(有未撤销的状态记录)""" if not self.business_object: return False # 只检查未撤销的记录 from stateflow.models import StateFlowRecord has_records = StateFlowRecord.objects.filter( business_object=self.business_object, is_cancelled=False ).exists() return has_records class Meta: verbose_name = '印染款式明细' verbose_name_plural = '印染款式明细' class PrintingJobBatchAdvanceRecord(ModelBase): """ 印染任务批量推进记录(审计) 记录一次“对多个 PrintingJob 用同一组参数推进同一步流程节点”的行为: - created_by/created_at:谁在何时做了批量推进 - parameters:本次推进提交的工艺参数(JSON) - state:本次被“完成”的流程节点(即推进时的 next_pending_state) - printing_jobs:本次批量推进涉及的所有明细 """ printing_order = models.ForeignKey( PrintingOrder, on_delete=models.PROTECT, related_name='batch_advance_records', verbose_name='印染订单', ) state = models.ForeignKey( stateflow_models.State, on_delete=models.PROTECT, related_name='printing_job_batch_advance_records', verbose_name='完成的流程节点', ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='printing_job_batch_advance_records', verbose_name='创建人', ) parameters = models.JSONField( default=dict, blank=True, verbose_name='工艺参数', help_text='批量推进时提交的参数(与单条推进一致)', ) printing_jobs = models.ManyToManyField( PrintingJob, related_name='batch_advance_records', verbose_name='印染明细', blank=False, ) class Meta: verbose_name = '印染任务批量推进记录' verbose_name_plural = '印染任务批量推进记录'