feat(pl): upsert plan product rows by id on update.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
6a7dfbcd38
commit
857067ec80
3 files changed
+126
-23
No files matched your search
+37
-22
@@ -128,6 +128,7 @@ from app.services.pl_plan_service import (
|
||||
apply_plan_site_feedback,
|
||||
apply_vehicle_prefill_to_plan,
|
||||
assert_contract_selectable_for_plan,
|
||||
apply_plan_product_fields,
|
||||
build_plan_product,
|
||||
calc_plan_total_amount,
|
||||
clear_draft_transport_for_plan,
|
||||
@@ -1640,16 +1641,15 @@ def update_plan(
|
||||
|
||||
plan_type = payload.plan_type or plan.plan_type
|
||||
|
||||
# 更新产品明细
|
||||
# 更新产品明细(按 id upsert,保持 pl_plan_product.id 稳定)
|
||||
if payload.products is not None:
|
||||
existing_products = (
|
||||
existing_all = (
|
||||
db.query(PlPlanProduct)
|
||||
.filter(
|
||||
PlPlanProduct.plan_id == id,
|
||||
PlPlanProduct.del_flag == 0,
|
||||
)
|
||||
.filter(PlPlanProduct.plan_id == id)
|
||||
.all()
|
||||
)
|
||||
existing_all_by_id = {row.id: row for row in existing_all}
|
||||
existing_products = [row for row in existing_all if row.del_flag == 0]
|
||||
old_hazard_product_ids = collect_hazard_product_ids(db, tenant_no, existing_products)
|
||||
cert_by_product_id = {
|
||||
row.product_id: (
|
||||
@@ -1660,11 +1660,7 @@ def update_plan(
|
||||
for row in existing_products
|
||||
}
|
||||
|
||||
db.query(PlPlanProduct).filter(
|
||||
PlPlanProduct.plan_id == id,
|
||||
PlPlanProduct.del_flag == 0,
|
||||
).update({"del_flag": 1})
|
||||
|
||||
kept_ids: set[str] = set()
|
||||
plan_products: list[PlPlanProduct] = []
|
||||
for idx, prod in enumerate(payload.products):
|
||||
is_toxic, is_explosive = resolve_hazard_flags_from_crm(
|
||||
@@ -1689,17 +1685,36 @@ def update_plan(
|
||||
"legal_use_cert_url": legal_use_cert,
|
||||
}
|
||||
)
|
||||
pp = build_plan_product(
|
||||
tenant_no=tenant_no,
|
||||
plan_id=id,
|
||||
plan_type=plan_type,
|
||||
prod=prod_payload,
|
||||
order_num=idx,
|
||||
user_id=user.id,
|
||||
dept_id=user.dept_id,
|
||||
)
|
||||
db.add(pp)
|
||||
plan_products.append(pp)
|
||||
row_id = (prod.id or "").strip() or None
|
||||
existing = existing_all_by_id.get(row_id) if row_id else None
|
||||
if existing is not None:
|
||||
apply_plan_product_fields(
|
||||
existing,
|
||||
plan_type=plan_type,
|
||||
prod=prod_payload,
|
||||
order_num=idx,
|
||||
user_id=user.id,
|
||||
)
|
||||
kept_ids.add(existing.id)
|
||||
plan_products.append(existing)
|
||||
else:
|
||||
pp = build_plan_product(
|
||||
tenant_no=tenant_no,
|
||||
plan_id=id,
|
||||
plan_type=plan_type,
|
||||
prod=prod_payload,
|
||||
order_num=idx,
|
||||
user_id=user.id,
|
||||
dept_id=user.dept_id,
|
||||
)
|
||||
db.add(pp)
|
||||
kept_ids.add(pp.id)
|
||||
plan_products.append(pp)
|
||||
|
||||
for row in existing_products:
|
||||
if row.id not in kept_ids:
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
|
||||
new_hazard_product_ids = collect_hazard_product_ids(db, tenant_no, payload.products)
|
||||
if old_hazard_product_ids != new_hazard_product_ids:
|
||||
|
||||
@@ -111,12 +111,13 @@ def build_plan_product(
|
||||
order_num: int,
|
||||
user_id: str,
|
||||
dept_id: str,
|
||||
product_row_id: Optional[str] = None,
|
||||
) -> PlPlanProduct:
|
||||
qty = _product_quantity(prod)
|
||||
estimates = _estimate_qty_fields(plan_type, qty if qty else None)
|
||||
tax_price = prod.tax_price
|
||||
return PlPlanProduct(
|
||||
id=generate_uuid(),
|
||||
id=(product_row_id or "").strip() or generate_uuid(),
|
||||
tenant_no=tenant_no,
|
||||
plan_id=plan_id,
|
||||
product_id=prod.product_id,
|
||||
@@ -152,6 +153,51 @@ def build_plan_product(
|
||||
)
|
||||
|
||||
|
||||
def apply_plan_product_fields(
|
||||
row: PlPlanProduct,
|
||||
*,
|
||||
plan_type: str,
|
||||
prod: PlanProductCreate,
|
||||
order_num: int,
|
||||
user_id: str,
|
||||
) -> None:
|
||||
"""就地更新已有计划产品行字段(保持 id),用于 update upsert。"""
|
||||
qty = _product_quantity(prod)
|
||||
estimates = _estimate_qty_fields(plan_type, qty if qty else None)
|
||||
tax_price = prod.tax_price
|
||||
row.product_id = prod.product_id
|
||||
row.product_no = prod.product_no
|
||||
row.product_name = prod.product_name
|
||||
row.brand_mode = prod.brand_mode
|
||||
row.brand_id = prod.brand_id
|
||||
row.brand_name = prod.brand_name
|
||||
row.model = prod.model
|
||||
row.unit = prod.unit or "吨"
|
||||
row.category_name = prod.category_name
|
||||
row.package_spec = prod.package_spec
|
||||
row.delivery_method = prod.delivery_method
|
||||
row.quantity = qty if qty else None
|
||||
row.actual_qty = Decimal(str(prod.actual_qty)) if prod.actual_qty is not None else None
|
||||
row.tax_price = tax_price
|
||||
row.tax_amount = prod.tax_amount
|
||||
row.tax_subtotal = prod.tax_subtotal
|
||||
row.is_explosive = prod.is_explosive
|
||||
row.is_toxic = prod.is_toxic
|
||||
row.purchase_cert_url = prod.purchase_cert_url
|
||||
row.transport_cert_url = prod.transport_cert_url
|
||||
row.legal_use_cert_url = prod.legal_use_cert_url
|
||||
row.inbound_price = tax_price
|
||||
row.outbound_price = tax_price
|
||||
row.inbound_amount = prod.tax_subtotal
|
||||
row.outbound_amount = prod.tax_subtotal
|
||||
row.remark = prod.remark
|
||||
row.order_num = order_num
|
||||
row.update_by = user_id
|
||||
row.del_flag = 0
|
||||
for key, value in estimates.items():
|
||||
setattr(row, key, value)
|
||||
|
||||
|
||||
def _apply_vehicle_snapshot_to_plan_transport(
|
||||
db: Session,
|
||||
*,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""build_plan_product 应支持传入 product_row_id,编辑时保持 pl_plan_product.id 稳定。"""
|
||||
from app.schemas.pl.plan import PlanProductCreate
|
||||
from app.services import pl_plan_service as svc
|
||||
from app.services.pl_plan_service import build_plan_product
|
||||
|
||||
|
||||
def _minimal_prod(**kwargs) -> PlanProductCreate:
|
||||
data = {
|
||||
"product_id": "crm-p1",
|
||||
"product_no": "P001",
|
||||
"product_name": "甲醇",
|
||||
}
|
||||
data.update(kwargs)
|
||||
return PlanProductCreate(**data)
|
||||
|
||||
|
||||
def test_build_plan_product_keeps_row_id():
|
||||
result = build_plan_product(
|
||||
tenant_no="t1",
|
||||
plan_id="plan-1",
|
||||
plan_type="outbound",
|
||||
prod=_minimal_prod(),
|
||||
order_num=0,
|
||||
user_id="u1",
|
||||
dept_id="d1",
|
||||
product_row_id="pp-1",
|
||||
)
|
||||
assert result.id == "pp-1"
|
||||
|
||||
|
||||
def test_build_plan_product_generates_when_missing(monkeypatch):
|
||||
monkeypatch.setattr(svc, "generate_uuid", lambda: "gen-1")
|
||||
result = build_plan_product(
|
||||
tenant_no="t1",
|
||||
plan_id="plan-1",
|
||||
plan_type="outbound",
|
||||
prod=_minimal_prod(),
|
||||
order_num=0,
|
||||
user_id="u1",
|
||||
dept_id="d1",
|
||||
)
|
||||
assert result.id == "gen-1"
|
||||
Reference in new issue
Block a user