fix: 允许颜色缺价后继续采集 (#42)
This commit is contained in:
@@ -699,18 +699,6 @@ class PddCollectService:
|
||||
self._wait_spec_panel(device)
|
||||
|
||||
color_dimension, color_samples = self._collect_color_prices(device)
|
||||
missing_prices = [
|
||||
color.text
|
||||
for color in color_dimension.values
|
||||
if color.available
|
||||
and color_samples[color.text].price_cent is None
|
||||
]
|
||||
if missing_prices:
|
||||
raise PddCollectError(
|
||||
"PDD_DATA_PRICE_MISSING",
|
||||
"以下可用颜色没有采集到稳定价格:"
|
||||
+ "、".join(missing_prices),
|
||||
)
|
||||
size_dimension = self._collect_size_dimension(device)
|
||||
dimensions = (color_dimension,)
|
||||
if size_dimension is not None:
|
||||
@@ -723,11 +711,11 @@ class PddCollectService:
|
||||
skus = self._build_color_price_skus(
|
||||
dimensions, color_samples
|
||||
)
|
||||
has_available_price = any(
|
||||
item.price_cent is not None for item in skus if item.available
|
||||
)
|
||||
if not skus or not has_available_price:
|
||||
raise PddCollectError("PDD_DATA_PRICE_MISSING", "没有采集到可用 SKU 的价格")
|
||||
if not skus:
|
||||
raise PddCollectError(
|
||||
"PDD_DATA_SPEC_INCOMPLETE",
|
||||
"规格面板没有生成可提交的规格组合",
|
||||
)
|
||||
except PddCollectError as exc:
|
||||
if not exc.diagnostics and self._last_goods_xml:
|
||||
artifact = self._save_xml("collect-failed", self._last_goods_xml)
|
||||
@@ -1207,21 +1195,18 @@ class PddCollectService:
|
||||
options = {"color": color.text}
|
||||
if size is not None:
|
||||
options["size"] = size.text
|
||||
available = (
|
||||
color.available
|
||||
and (size is None or size.available)
|
||||
and sample.price_cent is not None
|
||||
)
|
||||
# 是否可用来自规格控件状态,不能用“是否采到价格”代替。
|
||||
available = color.available and (size is None or size.available)
|
||||
results.append(
|
||||
SkuResult(
|
||||
options,
|
||||
sample.price_cent if available else None,
|
||||
sample.price_cent,
|
||||
available,
|
||||
sample.raw_price if available else None,
|
||||
sample.raw_price,
|
||||
{"color": color.text}
|
||||
if sample.price_cent is not None
|
||||
else {},
|
||||
sample.list_price_cent if available else None,
|
||||
sample.list_price_cent,
|
||||
)
|
||||
)
|
||||
return tuple(results)
|
||||
|
||||
@@ -232,6 +232,13 @@ class IgnoredColorClickDevice(SnakeColorDevice):
|
||||
self.clicks.append((x, y))
|
||||
|
||||
|
||||
class AllMissingColorPriceDevice(IgnoredColorClickDevice):
|
||||
"""所有颜色点击都未生效,模拟全部颜色采价为空。"""
|
||||
|
||||
def _spec_xml(self):
|
||||
return super()._spec_xml().replace('selected="true"', 'selected="false"')
|
||||
|
||||
|
||||
def keep_only_one_sku(xml_data: str) -> str:
|
||||
"""从脱敏固件中删除蓝色和 L,只保留一个组合。"""
|
||||
|
||||
@@ -562,6 +569,32 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
|
||||
self.assertIsNone(sample.price_cent)
|
||||
|
||||
def test_all_missing_color_prices_still_collects_sizes(self):
|
||||
device = AllMissingColorPriceDevice(self.home_xml)
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: device),
|
||||
"USB-001",
|
||||
"client-001",
|
||||
sleeper=lambda _seconds: None,
|
||||
)
|
||||
|
||||
data = service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
).to_pdd_data()
|
||||
|
||||
self.assertEqual(
|
||||
[item["text"] for item in data["dimensions"][1]["values"]],
|
||||
["M", "L"],
|
||||
)
|
||||
self.assertTrue(device.sizes_visible)
|
||||
self.assertTrue(data["skus"])
|
||||
self.assertTrue(all(item["price_cent"] is None for item in data["skus"]))
|
||||
self.assertTrue(all(item["raw_price"] is None for item in data["skus"]))
|
||||
self.assertTrue(
|
||||
all(item["price_observed_at"] == {} for item in data["skus"])
|
||||
)
|
||||
self.assertTrue(all(item["available"] for item in data["skus"]))
|
||||
|
||||
def test_missing_goods_id_is_structured_error(self):
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: object()),
|
||||
|
||||
@@ -59,6 +59,10 @@ Client 顶级导航仅包含:
|
||||
`price_observed_at` 只记录实际选中的颜色,不得虚构已选尺码。以后需要按尺码
|
||||
区分价格或组合库存时,必须另行确认并改用 `price_granularity=sku`。
|
||||
|
||||
颜色已售罄、无法选中或未采到稳定价格时,不重试采价,也不阻断后续流程;该
|
||||
颜色的价格相关字段留空。所有颜色处理完成后仍须采集尺码、保存本地结果并提交
|
||||
Admin。规格 `available` 只反映页面控件状态,不能根据价格是否为空推断。
|
||||
|
||||
对于“1.2 万+”等近似数值,同时保存规范化数值、原始文字和近似标记。
|
||||
|
||||
### 4.2 采购任务
|
||||
|
||||
@@ -525,6 +525,10 @@ CREATE TABLE app_settings (
|
||||
这里的 `available` 来自当前规格节点显示状态,不表示 Client 已逐个验证了每个
|
||||
颜色与尺码组合的实时库存。
|
||||
|
||||
颜色无法选中或没有采到稳定价格时,该颜色对应组合的 `price_cent`、`raw_price`
|
||||
和 `list_price_cent` 使用 `null`,`price_observed_at` 使用空对象。缺价不改变
|
||||
`available`,也不阻止尺码采集、结果入库和提交。
|
||||
|
||||
### 8.2 采购结果
|
||||
|
||||
采购任务在同一结构中增加:
|
||||
|
||||
@@ -310,6 +310,7 @@ Idempotency-Key: task-id:attempt-id:result-v1
|
||||
- `[必须]` Client 只有收到 `accepted: true` 后才能把本地任务标为 `succeeded`。
|
||||
- `[必须]` `price_cent` 是实际支付价的整数分;划线价可放在 `list_price_cent`。
|
||||
- `[必须]` `price_granularity` 只能是 `color` 或 `sku`。当前 Client 按颜色采样时填 `color`;同一颜色的尺码组合可以共享颜色价格,但 `price_observed_at` 只填写实际选中的颜色,不得虚构尺码。以后逐个选择完整组合采价时才使用 `sku`。
|
||||
- `[必须]` 采集任务中,颜色无法选中或未采到稳定价格时,相关 `price_cent`、`raw_price` 和 `list_price_cent` 允许为 `null`,`price_observed_at` 允许为空对象;Admin 不得仅因部分或全部颜色缺价拒绝结果。
|
||||
- `[必须]` `shop_name` 采不到时允许省略或留空;Admin 不得因此拒绝老版本 Client,也不得用空值覆盖已保存的店铺名。
|
||||
|
||||
### 6.1 Admin 必须无条件接受
|
||||
|
||||
Reference in New Issue
Block a user