204 lines
6.5 KiB
Python
204 lines
6.5 KiB
Python
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")
|