""" Shipment 模块业务逻辑层 """ from __future__ import annotations from typing import List from django.db import transaction from django.db.models import QuerySet from shipment.models import ExternalFinishedProduct, SalesItem, Shipment def get_sales_items_by_printing_order( printing_order_id: int, include_already_has_shipment: bool = False, ) -> QuerySet[SalesItem]: """ 通过生产订单ID查询对应的销售品 Args: printing_order_id: 生产订单ID include_already_has_shipment: 是否包含已关联出货单的销售品,默认为 False Returns: SalesItem 查询集 """ from printing.models import PrintingJob # 1. 获取该生产订单下所有 PrintingJob 的 ID job_ids = PrintingJob.objects.filter( printing_order_id=printing_order_id ).values_list("id", flat=True) # 2. 查询 SalesItem,过滤 printing_job_id 在这些 job_ids 中 queryset = SalesItem.objects.filter(printing_job_id__in=list(job_ids)) # 3. 根据参数决定是否过滤已出货的销售品 if not include_already_has_shipment: queryset = queryset.filter(shipment__isnull=True) return queryset.select_related("shipment").order_by("id") @transaction.atomic def create_shipment( customer_id: int, shipment_date, sales_item_ids: List[int], created_by, remark: str = "", area: str = "", ) -> Shipment: """ 创建出货单并关联销售品 Args: customer_id: 客户ID shipment_date: 出货日期 sales_item_ids: 要关联的销售品ID列表 created_by: 创建人 remark: 备注 Returns: 创建的 Shipment 实例 Raises: ValueError: 如果销售品不存在或已被关联到其他出货单 """ from basic_info.models import Customer # 验证客户存在 try: customer = Customer.objects.get(id=customer_id) except Customer.DoesNotExist: raise ValueError(f"客户 {customer_id} 不存在") # merchant 隔离:必须能解析出当前用户 merchant emp = getattr(created_by, "employee", None) merchant = getattr(emp, "merchant", None) if emp else None if not merchant: # superuser 也必须绑定 merchant(避免产生无法隔离的数据) raise ValueError("用户未关联商户,无法创建出货单") if customer.merchant_id != merchant.id: raise ValueError("无权限为该客户创建出货单") # 验证销售品 if sales_item_ids: # 查询销售品 sales_items = SalesItem.objects.filter(id__in=sales_item_ids) found_ids = set(sales_items.values_list("id", flat=True)) missing_ids = set(sales_item_ids) - found_ids if missing_ids: raise ValueError(f"以下销售品不存在: {list(missing_ids)}") # 检查是否有已关联出货单的销售品 already_shipped = sales_items.filter(shipment__isnull=False) if already_shipped.exists(): shipped_ids = list(already_shipped.values_list("id", flat=True)) raise ValueError(f"以下销售品已关联到其他出货单: {shipped_ids}") # 创建出货单 shipment = Shipment.objects.create( merchant=merchant, customer=customer, shipment_date=shipment_date, area=(area or "").strip(), remark=remark, created_by=created_by, ) # 关联销售品 if sales_item_ids: updated = SalesItem.objects.filter( id__in=sales_item_ids, merchant=merchant ).update(shipment=shipment) if updated != len(sales_item_ids): raise ValueError("存在不属于当前商户的销售品,无法关联到出货单") return shipment @transaction.atomic def create_external_shipment( customer_id: int, shipment_date, external_id: str, external_finished_products: List[dict], created_by, remark: str = "", area: str = "", ) -> Shipment: """ 创建出货单(external 版),并批量写入外部成品表并关联到出货单。 特点: - 不绑定任何 SalesItem - external_id 必填 - external_finished_products 必填(至少 1 条) """ from basic_info.models import Customer # 验证客户存在 try: customer = Customer.objects.get(id=customer_id) except Customer.DoesNotExist: raise ValueError(f"客户 {customer_id} 不存在") # merchant 隔离 emp = getattr(created_by, "employee", None) merchant = getattr(emp, "merchant", None) if emp else None if not merchant: raise ValueError("用户未关联商户,无法创建出货单") if customer.merchant_id != merchant.id: raise ValueError("无权限为该客户创建出货单") external_id = (external_id or "").strip() if not external_id: raise ValueError("external_id 不能为空") if not external_finished_products: raise ValueError("external_finished_products 不能为空") shipment = Shipment.objects.create( merchant=merchant, customer=customer, shipment_date=shipment_date, area=(area or "").strip(), remark=remark, created_by=created_by, external_id=external_id, ) objs = [] for item in external_finished_products: objs.append( ExternalFinishedProduct( shipment=shipment, style_name=item.get("style_name", ""), num_of_rolls=item.get("num_of_rolls", 0), remark=item.get("remark") or "", created_by=created_by, ) ) ExternalFinishedProduct.objects.bulk_create(objs) return shipment @transaction.atomic def create_sales_item( printing_job_id: int, name: str, quantity: str, unit: int, created_by, customer_id: int | None = None, remark: str = "", position: str = "", ) -> SalesItem: """ 手动创建销售品 Args: printing_job_id: 生产任务ID(必填) name: 销售品名称 quantity: 数量(字符串,会被转换为Decimal) unit: 单位(1=米, 2=件, 3=码, 4=个) created_by: 创建人 customer_id: 客户ID(可选) remark: 备注(可选) position: 货位(可选) Returns: 创建的 SalesItem 实例 Raises: ValueError: 如果生产任务不存在或不属于当前商户 """ from decimal import Decimal, InvalidOperation from printing.models import PrintingJob # 获取当前用户的商户 emp = getattr(created_by, "employee", None) merchant = getattr(emp, "merchant", None) if emp else None if not merchant: raise ValueError("用户未关联商户,无法创建销售品") # 验证生产任务存在且属于当前商户 try: printing_job = PrintingJob.objects.select_related("printing_order").get( id=printing_job_id, merchant=merchant, ) except PrintingJob.DoesNotExist: raise ValueError(f"生产任务 {printing_job_id} 不存在或不属于当前商户") # 转换数量为Decimal try: quantity_decimal = Decimal(str(quantity)) except (InvalidOperation, ValueError, TypeError) as e: raise ValueError(f"数量 {quantity} 格式无效: {e}") # 如果未提供客户ID,尝试从主订单获取 if ( customer_id is None and printing_job.printing_order and printing_job.printing_order.customer ): customer_id = printing_job.printing_order.customer_id # 创建销售品 sales_item = SalesItem.objects.create( shipment=None, # 初始状态:待分配 merchant=merchant, name=name, quantity=quantity_decimal, unit=unit, created_by=created_by, printing_job_id=printing_job.id, customer_id=customer_id, remark=remark, position=position, ) return sales_item