1
0
forked from erp-dev/erp

feat: salesitem can change/soft delete

This commit is contained in:
2026-04-06 14:49:02 +08:00
parent d7bc43082e
commit 9afbb73799
16 changed files with 1075 additions and 10 deletions

View File

@@ -4,6 +4,7 @@ Shipment 模块业务逻辑层
from __future__ import annotations
from decimal import Decimal, InvalidOperation
from typing import List
from django.db import transaction
@@ -14,6 +15,7 @@ from django.utils import timezone
from shipment.models import (
ExternalFinishedProduct,
SalesItem,
SalesItemChangeRecord,
Shipment,
ShipmentDelivery,
ShipmentDeliveryStatus,
@@ -82,6 +84,7 @@ def get_customers_with_unshipped_sales_items(*, merchant) -> QuerySet:
base_sales_items = SalesItem.objects.filter(
merchant=merchant,
shipment__isnull=True,
delete_at__isnull=True,
customer_id=OuterRef("pk"),
)
count_subquery = (
@@ -153,6 +156,7 @@ def get_sales_items_by_printing_order(
# 2. 查询 SalesItem过滤 printing_job_id 在这些 job_ids 中
queryset = SalesItem.objects.filter(printing_job_id__in=list(job_ids))
queryset = queryset.filter(delete_at__isnull=True)
# 3. 根据参数决定是否过滤已出货的销售品
if not include_already_has_shipment:
@@ -176,6 +180,7 @@ def get_sales_items_by_customer(
queryset = SalesItem.objects.filter(
merchant=merchant,
customer_id=customer_id,
delete_at__isnull=True,
)
if external_order_id:
@@ -193,6 +198,91 @@ def get_sales_items_by_customer(
return queryset.select_related("shipment").order_by("id")
@transaction.atomic
def update_sales_item(
sales_item: SalesItem,
*,
quantity: str | Decimal | None = None,
remark: str | None = None,
position: str | None = None,
operator=None,
) -> SalesItem:
"""
更新销售品的非关系字段。
当前仅允许修改:
- quantity
- remark
- position
"""
changes: dict[str, tuple[object, object]] = {}
if quantity is not None:
try:
normalized_quantity = Decimal(str(quantity))
except (InvalidOperation, ValueError, TypeError) as exc:
raise ValueError(f"数量 {quantity} 格式无效: {exc}")
if normalized_quantity != sales_item.quantity:
changes["quantity"] = (sales_item.quantity, normalized_quantity)
sales_item.quantity = normalized_quantity
if remark is not None:
normalized_remark = remark or ""
if normalized_remark != sales_item.remark:
changes["remark"] = (sales_item.remark, normalized_remark)
sales_item.remark = normalized_remark
if position is not None:
normalized_position = position or ""
if normalized_position != sales_item.position:
changes["position"] = (sales_item.position, normalized_position)
sales_item.position = normalized_position
if not changes:
return sales_item
sales_item.save(update_fields=[*changes.keys(), "updated_at"])
def _serialize_value(value):
if isinstance(value, Decimal):
return str(value)
return value
SalesItemChangeRecord.objects.create(
sales_item=sales_item,
operator=operator,
operated_at=timezone.now(),
before_values={
field: _serialize_value(old_value)
for field, (old_value, _) in changes.items()
},
after_values={
field: _serialize_value(new_value)
for field, (_, new_value) in changes.items()
},
)
return sales_item
@transaction.atomic
def delete_sales_item(
sales_item: SalesItem,
*,
deleted_by=None,
) -> SalesItem:
"""
软删除销售品。
"""
if sales_item.delete_at is not None:
return sales_item
sales_item.delete_at = timezone.now()
sales_item.delete_by = deleted_by
sales_item.save(update_fields=["delete_at", "delete_by", "updated_at"])
return sales_item
@transaction.atomic
def create_shipment_delivery(
*,
@@ -579,7 +669,7 @@ def create_shipment(
from printing.models import PrintingJob
# 查询销售品
sales_items = SalesItem.objects.filter(id__in=sales_item_ids)
sales_items = SalesItem.objects.filter(id__in=sales_item_ids, delete_at__isnull=True)
found_ids = set(sales_items.values_list("id", flat=True))
missing_ids = set(sales_item_ids) - found_ids
@@ -642,7 +732,7 @@ def create_shipment(
# 关联销售品
if sales_item_ids:
updated = SalesItem.objects.filter(
id__in=sales_item_ids, merchant=merchant
id__in=sales_item_ids, merchant=merchant, delete_at__isnull=True
).update(shipment=shipment)
if updated != len(sales_item_ids):
raise ValueError("存在不属于当前商户的销售品,无法关联到出货单")
@@ -752,7 +842,6 @@ def create_sales_item(
Raises:
ValueError: 如果生产任务不存在或不属于当前商户
"""
from decimal import Decimal, InvalidOperation
from printing.models import PrintingJob
# 获取当前用户的商户