1
0
forked from erp-dev/erp
Files
erpnew/shipment/models.py

284 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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='实际出货日期'
)
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