forked from erp-dev/erp
feat: shipment patch
This commit is contained in:
@@ -9,8 +9,9 @@ from typing import List
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Exists, IntegerField, OuterRef, QuerySet, Subquery
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
|
||||
from shipment.models import ExternalFinishedProduct, SalesItem, Shipment
|
||||
from shipment.models import ExternalFinishedProduct, SalesItem, Shipment, ShipmentStatus
|
||||
|
||||
|
||||
def get_customers_with_unshipped_sales_items(*, merchant) -> QuerySet:
|
||||
@@ -48,24 +49,50 @@ def get_customers_with_unshipped_sales_items(*, merchant) -> QuerySet:
|
||||
|
||||
|
||||
def get_sales_items_by_printing_order(
|
||||
printing_order_id: int,
|
||||
printing_order_id: int | str,
|
||||
include_already_has_shipment: bool = False,
|
||||
merchant=None,
|
||||
) -> QuerySet[SalesItem]:
|
||||
"""
|
||||
通过生产订单ID查询对应的销售品
|
||||
通过生产订单 ID 或 external_order_id 查询对应的销售品。
|
||||
|
||||
Args:
|
||||
printing_order_id: 生产订单ID
|
||||
printing_order_id: 生产订单内部 ID,或 external_order_id
|
||||
include_already_has_shipment: 是否包含已关联出货单的销售品,默认为 False
|
||||
merchant: 可选的商户约束;传入后会限定只在该商户下解析生产订单
|
||||
|
||||
Returns:
|
||||
SalesItem 查询集
|
||||
"""
|
||||
from printing.models import PrintingJob
|
||||
from printing.models import PrintingJob, PrintingOrder
|
||||
|
||||
# 1. 获取该生产订单下所有 PrintingJob 的 ID
|
||||
raw_value = str(printing_order_id).strip()
|
||||
order_queryset = PrintingOrder.objects.all()
|
||||
if merchant is not None:
|
||||
order_queryset = order_queryset.filter(merchant=merchant)
|
||||
|
||||
resolved_order_ids: list[int] = []
|
||||
|
||||
# 优先按内部 ID 解析,保持和当前 API 路径语义一致。
|
||||
if raw_value.isdigit():
|
||||
resolved_order_ids = list(
|
||||
order_queryset.filter(id=int(raw_value)).values_list("id", flat=True)[:1]
|
||||
)
|
||||
|
||||
# 内部 ID 未命中时,再按 external_order_id 查询。
|
||||
if not resolved_order_ids and raw_value:
|
||||
resolved_order_ids = list(
|
||||
order_queryset.filter(external_order_id=raw_value).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
)
|
||||
|
||||
if not resolved_order_ids:
|
||||
return SalesItem.objects.none().select_related("shipment").order_by("id")
|
||||
|
||||
# 1. 获取目标生产订单下所有 PrintingJob 的 ID
|
||||
job_ids = PrintingJob.objects.filter(
|
||||
printing_order_id=printing_order_id
|
||||
printing_order_id__in=resolved_order_ids
|
||||
).values_list("id", flat=True)
|
||||
|
||||
# 2. 查询 SalesItem,过滤 printing_job_id 在这些 job_ids 中
|
||||
@@ -110,6 +137,76 @@ def get_sales_items_by_customer(
|
||||
return queryset.select_related("shipment").order_by("id")
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def modify_status(
|
||||
shipment: Shipment,
|
||||
*,
|
||||
target_status: int,
|
||||
operator=None,
|
||||
approved_by=None,
|
||||
) -> Shipment:
|
||||
"""
|
||||
修改出货单状态,并记录状态修改时间。
|
||||
|
||||
规则:
|
||||
- 草稿 -> 只能已发布
|
||||
- 已发布 -> 已审核 / 已驳回 / 已取消
|
||||
- 已驳回 -> 已审核 / 已取消
|
||||
- 已审核 -> 已取消
|
||||
- 已取消 -> 不可再变
|
||||
- 重复设置同一状态保持幂等,直接返回
|
||||
"""
|
||||
current_status = shipment.status
|
||||
if current_status == target_status:
|
||||
return shipment
|
||||
|
||||
allowed_transitions = {
|
||||
ShipmentStatus.DRAFT: {ShipmentStatus.PUBLISHED},
|
||||
ShipmentStatus.PUBLISHED: {
|
||||
ShipmentStatus.APPROVED,
|
||||
ShipmentStatus.REJECTED,
|
||||
ShipmentStatus.CANCELLED,
|
||||
},
|
||||
ShipmentStatus.REJECTED: {
|
||||
ShipmentStatus.APPROVED,
|
||||
ShipmentStatus.CANCELLED,
|
||||
},
|
||||
ShipmentStatus.APPROVED: {
|
||||
ShipmentStatus.CANCELLED,
|
||||
},
|
||||
ShipmentStatus.CANCELLED: set(),
|
||||
}
|
||||
|
||||
if target_status not in allowed_transitions.get(current_status, set()):
|
||||
raise ValueError(
|
||||
f"不允许将出货单状态从 {shipment.get_status_display()} 修改为 "
|
||||
f"{ShipmentStatus(target_status).label}"
|
||||
)
|
||||
|
||||
if target_status == ShipmentStatus.APPROVED and approved_by is None:
|
||||
raise ValueError("目标状态为已审核时,approved_by 不能为空")
|
||||
|
||||
shipment.status = target_status
|
||||
shipment.status_modified_at = timezone.now()
|
||||
|
||||
if target_status == ShipmentStatus.CANCELLED:
|
||||
shipment.cancelled_by = operator
|
||||
|
||||
if target_status == ShipmentStatus.APPROVED:
|
||||
shipment.approved_by = approved_by
|
||||
|
||||
shipment.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"status_modified_at",
|
||||
"cancelled_by",
|
||||
"approved_by",
|
||||
"updated_at",
|
||||
]
|
||||
)
|
||||
return shipment
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def create_shipment(
|
||||
customer_id: int,
|
||||
@@ -154,6 +251,8 @@ def create_shipment(
|
||||
|
||||
# 验证销售品
|
||||
if sales_item_ids:
|
||||
from printing.models import PrintingJob
|
||||
|
||||
# 查询销售品
|
||||
sales_items = SalesItem.objects.filter(id__in=sales_item_ids)
|
||||
found_ids = set(sales_items.values_list("id", flat=True))
|
||||
@@ -168,6 +267,40 @@ def create_shipment(
|
||||
shipped_ids = list(already_shipped.values_list("id", flat=True))
|
||||
raise ValueError(f"以下销售品已关联到其他出货单: {shipped_ids}")
|
||||
|
||||
sales_items_list = list(sales_items)
|
||||
missing_printing_job_ids = [
|
||||
item.id for item in sales_items_list if not item.printing_job_id
|
||||
]
|
||||
if missing_printing_job_ids:
|
||||
raise ValueError(
|
||||
f"以下销售品缺少关联生产任务,无法创建出货单: {missing_printing_job_ids}"
|
||||
)
|
||||
|
||||
printing_job_ids = {item.printing_job_id for item in sales_items_list}
|
||||
printing_job_map = {
|
||||
job.id: job.printing_order_id
|
||||
for job in PrintingJob.objects.filter(id__in=printing_job_ids).only(
|
||||
"id", "printing_order_id"
|
||||
)
|
||||
}
|
||||
|
||||
invalid_printing_job_ids = sorted(printing_job_ids - set(printing_job_map.keys()))
|
||||
if invalid_printing_job_ids:
|
||||
affected_item_ids = sorted(
|
||||
item.id
|
||||
for item in sales_items_list
|
||||
if item.printing_job_id in invalid_printing_job_ids
|
||||
)
|
||||
raise ValueError(
|
||||
f"以下销售品关联的生产任务不存在,无法创建出货单: {affected_item_ids}"
|
||||
)
|
||||
|
||||
printing_order_ids = {
|
||||
printing_job_map[item.printing_job_id] for item in sales_items_list
|
||||
}
|
||||
if len(printing_order_ids) > 1:
|
||||
raise ValueError("出货单中的销售品必须来自同一个生产订单")
|
||||
|
||||
# 创建出货单
|
||||
shipment = Shipment.objects.create(
|
||||
merchant=merchant,
|
||||
|
||||
Reference in New Issue
Block a user