from __future__ import annotations from typing import TYPE_CHECKING from django.db import models from django.contrib.auth import get_user_model from django.utils import timezone from flower.common import ModelBase from basic_info import models as basic_models if TYPE_CHECKING: from printing.models import PrintingJob User = get_user_model() class UnitChoices(models.IntegerChoices): """销售品单位""" METER = 1, '米' PIECE = 2, '件' YARD = 3, '码' UNIT = 4, '个' class ShipmentStatus(models.IntegerChoices): """出货单状态""" PENDING_DELIVERY = 1, '待送货' DELIVERED = 2, '已交付' CANCELLED = 3, '已取消' class ExternalFinishedProduct(ModelBase): """ 外部成品表 用于兼容外部遗留系统的数据映射(可被多个出货单引用)。 """ style_name = models.CharField( max_length=200, verbose_name='款式名称', ) num_of_rolls = models.PositiveIntegerField( verbose_name='卷数', ) remark = models.CharField( max_length=200, null=True, blank=True, verbose_name='备注', ) created_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, related_name='created_external_finished_products', verbose_name='创建人', ) shipment = models.ForeignKey( 'Shipment', on_delete=models.CASCADE, null=True, blank=True, related_name='external_finished_products', verbose_name='出货单', help_text='关联的出货单(可空,兼容历史数据/逐步迁移)', ) class Meta: db_table = 'external_finished_product' verbose_name = '外部成品表' verbose_name_plural = '外部成品表' ordering = ['-created_at'] def __str__(self) -> str: return f'{self.style_name} ({self.num_of_rolls}卷)' class Shipment(ModelBase): """ 出货单 记录一次出货操作,包含多个销售品。 """ status = models.IntegerField( choices=ShipmentStatus.choices, default=ShipmentStatus.PENDING_DELIVERY, verbose_name='状态', ) cancelled_at = models.DateTimeField( null=True, blank=True, verbose_name='取消时间', ) cancelled_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='cancelled_shipments', verbose_name='取消人', ) external_id = models.CharField( max_length=120, null=True, blank=True, verbose_name='外部订单号', help_text='用于兼容外部遗留系统的订单号(可空)', ) merchant = models.ForeignKey( basic_models.Merchant, on_delete=models.PROTECT, related_name='shipments', verbose_name='所属商户', ) customer = models.ForeignKey( basic_models.Customer, on_delete=models.PROTECT, related_name='shipments', verbose_name='客户' ) shipment_date = models.DateField( verbose_name='出货日期', help_text='实际出货日期' ) area = models.CharField( max_length=30, blank=True, default='', verbose_name='地区', help_text='出货地区(可空字符串)', ) remark = models.TextField( blank=True, default='', verbose_name='备注' ) created_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, related_name='created_shipments', verbose_name='创建人' ) class Meta: db_table = 'shipment' verbose_name = '出货单' verbose_name_plural = '出货单' ordering = ['-created_at'] def __str__(self): return f'出货单 #{self.id} - {self.customer.name}' def cancel(self, operator: User | None) -> None: """ 取消出货单(记录取消时间与操作者) Args: operator: 操作者(通常为 request.user,可为空) """ self.status = ShipmentStatus.CANCELLED self.cancelled_at = timezone.now() self.cancelled_by = operator self.save(update_fields=['status', 'cancelled_at', 'cancelled_by', 'updated_at']) class SalesItem(ModelBase): """ 销售品 出货单中的具体商品明细。 销售品可以先创建(待分配状态),后续再关联到出货单。 """ shipment = models.ForeignKey( Shipment, on_delete=models.CASCADE, null=True, blank=True, related_name='items', verbose_name='出货单', help_text='为空表示待分配' ) merchant = models.ForeignKey( basic_models.Merchant, on_delete=models.PROTECT, related_name='sales_items', verbose_name='所属商户', ) name = models.CharField( max_length=200, verbose_name='名称' ) quantity = models.DecimalField( max_digits=12, decimal_places=2, verbose_name='数量' ) unit = models.IntegerField( choices=UnitChoices.choices, default=UnitChoices.PIECE, verbose_name='单位' ) created_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, related_name='created_sales_items', verbose_name='创建人' ) # 关联的生产明细订单(可空) # 使用 PositiveIntegerField 而非 ForeignKey,避免模块间强依赖 printing_job_id = models.PositiveIntegerField( null=True, blank=True, verbose_name='关联生产任务ID', help_text='关联的 PrintingJob ID(可空)' ) # 客户ID(可空,用于销售品级别的客户关联) customer_id = models.PositiveIntegerField( null=True, blank=True, verbose_name='客户ID', help_text='销售品级别的客户ID(可空)' ) position = models.CharField( max_length=200, blank=True, default='', verbose_name='货位' ) remark = models.CharField( max_length=200, blank=True, default='', verbose_name='备注' ) class Meta: db_table = 'sales_item' verbose_name = '销售品' verbose_name_plural = '销售品' ordering = ['id'] def __str__(self): return f'{self.name} x {self.quantity} {self.get_unit_display()}' def get_printing_job(self) -> PrintingJob | None: """ 获取关联的 PrintingJob 实例 Returns: PrintingJob 实例,如果不存在则返回 None """ if not self.printing_job_id: return None try: from printing.models import PrintingJob return PrintingJob.objects.get(id=self.printing_job_id) except PrintingJob.DoesNotExist: return None def get_customer(self) -> basic_models.Customer | None: """ 获取关联的 Customer 实例 Returns: Customer 实例,如果不存在则返回 None """ if not self.customer_id: return None try: return basic_models.Customer.objects.get(id=self.customer_id) except basic_models.Customer.DoesNotExist: return None