forked from erp-dev/erp
753 lines
27 KiB
Python
753 lines
27 KiB
Python
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):
|
||
"""开版管理订单"""
|
||
|
||
# 商户关联(多租户)
|
||
merchant = models.ForeignKey(
|
||
basic_models.Merchant,
|
||
on_delete=models.PROTECT,
|
||
null=True,
|
||
blank=True,
|
||
related_name='plate_orders',
|
||
verbose_name='商户',
|
||
help_text='所属商户,可空以兼容历史数据'
|
||
)
|
||
|
||
# 自动编号相关
|
||
design_code = models.CharField(max_length=50, blank=True, null=True, verbose_name='设计编号')
|
||
original_id = models.PositiveBigIntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name='克隆来源订单ID',
|
||
help_text='前端克隆概念:记录本订单由哪个订单复制而来(无外键约束)',
|
||
)
|
||
|
||
# 版相关信息
|
||
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']
|
||
indexes = [
|
||
models.Index(
|
||
fields=['merchant', 'plate_date'],
|
||
name='idx_plateord_merch_pltdt',
|
||
),
|
||
]
|
||
permissions = [
|
||
('can_invalidate_plateorder', '可以作废开版订单'),
|
||
('can_activate_plateorder', '可以恢复开版订单'),
|
||
('view_all_plateorders', '查看所有开版订单(突破客户可见性限制)'),
|
||
]
|
||
|
||
@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 PlateOrderTiiaUploadFailure(ModelBase):
|
||
"""
|
||
PlateOrder 图片上传腾讯云图库失败记录(用于定时任务/人工重试追踪)。
|
||
|
||
设计目标:最小可用——记录“哪天跑的任务 + 哪个 plate_order_id + 失败详情/次数”。
|
||
"""
|
||
run_date = models.DateField(
|
||
verbose_name='任务日期',
|
||
help_text='以任务处理的日期为准(通常为“昨天”)',
|
||
)
|
||
plate_order_id = models.PositiveBigIntegerField(
|
||
verbose_name='开版订单ID',
|
||
db_index=True,
|
||
help_text='PlateOrder.id(不使用外键,避免历史数据/删除带来的约束问题)',
|
||
)
|
||
error = models.TextField(
|
||
blank=True,
|
||
default='',
|
||
verbose_name='错误摘要',
|
||
)
|
||
details = models.JSONField(
|
||
default=list,
|
||
blank=True,
|
||
verbose_name='错误详情',
|
||
help_text='通常存储 upload_plate_order_images_to_tencent_tiia 返回的失败条目列表',
|
||
)
|
||
attempts = models.PositiveIntegerField(
|
||
default=0,
|
||
verbose_name='尝试次数',
|
||
)
|
||
last_attempt_at = models.DateTimeField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name='最后尝试时间',
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = '开版订单图片上传失败记录'
|
||
verbose_name_plural = '开版订单图片上传失败记录'
|
||
ordering = ['-created_at']
|
||
constraints = [
|
||
models.UniqueConstraint(
|
||
fields=['run_date', 'plate_order_id'],
|
||
name='uniq_plateorder_tiia_fail_run_date_po',
|
||
)
|
||
]
|
||
|
||
|
||
class PrintingOrder(ModelBase):
|
||
"""PrintingOrder model representing a printing order."""
|
||
|
||
# 商户关联(多租户)
|
||
merchant = models.ForeignKey(
|
||
basic_models.Merchant,
|
||
on_delete=models.PROTECT,
|
||
null=True,
|
||
blank=True,
|
||
related_name='printing_orders',
|
||
verbose_name='商户',
|
||
help_text='所属商户,可空以兼容历史数据'
|
||
)
|
||
|
||
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.DateTimeField(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='印染流程',
|
||
)
|
||
created_by = models.ForeignKey(
|
||
settings.AUTH_USER_MODEL,
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='printing_orders',
|
||
verbose_name='创建人',
|
||
)
|
||
external_order_id = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
null=True,
|
||
db_index=True,
|
||
verbose_name='外部订单编号',
|
||
)
|
||
external_customer_id = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
null=True,
|
||
verbose_name='外部客户ID',
|
||
)
|
||
external_customer_name = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
null=True,
|
||
verbose_name='外部客户名',
|
||
)
|
||
external_employee_name = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
null=True,
|
||
verbose_name='外部员工名',
|
||
)
|
||
kd_riqi = models.DateTimeField(
|
||
null=True,
|
||
blank=True,
|
||
db_index=True,
|
||
verbose_name='外部开单日期时间',
|
||
)
|
||
external_raw = models.JSONField(
|
||
default=dict,
|
||
blank=True,
|
||
verbose_name='外部原始数据',
|
||
help_text='保存外部 records 聚合后的 order 级原始数据快照',
|
||
)
|
||
|
||
def __str__(self):
|
||
return self.human_id
|
||
|
||
class Meta:
|
||
verbose_name = '印染订单'
|
||
verbose_name_plural = '印染订单'
|
||
permissions = [
|
||
('can_invalidate_printingorder', '可以作废印染订单'),
|
||
('can_activate_printingorder', '可以恢复印染订单'),
|
||
('view_all_printingorders', '查看所有印染订单(突破客户可见性限制)'),
|
||
]
|
||
|
||
@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."""
|
||
|
||
# 商户关联(多租户)
|
||
merchant = models.ForeignKey(
|
||
basic_models.Merchant,
|
||
on_delete=models.PROTECT,
|
||
null=True,
|
||
blank=True,
|
||
related_name='printing_jobs',
|
||
verbose_name='商户',
|
||
help_text='所属商户,可空以兼容历史数据'
|
||
)
|
||
|
||
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='业务进展',
|
||
)
|
||
is_production_completed = models.BooleanField(
|
||
default=False,
|
||
verbose_name='是否完成生产',
|
||
help_text='独立于流程计算状态的生产完成标记',
|
||
)
|
||
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='备注')
|
||
original_id = models.PositiveBigIntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name='克隆来源任务ID',
|
||
help_text='前端克隆概念:记录本任务由哪个任务复制而来(无外键约束)',
|
||
)
|
||
sub_id = models.IntegerField(
|
||
null=True,
|
||
blank=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='流程实例',
|
||
)
|
||
created_by = models.ForeignKey(
|
||
settings.AUTH_USER_MODEL,
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='printing_jobs',
|
||
verbose_name='创建人',
|
||
)
|
||
external_product_name = models.CharField(
|
||
max_length=200,
|
||
blank=True,
|
||
null=True,
|
||
verbose_name='外部产品名',
|
||
)
|
||
external_raw = models.JSONField(
|
||
default=dict,
|
||
blank=True,
|
||
verbose_name='外部原始数据',
|
||
help_text='保存外部 record 的完整原始数据',
|
||
)
|
||
|
||
def __str__(self):
|
||
return self.printing_order.human_id
|
||
|
||
@property
|
||
def billed_quantity(self) -> Decimal:
|
||
"""
|
||
开单数量:所有关联的非作废销售单明细数量之和
|
||
"""
|
||
from business.models import SalesOrderStatusEnum
|
||
|
||
total = self.sales_order_items.exclude(
|
||
sales_order__status=SalesOrderStatusEnum.CANCELLED,
|
||
).aggregate(total=models.Sum('quantity')).get('total')
|
||
return total or Decimal('0')
|
||
|
||
@property
|
||
def is_sales_order_bound(self) -> bool:
|
||
"""是否已被非作废销售单明细绑定。"""
|
||
from business.models import SalesOrderStatusEnum
|
||
|
||
cached = getattr(self, '_is_sales_order_bound_cache', None)
|
||
if cached is not None:
|
||
return bool(cached)
|
||
return self.sales_order_items.exclude(
|
||
sales_order__status=SalesOrderStatusEnum.CANCELLED,
|
||
).exists()
|
||
|
||
@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
|
||
|
||
@property
|
||
def progress_percentage(self) -> float:
|
||
"""计算进度百分比"""
|
||
if not self.business_object:
|
||
return 0.0
|
||
|
||
return self.business_object.get_progress_percentage()
|
||
|
||
@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 ''
|
||
|
||
class Meta:
|
||
verbose_name = '印染款式明细'
|
||
verbose_name_plural = '印染款式明细'
|
||
|
||
|
||
class PrintingJobBatchAdvanceRecord(ModelBase):
|
||
"""
|
||
印染任务批量操作记录(审计)
|
||
|
||
记录一次"对多个 PrintingJob 用同一组参数操作同一步流程节点"的行为:
|
||
- created_by/created_at:谁在何时做了批量操作
|
||
- parameters:本次操作提交的工艺参数(JSON)
|
||
- state:本次操作的流程节点
|
||
- printing_jobs:本次批量操作涉及的所有明细
|
||
- only_parameters:操作类型
|
||
- False(默认):批量推进状态
|
||
- True:仅批量补充工艺参数(不推进状态)
|
||
"""
|
||
|
||
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,
|
||
)
|
||
only_parameters = models.BooleanField(
|
||
default=False,
|
||
verbose_name='仅参数补充',
|
||
help_text='True=仅补充参数(不推进状态),False=批量推进状态',
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = '印染任务批量操作记录'
|
||
verbose_name_plural = '印染任务批量操作记录'
|
||
|
||
|
||
class PrintingOrderExternalSnapshotSyncAudit(ModelBase):
|
||
"""
|
||
外部订单快照同步审计记录。
|
||
|
||
记录一次“按 external_order_id 触发外部快照覆盖同步”的执行结果:
|
||
- before_snapshot: 同步前本地 PrintingOrder / PrintingJob 快照
|
||
- operator_user / operator_employee: 触发人
|
||
- is_success / failure_reason: 成功或失败及失败原因
|
||
- allow_reset_stateflow: 本次是否允许撤销已有工序
|
||
"""
|
||
|
||
printing_order = models.ForeignKey(
|
||
PrintingOrder,
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='external_snapshot_sync_audits',
|
||
verbose_name='印染订单',
|
||
)
|
||
external_order_id = models.CharField(
|
||
max_length=100,
|
||
db_index=True,
|
||
verbose_name='外部订单编号',
|
||
)
|
||
operator_user = models.ForeignKey(
|
||
settings.AUTH_USER_MODEL,
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='printing_order_external_snapshot_sync_audits',
|
||
verbose_name='操作者',
|
||
)
|
||
operator_employee = models.ForeignKey(
|
||
basic_models.Employee,
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='printing_order_external_snapshot_sync_audits',
|
||
verbose_name='操作员工',
|
||
)
|
||
before_snapshot = models.JSONField(
|
||
default=dict,
|
||
blank=True,
|
||
verbose_name='同步前快照',
|
||
)
|
||
allow_reset_stateflow = models.BooleanField(
|
||
default=False,
|
||
verbose_name='允许撤销工序',
|
||
)
|
||
is_success = models.BooleanField(
|
||
default=False,
|
||
verbose_name='是否成功',
|
||
)
|
||
failure_reason = models.TextField(
|
||
blank=True,
|
||
default='',
|
||
verbose_name='失败原因',
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = '外部订单快照同步审计记录'
|
||
verbose_name_plural = '外部订单快照同步审计记录'
|
||
ordering = ['-created_at', '-id']
|
||
|
||
def __str__(self) -> str:
|
||
return f'{self.external_order_id} #{self.id}'
|