feat(pl): remount dibang monthly plan when partner or product changes
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
180b5cdaa7
commit
503b388a3f
2 files changed
+247
-8
No files matched your search
@@ -698,21 +698,29 @@ def _update_existing_stop_inventory(
|
||||
"""就地强制同步出入库正文(含已过磅),并回写关联过磅单业务字段。"""
|
||||
label = inventory.batch_no or inventory.id
|
||||
|
||||
# —— 强制更新 partner / vehicle / 产品快照 / quantity / date(跨月改挂)——
|
||||
if partner and (inventory.partner_company or "").strip() != partner:
|
||||
old_partner = (inventory.partner_company or "").strip()
|
||||
old_product_id = (inventory.product_id or "").strip()
|
||||
old_spec = (inventory.specification or "").strip()
|
||||
old_plate = (inventory.vehicle_no or "").strip()
|
||||
old_date = _normalize_to_date(inventory.date)
|
||||
|
||||
# —— 强制更新 partner / vehicle / 产品快照 / quantity ——
|
||||
if partner and old_partner != partner:
|
||||
inventory.partner_company = partner
|
||||
|
||||
plate = (license_plate or "").strip()
|
||||
if plate and (inventory.vehicle_no or "").strip() != plate:
|
||||
plate_changed = bool(plate and old_plate != plate)
|
||||
if plate_changed:
|
||||
inventory.vehicle_no = plate
|
||||
|
||||
new_product_id = (product.product_id or "").strip()
|
||||
if new_product_id:
|
||||
incoming_product_id = (product.product_id or "").strip()
|
||||
if incoming_product_id:
|
||||
snapshot = resolve_crm_product(
|
||||
db, tenant_no, new_product_id, require_on_shelf=False
|
||||
db, tenant_no, incoming_product_id, require_on_shelf=False
|
||||
)
|
||||
apply_product_snapshot_to_inventory(inventory, snapshot)
|
||||
|
||||
# 数量差量先作用于当前挂靠月计划(改挂前);改挂时会整额扣回旧计划
|
||||
new_qty = float(product.quantity)
|
||||
old_qty = _resolve_link_contributed_qty(link, inventory)
|
||||
if abs(new_qty - old_qty) > 1e-9:
|
||||
@@ -732,7 +740,57 @@ def _update_existing_stop_inventory(
|
||||
inventory.quantity = Decimal(str(new_qty))
|
||||
link.contributed_qty = Decimal(str(new_qty))
|
||||
|
||||
old_date = _normalize_to_date(inventory.date)
|
||||
new_partner = (inventory.partner_company or "").strip()
|
||||
new_product_id = (inventory.product_id or "").strip()
|
||||
new_spec = (inventory.specification or "").strip()
|
||||
need_remount = (
|
||||
(partner and old_partner != new_partner)
|
||||
or (old_product_id and new_product_id and old_product_id != new_product_id)
|
||||
or (old_spec != new_spec and bool(new_product_id or old_product_id))
|
||||
)
|
||||
|
||||
remounted = False
|
||||
if need_remount:
|
||||
qty = _resolve_link_contributed_qty(link, inventory)
|
||||
_subtract_plan_contribution(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
link=link,
|
||||
inventory=inventory,
|
||||
user_id=user_id,
|
||||
)
|
||||
try:
|
||||
new_db_plan = _ensure_monthly_db_plan_for_inventory(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
plan=plan,
|
||||
inventory=inventory,
|
||||
dibang_date=dibang_date,
|
||||
contributed_qty=qty,
|
||||
user_id=user_id,
|
||||
dept_id=dept_id or "",
|
||||
)
|
||||
link.db_purchase_plan_id = new_db_plan.id
|
||||
inventory.purchase_plan_id = new_db_plan.id
|
||||
inventory.plan_no = (new_db_plan.plan_no or "").strip() or inventory.plan_no
|
||||
remounted = True
|
||||
except ValueError as exc:
|
||||
warnings.append(f"出入库单 {label} 往来/产品改挂失败:{exc}")
|
||||
elif plate_changed and link.db_purchase_plan_id:
|
||||
db_plan = (
|
||||
db.query(DbPurchasePlan)
|
||||
.filter(
|
||||
DbPurchasePlan.id == link.db_purchase_plan_id,
|
||||
DbPurchasePlan.tenant_no == tenant_no,
|
||||
DbPurchasePlan.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if db_plan:
|
||||
db_plan.vehicle_no = plate
|
||||
db_plan.update_by = user_id
|
||||
|
||||
# —— date:同月仅改日期;跨月改挂(本轮已因往来/产品改挂则不再二次扣量)——
|
||||
if old_date != dibang_date:
|
||||
same_month = (
|
||||
old_date is not None
|
||||
@@ -740,7 +798,7 @@ def _update_existing_stop_inventory(
|
||||
and old_date.month == dibang_date.month
|
||||
)
|
||||
inventory.date = dibang_date
|
||||
if not same_month:
|
||||
if not same_month and not remounted:
|
||||
qty = _resolve_link_contributed_qty(link, inventory)
|
||||
_subtract_plan_contribution(
|
||||
db,
|
||||
|
||||
@@ -458,3 +458,184 @@ def test_upsert_updates_inventory_and_weighing_plate_when_already_weighed(
|
||||
assert slip.tare_weight == 18730.0
|
||||
assert slip.net_weight == -18730.0
|
||||
assert not any("未同步" in w for w in warnings)
|
||||
|
||||
|
||||
@patch("app.services.pl_dibang_bridge_service._inventory_has_weighing_slip", return_value=False)
|
||||
@patch("app.services.pl_dibang_bridge_service._create_db_plan")
|
||||
@patch("app.services.pl_dibang_bridge_service.find_monthly_db_plan")
|
||||
@patch("app.services.pl_dibang_bridge_service.resolve_crm_product")
|
||||
def test_upsert_remounts_monthly_plan_when_partner_changes(
|
||||
mock_crm,
|
||||
mock_find_plan,
|
||||
mock_create_plan,
|
||||
_mock_weighing,
|
||||
):
|
||||
"""往来单位变更:同月利旧月计划并改挂,出入库 id 不变。"""
|
||||
mock_crm.return_value = MagicMock(
|
||||
product_id="prod-1",
|
||||
product_name="硫酸",
|
||||
specification="98%",
|
||||
)
|
||||
old_partner = "客户旧"
|
||||
new_partner = "客户新"
|
||||
old_plan = DbPurchasePlan(
|
||||
id="dbp-old",
|
||||
tenant_no="t1",
|
||||
plan_no="SX-OLD",
|
||||
customer_name=old_partner,
|
||||
planned_quantity=30.0,
|
||||
del_flag=0,
|
||||
)
|
||||
new_plan = DbPurchasePlan(
|
||||
id="dbp-new",
|
||||
tenant_no="t1",
|
||||
plan_no="SX-NEW",
|
||||
customer_name=new_partner,
|
||||
planned_quantity=0.0,
|
||||
del_flag=0,
|
||||
)
|
||||
mock_find_plan.return_value = new_plan
|
||||
|
||||
plan = PlPlan(
|
||||
id="plan-1",
|
||||
plan_no="PO1",
|
||||
plan_type="outbound",
|
||||
plan_date=date(2026, 9, 1),
|
||||
pickup_time=datetime(2026, 9, 1, 0, 0),
|
||||
license_plate="皖A12345",
|
||||
tenant_no="t1",
|
||||
)
|
||||
transport = PlTransport(
|
||||
id="tr-1", tenant_no="t1", license_plate="皖A12345", del_flag=0
|
||||
)
|
||||
load = PlPlanLoad(
|
||||
id="load-1",
|
||||
plan_id="plan-1",
|
||||
customer_name="山峡",
|
||||
generate_dibang=1,
|
||||
dibang_direction="outbound",
|
||||
load_time=datetime(2026, 9, 1, 0, 0),
|
||||
tenant_no="t1",
|
||||
del_flag=0,
|
||||
)
|
||||
# 装货段往来取送货客户:设为新往来,触发改挂
|
||||
delivery = PlPlanDelivery(
|
||||
id="del-1",
|
||||
plan_id="plan-1",
|
||||
customer_name=new_partner,
|
||||
seq=1,
|
||||
generate_dibang=0,
|
||||
tenant_no="t1",
|
||||
del_flag=0,
|
||||
)
|
||||
stop_product = PlPlanLoadProduct(
|
||||
id="lp-1",
|
||||
load_id="load-1",
|
||||
plan_product_id="pp-1",
|
||||
product_id="prod-1",
|
||||
product_name="硫酸",
|
||||
model="98%",
|
||||
unit="吨",
|
||||
plan_qty=Decimal("30"),
|
||||
tenant_no="t1",
|
||||
del_flag=0,
|
||||
order_num=1,
|
||||
)
|
||||
inventory = DbInboundOutbound(
|
||||
id="inv-1",
|
||||
tenant_no="t1",
|
||||
date=date(2026, 9, 1),
|
||||
partner_company=old_partner,
|
||||
business_type="出库",
|
||||
product_id="prod-1",
|
||||
product="硫酸",
|
||||
specification="98%",
|
||||
quantity=30.0,
|
||||
batch_no="CRKDH0000001",
|
||||
vehicle_no="皖A12345",
|
||||
status="待出库",
|
||||
purchase_plan_id="dbp-old",
|
||||
plan_no="SX-OLD",
|
||||
del_flag=0,
|
||||
)
|
||||
link = PlTransportDbLink(
|
||||
id="link-1",
|
||||
tenant_no="t1",
|
||||
plan_id="plan-1",
|
||||
transport_id="tr-1",
|
||||
plan_product_id="pp-1",
|
||||
stop_type="load",
|
||||
stop_id="load-1",
|
||||
db_purchase_plan_id="dbp-old",
|
||||
db_inventory_id="inv-1",
|
||||
contributed_qty=Decimal("30"),
|
||||
del_flag=0,
|
||||
)
|
||||
|
||||
db = MagicMock()
|
||||
|
||||
def link_q(m):
|
||||
filtered = MagicMock()
|
||||
filtered.all.return_value = [link]
|
||||
filtered.first.return_value = link
|
||||
m.filter.return_value = filtered
|
||||
return m
|
||||
|
||||
def load_q(m):
|
||||
m.filter.return_value.first.return_value = load
|
||||
return m
|
||||
|
||||
def delivery_q(m):
|
||||
m.filter.return_value.order_by.return_value.all.return_value = [delivery]
|
||||
return m
|
||||
|
||||
def load_prod_q(m):
|
||||
m.filter.return_value.order_by.return_value.all.return_value = [stop_product]
|
||||
return m
|
||||
|
||||
def delivery_prod_q(m):
|
||||
m.filter.return_value.order_by.return_value.all.return_value = []
|
||||
return m
|
||||
|
||||
def inv_q(m):
|
||||
m.filter.return_value.first.return_value = inventory
|
||||
return m
|
||||
|
||||
def plan_q(m):
|
||||
# _subtract_plan_contribution 查旧计划;后续可能再查
|
||||
m.filter.return_value.first.return_value = old_plan
|
||||
return m
|
||||
|
||||
db.query.side_effect = _make_query_router(
|
||||
{
|
||||
PlTransportDbLink: link_q,
|
||||
PlPlanLoad: load_q,
|
||||
PlPlanDelivery: delivery_q,
|
||||
PlPlanLoadProduct: load_prod_q,
|
||||
PlPlanDeliveryProduct: delivery_prod_q,
|
||||
DbInboundOutbound: inv_q,
|
||||
DbPurchasePlan: plan_q,
|
||||
}
|
||||
)
|
||||
|
||||
results, warnings = upsert_stop_dibang_links(
|
||||
db,
|
||||
tenant_no="t1",
|
||||
plan=plan,
|
||||
transport=transport,
|
||||
stop_options=[
|
||||
StopDibangOption(stop_type="load", stop_id="load-1", generate_dibang=True),
|
||||
],
|
||||
user_id="u1",
|
||||
dept_id="d1",
|
||||
)
|
||||
|
||||
assert warnings == []
|
||||
assert len(results) == 1
|
||||
assert inventory.id == "inv-1"
|
||||
assert inventory.partner_company == new_partner
|
||||
assert inventory.purchase_plan_id == new_plan.id
|
||||
assert link.db_purchase_plan_id == new_plan.id
|
||||
assert inventory.plan_no == "SX-NEW"
|
||||
mock_find_plan.assert_called()
|
||||
mock_create_plan.assert_not_called()
|
||||
Reference in new issue
Block a user