200 lines
6.3 KiB
Python
200 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
|
|
from persist import build_result
|
|
from pipeline import KIND_BUSINESS_FAIL, KIND_SUCCESS, KIND_SYSTEM_FAIL, SelectionResult
|
|
from rules import Allocation, Candidate, PurchaseLine, Rejected, normalize_vendor
|
|
|
|
|
|
HANGZHOU = "杭州小药药自营旗舰店"
|
|
CHANGSHA = "长沙小药药自营旗舰店"
|
|
GUANGDONG = "广东壹号药业有限公司"
|
|
|
|
|
|
def _purchase(**overrides) -> PurchaseLine:
|
|
data = {
|
|
"approval_no": "国药准字H20100001",
|
|
"sale_price": 100.0,
|
|
"qty": 10.0,
|
|
"oms_id": 2026100201,
|
|
"bar_code": "06901234567890",
|
|
"generic_name": "阿莫西林",
|
|
"org_code": "907",
|
|
}
|
|
data.update(overrides)
|
|
return PurchaseLine(**data)
|
|
|
|
|
|
def _candidate(**overrides) -> Candidate:
|
|
vendor_raw = overrides.get("vendor_raw", GUANGDONG)
|
|
data = {
|
|
"mall_name": "1号药城",
|
|
"sku_id": "S1",
|
|
"name": "阿莫西林",
|
|
"spec": "0.25g",
|
|
"price": 90.0,
|
|
"expiry_raw": "13",
|
|
"expiry_months": 13.0,
|
|
"vendor_raw": vendor_raw,
|
|
"vendor_norm": normalize_vendor(vendor_raw),
|
|
"stock": 20.0,
|
|
"extra": {},
|
|
}
|
|
data.update(overrides)
|
|
if "vendor_raw" in overrides and "vendor_norm" not in overrides:
|
|
data["vendor_norm"] = normalize_vendor(overrides["vendor_raw"])
|
|
return Candidate(**data)
|
|
|
|
|
|
def _success(allocations: list[Allocation], malls: list[str] | None = None) -> SelectionResult:
|
|
return SelectionResult(
|
|
kind=KIND_SUCCESS,
|
|
reason="SELECTED",
|
|
allocations=allocations,
|
|
rejected=[],
|
|
malls=malls or ["1号药城", "药帮忙"],
|
|
)
|
|
|
|
|
|
def test_split_two_platforms_writes_allocated_qty():
|
|
result = build_result(
|
|
_purchase(),
|
|
_success(
|
|
[
|
|
Allocation(
|
|
_candidate(
|
|
mall_name="药帮忙",
|
|
vendor_raw=HANGZHOU,
|
|
price=88.0,
|
|
),
|
|
3,
|
|
),
|
|
Allocation(_candidate(price=90.0), 7),
|
|
]
|
|
),
|
|
)
|
|
assert result.deal_flag == 1
|
|
assert "选品成功" in result.deal_result
|
|
assert len(result.cart_rows) == 2
|
|
ybm = next(row for row in result.cart_rows if row["platform_type"] == 48)
|
|
yaoex = next(row for row in result.cart_rows if row["platform_type"] == 66)
|
|
assert ybm["purchase_qty"] == 3
|
|
assert ybm["purchase_price"] == Decimal("88.0000")
|
|
assert ybm["purchase_total"] == Decimal("264.0000")
|
|
assert ybm["supplier_name"] == HANGZHOU
|
|
assert yaoex["purchase_qty"] == 7
|
|
assert yaoex["purchase_total"] == Decimal("630.0000")
|
|
assert all(row["status"] == 1 for row in result.cart_rows)
|
|
assert all(row["multi_platform_flag"] == 1 for row in result.cart_rows)
|
|
assert all(row["push_status"] == 0 for row in result.cart_rows)
|
|
|
|
|
|
def test_two_warehouses_same_platform_flag_off():
|
|
result = build_result(
|
|
_purchase(),
|
|
_success(
|
|
[
|
|
Allocation(
|
|
_candidate(mall_name="药帮忙", vendor_raw=HANGZHOU, price=88.0),
|
|
3,
|
|
),
|
|
Allocation(
|
|
_candidate(mall_name="药帮忙", vendor_raw=CHANGSHA, price=90.0),
|
|
7,
|
|
),
|
|
],
|
|
malls=["药帮忙"],
|
|
),
|
|
)
|
|
assert len(result.cart_rows) == 2
|
|
assert all(row["platform_type"] == 48 for row in result.cart_rows)
|
|
assert all(row["multi_platform_flag"] == 0 for row in result.cart_rows)
|
|
|
|
|
|
def test_stock_insufficient_business_fail():
|
|
result = build_result(
|
|
_purchase(),
|
|
SelectionResult(
|
|
kind=KIND_BUSINESS_FAIL,
|
|
reason="STOCK_INSUFFICIENT",
|
|
allocations=[],
|
|
rejected=[Rejected(_candidate(vendor_raw="路边店"), "NOT_SELF_WAREHOUSE")],
|
|
malls=["1号药城", "药帮忙"],
|
|
),
|
|
)
|
|
assert result.deal_flag == 2
|
|
assert "库存不足" in result.deal_result
|
|
assert len(result.cart_rows) == 2
|
|
assert all(row["status"] == 2 for row in result.cart_rows)
|
|
assert all("库存不足" in (row["fail_reason"] or "") for row in result.cart_rows)
|
|
assert all(row["multi_platform_flag"] == 1 for row in result.cart_rows)
|
|
|
|
|
|
def test_margin_fail_text_is_4_percent():
|
|
result = build_result(
|
|
_purchase(),
|
|
SelectionResult(
|
|
kind=KIND_BUSINESS_FAIL,
|
|
reason="MARGIN_LT_4PCT",
|
|
allocations=[],
|
|
rejected=[],
|
|
malls=["1号药城"],
|
|
),
|
|
)
|
|
assert "毛利不大于 4%" in result.deal_result
|
|
assert "毛利不大于 4%" in (result.cart_rows[0]["fail_reason"] or "")
|
|
|
|
|
|
def test_system_fault_deal_result_fits_varchar2_200_byte():
|
|
blob = "药帮忙接口返回非JSON" + ("<html>probe" * 40)
|
|
result = build_result(
|
|
_purchase(),
|
|
SelectionResult(
|
|
kind=KIND_SYSTEM_FAIL,
|
|
reason="SYSTEM_FAULT",
|
|
allocations=[],
|
|
rejected=[],
|
|
malls=["1号药城", "药帮忙"],
|
|
mall_errors={"药帮忙": f"QUERY_FAILED:{blob}"},
|
|
),
|
|
)
|
|
assert len(result.deal_result.encode("gbk")) <= 200
|
|
assert result.deal_result.startswith("系统故障")
|
|
|
|
|
|
def test_system_fault_uses_per_mall_errors():
|
|
result = build_result(
|
|
_purchase(),
|
|
SelectionResult(
|
|
kind=KIND_SYSTEM_FAIL,
|
|
reason="SYSTEM_FAULT",
|
|
allocations=[],
|
|
rejected=[],
|
|
malls=["1号药城", "药帮忙"],
|
|
mall_errors={"药帮忙": "LOGIN_FAILED:timeout"},
|
|
),
|
|
)
|
|
assert result.deal_flag == 0
|
|
assert result.cart_rows == []
|
|
assert result.deal_result.startswith("系统故障")
|
|
assert "LOGIN_FAILED:timeout" in result.deal_result
|
|
|
|
|
|
def test_failure_total_is_price_times_plan_qty():
|
|
result = build_result(
|
|
_purchase(sale_price=12.5, qty=3),
|
|
SelectionResult(
|
|
kind=KIND_BUSINESS_FAIL,
|
|
reason="NO_HIT",
|
|
allocations=[],
|
|
rejected=[],
|
|
malls=["1号药城"],
|
|
),
|
|
)
|
|
row = result.cart_rows[0]
|
|
assert row["purchase_price"] == Decimal("12.5000")
|
|
assert row["purchase_qty"] == 3
|
|
assert row["purchase_total"] == Decimal("37.5000")
|
|
assert row["supplier_name"] == "无符合规则供应商"
|