执行中的运输计划不再随意改单;库存流水记到装卸产品明细;手工派车日历区分酸碱并记录数量。同时收紧合同附件与子表的差量保存,并清理已废弃的 bak 备份目录。 Co-authored-by: Cursor <cursoragent@cursor.com>
241 lines
8.1 KiB
Python
241 lines
8.1 KiB
Python
"""运输计划多段:内部客户地磅默认值与数量平衡校验"""
|
|
from decimal import Decimal
|
|
|
|
from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
|
|
from app.services.pl_plan_stop_rules import (
|
|
load_delivery_product_gap,
|
|
normalize_stop_dibang_fields,
|
|
resolve_stop_dibang_defaults,
|
|
warn_qty_imbalance,
|
|
)
|
|
|
|
|
|
def test_internal_shanxia_load_outbound():
|
|
name = "安庆市山峡化工有限公司"
|
|
assert name in INTERNAL_CUSTOMER_NAMES
|
|
assert resolve_stop_dibang_defaults(name, "load") == (True, "outbound")
|
|
|
|
|
|
def test_internal_shanxia_delivery_inbound():
|
|
name = "安庆市山峡化工有限公司"
|
|
assert resolve_stop_dibang_defaults(name, "delivery") == (True, "inbound")
|
|
|
|
|
|
def test_internal_yundong_load_outbound():
|
|
name = "安徽云栋化工有限公司"
|
|
assert resolve_stop_dibang_defaults(name, "load") == (True, "outbound")
|
|
|
|
|
|
def test_internal_yundong_delivery_inbound():
|
|
name = "安徽云栋化工有限公司"
|
|
assert resolve_stop_dibang_defaults(name, "delivery") == (True, "inbound")
|
|
|
|
|
|
def test_external_customer_no_dibang():
|
|
assert resolve_stop_dibang_defaults("外部客户有限公司", "load") == (False, "none")
|
|
assert resolve_stop_dibang_defaults("外部客户有限公司", "delivery") == (False, "none")
|
|
|
|
|
|
def test_delivery_defaults_use_delivery_customer():
|
|
"""送货段地磅默认以本段客户是否为内部客户为准。"""
|
|
internal = "安庆市山峡化工有限公司"
|
|
assert resolve_stop_dibang_defaults(internal, "delivery") == (True, "inbound")
|
|
assert resolve_stop_dibang_defaults("外部客户有限公司", "delivery") == (False, "none")
|
|
|
|
|
|
def test_normalize_stop_dibang_fields_repairs_none_direction():
|
|
gen, direction = normalize_stop_dibang_fields(
|
|
"安庆市山峡化工有限公司",
|
|
"delivery",
|
|
1,
|
|
"none",
|
|
)
|
|
assert gen == 1
|
|
assert direction == "inbound"
|
|
|
|
|
|
def test_normalize_respects_explicit_uncheck_for_internal_customer():
|
|
"""内部客户显式传 0(如可选车皖H21522 取消勾选)时须保留,不得用默认值强制回 1。"""
|
|
gen, direction = normalize_stop_dibang_fields(
|
|
"安庆市山峡化工有限公司",
|
|
"load",
|
|
0,
|
|
"none",
|
|
)
|
|
assert gen == 0
|
|
assert direction == "none"
|
|
|
|
|
|
def test_normalize_respects_explicit_uncheck_for_internal_delivery():
|
|
gen, direction = normalize_stop_dibang_fields(
|
|
"安徽云栋化工有限公司",
|
|
"delivery",
|
|
0,
|
|
"inbound",
|
|
)
|
|
assert gen == 0
|
|
assert direction == "none"
|
|
|
|
|
|
def test_normalize_forces_off_for_external_even_when_checked():
|
|
gen, direction = normalize_stop_dibang_fields(
|
|
"外部客户有限公司",
|
|
"load",
|
|
1,
|
|
"outbound",
|
|
)
|
|
assert gen == 0
|
|
assert direction == "none"
|
|
|
|
|
|
def test_normalize_keeps_explicit_check_for_internal():
|
|
gen, direction = normalize_stop_dibang_fields(
|
|
"安庆市山峡化工有限公司",
|
|
"load",
|
|
1,
|
|
"outbound",
|
|
)
|
|
assert gen == 1
|
|
assert direction == "outbound"
|
|
|
|
|
|
def test_external_empty_name_no_dibang():
|
|
assert resolve_stop_dibang_defaults(None, "load") == (False, "none")
|
|
assert resolve_stop_dibang_defaults(" ", "delivery") == (False, "none")
|
|
|
|
|
|
def test_warn_qty_balanced_returns_empty():
|
|
plan = [{"quantity": Decimal("30")}]
|
|
load = [{"plan_qty": Decimal("30")}]
|
|
delivery = [{"plan_qty": Decimal("30")}]
|
|
assert warn_qty_imbalance(plan, load, delivery) == []
|
|
|
|
|
|
def test_warn_qty_uses_plan_qty_on_plan_products():
|
|
plan = [{"plan_qty": Decimal("28")}]
|
|
load = [{"plan_qty": Decimal("28")}]
|
|
delivery = [{"plan_qty": Decimal("28")}]
|
|
assert warn_qty_imbalance(plan, load, delivery) == []
|
|
|
|
|
|
def test_warn_qty_load_vs_delivery_imbalance():
|
|
plan = [{"quantity": Decimal("28")}]
|
|
load = [{"plan_qty": Decimal("30")}]
|
|
delivery = [{"plan_qty": Decimal("28")}]
|
|
warnings = warn_qty_imbalance(plan, load, delivery)
|
|
assert warnings == [
|
|
"装货合计(30)与送货合计(28)不一致,请核对",
|
|
]
|
|
|
|
|
|
def test_warn_qty_delivery_vs_load_imbalance():
|
|
plan = [{"quantity": Decimal("30")}]
|
|
load = [{"plan_qty": Decimal("30")}]
|
|
delivery = [{"plan_qty": Decimal("25")}]
|
|
warnings = warn_qty_imbalance(plan, load, delivery)
|
|
assert warnings == [
|
|
"装货合计(30)与送货合计(25)不一致,请核对",
|
|
]
|
|
|
|
|
|
def test_warn_qty_load_delivery_decimal_imbalance():
|
|
plan = [{"quantity": Decimal("30")}]
|
|
load = [{"plan_qty": Decimal("30")}]
|
|
delivery = [{"plan_qty": Decimal("30.31")}]
|
|
warnings = warn_qty_imbalance(plan, load, delivery)
|
|
assert warnings == [
|
|
"装货合计(30)与送货合计(30.31)不一致,请核对",
|
|
]
|
|
|
|
|
|
def test_warn_qty_multi_stop_30_equals_10_15_5_balanced():
|
|
"""验收场景:计划 30 = 送货 10+15+5,装货 30。"""
|
|
plan = [{"quantity": Decimal("30")}]
|
|
load = [{"plan_qty": Decimal("30")}]
|
|
delivery = [
|
|
{"plan_qty": Decimal("10")},
|
|
{"plan_qty": Decimal("15")},
|
|
{"plan_qty": Decimal("5")},
|
|
]
|
|
assert warn_qty_imbalance(plan, load, delivery) == []
|
|
|
|
|
|
def test_warn_qty_sums_multiple_rows_balanced_across_stops():
|
|
plan = [{"quantity": Decimal("10")}, {"quantity": Decimal("20")}]
|
|
load = [{"plan_qty": Decimal("10")}, {"plan_qty": Decimal("15")}]
|
|
delivery = [{"plan_qty": Decimal("25")}]
|
|
assert warn_qty_imbalance(plan, load, delivery) == []
|
|
|
|
|
|
def test_warn_qty_sums_multiple_rows_imbalanced_across_stops():
|
|
plan = [{"quantity": Decimal("10")}, {"quantity": Decimal("20")}]
|
|
load = [{"plan_qty": Decimal("10")}, {"plan_qty": Decimal("15")}]
|
|
delivery = [{"plan_qty": Decimal("24")}]
|
|
warnings = warn_qty_imbalance(plan, load, delivery)
|
|
assert warnings == [
|
|
"装货合计(25)与送货合计(24)不一致,请核对",
|
|
]
|
|
|
|
|
|
def test_same_product_quantities_sum_across_packages_and_delivery_stops():
|
|
load = [
|
|
{"product_id": "a", "product_name": "尿素", "plan_qty": Decimal("8")},
|
|
{"product_id": "a", "product_name": "尿素", "plan_qty": Decimal("2")},
|
|
]
|
|
delivery = [
|
|
{"product_id": "a", "product_name": "尿素", "plan_qty": Decimal("6")},
|
|
{"product_id": "a", "product_name": "尿素", "plan_qty": Decimal("4")},
|
|
]
|
|
assert warn_qty_imbalance([], load, delivery) == []
|
|
|
|
|
|
def test_warn_qty_names_each_product_when_sums_differ():
|
|
load = [
|
|
{"product_id": "a", "product_name": "尿素", "plan_qty": Decimal("10")},
|
|
{"product_id": "b", "product_name": "甲醇", "plan_qty": Decimal("5")},
|
|
]
|
|
delivery = [
|
|
{"product_id": "a", "product_name": "尿素", "plan_qty": Decimal("6")},
|
|
{"product_id": "b", "product_name": "甲醇", "plan_qty": Decimal("9")},
|
|
]
|
|
assert warn_qty_imbalance([], load, delivery) == [
|
|
"产品尿素:装货合计(10)与送货合计(6)不一致,请核对",
|
|
"产品甲醇:装货合计(5)与送货合计(9)不一致,请核对",
|
|
]
|
|
|
|
|
|
def test_product_set_mismatch_names_both_sides():
|
|
load = {
|
|
"products": [
|
|
{"product_id": "a", "product_name": "A"},
|
|
{"product_id": "d", "product_name": "D"},
|
|
]
|
|
}
|
|
deliveries = [
|
|
{"products": [{"product_id": "a", "product_name": "A"}]},
|
|
{
|
|
"products": [
|
|
{"product_id": "b", "product_name": "B"},
|
|
{"product_id": "c", "product_name": "C"},
|
|
]
|
|
},
|
|
]
|
|
assert load_delivery_product_gap(load, deliveries) == (
|
|
"装货段有产品 D,送货段没有;送货段有产品 B、C,装货段没有。两边产品必须一致,不能保存"
|
|
)
|
|
|
|
|
|
def test_same_product_with_different_packages_is_one_product():
|
|
load = {
|
|
"products": [
|
|
{"product_id": "a", "product_name": "尿素", "package_spec": "桶"},
|
|
{"product_id": "a", "product_name": "尿素", "package_spec": "散装"},
|
|
]
|
|
}
|
|
deliveries = [{"products": [{"product_id": "a", "product_name": "尿素", "package_spec": "袋"}]}]
|
|
assert load_delivery_product_gap(load, deliveries) == ""
|
|
|
|
|
|
def test_empty_product_rows_match():
|
|
assert load_delivery_product_gap({"products": []}, [{"products": []}]) == ""
|