fix: 采购价格上限按数量计算总价 (#181)

This commit is contained in:
chengma
2026-08-12 14:43:18 +08:00
parent 8c7be7bd07
commit 253be7cb61
24 changed files with 203 additions and 106 deletions
+1 -1
View File
@@ -478,7 +478,7 @@ class HttpAdminGateway(AdminGateway):
"ADMIN_INVALID_RESPONSE",
(
"Admin 采购任务缺少有效的 goods_id、动态 options、"
"quantity 或 max_price_cent"
"quantity 或订单总价上限 max_price_cent"
),
False,
request_id,
+1
View File
@@ -19,6 +19,7 @@ class PurchasePageState:
goods_id: str
selected_options: Mapping[str, str] = field(default_factory=dict)
quantity: int = 0
# 当前页面显示的采购金额;最终确认页必须是订单总价,单位人民币分。
price_cent: int = 0
candidate_count: int = 1
in_stock: bool = True
+5 -1
View File
@@ -427,7 +427,11 @@ def _keyboard_is_shown(device: Any) -> Optional[bool]:
def _price_cent(root: ET.Element) -> int:
"""优先读取页面上半部的当前单价,不把划线价当成当前价。"""
"""读取页面上半部的当前采购金额,不把划线价当成当前价。
商品/规格页可能显示单价或小计;最终确认页应显示订单总价。业务层只在
最终确认页把该值与订单总价上限比较。
"""
parsed_nodes: list[tuple[int, int, str]] = []
screen_bottom = 0
+9 -4
View File
@@ -37,7 +37,7 @@ class PurchaseTarget:
goods_url: str
options: Mapping[str, str]
quantity: int
max_price_cent: int
max_price_cent: int # 人民币订单总价上限,单位分
@dataclass(frozen=True)
@@ -386,7 +386,7 @@ class PurchaseTaskService:
):
raise PddPurchaseError(
"PURCHASE_TASK_INVALID",
"人民币价格上限必须是大于 0 的整数分",
"人民币订单总价上限必须是大于 0 的整数分",
step="purchase_prepare",
)
return PurchaseTarget(
@@ -450,11 +450,16 @@ class PurchaseTaskService:
"无法读取稳定的当前人民币价格,已停止演练",
step="purchase_verify_quantity_price",
)
if state.price_cent > target.max_price_cent:
# 商品页或规格面板可能显示单价,也可能随数量显示小计;只有最终确认页
# 的金额具有稳定的订单总价语义,因此总价保护只在确认页执行。
if (
state.page_kind == "order_confirmation"
and state.price_cent > target.max_price_cent
):
raise PddPurchaseError(
"PURCHASE_PRICE_EXCEEDED",
(
f"当前价格 {state.price_cent} 分超过上限 "
f"当前订单总价 {state.price_cent} 分超过订单总价上限 "
f"{target.max_price_cent} 分,已停止演练"
),
step="purchase_verify_quantity_price",
+1 -1
View File
@@ -89,7 +89,7 @@ def admin_task_to_new_claimed_task(task: AdminTask) -> NewClaimedTask:
or price_cent <= 0
):
raise ValueError(
"采购任务 payload.max_price_cent 必须是大于 0 的整数分"
"采购任务 payload.max_price_cent 必须是大于 0 的人民币订单总价上限(整数分)"
)
target_color = normalized_options.get("color")
target_size = normalized_options.get("size")
+3 -2
View File
@@ -251,18 +251,19 @@ class PurchaseTaskServiceTest(unittest.TestCase):
self.assertEqual(observed, [("PUR-STARTED", TaskStatus.RUNNING)])
def test_price_above_limit_stops_before_confirmation(self):
def test_order_total_above_limit_stops_on_confirmation(self):
self._prepare_task()
adapter = RecordingDryRunAdapter(price_cent=5001)
outcome = self._service(adapter).execute_one_local()
self.assertEqual(outcome.kind, "task_failed")
self.assertNotIn(("enter_confirmation",), adapter.calls)
self.assertIn(("enter_confirmation",), adapter.calls)
detail = self.repository.get_task("PUR-001")
assert detail is not None
self.assertEqual(detail.status, TaskStatus.FAILED)
self.assertEqual(detail.last_error_code, "PURCHASE_PRICE_EXCEEDED")
self.assertIn("订单总价", detail.last_error_message)
def test_unavailable_goods_stops_before_specs_and_is_not_retryable(self):
self._prepare_task()