forked from erp-dev/erp
feat: salesitem can change/soft delete
This commit is contained in:
@@ -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
|
||||
|
||||
# 获取当前用户的商户
|
||||
|
||||
Reference in New Issue
Block a user