|
|
@@ -34,6 +34,7 @@ from app.services.pl_feedback_brand_qty import (
|
|
|
persist_io_item_brand_qtys,
|
|
|
require_brand_qtys_if_needed,
|
|
|
)
|
|
|
+from app.services.pl_product_display import apply_product_alias_display, load_crm_product_alias_maps
|
|
|
|
|
|
|
|
|
OrderHeader = Union[PlInboundOrder, PlOutboundOrder]
|
|
|
@@ -352,6 +353,19 @@ def update_io_order_items_qty_and_brand(
|
|
|
.all()
|
|
|
)
|
|
|
products_by_id = {r.id: r for r in rows}
|
|
|
+ # 明细仍指向已软删计划产品行时,用原行品牌约定(避免 brand_mode 空导致只删不写)
|
|
|
+ missing_ids = [pid for pid in plan_product_ids if pid and pid not in products_by_id]
|
|
|
+ if missing_ids:
|
|
|
+ stale_rows = (
|
|
|
+ db.query(PlPlanProduct)
|
|
|
+ .filter(
|
|
|
+ PlPlanProduct.id.in_(missing_ids),
|
|
|
+ PlPlanProduct.tenant_no == tenant_no,
|
|
|
+ )
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ for row in stale_rows:
|
|
|
+ products_by_id.setdefault(row.id, row)
|
|
|
|
|
|
brand_model = _io_item_brand_qty_model(kind)
|
|
|
|
|
|
@@ -364,6 +378,9 @@ def update_io_order_items_qty_and_brand(
|
|
|
brand_touched = item_brand_qtys is not None and item.id in item_brand_qtys
|
|
|
if brand_touched:
|
|
|
rows = item_brand_qtys[item.id]
|
|
|
+ # 客户端带了品牌明细时,即使计划行 brand_mode 丢失也按 any 规则校验/落库
|
|
|
+ if not brand_mode and rows:
|
|
|
+ brand_mode = "any"
|
|
|
elif enforce_brand:
|
|
|
rows = []
|
|
|
else:
|
|
|
@@ -492,27 +509,34 @@ def merge_plan_product_into_item(
|
|
|
) -> None:
|
|
|
if not product:
|
|
|
item.setdefault("productNo", None)
|
|
|
+ item.setdefault("categoryName", None)
|
|
|
item.setdefault("packageSpec", None)
|
|
|
item.setdefault("deliveryMethod", None)
|
|
|
item.setdefault("remark", None)
|
|
|
item["unitLabel"] = resolve_unit_label(unit_map, item.get("unit"))
|
|
|
return
|
|
|
# 优先 CRM 产品编码;计划冗余 product_no 可能被误存为 product_id
|
|
|
- product_id = (product.product_id or item.get("productId") or "").strip()
|
|
|
+ product_id = (product.product_id or item.get("productId") or item.get("product_id") or "").strip()
|
|
|
plan_no = (product.product_no or "").strip()
|
|
|
resolved_no = (crm_product_no or "").strip()
|
|
|
if not resolved_no and plan_no and plan_no != product_id:
|
|
|
resolved_no = plan_no
|
|
|
+ # CRM 缺失时仍回退计划 product_no,避免前端编码列空白
|
|
|
+ if not resolved_no and plan_no:
|
|
|
+ resolved_no = plan_no
|
|
|
item["productNo"] = resolved_no or None
|
|
|
- item["packageSpec"] = product.package_spec
|
|
|
+ item["categoryName"] = product.category_name
|
|
|
+ item["packageSpec"] = product.package_spec or item.get("packageSpec")
|
|
|
item["deliveryMethod"] = product.delivery_method
|
|
|
- item["remark"] = (product.remark or "").strip() or None
|
|
|
+ item["remark"] = (product.remark or "").strip() or item.get("remark")
|
|
|
if not item.get("specification") and product.model:
|
|
|
item["specification"] = product.model
|
|
|
item["unitLabel"] = resolve_unit_label(unit_map, item.get("unit") or product.unit)
|
|
|
# 无品牌拆分量时,回退计划产品上的品牌快照
|
|
|
if product.brand_name and not item.get("brandSummary"):
|
|
|
item["brandName"] = product.brand_name
|
|
|
+ if product.brand_mode:
|
|
|
+ item.setdefault("brandMode", product.brand_mode)
|
|
|
|
|
|
|
|
|
def _format_io_product_summary_line(item: dict) -> str:
|
|
|
@@ -562,8 +586,11 @@ def enrich_io_items_with_brand_feedback(
|
|
|
item_id = (it.get("id") or "").strip()
|
|
|
if item_id:
|
|
|
order_item_ids.append(item_id)
|
|
|
- pp = products_by_id.get(it.get("planProductId") or "")
|
|
|
- product_id = ((pp.product_id if pp else None) or it.get("productId") or "").strip()
|
|
|
+ plan_product_id = (it.get("planProductId") or it.get("plan_product_id") or "").strip()
|
|
|
+ pp = products_by_id.get(plan_product_id)
|
|
|
+ product_id = (
|
|
|
+ (pp.product_id if pp else None) or it.get("productId") or it.get("product_id") or ""
|
|
|
+ ).strip()
|
|
|
if product_id:
|
|
|
crm_product_ids.add(product_id)
|
|
|
|
|
|
@@ -608,8 +635,11 @@ def enrich_io_items_with_brand_feedback(
|
|
|
)
|
|
|
|
|
|
for it in items:
|
|
|
- pp = products_by_id.get(it.get("planProductId") or "")
|
|
|
- product_id = ((pp.product_id if pp else None) or it.get("productId") or "").strip()
|
|
|
+ plan_product_id = (it.get("planProductId") or it.get("plan_product_id") or "").strip()
|
|
|
+ pp = products_by_id.get(plan_product_id)
|
|
|
+ product_id = (
|
|
|
+ (pp.product_id if pp else None) or it.get("productId") or it.get("product_id") or ""
|
|
|
+ ).strip()
|
|
|
|
|
|
if pp:
|
|
|
it["brandMode"] = pp.brand_mode
|
|
|
@@ -667,9 +697,9 @@ def enrich_io_order_dict(
|
|
|
return data
|
|
|
|
|
|
product_ids = [
|
|
|
- it.get("planProductId")
|
|
|
+ (it.get("planProductId") or it.get("plan_product_id") or "").strip()
|
|
|
for it in raw_items
|
|
|
- if it.get("planProductId")
|
|
|
+ if (it.get("planProductId") or it.get("plan_product_id") or "").strip()
|
|
|
]
|
|
|
products_by_id: dict[str, PlPlanProduct] = {}
|
|
|
if product_ids:
|
|
|
@@ -683,6 +713,39 @@ def enrich_io_order_dict(
|
|
|
.all()
|
|
|
)
|
|
|
products_by_id = {r.id: r for r in rows}
|
|
|
+ # 明细仍挂已软删计划产品时,保留原行用于品牌约定与 CRM 产品定位(勿误用其它在用产品)
|
|
|
+ missing_ids = [pid for pid in product_ids if pid and pid not in products_by_id]
|
|
|
+ if missing_ids:
|
|
|
+ stale_rows = (
|
|
|
+ db.query(PlPlanProduct)
|
|
|
+ .filter(
|
|
|
+ PlPlanProduct.id.in_(missing_ids),
|
|
|
+ PlPlanProduct.tenant_no == tenant_no,
|
|
|
+ )
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ for row in stale_rows:
|
|
|
+ products_by_id.setdefault(row.id, row)
|
|
|
+
|
|
|
+ # 计划产品行可能被编辑重建导致 plan_product_id 失效:按计划再拉全量做 product_id / 序号兜底
|
|
|
+ plan_products: list[PlPlanProduct] = []
|
|
|
+ products_by_crm_id: dict[str, PlPlanProduct] = {}
|
|
|
+ if plan_id:
|
|
|
+ plan_products = (
|
|
|
+ db.query(PlPlanProduct)
|
|
|
+ .filter(
|
|
|
+ PlPlanProduct.plan_id == plan_id,
|
|
|
+ PlPlanProduct.tenant_no == tenant_no,
|
|
|
+ PlPlanProduct.del_flag == 0,
|
|
|
+ )
|
|
|
+ .order_by(PlPlanProduct.order_num.asc(), PlPlanProduct.create_time.asc())
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ for row in plan_products:
|
|
|
+ products_by_id.setdefault(row.id, row)
|
|
|
+ crm_key = (row.product_id or "").strip()
|
|
|
+ if crm_key and crm_key not in products_by_crm_id:
|
|
|
+ products_by_crm_id[crm_key] = row
|
|
|
|
|
|
crm_ids = {
|
|
|
(pp.product_id or "").strip()
|
|
|
@@ -690,7 +753,7 @@ def enrich_io_order_dict(
|
|
|
if (pp.product_id or "").strip()
|
|
|
}
|
|
|
for it in raw_items:
|
|
|
- pid = (it.get("productId") or "").strip()
|
|
|
+ pid = (it.get("productId") or it.get("product_id") or "").strip()
|
|
|
if pid:
|
|
|
crm_ids.add(pid)
|
|
|
crm_no_by_id: dict[str, str] = {}
|
|
|
@@ -709,15 +772,45 @@ def enrich_io_order_dict(
|
|
|
if no:
|
|
|
crm_no_by_id[row.id] = no
|
|
|
|
|
|
- for it in raw_items:
|
|
|
- pp = products_by_id.get(it.get("planProductId") or "")
|
|
|
- product_id = ((pp.product_id if pp else None) or it.get("productId") or "").strip()
|
|
|
+ standard_by_id, alias_options_by_id = load_crm_product_alias_maps(
|
|
|
+ db, tenant_no=tenant_no, product_ids=crm_ids
|
|
|
+ )
|
|
|
+
|
|
|
+ for idx, it in enumerate(raw_items):
|
|
|
+ plan_product_id = (it.get("planProductId") or it.get("plan_product_id") or "").strip()
|
|
|
+ if plan_product_id and not it.get("planProductId"):
|
|
|
+ it["planProductId"] = plan_product_id
|
|
|
+ pp = products_by_id.get(plan_product_id) if plan_product_id else None
|
|
|
+ # 仅在明细未挂 planProductId 时用 CRM/序号兜底;已挂 id 时勿换成其它在用产品(会错带指定品牌)
|
|
|
+ if not pp and not plan_product_id:
|
|
|
+ crm_key = (it.get("productId") or it.get("product_id") or "").strip()
|
|
|
+ if crm_key:
|
|
|
+ pp = products_by_crm_id.get(crm_key)
|
|
|
+ if not pp and len(plan_products) == len(raw_items):
|
|
|
+ pp = plan_products[idx]
|
|
|
+ if pp and not it.get("planProductId"):
|
|
|
+ it["planProductId"] = pp.id
|
|
|
+ product_id = (
|
|
|
+ (pp.product_id if pp else None) or it.get("productId") or it.get("product_id") or ""
|
|
|
+ ).strip()
|
|
|
+ # 优先用计划产品已存名(可能是别名)做拆分;明细行 productName 同步
|
|
|
+ saved_name = (pp.product_name if pp else None) or it.get("productName") or it.get(
|
|
|
+ "product_name"
|
|
|
+ )
|
|
|
+ if saved_name:
|
|
|
+ it["productName"] = saved_name
|
|
|
merge_plan_product_into_item(
|
|
|
it,
|
|
|
pp,
|
|
|
unit_map=unit_map,
|
|
|
crm_product_no=crm_no_by_id.get(product_id),
|
|
|
)
|
|
|
+ apply_product_alias_display(
|
|
|
+ it,
|
|
|
+ product_id=product_id,
|
|
|
+ standard_by_id=standard_by_id,
|
|
|
+ alias_options_by_id=alias_options_by_id,
|
|
|
+ )
|
|
|
if public_unit_as_label:
|
|
|
it["unit"] = it.get("unitLabel") or resolve_unit_label(unit_map, it.get("unit"))
|
|
|
|