| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- from decimal import Decimal
- import pytest
- from app.services.pl_brand_archive import (
- expand_export_rows_by_brand_qty,
- expand_export_rows_for_plan_brand,
- format_plan_product_constraint_brand,
- is_archived_for_export_brand,
- resolve_export_brand_qty_rows,
- validate_brand_qty_rows,
- )
- from types import SimpleNamespace
- def test_resolve_export_brand_qty_rows_prefers_stop_level():
- by_stop = {
- "sp1": [{"brandId": "b1", "brandName": "君正", "qty": Decimal("32")}],
- }
- by_plan = {
- "pp1": [{"brandId": "b2", "brandName": "红四方", "qty": Decimal("10")}],
- }
- rows = resolve_export_brand_qty_rows(
- stop_product_id="sp1",
- plan_product_id="pp1",
- by_stop_id=by_stop,
- by_plan_product_id=by_plan,
- )
- assert len(rows) == 1
- assert rows[0]["brandName"] == "君正"
- def test_resolve_export_brand_qty_rows_falls_back_to_plan_level():
- rows = resolve_export_brand_qty_rows(
- stop_product_id="missing",
- plan_product_id="pp1",
- by_stop_id={},
- by_plan_product_id={
- "pp1": [{"brandId": "b1", "brandName": "君正", "qty": Decimal("32")}],
- },
- )
- assert len(rows) == 1
- assert rows[0]["brandName"] == "君正"
- def test_resolve_export_brand_qty_rows_empty():
- assert (
- resolve_export_brand_qty_rows(
- stop_product_id=None,
- plan_product_id=None,
- by_stop_id={},
- by_plan_product_id={},
- )
- == []
- )
- def test_expand_export_rows_by_brand_qty_splits_and_blanks():
- base = {"productName": "片碱", "planQty": 34, "actualQty": 32, "brandName": "旧"}
- blank = expand_export_rows_by_brand_qty(base, [])
- assert len(blank) == 1
- assert blank[0]["brandName"] == ""
- assert blank[0]["actualQty"] == 32
- split = expand_export_rows_by_brand_qty(
- base,
- [
- {"brandId": "b1", "brandName": "君正", "qty": Decimal("20")},
- {"brandId": "b2", "brandName": "红四方", "qty": Decimal("12")},
- ],
- )
- assert [r["brandName"] for r in split] == ["君正", "红四方"]
- assert [r["actualQty"] for r in split] == [Decimal("20"), Decimal("12")]
- assert all(r["planQty"] == 34 for r in split)
- def test_archive_status_gate_for_export_brand():
- assert is_archived_for_export_brand("archived") is True
- assert is_archived_for_export_brand("unarchived") is False
- assert is_archived_for_export_brand("draft") is False
- product = SimpleNamespace(brand_mode="required", brand_name="君正")
- assert format_plan_product_constraint_brand(product) == "君正"
- assert (
- format_plan_product_constraint_brand(
- SimpleNamespace(brand_mode="any", brand_name="君正")
- )
- == ""
- )
- def test_expand_export_rows_for_plan_brand_draft_uses_product_not_qty():
- base = {"productName": "片碱", "planQty": 34, "actualQty": 32, "brandName": ""}
- product = SimpleNamespace(id="pp1", brand_mode="required", brand_name="君正")
- rows = expand_export_rows_for_plan_brand(
- base,
- archive_status="draft",
- plan_product=product,
- stop_product_id="lp1",
- by_stop_id={
- "lp1": [
- {"brandId": "b1", "brandName": "红四方", "qty": Decimal("20")},
- {"brandId": "b2", "brandName": "君正", "qty": Decimal("12")},
- ]
- },
- )
- assert len(rows) == 1
- assert rows[0]["brandName"] == "君正"
- assert rows[0]["actualQty"] == 32
- def test_expand_export_rows_for_plan_brand_archived_splits_qty():
- base = {"productName": "片碱", "planQty": 34, "actualQty": 32, "brandName": ""}
- product = SimpleNamespace(id="pp1", brand_mode="any", brand_name=None)
- rows = expand_export_rows_for_plan_brand(
- base,
- archive_status="archived",
- plan_product=product,
- stop_product_id="lp1",
- by_stop_id={
- "lp1": [
- {"brandId": "b1", "brandName": "君正", "qty": Decimal("20")},
- {"brandId": "b2", "brandName": "红四方", "qty": Decimal("12")},
- ]
- },
- )
- assert [r["brandName"] for r in rows] == ["君正", "红四方"]
- assert [r["actualQty"] for r in rows] == [Decimal("20"), Decimal("12")]
- def test_required_allows_other_brand_when_sum_matches():
- validate_brand_qty_rows(
- brand_mode="required",
- required_brand_id="b1",
- actual_qty=Decimal("10"),
- rows=[{"brandId": "b2", "brandName": "红四方", "qty": Decimal("10")}],
- )
- def test_required_allows_multi_brand_when_sum_matches():
- validate_brand_qty_rows(
- brand_mode="required",
- required_brand_id="b1",
- actual_qty=Decimal("10"),
- rows=[
- {"brandId": "b1", "brandName": "君正", "qty": Decimal("6")},
- {"brandId": "b2", "brandName": "红四方", "qty": Decimal("4")},
- ],
- )
- def test_required_sum_must_match():
- with pytest.raises(ValueError, match="合计"):
- validate_brand_qty_rows(
- brand_mode="required",
- required_brand_id="b1",
- actual_qty=Decimal("10"),
- rows=[
- {"brandId": "b1", "brandName": "君正", "qty": Decimal("6")},
- {"brandId": "b2", "brandName": "红四方", "qty": Decimal("3")},
- ],
- )
- def test_any_sum_must_match():
- with pytest.raises(ValueError, match="合计"):
- validate_brand_qty_rows(
- brand_mode="any",
- required_brand_id=None,
- actual_qty=Decimal("10"),
- rows=[
- {"brandId": "b1", "brandName": "君正", "qty": Decimal("6")},
- {"brandId": "b2", "brandName": "红四方", "qty": Decimal("3")},
- ],
- )
- def test_any_ok():
- validate_brand_qty_rows(
- brand_mode="any",
- required_brand_id=None,
- actual_qty=Decimal("10"),
- rows=[
- {"brandId": "b1", "brandName": "君正", "qty": Decimal("6")},
- {"brandId": "b2", "brandName": "红四方", "qty": Decimal("4")},
- ],
- )
- def test_no_brand_ignores_rows():
- validate_brand_qty_rows(
- brand_mode=None,
- required_brand_id=None,
- actual_qty=Decimal("5"),
- rows=[],
- )
- def test_delta_for_plan_type():
- from app.services.pl_inventory_service import qty_delta_for_plan
- assert qty_delta_for_plan("inbound", Decimal("3")) == Decimal("3")
- assert qty_delta_for_plan("outbound", Decimal("3")) == Decimal("-3")
- assert qty_delta_for_plan("direct", Decimal("3")) == Decimal("0")
|