| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- import pytest
- from fastapi import HTTPException
- from types import SimpleNamespace
- from app.schemas.pl.plan import (
- PlanContractLink,
- PlanDeliveryBody,
- PlanLoadBody,
- PlanStopProductBody,
- )
- from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
- from app.services.pl_plan_stop_service import (
- assert_at_most_one_contract,
- assert_delivery_products_in_selected_contract,
- assert_external_stop_products_have_contract_line,
- assert_load_products_in_selected_contract,
- assert_stop_contract_required,
- )
- def test_assert_at_most_one_contract_allows_zero_or_one():
- assert assert_at_most_one_contract([], "装货合同") == []
- one = [PlanContractLink(contractId="c1", contractNo="N1")]
- assert assert_at_most_one_contract(one, "装货合同") == one
- def test_assert_at_most_one_contract_rejects_two():
- links = [
- PlanContractLink(contractId="c1", contractNo="N1"),
- PlanContractLink(contractId="c2", contractNo="N2"),
- ]
- with pytest.raises(HTTPException) as exc:
- assert_at_most_one_contract(links, "供方合同")
- assert exc.value.status_code == 422
- assert "只能选择 1 份" in str(exc.value.detail)
- def test_assert_load_products_union_across_two_contracts():
- db = _FakeDb(["p1", "p2"])
- load = PlanLoadBody(
- customerId="cust-1",
- customerName="外部客户A",
- contracts=[
- PlanContractLink(contractId="c1", contractNo="N1"),
- PlanContractLink(contractId="c2", contractNo="N2"),
- ],
- products=[
- PlanStopProductBody(
- productId="p2",
- productNo="p2",
- productName="产品p2",
- unit="吨",
- planQty=1,
- )
- ],
- )
- assert_load_products_in_selected_contract(db, "shanxia2.0", load)
- class _FakeQuery:
- def __init__(self, rows):
- self._rows = rows
- def filter(self, *args, **kwargs):
- return self
- def all(self):
- return self._rows
- class _FakeDb:
- def __init__(self, product_ids):
- self._rows = [SimpleNamespace(product_id=pid) for pid in product_ids]
- def query(self, model):
- return _FakeQuery(self._rows)
- def _load_with_products(product_ids, contract_id="c1"):
- return PlanLoadBody(
- customerId="cust-1",
- customerName="外部客户A",
- contracts=[PlanContractLink(contractId=contract_id, contractNo="N1")],
- products=[
- PlanStopProductBody(
- productId=pid,
- productNo=pid,
- productName=f"产品{pid}",
- unit="吨",
- planQty=1,
- )
- for pid in product_ids
- ],
- )
- def test_assert_load_products_in_contract_passes_when_all_match():
- db = _FakeDb(["p1", "p2"])
- load = _load_with_products(["p1"])
- assert_load_products_in_selected_contract(db, "shanxia2.0", load)
- def test_assert_load_products_in_contract_rejects_outside():
- db = _FakeDb(["p1"])
- load = _load_with_products(["p2"])
- with pytest.raises(HTTPException) as exc:
- assert_load_products_in_selected_contract(db, "shanxia2.0", load)
- assert exc.value.status_code == 422
- assert "不在所选合同明细中" in str(exc.value.detail)
- def test_assert_load_products_skips_when_no_contract():
- db = _FakeDb([])
- load = PlanLoadBody(
- customerId="cust-1",
- customerName="外部客户A",
- contracts=[],
- products=[
- PlanStopProductBody(
- productId="p1",
- productNo="p1",
- productName="产品p1",
- unit="吨",
- planQty=1,
- )
- ],
- )
- assert_load_products_in_selected_contract(db, "shanxia2.0", load)
- def test_assert_load_products_skips_internal_customer():
- from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- # Empty/mismatched contract products — would 422 for external customer.
- db = _FakeDb([])
- load = PlanLoadBody(
- customerId="cust-1",
- customerName=internal,
- contracts=[PlanContractLink(contractId="c1", contractNo="N1")],
- products=[
- PlanStopProductBody(
- productId="p1",
- productNo="p1",
- productName="产品p1",
- unit="吨",
- planQty=1,
- )
- ],
- )
- assert_load_products_in_selected_contract(db, "shanxia2.0", load)
- def test_assert_plan_products_outside_contract_raises():
- db = _FakeDb(["p1"])
- load = _load_with_products(["p1"])
- plan_products = [
- SimpleNamespace(product_id="p2", product_name="计划外产品"),
- ]
- with pytest.raises(HTTPException) as exc:
- assert_load_products_in_selected_contract(
- db, "shanxia2.0", load, plan_products=plan_products
- )
- assert exc.value.status_code == 422
- assert "计划外产品" in str(exc.value.detail)
- assert "不在所选合同明细中" in str(exc.value.detail)
- def _delivery_with_products(product_ids, contract_id="c1"):
- return PlanDeliveryBody(
- seq=1,
- customerId="cust-2",
- customerName="外部客户B",
- contracts=[PlanContractLink(contractId=contract_id, contractNo="N2")],
- products=[
- PlanStopProductBody(
- productId=pid,
- productNo=pid,
- productName=f"产品{pid}",
- unit="吨",
- planQty=1,
- )
- for pid in product_ids
- ],
- )
- def test_assert_delivery_products_in_contract_passes_when_all_match():
- db = _FakeDb(["p1", "p2"])
- delivery = _delivery_with_products(["p1"])
- assert_delivery_products_in_selected_contract(db, "shanxia2.0", delivery, 1)
- def test_assert_delivery_products_in_contract_rejects_outside():
- db = _FakeDb(["p1"])
- delivery = _delivery_with_products(["p2"])
- with pytest.raises(HTTPException) as exc:
- assert_delivery_products_in_selected_contract(db, "shanxia2.0", delivery, 2)
- assert exc.value.status_code == 422
- assert "送货段#2产品" in str(exc.value.detail)
- assert "不在所选合同明细中" in str(exc.value.detail)
- def test_assert_delivery_products_skips_when_no_contract():
- db = _FakeDb([])
- delivery = PlanDeliveryBody(
- seq=1,
- customerId="cust-2",
- customerName="外部客户B",
- contracts=[],
- products=[
- PlanStopProductBody(
- productId="p1",
- productNo="p1",
- productName="产品p1",
- unit="吨",
- planQty=1,
- )
- ],
- )
- assert_delivery_products_in_selected_contract(db, "shanxia2.0", delivery, 1)
- def test_assert_delivery_products_skips_internal_customer():
- from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- db = _FakeDb([])
- delivery = PlanDeliveryBody(
- seq=1,
- customerId="cust-2",
- customerName=internal,
- contracts=[PlanContractLink(contractId="c1", contractNo="N1")],
- products=[
- PlanStopProductBody(
- productId="p1",
- productNo="p1",
- productName="产品p1",
- unit="吨",
- planQty=1,
- )
- ],
- )
- assert_delivery_products_in_selected_contract(db, "shanxia2.0", delivery, 1)
- def test_formal_save_rejects_external_product_without_contract_line():
- load = _load_with_products(["p1"])
- with pytest.raises(HTTPException) as exc:
- assert_external_stop_products_have_contract_line(load, [])
- assert exc.value.status_code == 422
- assert exc.value.detail == "装货产品「产品p1」没有合同编号,请从当前合同补到原来的产品行后再保存"
- def test_formal_save_accepts_external_product_bound_on_the_same_row():
- load = _load_with_products(["p1"])
- load.products[0].contract_id = "c1"
- load.products[0].contract_product_id = "line-1"
- assert assert_external_stop_products_have_contract_line(load, []) is None
- def test_formal_save_skips_internal_product_without_contract_line():
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- load = _load_with_products(["p1"])
- load.customer_name = internal
- assert assert_external_stop_products_have_contract_line(load, []) is None
- def test_formal_save_names_the_delivery_segment_for_an_untagged_product():
- delivery = _delivery_with_products(["p1"])
- with pytest.raises(HTTPException) as exc:
- assert_external_stop_products_have_contract_line(
- _load_with_products([]),
- [delivery],
- )
- assert exc.value.status_code == 422
- assert "送货段#1产品" in str(exc.value.detail)
- def test_assert_stop_contract_required_rejects_external_without_contract():
- with pytest.raises(HTTPException) as exc:
- assert_stop_contract_required("外部客户A", [], "装货客户")
- assert exc.value.status_code == 422
- assert exc.value.detail == "请为装货客户选择合同"
- def test_assert_stop_contract_required_allows_external_with_contract():
- links = [PlanContractLink(contractId="c1", contractNo="N1")]
- assert_stop_contract_required("外部客户A", links, "装货客户") is None
- def test_assert_stop_contract_required_skips_internal_customer():
- from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
- internal = next(iter(INTERNAL_CUSTOMER_NAMES))
- assert_stop_contract_required(internal, [], "装货客户") is None
|