forked from erp-dev/erp
369 lines
14 KiB
Python
369 lines
14 KiB
Python
from django.db import models
|
||
from django.conf import settings
|
||
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.FileField(upload_to='plate_images/', null=True, blank=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='跟单员'
|
||
)
|
||
|
||
# 客户和地址信息
|
||
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.DateField(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='布料')
|
||
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='客户修改意见')
|
||
|
||
# 流程管理
|
||
process = models.IntegerField(default=settings.PLATE_ORDER_DEFAULT_PROCESS_ID, 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
|
||
super().save(*args, **kwargs)
|
||
|
||
# 如果有 process 且没有 business_object,则自动创建
|
||
if self.process and not self.business_object:
|
||
try:
|
||
from stateflow.models import Process
|
||
process_obj = Process.objects.get(id=self.process)
|
||
|
||
# 创建 BusinessObject
|
||
business_object = stateflow_models.BusinessObject.objects.create(
|
||
process=process_obj
|
||
)
|
||
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 不为 None + 进度 0%:返回"未开始"
|
||
- current_state 不为 None + 进度 > 0%:返回 current_state.name(正在进行)
|
||
"""
|
||
if not self.business_object:
|
||
return '未开始'
|
||
|
||
current_state = self.business_object.get_current_state()
|
||
|
||
if current_state is None:
|
||
# 没有待执行的节点,已完成
|
||
return '已完成'
|
||
|
||
# 有待执行的节点,配合进度判断是"未开始"还是"进行中"
|
||
progress = self.business_object.get_progress_percentage()
|
||
if progress == 0:
|
||
return '未开始'
|
||
|
||
return current_state.name
|
||
|
||
@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
|
||
).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='生产注意事项')
|
||
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 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='产品',
|
||
)
|
||
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 status(self) -> str:
|
||
"""
|
||
返回当前状态名称(下一个待执行的状态名称)
|
||
|
||
规则:
|
||
- current_state 为 None:返回"已完成"(没有待执行的节点)
|
||
- current_state 不为 None + 进度 0%:返回"未开始"
|
||
- current_state 不为 None + 进度 > 0%:返回 current_state.name(正在进行)
|
||
"""
|
||
if not self.business_object:
|
||
return '未开始'
|
||
|
||
current_state = self.business_object.get_current_state()
|
||
|
||
if current_state is None:
|
||
# 没有待执行的节点,已完成
|
||
return '已完成'
|
||
|
||
# 有待执行的节点,配合进度判断是"未开始"还是"进行中"
|
||
progress = self.business_object.get_progress_percentage()
|
||
if progress == 0:
|
||
return '未开始'
|
||
|
||
return current_state.name
|
||
|
||
@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:
|
||
"""判断是否已开始(有BusinessObject且有状态记录,或不在初始状态)"""
|
||
if not self.business_object:
|
||
return False
|
||
|
||
# 检查是否有状态流转记录
|
||
from stateflow.models import StateFlowRecord
|
||
has_records = StateFlowRecord.objects.filter(
|
||
business_object=self.business_object
|
||
).exists()
|
||
|
||
return has_records
|
||
|
||
class Meta:
|
||
verbose_name = '印染款式明细'
|
||
verbose_name_plural = '印染款式明细'
|