feat(product-commerce): 系列聚合去重与采集开关两层语义
配置来源收敛 - 系列平台编码 tm/jd/dy/jd_self 与 9 类飞书地址改为整系列并集去重后挂锚点, 子款编码保留在 series.platform_ids 备查;多表冲突保留最高优先级并告警 - 写表脚本(当日/天猫主图/京东主图)去掉本地 JSON 回退,地址缺失或读取失败 直接 fail closed,避免库与表地址不一致被静默掩盖 - 测试注入 records_provider 时禁用配置缓存读写,防止假数据写入生产快照 采集开关(dim_style.collect_enabled) - 锚点为系列总开关:false 则整系列不采 - 锚点为 true 时,子款自身 false 仅把自己的 ERP 编码排除出聚合,其余子款照常 - 排除结果记录在 series.erp_excluded 并在运行时提示 聚水潭全店铺日报防错 - 列布局 fail closed、行 td 数校验、满页判定 + 总页数兜底、值域交叉校验 - 新增单日总量闸:超近 14 天中位数 8 倍拒绝落库 其他 - 新增电商费用归集配置 ad_cost_rollup.json 与积分兑换周报采集 - 店铺情报、供应链到货同步、通知路由与相关配置/文档同步更新 - 忽略本地 dsh workbench 脚手架(含 node_modules)
This commit is contained in:
1 parent
2f31d5872f
commit
622a609800
66 files changed
+4156
-469
No files matched your search
@@ -286,6 +286,7 @@ def test_queue_claims_rows_with_skip_locked_and_maps_tasks() -> None:
|
||||
queue = ArrivalSyncQueue(
|
||||
{"GYXX_ARRIVAL_SYNC_OPERATOR": "worker"},
|
||||
connect_factory=lambda: connection,
|
||||
tenant_column=True,
|
||||
)
|
||||
|
||||
tasks = queue.claim(tenant_id=1, stale_after_seconds=60)
|
||||
@@ -307,7 +308,10 @@ def test_queue_claims_rows_with_skip_locked_and_maps_tasks() -> None:
|
||||
|
||||
def test_queue_completion_is_fenced_by_the_claimed_target_timestamp() -> None:
|
||||
connection = _Connection()
|
||||
queue = ArrivalSyncQueue(connect_factory=lambda: connection)
|
||||
queue = ArrivalSyncQueue(
|
||||
connect_factory=lambda: connection,
|
||||
tenant_column=True,
|
||||
)
|
||||
task = ArrivalSyncTask(
|
||||
id=8,
|
||||
tenant_id=1,
|
||||
@@ -358,3 +362,76 @@ def test_cdp_session_is_checked_before_optional_password_validation(
|
||||
)
|
||||
|
||||
assert manager.login_with_browser() is True
|
||||
|
||||
|
||||
def test_single_company_queue_omits_tenant_filters() -> None:
|
||||
"""单公司化后队列表没有 tenant_id,认领与回写都不能再带租户条件。"""
|
||||
|
||||
connection = _Connection(
|
||||
claim_rows=[
|
||||
{
|
||||
"id": 9,
|
||||
"sku_code": "SKU-9",
|
||||
"expected_arrival_time": datetime(2026, 10, 19, 9, 0),
|
||||
}
|
||||
]
|
||||
)
|
||||
queue = ArrivalSyncQueue(
|
||||
{"GYXX_ARRIVAL_SYNC_OPERATOR": "worker"},
|
||||
connect_factory=lambda: connection,
|
||||
tenant_column=False,
|
||||
)
|
||||
|
||||
tasks = queue.claim(tenant_id=1, stale_after_seconds=60)
|
||||
|
||||
claim_sql, claim_params = connection.calls[0]
|
||||
assert "tenant_id" not in claim_sql
|
||||
assert claim_params == (60, "worker")
|
||||
assert tasks == [
|
||||
ArrivalSyncTask(
|
||||
id=9,
|
||||
tenant_id=1,
|
||||
sku_code="SKU-9",
|
||||
expected_arrival_time=datetime(2026, 10, 19, 9, 0),
|
||||
)
|
||||
]
|
||||
|
||||
assert queue.mark_success(tasks[0]) is True
|
||||
success_sql, success_params = connection.calls[-1]
|
||||
assert "tenant_id" not in success_sql
|
||||
assert success_params == ("worker", 9, datetime(2026, 10, 19, 9, 0))
|
||||
|
||||
|
||||
def test_queue_schema_probe_falls_back_when_tenant_column_absent() -> None:
|
||||
class _ProbeCursor:
|
||||
def __enter__(self) -> "_ProbeCursor":
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def execute(_sql: str, _params: tuple[object, ...]) -> None:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def fetchone() -> None:
|
||||
return None
|
||||
|
||||
class _ProbeConnection:
|
||||
def __enter__(self) -> "_ProbeConnection":
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
@contextmanager
|
||||
def cursor(self, **_kwargs: object):
|
||||
yield _ProbeCursor()
|
||||
|
||||
queue = ArrivalSyncQueue(
|
||||
connect_factory=lambda: _ProbeConnection(),
|
||||
tenant_column=None,
|
||||
)
|
||||
|
||||
assert queue._has_tenant_column() is False
|
||||
Reference in new issue
Block a user