| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- from decimal import Decimal
- from unittest.mock import MagicMock, patch
- from app.schemas.pl.plan import PlanDeliveryRead, PlanLoadRead, PlanStopProductRead
- from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
- from app.services.pl_plan_archive_export import (
- _append_paired_product_rows,
- _append_stop_product_rows,
- _plan_common_export_fields,
- build_plan_archive_export_rows,
- resolve_archive_list_io_column,
- )
- def _stop_product(**kwargs) -> PlanStopProductRead:
- defaults = {
- "id": "sp1",
- "planId": "p1",
- "tenantNo": "t1",
- "productId": "prod1",
- "productNo": "P001",
- "productName": "甲醇",
- "model": "99%",
- "unit": "吨",
- "packageSpec": "散装",
- "planQty": Decimal("10"),
- "createTime": "2026-08-27T00:00:00",
- "updateTime": "2026-08-27T00:00:00",
- }
- defaults.update(kwargs)
- return PlanStopProductRead(**defaults)
- def test_resolve_archive_list_io_column():
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- assert resolve_archive_list_io_column("load", internal) == "outbound"
- assert resolve_archive_list_io_column("load", "外部客户") == "inbound"
- assert resolve_archive_list_io_column("delivery", internal) == "inbound"
- assert resolve_archive_list_io_column("delivery", "外部客户") == "outbound"
- def test_append_stop_product_rows_io_and_amount():
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- product = _stop_product(
- inboundQty=Decimal("9.5"),
- inboundPrice=Decimal("100"),
- inboundAmount=None,
- )
- load = PlanLoadRead(
- id="load1",
- planId="p1",
- tenantNo="t1",
- customerName=internal,
- address="装货地址",
- loadTime="2026-08-27T08:00:00",
- products=[product],
- createTime="2026-08-27T00:00:00",
- updateTime="2026-08-27T00:00:00",
- )
- rows: list[dict] = []
- plan_common = {
- "planNo": "PL20260827001",
- "planDate": "2026-08-27",
- }
- row_no = _append_stop_product_rows(rows, 0, plan_common, "load", load, None, load.products)
- assert row_no == 1
- assert len(rows) == 1
- row = rows[0]
- assert row["stopKind"] == "装货"
- assert row["stopCustomer"] == internal
- assert row["loadCustomer"] == internal
- assert row["actualQty"] == Decimal("9.5")
- assert row["outboundQty"] == Decimal("9.5")
- assert row["loadOutboundQty"] == Decimal("9.5")
- assert row["inboundQty"] is None
- assert row["unitPrice"] == Decimal("100")
- assert row["loadUnitPrice"] == Decimal("100")
- assert row["amount"] == Decimal("950")
- assert row["loadAmount"] == Decimal("950")
- def test_append_stop_product_rows_delivery_external_outbound():
- product = _stop_product(
- id="sp2",
- outboundQty=Decimal("8"),
- outboundPrice=Decimal("120"),
- outboundAmount=Decimal("960"),
- )
- delivery = PlanDeliveryRead(
- id="del1",
- planId="p1",
- tenantNo="t1",
- seq=1,
- customerName="外部客户",
- address="送货地址",
- deliveryTime="2026-08-28T10:00:00",
- products=[product],
- createTime="2026-08-27T00:00:00",
- updateTime="2026-08-27T00:00:00",
- )
- rows: list[dict] = []
- row_no = _append_stop_product_rows(rows, 0, {"planNo": "P1"}, "delivery", delivery, 1, delivery.products)
- row = rows[0]
- assert row_no == 1
- assert row["stopKind"] == "送货 #1"
- assert row["deliveryCustomer"] == "外部客户"
- assert row["outboundQty"] == Decimal("8")
- assert row["deliveryOutboundQty"] == Decimal("8")
- assert row["inboundQty"] is None
- assert row["amount"] == Decimal("960")
- assert row["deliveryAmount"] == Decimal("960")
- def test_append_paired_product_rows_inner_join_one_row_per_product():
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- load_a = _stop_product(
- id="lp1",
- productId="prod-a",
- productName="甲醇",
- packageSpec="散装",
- inboundQty=Decimal("10"),
- inboundPrice=Decimal("100"),
- )
- load_b = _stop_product(
- id="lp2",
- productId="prod-b",
- productName="乙醇",
- packageSpec="桶装",
- inboundQty=Decimal("5"),
- inboundPrice=Decimal("80"),
- )
- # 装货独有,送货无匹配 → 不出行
- load_only = _stop_product(
- id="lp3",
- productId="prod-c",
- productName="仅装货",
- packageSpec="散装",
- inboundQty=Decimal("1"),
- )
- delivery_a = _stop_product(
- id="dp1",
- productId="prod-a",
- productName="甲醇",
- packageSpec="散装",
- outboundQty=Decimal("9.5"),
- outboundPrice=Decimal("120"),
- outboundAmount=Decimal("1140"),
- )
- # 同产品不同包装 → 不匹配
- delivery_a_other_pkg = _stop_product(
- id="dp1b",
- productId="prod-a",
- productName="甲醇",
- packageSpec="桶装",
- outboundQty=Decimal("1"),
- outboundPrice=Decimal("1"),
- )
- delivery_b = _stop_product(
- id="dp2",
- productId="prod-b",
- productName="乙醇",
- packageSpec="桶装",
- outboundQty=Decimal("4"),
- outboundPrice=Decimal("90"),
- )
- # 送货独有 → 不出行
- delivery_only = _stop_product(
- id="dp3",
- productId="prod-d",
- productName="仅送货",
- packageSpec="散装",
- outboundQty=Decimal("2"),
- )
- load = PlanLoadRead(
- id="load1",
- planId="p1",
- tenantNo="t1",
- customerName=internal,
- address="装货地址",
- loadTime="2026-08-27T08:00:00",
- products=[load_a, load_b, load_only],
- createTime="2026-08-27T00:00:00",
- updateTime="2026-08-27T00:00:00",
- )
- delivery = PlanDeliveryRead(
- id="del1",
- planId="p1",
- tenantNo="t1",
- seq=1,
- customerName="外部客户",
- address="送货地址",
- deliveryTime="2026-08-28T10:00:00",
- products=[delivery_a, delivery_a_other_pkg, delivery_b, delivery_only],
- createTime="2026-08-27T00:00:00",
- updateTime="2026-08-27T00:00:00",
- )
- rows: list[dict] = []
- row_no = _append_paired_product_rows(rows, 0, {"planNo": "PL001"}, load, delivery, 1)
- assert row_no == 2
- assert len(rows) == 2
- by_name = {r["productName"]: r for r in rows}
- assert set(by_name) == {"甲醇", "乙醇"}
- methanol = by_name["甲醇"]
- assert methanol["loadCustomer"] == internal
- assert methanol["deliveryCustomer"] == "外部客户"
- assert methanol["deliveryStopKind"] == "送货 #1"
- assert methanol["packageSpec"] == "散装"
- assert methanol["loadOutboundQty"] == Decimal("10") # 内部客户装货 → 出库
- assert methanol["loadInboundQty"] is None
- assert methanol["loadUnitPrice"] == Decimal("100")
- assert methanol["loadAmount"] == Decimal("1000")
- assert methanol["deliveryOutboundQty"] == Decimal("9.5")
- assert methanol["deliveryInboundQty"] is None
- assert methanol["deliveryUnitPrice"] == Decimal("120")
- assert methanol["deliveryAmount"] == Decimal("1140")
- ethanol = by_name["乙醇"]
- assert ethanol["loadOutboundQty"] == Decimal("5")
- assert ethanol["deliveryOutboundQty"] == Decimal("4")
- assert ethanol["deliveryAmount"] == Decimal("360")
- def test_build_plan_archive_export_rows_pair_mode_and_single_side():
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- load_product = _stop_product(
- inboundQty=Decimal("10"),
- inboundPrice=Decimal("100"),
- )
- delivery_product = _stop_product(
- id="dp1",
- outboundQty=Decimal("9"),
- outboundPrice=Decimal("110"),
- outboundAmount=Decimal("990"),
- )
- load = PlanLoadRead(
- id="load1",
- planId="p1",
- tenantNo="t1",
- customerName=internal,
- address="装货地址",
- loadTime="2026-08-27T08:00:00",
- products=[load_product],
- createTime="2026-08-27T00:00:00",
- updateTime="2026-08-27T00:00:00",
- )
- delivery = PlanDeliveryRead(
- id="del1",
- planId="p1",
- tenantNo="t1",
- seq=2,
- customerName="外部客户",
- address="送货地址",
- deliveryTime="2026-08-28T10:00:00",
- products=[delivery_product],
- createTime="2026-08-27T00:00:00",
- updateTime="2026-08-27T00:00:00",
- )
- plan = MagicMock()
- plan.id = "p1"
- plan.plan_no = "PL001"
- plan.contract_no = "C1"
- plan.plan_date = "2026-08-27"
- plan.archive_status = "archived"
- plan.follow_status = "arranged"
- plan.plan_type = "direct"
- plan.license_plate = "皖A12345"
- plan.trailer_no = None
- plan.erp_entered = 0
- plan.freight_amount = None
- plan.remark = ""
- plan.transport_id = None
- plan.customer_name = "外部客户"
- plan.supplier_name = None
- plan.destination_name = None
- plan.delivery_time = None
- with patch(
- "app.services.pl_plan_archive_export.load_plan_stops_grouped",
- return_value={"p1": (load, [delivery])},
- ), patch(
- "app.services.pl_plan_archive_export._load_legacy_products_by_plan",
- return_value={},
- ), patch(
- "app.services.pl_plan_archive_export._load_contract_nos_by_plan",
- return_value={"p1": []},
- ):
- paired = build_plan_archive_export_rows(
- MagicMock(), "t1", [plan], {}, stop_kind=None
- )
- load_only = build_plan_archive_export_rows(
- MagicMock(), "t1", [plan], {}, stop_kind="load"
- )
- delivery_only = build_plan_archive_export_rows(
- MagicMock(), "t1", [plan], {}, stop_kind="delivery"
- )
- assert len(paired) == 1
- assert paired[0]["productName"] == "甲醇"
- assert paired[0]["loadCustomer"] == internal
- assert paired[0]["deliveryCustomer"] == "外部客户"
- assert paired[0]["deliveryStopKind"] == "送货 #2"
- assert "stopKind" not in paired[0]
- assert len(load_only) == 1
- assert load_only[0]["stopKind"] == "装货"
- assert load_only[0]["loadCustomer"] == internal
- assert load_only[0]["deliveryCustomer"] == ""
- assert len(delivery_only) == 1
- assert delivery_only[0]["stopKind"] == "送货 #2"
- assert delivery_only[0]["deliveryCustomer"] == "外部客户"
- assert delivery_only[0]["loadCustomer"] == ""
- class _FakePlan:
- id = "p1"
- plan_no = "PL001"
- contract_no = "SX/X-JT20260827001"
- plan_date = "2026-08-27"
- archive_status = "archived"
- follow_status = "arranged"
- plan_type = "direct"
- license_plate = "皖A12345"
- trailer_no = "挂001"
- erp_entered = 1
- freight_amount = Decimal("500")
- remark = "测试"
- def test_plan_common_export_fields():
- fields = _plan_common_export_fields(_FakePlan(), None)
- assert fields["planId"] == "p1"
- assert fields["planNo"] == "PL001"
- assert fields["contractNo"] == "SX/X-JT20260827001"
- assert fields["archiveStatus"] == "已归档"
- assert fields["followStatus"] == "已安排"
- assert fields["planType"] == "直销单"
- assert fields["licensePlate"] == "皖A12345挂001"
- assert fields["erpEntered"] == "是"
- def test_plan_common_export_fields_merges_link_contract_nos():
- fields = _plan_common_export_fields(
- _FakePlan(),
- None,
- contract_nos=["SX/X-JT20260827001", "SX/X-JT20260827002"],
- )
- assert fields["contractNo"] == "SX/X-JT20260827001、SX/X-JT20260827002"
|