Files
docs/自营业务/领星一件代发数据统计一期方案.md

1196 lines
211 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 领星一件代发数据统计一期方案
## 客户名称与商品资料接入(2026-10-14 最新确认)
此节覆盖下文销量/库存不得选择货主的旧页面限制;经营总览仍不增加客户筛选。销量分析、库存明细、库存周转允许按名称或编码选择客户,默认全部,货主汇总与 SKU 排名均使用原有同仓、同货主、同 SKU 统计口径。名称缺失仍显示外部业务编码,不使用内部数据库主键。
Lastleg 维护 `ll_selfop_owner` 和 `ll_selfop_product`,以完整资料 run 为版本;稳定关联键为来源账号+货主编码,商品再加 SKU,不按名称连接,也不自动创建 IAM 客户或对外收费关系。`ll_selfop_sku_profile` 保留原统计版本职责,不被商品目录替代。客户、商品资料整批完成分页元数据、总数、唯一身份校验后同事务发布;不完整批次不参与查询。`GET /owner-directory` 及各列表/榜单名称均取 queryAsOf 之前最近完整发布的账号资料批次,刷新后才看到新名称;不重采历史出库。
持续 BUSINESS 模式每天登记一项账号级 MASTER_DATA 任务,未结束批次跨日续跑。客户列表每页1000、商品每页100,沿用 Integration 唯一签名出口和账号1200ms间隔、短 permit、lease/fence、限流及未知结果保护。资料接口每次只处理1页就释放账号租约,并持久化至少5秒后的下次调度时间;等待期间日销量、计划库存、历史销量仍优先,普通销量与库存的1200ms账号间隔不变。5秒是本轮保守运行参数,不代表供应商承诺的限流阈值;再次429仍遵守账号冷却与复核保护;正常让出保持同一run与下一页,失败或丢失租约的新尝试从头扫描且不能发布旧半批数据。不开第二条真实出口。
商品保留申报长宽高及单位、WMS实测长宽高及单位,两者不互相冒充;空值保留 NULL。库存/出库立方数按 2026-10-14 用户确认扩展到库存周转视图,尺寸优先级、换算、缺失及历史估算口径统一维护在[库存周转视图设计](库存周转视图设计.md)的“库存与出库立方数”章节。结构基线见后端 `lastleg-service/src/main/resources/db/selfop-master-data.sql`;发布和真实首批同步是否完成只记录工程主契约,本设计不表示已部署。
## 持续 BUSINESS 自动采集(2026-10-13 最新确认)
2026-10-16 用户确认补齐出库自动恢复:持续 BUSINESS 出库根及其分仓任务遇到普通执行异常、数据库临时锁/连接故障、供应方明确标为可重试的拒绝响应,以及目录重复、条数不符、目录变化或分页总数变化时,首次失败后最多再自动重试3次,分别等待10/20/40秒;以同一任务持久化的 FAILED 次数计数,YIELDED、等待账号间隔不消耗失败次数。第4次失败转DEAD、清空下次重试时间;日计划幂等逻辑不得另建同日根绕过上限。普通异常沿用目录、已验证暂存、归并屏障及任务身份;目录完整性错误仍按原规则清除目录检查点重新校验,不把失败批次直接发布。数据库永久错误、业务身份/范围错误、鉴权、限流、传输结果未知、停止会话和失效租约均不自动绕过保护;库存和资料继续使用原有可重试错误及次数规则。既有DEAD任务不因发布新代码自动复活。
失败尝试在现有 `error_message` 保存有界结构化诊断,包含执行阶段、异常类型、SQLState/数据库错误码(如有)、累计失败次数、重试上限和是否安排重试;不保存原始异常消息、SQL、参数、供应商响应或凭据。采集历史与任务详情沿用现有 `errorCode`/`failureSummary` 返回可读解释,日志按任务号、运行ID、受控诊断和代码栈位置定位。计算/发布异常使用实际调用阶段;采集内部异常使用该尝试最近持久化检查点。实际部署和验证状态仅见工程主契约。
用户随后明确取消 BUSINESS 的22:36:59整体许可截止及现有manual/新根人为会话截止。最新代码目标为所有BUSINESS不因这些截止停采,不以continuous开关决定是否免截止;开关仍控制服务端持续计划。VALIDATION仍有界,短permit有效期、有效lease/fence、匹配非空grant、唯一出口、限流/认证/未知结果保护均保留。已有DEAD不自动复活,消费计数和检查点不重置。下文关于BUSINESS保留冻结根截止的旧要求由本条覆盖;实际环境是否生效仍由工程主契约证据确认。
本节覆盖下文 TEST 仅采七日、禁止自动计划/两个月首采,以及 BUSINESS 必须因整体独占窗口或根总时长到期停采的旧限制;未显式开启持续模式时,有限许可及 VALIDATION 的原门禁继续生效。运行版本、部署状态和已验收证据仅维护工程主契约。
本批以北京时间完整自然日为单元,将最近两个月完整日持久化到服务端任务池。首次固定为 2026-08-13 至 2026-10-12(含两端),不是固定六十天;已完成日期、当前运行的 10 月 7 日及旧队列可能接续的 10 月 6 日必须核对并复用,不能因重启或开启新模式重复采集。历史按近到远推进;每日北京时间 00:05 幂等加入前一完整日,优先于尚未启动的历史任务,不抢占或重建正在执行的根。首次下一日计划为 2026-10-14 00:05;日期推进、任务去重、完成/失败状态和检查点由服务端持久维护,不依赖本机 Node 队列。重启及跨夜继续未完成任务,漏掉计划时刻后补齐应有完整日,不将已完成日重新排入池。
Lastleg `baiyue.self-operated-analytics.continuous-business-collection-enabled` 与 Integration `baiyue.integration.lingxing-wms.egress.continuous-business-collection-enabled` 同名开关均默认 false(环境变量分别为 `SELFOP_CONTINUOUS_BUSINESS_COLLECTION_ENABLED`、`LINGXING_CONTINUOUS_BUSINESS_COLLECTION_ENABLED`)。仅双方新代码及配对 BUSINESS 配置显式开启时允许持续采集;该模式不因旧整体 grant 到期、跨午夜或 BUSINESS 根总时长停止。每轮调度仍用正值批次及执行时长让出,任务状态和失败恢复条件仍生效,不能把持续模式当作复活 DEAD 的授权。
持续模式仍要求两端非空、匹配的独占 grant、单账号唯一出口、租约/fence、持久 pending 及未知结果保护。每个外发 permit 必须有不超过 30 秒的短有效期并校验用途/账号/代次;不能把有限 grant 整体截止当成无限长 permit,也不能重放旧 permit。账号统一最小启动间隔保持 1200ms、在途最多一笔,保留请求预留/确认统计。BUSINESS 不设每日与根累计次数额度,不能清零历史消耗;VALIDATION 不进入持续模式,保持有界样本和授权期限。真实 429、认证错误、结果未知或失效 fence 继续暂停/拒绝后续真实出口,并保留检查点,不自动绕过复核。
BUSINESS 的 `budgetDate` 随北京时间跨日更新,但 `reservedHttp/confirmedHttp` 保留累计值,不将新日期误解为当日新增次数或零点清零;根计数同样累计。有限 VALIDATION 日额度按原日边界换日,冷却、暂停和未确认 pending 跨日保留。查询和页面对 BUSINESS 显示累计请求消耗,不能按该日期把累计量标为当日消耗。
持续 BUSINESS 库存扫描在收到完整响应后遇到重复库存键、扫描唯一条数不符或分页总数变化,可按 `maxAttempts` 限制失败次数并指数退避重试(首次10秒,上限1小时);主动让出不计失败。每次失败保留原尝试证据并清空分页检查点,新尝试从第一页完整读取,以新批次隔离失败数据,禁止去重补齐或拼接后发布。达到上限转 DEAD 并清空下次重试时间;已有 DEAD 不自动复活。该例外不放开出库任务、鉴权、限流、身份错误、未知响应、传输结果未知及账号暂停保护。重试保留原定时任务和计划日;已开始执行的库存任务可在次日继续到期重试或中断续跑,仍受原失败次数、任务终态和出口保护约束;尚未开始的历史计划不补跑。只有同计划日成功的合格观测可成为日代表,跨日重试仍可更新当前库存但不能回填历史日样本。
平台授权人员显式恢复持续计划任务时,沿现有 `POST sync-tasks` 的 `RETRY`、独立 sync 权限和幂等审计。TEST 在持续 BUSINESS 开关开启且持久化计划身份严格匹配时允许恢复,不能继续按旧的“仅人工根”条件拒绝;开关关闭、旧定时槽位或伪造计划身份仍拒绝。恢复沿用原任务、计划日期和请求计数,不把计划任务改成手工补采,也不跳过原业务停止原因、冷却、租约及执行保护。
发布必须先确认旧 cutover 的固定版本部署及续接状态,再在自然日完成、两仓发布、无活动根/未完成出口请求的边界,由总控指定准确候选提交和发布操作。两服务新代码部署并验证后,配对配置再显式开启,避免新旧代码混用;旧本机 helper 只读,未经核对和明确指令不得修改、停止或与其并行发布。发布完成不等于真实验收通过。
验收分别提供代码门禁、实际镜像与配对配置、真实 HTTP/任务池证据:完整日期集合、无重复或漏日、已完成日不重采、每日 00:05 与优先顺序、旧截止过去仍有效持续、重启续跑和出口保护。两仓完成与独立完整日覆盖按原发布条件证明;库存观测不倒推历史、未采日不填零。危险的 429/鉴权/fence/未知响应分支用离线定向测试证明,不为验收主动制造供应商限流或破坏真实任务。
库存全量扫描使用独立配置 `baiyue.self-operated-analytics.inventory-page-size`,默认 1000,允许 1~1000;出库目录仍使用 `page-size`,默认 100。库存不再受出库页大小限制,以减少分页请求和完整扫描耗时;账号统一 1200ms 间隔、全仓全货主全品质及零库存保留规则不变。大页不等于稳定快照,跨页重复、总数变化及唯一键计数校验仍必须通过,失败不能去重后发布。
显式重试持续库存任务时,若原计划身份有效、持久化停止原因为空,且最近一次真实尝试仅因重复键、总数变化或唯一键计数失败,可恢复原 DEAD 根并在同一事务创建重试审计。不能把这种扫描失败自动写成整个会话已停止;恢复前后计划日、失败尝试和请求计数保留。已有持久化停止原因、限流、鉴权和结果未知仍拒绝;被拒绝的恢复命令不新增会话停止原因。此项运行是否生效见工程主契约,不以源码测试代替真实恢复验收。
## 库存周转视图新增范围(2026-10-13)
用户要求收集开发讨论并交总控开发“库存周转”,新增视图、接口、指标与验收以[库存周转视图设计](库存周转视图设计.md)为唯一维护入口:库存分析普通进入默认该视图,7/15/30 个完整日出库同屏,SKU 默认、货主汇总可选;原库存明细 10/30 天查询及总览严格下钻保持明确入口。此处仅允许新视图的货主汇总,不恢复销量分析或经营总览的独立货主控件。
新视图同时区分预计可售天数与基于完整每日库存观测的期间件数周转;后者缺少合格样本时显示不足,不使用当前库存或首次采集日期倒推。该扩展替代下文“期间周转不属于本轮”的范围限制,原始库存日代表选择、数据完整性、版本与未知规则继续有效。成本分摊和仓租优惠仍仅提供内部评估依据,实际计费与正式评级另行确认。
## 本轮 TEST 采集与可售天数口径(2026-10-13 用户确认)
本轮 TEST 采集范围为截至 2026-10-12 的最近 7 个完整北京时间自然日(2026-10-06 至 2026-10-12),由新向旧逐日补采,保留已经运行及完整采集的日期,不重复按仓扫描账号级出库目录。该范围替代下文对本轮 TEST 仅做小样本的限制,BUSINESS 取消账号每日与根会话的请求次数额度,保持 1.2 秒请求间隔、有限账号独占窗口、根截止时间、请求消耗计数、完整分页验证及真实限流暂停;不改变正式生产历史首采安排。
用户所称“周转率”本轮采用预计可售天数:当前正品可售库存 ÷(所选完整窗口实际出库件数 ÷ 窗口天数)。库存分析支持 10 天、30 天及明确的统计截止日(默认昨天),先满足完整近 10 天,再补齐 30 天;库存观测时间与销量统计区间分别显示。未完整采集的日期不能当成零销量,未齐窗口不能给出完整口径日均销量及预计可售天数;窗口完整且销量为零时明确显示无出库,不除以零。经营总览和原有榜单仍遵循各自 30 天或 7/30 天口径。部署与验收状态只维护在工程主契约。
## 当前页面重点:以 SKU 为中心(2026-10-12 用户截图纠正)
用户确认主要查看 SKU 统计,货主仅用于识别该 SKU 的归属。销量、库存页面取消独立货主选择、货主选项翻页及货主经营概览区域,不再以先选货主作为查看统计的入口;仓库与 SKU 为主要查询条件,销量时间范围和库存关注条件在紧凑、统一的筛选区承载。列表优先展示 SKU、商品名称和业务指标,所属货主作为辅助信息显示。同仓同名 SKU 仍按实际货主分别统计;趋势下钻继续携带该行的货主编码,不能为了简化界面混合不同货主的数量。
页面收起来源账号键、技术时区及长篇计算说明,必要规则以简短辅助说明或同页折叠内容承载;未选仓库、暂无数据和加载失败明确区分。下文关于货主独立选择及同页概览的旧页面安排由本节替代;后端已存在的货主查询能力与数据隔离不因此删除或改变。逐操作承载与人工视觉证据维护在 Admin 模块 `operation-matrix.md`,部署和验收状态仅记入两份工程主契约。
用户本轮新增“以图表汇总老板关心的数据”的独立页面,正式设计见[经营总览图表设计](经营总览图表设计.md)。自营业务增加 `/self-operated/overview`,形成经营总览、销量分析、库存分析、数据采集四个入口;总览继续以 SKU 为中心,按单仓展示出库趋势、库存消化分布及三个 Top 10,不恢复货主筛选。新增聚合与精确下钻合同以该设计为唯一入口,采集、事实、基础公式和权限继续遵循本文。
本文规划平台自营业务的领星出库与库存统计,供业务和研发确认实现范围。用户已明确实际使用领星 WMS,凭据用于自研系统/服务商内部系统对接,采用 OMP 管理后台 API。第一期仅供平台内部查看:每天 08:00、20:00 获取一件代发出库单,销量历史首次获取最近 2 个月、后续增量获取;库存每天同步一次、每次全量,形成 SKU 每日销量、热销 Top 100、滞销 Top 100 和库存可售天数。正式历史首采安排在生产执行,测试只做有硬上限的小样本流程验证,同一真实账号不由多个环境同时采集。SKU 销量列表与趋势图支持已确认的“今日/近 3 天/本周(周一起)/近 15 天/本月”,并可选择历史日期范围。
**建议采用现有两个 Java 服务协作:`integration-service` 连接领星,`lastleg-service` 新增独立自营分析子域,保存同步事实并计算指标。** 一期不新增微服务或调度平台;领星库存独立存储,不写入现有客户仓储库存。
日期:2026-10-12。**本对话维护 API 只读联调依据与设计,代码由开发对话实施。当前工作区已有 Integration 和 Lastleg 初步源码及十表基线,本次只读核对源码,不修改或运行这些实现;部署、真实落库和性能尚未在本对话验收。** 外部请求参数、签名、响应差异及实测证据统一见[接口联调手册](领星WMS接口联调手册.md)。用户已确认领星返回时间已转换为北京时间,本期直接按返回日期统计,来源与业务时区统一为 `Asia/Shanghai`。08:00 库存同步、近 30 天榜单及其他默认值仍为设计建议,区别于已确认的时区、同步频次、首次采集范围和使用范围。
进入开发前先读本文的[开发准入与正式启用门槛](#开发准入与正式启用门槛),区分可立即编码的范围与必须实测后才能启用的统计能力。
下文源码核对及“尚未实现”描述为早前交接时的检查快照;开发总控正在同一工作区持续实现,当前完成情况以总控的最新代码与验证证据为准。本次追加的是明确的设计合同和验收要求,不将旧检查快照当作重新开发已有功能的依据。
## 一期交付范围
| 内容 | 一期范围 |
| --- | --- |
| 数据来源 | 领星 WMS 自研系统/服务商内部系统接口(OMP);接入一件代发出库列表、详情及产品库存 |
| 库存业务种类 | 仅 SKU 产品库存,采集正品、次品和零库存;不采整箱库存,不混入整箱中转出库单 |
| 账号数据范围 | 已确认当前账号可见全部 70 个货主、2 个仓库纳入自营统计;保存货主、仓库维度,不按同名 SKU 跨货主合并 |
| 分仓方式 | 产品库存每仓单独请求、任务、快照和发布;销量按来源仓库独立覆盖和统计;各仓独立配置时效与后续预警规则 |
| 使用主体 | 平台自营业务,仅授权的平台人员可读;不开放客户工作台或 PDA |
| 出库同步 | 生产首次取最近 2 个月,后续每天 08:00、20:00 增量获取,包含新单发现、已知单复查与范围内历史核对;测试按显式有界任务验证,允许完整单仓库存及短出库窗口发布,出库日期覆盖边界见后文 |
| 库存同步 | 每天一次、每次全量获取完整快照,建议安排在 08:00 批次 |
| 统计能力 | SKU 每日出库销量,今日/近 3 天/本周/近 15 天/本月的区间累计与逐日趋势、历史日期查询;另提供完整 7/30 天热销榜、完整 30 天滞销榜、近 30 天日均销量与库存可售天数 |
| 数据可信度 | 最近成功时间、订单覆盖区间、库存观测时间、缺失与异常状态、失败重试记录 |
| 交付形式 | 后端任务、数据库事实及统计、平台查询与受控重试 API,以及 Admin 经营总览、销量分析、库存分析、数据采集页面;总览图形与下钻见独立设计,其余菜单、进度与验收按本文规划,视觉按逐操作矩阵确认。后端完成不能替代页面交付 |
| 货主经营评估 | 本仓货主概览及 SKU 同屏出库/库存/可售天数、无出库与消化时间筛选、冻结/次品关注、每日库存观测留存;只给经营依据,不自动评级 |
| 后续扩展 | 真实库龄、期间周转、内部经营评级与成本分摊,以及库存预警、补货建议、采购与在途联动;一期不实现自动评级、仓租加价、规则引擎或自动采购 |
这里的“销量”暂定义为**已出库商品件数**。它不等于支付订单量、成交收入或扣除退货后的净销量。界面和 API 应使用“出库销量”这一明确名称;若后续需要真实成交销量,应另接销售订单和退货事实。
### 货主经营目标与本次补充范围
2026-10-12 用户进一步确认:仍为自营业务,先评估各货主的库存效率,后续用于内部成本分摊,不是当前对外仓储客户收费。业务需要从“某仓某货主”进入,查看各 SKU 的出库速度、库存消化时间和占仓关注项,再形成可解释的内部经营评价。领星货主编码继续作为外部业务维度,不自动绑定平台客户、财务账单或收费对象。
货主评估的后台口径见[货主概览与库存关注](#货主概览与库存关注),历史观测见[每日库存观测留存](#每日库存观测留存);当前页面按页首最新确认以 SKU 为中心,保留货主归属和后台查询能力,不恢复货主控件或概览面板。新增经营总览第四个入口复用既有事实与日表,不另建评分平台。真实库龄、正式期间周转指标、评级阈值及成本分摊属于[后续内部评级与成本分摊](#后续内部评级与成本分摊),不阻断已有采集与统计,也不因公开接口线索自动增加真实请求。
## Java 服务归属
### 现有工程依据
| 现状 | 对方案的影响 |
| --- | --- |
| [Integration 指引](../../baiyue-firstleg/integration-service/AGENTS.md)明确负责第三方适配,不保存业务主数据 | 认证、签名、限流和协议转换放在 Integration;统计表和业务同步进度放在业务服务 |
| 船节点接入已采用 Integration 适配、Firstleg 编排与持久化的分工 | 本方案沿用同类职责划分,不把集成服务扩展成报表服务 |
| [Lastleg](../../baiyue-firstleg/lastleg-service/AGENTS.md)承载海外出库、库存及相关业务,已有 MyBatis、MySQL、内部调用和统一鉴权基础 | 一期可在服务内增加独立自营分析包及独立表,无需新增部署单元 |
| 现有 `ll_inventory_balance` 以客户入仓单和箱数管理,`ll_dropship_sku_inventory` 绑定客户、订单及托盘档案 | 不能直接复用为领星的账号、仓库、SKU 库存快照 |
| 领星适配、同步控制及指标需分阶段交付 | API 只读读取成功不等于 Java 完整适配、定时同步、业务落库或统计能力已经实现 |
一期放入 Lastleg 是基于出库与库存的业务关联和现有工程能力作出的选择。未来若自营业务扩展到多平台经营、采购、利润与供应链计划,再评估独立自营业务服务,不把全部经营逻辑持续塞入 Lastleg。
### 职责与调用关系
```mermaid
flowchart LR
LX[领星 WMS OMP接口] -->|列表 详情 库存| IT[Integration 领星适配]
IT -->|内部查询响应| JOB[Lastleg 自营同步任务]
JOB --> FACT[外部出库事实和库存快照]
FACT --> STAT[日销量与库存分析]
STAT --> API[平台只读 API]
API --> GW[统一 Gateway]
GW --> USER[平台内部使用]
```
图中箭头表示数据流。实际拉取由 Lastleg 发起内部请求,Integration 再调用领星;不使用用户 JWT 调用内部接口,不跨服务直查数据库。
仓库是一级统计与运行范围,页面和查询必须先选仓。产品库存按 `whCode` 分仓拉取;出库分页不支持仓库筛选,所以使用账号级共享发现,再依据返回的 `whCode` 分发到各仓任务。不能为每个仓重复读取同一账号的全部目录和详情。各仓有独立快照、发现覆盖、发布版本、成功时间和过期状态;跨仓概览如后续增加,只能展示各仓结果及各自版本,不在一期合并出一个全局可售天数或全局告警阈值。
| 服务 | 负责内容 | 数据归属 |
| --- | --- | --- |
| Integration | WMS 加签、账号配置解析、分页请求、超时、限流、错误归一化、必要字段转换 | 凭据由外部安全配置注入;不保存订单、库存和销量 |
| Lastleg 自营分析子域 | 定时编排、批次与租约、单据复查、事实落库、库存快照、日统计、榜单和可售天数 | 全部业务表留在 Lastleg 自身 Schema |
| Gateway 与 IAM | 平台入口、统一身份及权限控制 | 复用现行机制,实现时登记具体路由和权限 |
建议目标代码目录如下,实际已实现范围以代码和验证结果为准:
```text
baiyue-firstleg/
integration-service/src/main/java/com/baiyue/integration/
provider/lingxing/ WMS 客户端及签名器
dto/lingxing/ 外部字段与内部查询契约
controller/LingxingInternalController.java
config/LingxingWmsProperties.java
lastleg-service/src/main/java/com/baiyue/lastleg/
selfoperated/analytics/
client/ Integration 内部客户端
sync/ 调度 复查 批次 租约
outbound/ 外部出库事实
inventory/ 外部库存快照
statistics/ 日销量 榜单 可售天数
controller/ 平台只读查询
mapper/ 本子域数据访问
```
自营数据以来源账号及其业务范围为边界。用户已确认当前账号可见全部 70 个货主、2 个仓库均纳入自营统计,初期按账号全集读取,不要求再提供子集白名单。70/2 是本次观测数量,不能硬编码为上限;范围清单及其摘要保存在同步版本中,后续范围变化重新核验覆盖,不能继承旧范围的完整性结论。
OMP 的 `customerCode` 是外部货主编码,必须保存,不创建虚构客户、不填入现有平台 `customerId`,也不借用客户订单表的 `tenant_id=0` 存自营订单。来源仓库编码保留为外部维度;一期无需为了统计在 Warehouse 中自动创建仓库。未来与自有仓业务关联时再显式建立映射。
## 领星接口选择与字段口径
### 使用 WMS 自研系统接口(OMP)
用户已确认凭据用于自研系统/服务商内部系统对接,本方案收敛到 [OMP 官方接口](https://apidoc-omp.xlwms.com/llms.txt)。WMS 另有面向智能设备的接口,不能混用其路径、字段或状态码。第一期接当前一个来源账号的全部已确认自营范围,不自动合并跨账号或跨货主的同名 SKU。
| 用途 | 官方接口 | 关键字段与边界 |
| --- | --- | --- |
| 出库单发现 | `POST /openapi/v2/order/delivery/page` | 以 `timeType=outboundTime` 查询出库时间窗口;`status=100` 已出库、`99` 已取消。返回 `deliveryNo`、`sourceNo`、`customerCode`、`whCode`、`outboundTime` 及 `productList`。见[出库列表](https://apidoc-omp.xlwms.com/reference/post_openapi-v2-order-delivery-page-1) |
| 出库明细 | `POST /openapi/v2/order/delivery/detail` | 从列表带入 `deliveryNo`、`customerCode`、`whCode`;以 `skuVOList.productSku` 与 `actualDeliveryQty` 核对实际出库量,`deliveryQty` 为预报量。见[出库详情](https://apidoc-omp.xlwms.com/reference/post_openapi-v2-order-delivery-detail-1) |
| 当前产品库存 | `POST /openapi/v2/stock/stockSku/page` | 全量不传品质过滤、`isHideInventory=0`;保存 `stkQty` 实物、`freezeQty` 冻结、`useStkQty` 可用、`transitQty` 在途。计算可售量时只取 `productQuality=0` 正品。见[产品库存](https://apidoc-omp.xlwms.com/reference/post_openapi-v2-stock-stocksku-page-1) |
出库列表分页使用 `current`、`size`,默认每页 10 条、最大 1000 条;可用 `customerCodes` 筛货主,公开入参没有 `whCode`。账号发现完成后根据返回 `whCode` 分仓,记录每仓应处理的目录集合与订单数量完成情况;只有共享目录窗口完整且该仓所需订单数量完整,才推进该仓覆盖。不能把某页未出现某仓解释为该仓该窗口零出库。返回时间按用户确认的北京时间解释;其他仓/时间类型的边界和查询窗口限制仍需核验。当前已出库单样本验证起点、终点均包含,具体条件见手册,不能从其他产品接口搬用约束。
### 最小字段映射
| 统计含义 | 一期取值与规则 |
| --- | --- |
| 出库单标识 | 来源账号+货主 `customerCode`+`deliveryNo`;另存 `sourceNo` 供业务展示和对照,不把两类单号混用,仓库变化必须复核 |
| SKU 标识 | 来源账号+货主 `customerCode`+原始 `productSku`;仓级统计再加 `whCode`,不按名称归并或随意改大小写 |
| 外部唯一键 | `uniqueKey4tenant` 仅作为来源证据保存;业务关联使用明确的账号、货主、仓库、SKU、品质字段,不拆字符串推断身份 |
| 出库件数 | 按当前确认采用列表优先:已出库列表 `productList` 的 SKU 与 `qty` 均完整时直接使用 `qty`;仅单笔订单缺明细、SKU 或数量时补详情并使用整单 `skuVOList.actualDeliveryQty`。真实数值零不触发补取,空目录不拉详情。同单同 SKU 先归并,不叠加包裹层数量,不混加列表与详情,不使用预报 `deliveryQty`。此为用户确认的采集取值策略,未证明列表数量与实际出库数量在所有订单上语义等价 |
| 出库日期 | `outboundTime` 已是北京时间,校验并解析后直接取其日期作为业务日;不再转为美国时间。缺失或无法解析时进入异常,不用创建时间或拉取时间补值 |
| 在库数量 | `stkQty` 实物数量;在途 `transitQty` 单列,不相加充当在库 |
| 可售数量 | 自营授权货主、指定仓库、正品 `productQuality=0` 的 `useStkQty`;不再次扣减冻结量,也不使用实物量自行推算 |
| 单独留存 | `freezeQty`、`transitQty`,账号、货主、仓库、品质、同步批次、观测时间及质量标记 |
正品可售的明确零值例外:在已发布的完整库存批次中,同一货主+SKU只有次品行,且这些行均为VALID、实物/可用/冻结均明确为0时,正品可售聚合为0。存在正品行但其可用量缺失、缺行占位、异常品质、数量未知或非零时不适用;库存单位与时效校验仍生效。经营总览、库存明细及库存周转共用此规则,不改写来源快照。
库存保存产品库存接口全部品质的记录,只将正品可用库存纳入可售量,不将次品或在途混入。代理仓响应中的 `stockCompare` 是对照信息,不与本条库存再次累加。库存读取来自从库,有秒级同步延迟;库存组成、零值和空值的实际语义仍需页面对账。数量使用十进制类型保留源精度,列表 `qty` 的类型与描述存在差异,不应直接整数截断。金额、收件人、电话和地址不属于本期统计所需字段。
OMP 请求使用 `appKey`、秒级 `timestamp`、`sign` 及业务 `data`,签名按照 [OMP 官方加签说明](https://apidoc-omp.xlwms.com/docs/%E5%BC%80%E5%8F%91%E9%AA%8C%E7%AD%BE%E5%B7%A5%E5%85%B7.md)验证固定测试向量与参数序列化。密钥与签名不写日志、不提交源码;请求时钟同步、配额、并发和超时均由适配器管理。
### 已验证与待开发能力
| 能力 | 截至 2026-10-12 的结果 |
| --- | --- |
| WMS 一件代发分页 | 本地 Java 服务取回 1 条已出库摘要;未执行整月全量详情采集 |
| WMS 一件代发详情 | 只读 HTTP 请求成功,主体身份与分页一致;样本 SKU 实出数量为数值 1 |
| 产品库存 | 只读 HTTP 完成两轮全分页,各 7 页、6,635 条;两轮业务键集合与四类数量一致。正品、次品、零库存均有返回 |
| 追加前置验证 | 正常单及取消单均可按单号无时间/状态定位;一个已出库样本的起止秒均包含;固定源时间窗口 372 单使用两种页大小得到同一集合;已验证多 SKU 详情 |
| 分页异常与恢复 | B 仓一次返回总条数 5,741、唯一业务键仅 5,740,判定该扫描不完整;随后从第一页完整重读两轮,均为 5,741 个唯一键且集合、数量一致。不能据重试成功删除失败证据 |
| 货主/仓库过滤 | 产品库存按样本的货主和仓库查询成功,返回记录全部符合指定范围;正式全量按已确认账号全集读取 |
| Java 内部 API 源码 | 当前 `LingxingInternalController` 已有出库摘要、仓库目录、按单定位、出库详情、产品库存五类入口;本轮只读核对源码,新增能力不自动视为部署和真实 HTTP 验收通过 |
| Lastleg 业务源码 | 当前已有十表基线、事实归并、日候选构建、发布及初步查询代码;五种图表合同和本次性能要求尚未全部收敛,具体差距见下文源码核对。实际业务落库、调度启用和压测未在本对话核验 |
凭据变量按用户在开发总控对话中的确认统一为 `LINGXING_OMS_APP_KEY`、`LINGXING_OMS_APP_SECRET`,当前应用及 Compose 配置已采用这两个名称;变量名称中的 OMS 不改变实际 WMS OMP 产品和 API,真实出口开关仍为 `LINGXING_WMS_ENABLED`。真实值通过私有环境配置或 Jenkins 凭据注入,不写入文档、源码或输出。此前 Java 验证服务已停止。具体参数、字段类型、库存核验计数及限制以[接口联调手册](领星WMS接口联调手册.md)为唯一详细记录;一次只读全分页核验不代表自动同步功能已完成。
## 生产首采与测试请求边界
用户本轮确认:**最近 2 个月的第一次正式历史全量采集在生产执行,测试以有界任务验证流程;本轮用户允许自主调整采集时长,执行安排为先完整单仓库存,再短出库时间片,不自动开展两个月首采。** 这是采集安排,不代表生产已经部署、已经开始首采,或本轮获得执行部署/SQL 的授权。本轮仅更新设计,不调用领星。现有单 TEST 部署事实仍按正式工程契约维护,不因该安排宣称生产环境已就绪。
### 同一真实账号只保留一个采集环境
一期采用生产独占真实账号采集的运行约束。正式生产开始前,本地/测试的自动任务、补偿、人工请求和独立探针均关闭真实出口;生产运行期间,测试以离线合成数据验证完整业务流程。账号配置别名不同、两个环境使用各自数据库锁或各设每秒一次,都不能形成同账号跨环境互斥。
确需真实小样本时,安排一次有截止时间的独占验证窗口:先停止原采集环境的新请求、自动重试和补偿,确认在途请求已结束,再允许验证环境读取;验证结束关闭其真实出口后,才交还正式环境。交接同时记录并接续同一账号当日已确认/保守预留请求计数、未到期冷却和暂停原因,验证请求消耗必须接续到交还后的账号消耗记录(VALIDATION 仍检查对应额度);状态未确认或冷却未到期时不恢复请求,不能因换环境就清零。持有同一凭据的其他服务或脚本也纳入排查。不通过同时启动两边来试探是否会限流。若以后需要跨环境自动切换或多实例同时待命,应另实现共同的账号级协调入口;当前各自数据库租约和 JVM 锁不构成这种能力。
验证设计增加 `collectionMode=OFF/VALIDATION/BUSINESS`:测试默认 OFF。只做截断小样本时使用 VALIDATION;为在 TEST 核验真实报表,可在显式有限独占窗口及根截止时间内使用 BUSINESS;BUSINESS 不以累计请求次数停采,限人工命令及其合法派生任务,禁止自动计划、自动历史复查及两个月首采。生产 BUSINESS 仍按正式发布流程启用。模式由服务端环境配置确定,前端不得把验证任务提升为正式任务。Integration 的真实出口开关同时受控;仅关闭销量发布不能阻止出库请求,更不能阻止库存发布。模式和采集用途均展示在采集页;这里不写入或更换凭据。
TEST 人工 BACKFILL 首次发布不依赖自动两个月首采:没有已冻结的首采起点时,仅以同仓同 scope_hash、BUSINESS、非验证根、WAREHOUSE_SYNC、APPLIED 且目录完整的切片最早起点作为覆盖计算锚点,再按连续区间证明计算终点;不改写 initial_from_utc,不跨过断档。存在既定首采起点时仍使用原起点,不跳过缺失历史。完整五分钟切片不代表完整业务日,查询保留 PARTIAL/已采集量及其他日期 UNKNOWN。账号目录一次获取后只派生当前配置仓库的分仓任务,不为移除的历史 scope 生成新任务。
### 测试样本的整轮硬上限
“10 页”按一次独占验证会话的**累计分页 HTTP 调用**理解,包括仓库目录、出库目录、库存分页、第二轮复核和重试,不是每仓/每次执行各 10 页。以下为本地保护设计默认,可以在验证前调低;不是供应商配额,也不是必须用满的采集目标。
| 项目 | 默认上限或规则 |
| --- | --- |
| 所有分页请求合计 | 最多 10 次;各接口 `pageSize` 最多 10,取得足够样本后提前停止 |
| 出库详情 | 最多 20 个唯一单据,同时最多 20 次详情 HTTP 调用;不因目录有更多单而自动全部展开 |
| 库存样本 | 两仓分别验证,每仓最多 2 次分页请求,计入上述 10 次总分页;不必为了满足条数跨窗搜索 |
| 真实 HTTP 总数 | 最多 40 次,包含分页、详情、按单定位、仓库查询、失败请求及任何重试;所有子任务共用,不按接口或仓重置 |
| 运行时长 | 最多 5 分钟;达到截止时间后不再发起请求,在途请求按受控超时结束 |
| 并发及间隔 | 同账号在途请求最多 1 个,测试起始请求间隔建议至少 5 秒;仍不能保证不会遇到账号已有流量/日配额限制 |
| 限流及恢复 | 真实验证禁用适配器自动重试,首次 429 或明确限流业务码即停止整轮会话并保留检查点;其他失败是否再试也必须在原预算内显式计数 |
预算在服务端持久化,以唯一验证会话根任务累计;发起每次真实请求前先检查根会话未停止/失败/到期,再原子扣减额度,子任务、再次打开页面、人工重试、进程重启都不能重置。适配器内部重试必须计入真实请求数;验证模式直接关闭该自动重试,避免 Lastleg 只计一次却实际发出多次。到任一上限就以 task/run 均为 `SAMPLE_DONE` 结束正常样本,记录停止原因;429/鉴权等失败为 run=`FAILED`、验证根 task=`DEAD` 且 `nextRetryAt=null`,不能伪装成预算正常结束。根停止后子任务不再发请求,在途完成后中止并保留根停止原因。验证任务不得进入 `YIELDED` 自动续跑、定时补偿或自动创建下一验证会话;普通正式任务的 DEAD 恢复规则不得复活已结束验证会话。再次真实验证须先核对账号占用、既有消耗及冷却,再显式建立新的有界会话。
验证任务 `executionPurpose=VALIDATION` 只允许请求、暂存与结构/身份校验:**不归并正式订单事实,不更新账号目录水位或分仓覆盖,不构建/发布销量与库存,不建立正式观察依据**。正式任务为 `BUSINESS`,执行、恢复、归并和发布每层都校验用途;子任务继承,不能因重新建 run 丢失限制。库存截断样本不能触发缺行占位、清零、旧快照替换;未读到的 SKU 只是未读取。即使某个极小窗口或空页完整,也不能把验证用途转换成正式版本。
未知真实仓码时,使用现有人工命令入口的 `WAREHOUSE_DISCOVERY` 操作,仅提交请求标识,不传仓码或出库时间。服务端建立真实账号级 VALIDATION 根任务,预算为一页最多 10 条、一次 HTTP、最长 60 秒;只允许仓库目录操作,目录结果和截断标记保存到尝试记录后结束,不接续出库或库存请求。普通 `VALIDATION` 命令冻结所选的唯一仓码;账号级出库分页返回其他仓单据时不获取其详情,库存也仅采所选仓,不因后续配置改变而扩大范围。两仓小样本分别创建根任务、串行执行,各自最多 300 秒和 40 次 HTTP,共享同一账号的累计日预算、间隔与冷却;目录发现不重置这些记录。
采集页对这类任务显示“测试样本已结束/限流停止”,并展示已消耗页数、详情数、真实请求数、预算、截止时间和停止原因,不显示“最近 2 个月同步成功”。真实小样本验证签名、分页、详情、库存字段、暂存、限流停止和页面状态;完整归并/发布、分页完整性异常、取消、改仓、重启和图表全链路用离线合成数据测试,测试结果与真实联调证据分开。不得在运行接口失败时回退为模拟成功。
### BUSINESS 完整任务的账号保护
2026-10-13 用户确认:BUSINESS 移除账号每日请求数和根会话累计请求数的硬上限,不因已确认或预留次数达到某个数值停止、等待次日或要求追加额度。`dailyLimit=0`、根 `maxHttp=0` 明确表示 BUSINESS 无请求次数额度,不是缺失配置或零个可用请求;VALIDATION 仍保留正值的日额度及整轮样本上限。每次真实外发仍先核对用途、账号授权、独占许可、根状态与期限、间隔、冷却及暂停原因,并原子记录预留/确认次数;迟到响应继续记账,重启、换仓、派生任务或续独占许可均不清零消耗。返回结果未知时保留保守预留与未知结果保护,不能用取消额度来跳过核验。缺页、详情未齐或覆盖不完整的结果不得发布为完整版本。
TEST 人工 BUSINESS 根创建时仍将 `timeBudgetSeconds` 冻结为 `deadlineAtUtc`,根与后代共用,重启不延长;有限独占授权期限也独立有效。授权/期限/根状态不满足时停止新的真实请求,不借用验证用途的 `SAMPLE_DONE`,已完整取得的数据仍可按原发布门禁完成纯本地校验/发布。`requestBudget` 的正值只限制每轮调度的请求批次,配合每轮执行时长公平让出执行权;达到批次边界保存检查点并 `YIELDED` 自动续跑,不终止 BUSINESS 根、不扣减一个有限的采集总额度,也不等待每日重置。每次内部调用的受控尝试次数仍约束该次实际外发和重试,不等同整轮采集额度。
BUSINESS 同账号在途请求最多一个,请求最小间隔保持 1.2 秒并排队等待,不靠反复触发本地限流错误节流。出库目录、详情、定位、两仓库存、复查和重试统一使用账号节流、消耗记录及保护。生产两个月首采仍使用正式可恢复任务,按日/小时切片推进;正式启用前确认供应商限制、该账号其他调用方及唯一真实出口,不把本地取消次数额度理解为供应商承诺无限调用。库存、当前增量与必要修订优先调度,历史首采按剩余执行时段推进;每轮正值批次预算保证公平性,不作为停采总额。
429 后暂停该账号全部采集族的新请求,保存持久的账号冷却截止和原因,避免库存、出库和重试各自继续请求。适配器收到限流即阻止该账号后续出网并交回调度器处理,不在内部循环重试 429。若响应提供有效重试时间则至少等待到该时刻,否则按已配置的有限指数退避;等待期间只展示上次发布数据。持续限流达到配置上限后停止自动尝试并要求核对配额,恢复不重置未到期的冷却或请求消耗记录(VALIDATION 日额度仍有效)。认证/权限错误直接停止对应采集并处理配置。默认间隔只是本地保护;此前串行 1.1 秒仍遇到过 429,不能承诺完全不触发供应商限流。
### 实现与验收边界
- 生产初采及范围重建按两个月确定边界,保留按日切片及上游单次窗口限制,不把一个请求直接扩大为两个月;TEST 人工有界任务不据此启动生产首采。
- BUSINESS 每轮执行批次与时长只控制公平调度,`YIELDED` 保存检查点后自动续跑;账号每日与根累计请求数不设停采上限。有限独占许可、根截止时间及保护仍独立约束。VALIDATION 的账号日额度和整轮样本硬上限继续有效,到上限结束,不借正式任务续跑绕过。领取任务数量不等于请求页数,执行开关不等于允许自动规划。
- Integration 每次真实请求及内部重试均须计入账号消耗,VALIDATION 另检查额度,并遵守间隔、用途、冷却和出口限制;单 JVM 锁不构成跨环境互斥,仍遵循单一真实出口的独占验证要求。
- VALIDATION 必须在库存和销量两条写链都拒绝正式发布,不能仅依赖销量发布开关。
本节规定验收要求,不维护实现或部署进度。当前源码、真实运行与未关闭验收项的证据统一见[测试环境单机部署与配置契约](../测试环境单机部署与配置契约.md)。
## 同步时间和统计日期
**2026-10-12 用户最终确认:领星已经把返回时间转换为北京时间,直接使用返回时间即可。** 当前两个仓库的 `sourceTimezone`、`businessTimezone` 均使用 `Asia/Shanghai`;任务和报表同样按北京时间执行,不再按洛杉矶、纽约或其他仓库所在地时区二次转换。时区选择已确认,不再作为待业务补充项;仓库资料 `timeZone` 仍原样保留为元数据,不覆盖此口径。
当前默认时点适用于两个仓库,但配置和任务必须按仓保存,不能固化为只有一个全局库存任务。每仓分别维护 `sourceTimezone`、`businessTimezone`、`outboundCron`、`inventoryCron`、`inventoryStaleAfter`;本期两仓时区配置相同,默认过期阈值 26 小时。未来补货交期、安全库存和预警阈值也以仓库为配置边界,本期不提前创建告警执行逻辑。
账号共享出库目录的 `startTime/endTime` 按北京时间格式化传参,返回值按相同口径解析并按 `whCode` 分仓。`business_date` 直接取有效返回时间的日期;源字符串原样保留。内部 UTC 时间点仍按现有存储规范生成,例如源值 `2026-10-12 00:30:00` 对应 UTC `2026-10-11 16:30:00Z`,业务日期始终是 `2026-10-12`;不能直接截取 UTC 日期统计,也不能先按美国时区解释再加时差。来源时区确认不代替实现层的格式、午夜边界和页面同单对账验收。
| 任务 | 时间(北京时间) | 成功后动作 |
| --- | --- | --- |
| 出库单同步 | 每天 08:00、20:00 | 合并完整批次,重算发生变化的历史日期和当日累计,更新销量榜单 |
| 库存全量快照 | 每天 08:00 | 发布当日实际观测快照,以最新成功销量结果重算可售天数 |
| 失败恢复 | 对原任务有限重试,重启后检查遗漏时段 | 继续未完成批次,不伪装成新的计划采集,也不绕过限流 |
Java 可用 Spring 调度触发,建议 cron 分别为 `0 0 8,20 * * *` 和 `0 0 8 * * *`,本期显式指定 `Asia/Shanghai`,不随服务器默认时区变化。任务先持久化再执行;仓库 A/B 和出库/库存分别记录成败。账号目录发现可以共享,已完成的仓库无需等另一仓库存成功才发布;任何汇总必须暴露每仓各自的时间与版本。
08:00 获取的库存是实际拉取期间的当前库存,不是昨日 24:00 库存。20:00 只更新出库数据时,库存仍来自当天早上的快照,不根据当天出库数自行扣减。页面应显示“销量截至……,库存采集于……”,避免把每天一次的库存描述成实时库存。
正式首次采集的“最近 2 个月”是滚动时间区间:令 `start` 为本次生产首采启动时刻,范围为 **`[start.minusMonths(2), start)`**,按北京时间计算,接口实际开闭边界仍按其契约处理。它不是固定 60 天,也不是前两个完整日历月;月份长度及起止时刻会影响覆盖范围。测试样本不得替代或推进生产首采基线,后续增量从正式成功覆盖位置继续,并按后文复查已知单据。
报表的近 7/30 天仍是截至昨日的 7/30 个完整自然日。例如,10 月 12 日 08:00 首采目标为 **8 月 12 日 08:00 至 10 月 12 日 08:00**;实际完整采集并发布后,已包含近 30 天所需的 **9 月 12 日 00:00 至 10 月 12 日 00:00**。目标设为两个月本身不证明覆盖已完成,执行期间仍逐指标检查。8 月 12 日首日可能不完整,10 月 12 日“今日”仍仅累计;纯库存 SKU 的观察期、修订待核对和数量资格仍独立判断,上游更正仍可修订历史。
上述完整日窗口用于正式榜单和可售天数。用户确认的五种 SKU 图表范围都包含今日,展示的是当前已采集累计,必须保留未完成日标识;“本月”从当月 1 日开始,“本周”从本周一开始,不替换为滚动 30 天或 7 天。范围、日粒度及空值规则见[SKU 销量列表与趋势图](#sku-销量列表与趋势图)。切换图表只查询本地已发布统计,不重新拉取领星,也不改变首次采集范围和同步频率。
## 出库同步与历史修正
### 首次初始化
1. 在生产固定当前来源账号及其全部已确认货主/仓库范围,记录首采启动时刻 `E0`,持久化下界 `B=E0.minusMonths(2)`,范围为 `[B,E0)`。重试不重新计算 B 或 E0,不因任务耗时把首采窗口向后漂移。正式初始化在实现并验收后执行;本轮不启动生产采集。
2. 使用 `timeType=outboundTime`、`status=100` 在该范围内完整分页建立已出库目录,再获取所需详情。早先创建、最近才出库的订单可按出库时间被查询,不以创建日期排除。全量覆盖仍须通过分页、时间边界、货主及仓库范围校验,不能用读到 1 条代替完整性验收。
3. 保留已发现单据及其统计贡献,后续增量继续发现新出库,并按单号复查旧单的取消、数量调整或出库时间修正。当前接口未声明更新时间筛选,首采成功不能证明未来所有旧单更正都能自动捕获;修正回查策略按下节执行。
4. 获取第一次库存全量快照;SKU 集合取出库与库存的并集,确保有库存、零销量的 SKU 也能进入后续分析。
5. 完成字段、数量和范围核对后记录实际采集基线;正式日报、榜单和日均量只在对应完整自然日窗口确已覆盖时发布,否则标记覆盖不足。首次接入查询领星历史是业务数据采集,不是为本地旧版本数据设计兼容迁移。
实施时将固定两个月首采区间按自然日切片,记录每片起止、页数、去重目录数、待获取详情数和所属仓库;高密度日按小时继续拆分,不能把两个月塞进一次上游请求或一个长事务。先验证目录再按业务键获取详情,最后才能对该仓该片声明完整覆盖。此前最近一个月样本目录曾显示约 6.1 万单,按每单一次详情、理论 1 次/秒已接近 17 小时;这只是旧样本的算术量级,不是两个月的实际规模、安全速率或完成时间。初始化必须持久化、限流和恢复,不能用一次 HTTP 请求等待全部完成。库存、当前增量与必要修订优先调度,历史首采使用剩余执行时段分段推进,保留缺口,不能为追赶首采增加同账号并发。
### 每天两次的执行链
OMP 列表支持按出库时间查询,但公开 `timeType` 未列订单更新时间,一期按以下三路组合拉取,不假设存在可靠的 `updatedAt` 游标:
| 路径 | 目的与处理方式 |
| --- | --- |
| 新出库发现 | 以 `timeType=outboundTime`、`status=100` 按出库时间窗口分页扫描,从上次成功覆盖位置开始,建议重叠 48 小时;固定本批截止时刻,扫描完成才推进发现水位 |
| 已知单复查 | 按已知单号获取当前详情,不再仅筛选已出库状态;识别已计入单据的取消、数量、SKU、仓库及出库日期修订,更新原统计贡献 |
| 历史核对 | 在已确认采集范围内轮转重扫历史出库窗口,发现迟录入或回溯改期单据;与按单复查配合,不能把已取消单从 `status=100` 结果消失视为充分更正证据。范围外补查须先明确范围,再提供按日期/单号重采集入口 |
48 小时是建议的增量重叠配置,首次最近 2 个月是用户本轮确认的采集范围,二者都不是领星保证的更正期限。按出库时间增量能发现老建单的新出库,但有限回查不能证明任意旧单的回溯更正都已捕获。正式统计验收前须明确供应商历史保留、最大修正期限或补充变更能力;未覆盖的修正范围继续显示缺口与可回修说明,不把时间水位当成所有旧单永不变化的保证。
修订复查不能只留一句“定期检查”:按仓为已知单保存轮转游标,按稳定内部 ID 升序分页,处理完再推进;新单发现优先,复查使用独立的每轮数量/时间预算,避免反复从第一单开始导致尾部永远不被检查。历史重扫在已确认 `[B,发现水位)` 范围内按时间片轮转。建议初期复查一轮目标不超过 7 天,预算下限按 `已知单数÷每周执行槽位数` 估算,再结合账号总配额确认;达不到预算时展示修订核对滞后,不伪称历史实时准确。应持久化游标和最后完整轮转时刻,手工补查走相同幂等和租约规则。
分页按实际接口契约处理,不能因单页少于期望就猜测全量结束。固定查询窗口、保存页数和去重数量,校验服务端总数等可用信息;若无稳定快照或排序保证,重复扫描并比较单号集合,遇到变化继续核对或标记本批不完整,不能只凭总数相等认定完整。时间筛选边界通过边界秒样本验证,采用重叠窗口消除相邻批次开闭区间风险。
本轮一个已出库样本观察到查询起点、终点均包含。内部切片仍统一采用半开区间 `[B,E)`:源请求可以包含端点 E,源值与边界均按 `Asia/Shanghai` 标准化后,将 `outbound_at_utc == E` 放入下一切片,不能在相邻切片重复累计;同单幂等继续生效。覆盖仍须通过本仓完整目录、详情与归并校验,不能仅凭时区已确认推进水位。出库第一页的合法空结果可能回显 `size=10`,精确允许条件见手册;非空页不得放宽分页校验。
### 幂等和变更处理
- 订单按来源账号、货主 `customerCode` 与 `deliveryNo` 唯一。没有稳定明细行 ID 时,以完整详情替换该单 SKU 明细,再按同单同 SKU 汇总;不使用行号作为业务唯一键,不对累计销量简单执行加法。
- 同单重复返回不增加销量。单据的数量、SKU、状态、仓库或出库日期变化,重算旧维度与新维度涉及的全部日期;跨日更正必须扣除旧日贡献。
- 已计入订单变为取消或其他非有效出库状态时,撤销其原统计贡献并保留修订记录。接口没有返回某单不等于取消,不自动删除事实。
- 出库后的正常退货不在本期自动冲减销量;若领星把原出库单撤销,应按已确认的撤销语义处理。两者不能混为同一动作。
- 源文档未承诺版本号或更新时间时,同账号同任务串行处理,不并发覆盖同一订单;保留内容摘要及批次。发生相互矛盾的响应应复查,不能用客户端收到响应的先后推断源版本。
## 库存快照与数据可靠性
用户已确认正式库存每次全量:每天分别对每个仓库发送 `whCode`,在该仓读取全部货主的产品库存,不传品质过滤,设置 `isHideInventory=0` 保留零库存,不使用最近 2 个月的订单范围、SKU 搜索或库存数量阈值裁剪全量。每仓单独保存采集开始、完成时间和批次标识;数据代表该仓采集区间的观测结果,不承诺跨页严格同一瞬间。测试截断样本仅用于验证,不得作为全量快照发布或参与缺行推断。此前已验证全分页读取,定时采集与快照落库留给后续开发。
先保存未发布批次,所有页成功并通过范围、身份、分页和重复键校验后才允许发布。网络失败、结构错误或范围泄漏导致整批失败;个别已读到的数值缺失或异常可以随完整观测批次保存为 `UNKNOWN/INVALID`,对应 SKU 和受影响汇总不输出可信可售天数。部分分页失败时保留上一完整快照,不能把几页成功数据拼成当天完整库存。实际采集晚于计划时点时保存真实时间,不补写为 08:00。
2026-10-13 用户确认:正式库存采用每仓一轮完整分页观测,不再要求第二轮库存数量与第一轮一致。每页只采集一次,保留同一尝试的断点续页;发布前仍须所有页成功、总量一致、业务键唯一、范围与数量校验通过。页面明确显示该已发布版本的采集开始与完成时间,不能用发布时间代替,也不承诺各页属于同一瞬间。失败尝试不得拼接或转为完整版本;出库目录的扫描与归并规则不受此次库存口径调整影响。
这不是只为理论故障准备的检查:本轮 B 仓曾连续各页报告 `total=5741`,返回行数也为 5,741,但唯一业务键只有 5,740;去重后继续发布会漏掉一个维度。该尝试必须整批拒绝,从第一页建立新尝试重新采集,不使用 `INSERT IGNORE`、累加重复行或拼接前后两轮来凑数。随后两次完整重读恢复一致,只证明该时段可恢复;重复原因尚不能区分业务变化与排序不稳定。有限重试耗尽时保留旧版并显示失败,不能无限循环直至看似成功。
库存查询须保留零库存;但某行缺失仍不足以证明它为零。每日按账号+货主+仓库+SKU+品质比对已知维度:本轮缺失时标记 `UNKNOWN`,旧数量只能标注为上次观测;同 SKU 在另一仓或另一货主返回也不能掩盖缺失。字段缺失、空字符串与合法零值分别处理,先通过样本确认源数值空值的返回约定。首次未发现的商品通过后续商品目录或人工核对补足,不从不存在的行制造零库存。[产品库存字段与筛选](https://apidoc-omp.xlwms.com/reference/post_openapi-v2-stock-stocksku-page-1)
一期不做跨仓合并可售天数。后续若增加跨仓概览,须核对各仓覆盖与时效,不能让 SQL 求和忽略未知仓后给出“全部完整”的结果。
建议库存快照超过 26 小时仍无新成功批次时标记 `STALE`;该阈值可配置。数值缺失、负库存或已核准的业务校验失败时保存异常证据并停止该 SKU 的可信可售天数输出,不静默截成零。当前只确认四个独立来源数量字段,尚未确认它们之间的恒等式;不得自行以 `stkQty = freezeQty + useStkQty` 等公式判定来源数据无效。零销量也须建立在销售窗口采集完整的前提上,接口失败不能转换成零销量。
## 采集保存什么以及如何计算
**当前设计和已有源码都采用出库事实与日统计两层。** 领星按一件代发出库单提供目录和详情,本系统先取每张单,再保存其中每个 SKU 的实际数量;不会为同一张单的每个 SKU 重复请求详情,也不会只留下一个累计销量。`OutboundFactService` 已有单头写入、整单替换 SKU 明细的代码,`OutboundCandidateBuilder` 已有日聚合代码;本轮未运行数据库,不能据源码断言实际已完成业务落库或整月采集。
| 数据层 | 保存粒度 | 用途 |
| --- | --- | --- |
| 出库单头 `ll_selfop_outbound_order` | 来源账号+货主+出库单号一条;保存状态、仓库、来源出库时间与北京时间业务日 | 幂等识别同一张单,处理取消、改期、迁仓 |
| 出库 SKU 明细 `ll_selfop_outbound_item` | 一张单内一个 SKU 一条,同单重复 SKU 行按相同单位合并;保留实出量 | 知道每个 SKU 的销量由哪些单据贡献,可校验和重算 |
| 修订依据 `ll_selfop_sync_stage` 与任务/执行记录 | 每次有效观测及归并前后载荷、采集代次 | 追溯变化原因和恢复未完成任务;只保存统计所需字段 |
| 日销量 `ll_selfop_sku_daily_sales` | 一个销量版本下,来源账号+货主+仓库+SKU+北京时间日期一条 | 页面趋势、区间累计、榜单与日均量的主要查询数据 |
| 仓库日汇总 `ll_selfop_warehouse_daily_sales` | 一个销量版本下,来源账号+仓库+北京时间日期一条 | 经营总览的仓级趋势与销量合计;与 SKU 日事实同批原子构建 |
| 产品库存 `ll_selfop_inventory_snapshot` | 一个库存版本下,来源账号+货主+仓库+SKU+品质一条 | 独立保存每天真实全量库存观测,不用出库明细倒推当前库存 |
例如同仓、同货主在同一天,单据 O1 实出 `SKU-A=2、SKU-B=1`,O2 实出 `SKU-A=3`:事实层保存 2 个单头和 3 条 SKU 明细;日表保存 A 的 5 件/2 单、B 的 1 件/1 单。同一仓实际只有 2 张出库单,不能将各 SKU 的单数相加成 3 单。所有示例均为合成数据。
重复获取 O1 不会再次增加销量;若 O1 被确认取消,其 A 的 2 件、B 的 1 件贡献都撤回,A 的日销量变为 3 件。改数量、改日期、改仓库同样以当前有效事实重算,发布新统计版本。订单头及 SKU 明细保存最后确认的状态,修订前后证据单独保留;不是每轮把全部订单再复制一份。旧图表版本不可直接连接当前可变明细来证明当时的数值,历史审计应结合该版事实修订号和修订证据。
计算顺序为:**有效状态与实际数量校验 → 单据幂等归并 → 按仓/货主/SKU/北京时间日汇总 → 发布完整日统计版本 → 查询所选日期并求和**。3 天、周、15 天、月的销量从日表汇总;热销、滞销和库存可售天数继续使用各自已约定的窗口与资格。页面查询不重新抓领星,也不每次扫描出库单明细。具体读路径、索引和时延验收见[查询性能与数据增长](#查询性能与数据增长)。
## 数据模型建议
当前模型包含十一张表:一张账号共享出库发现协调表,十张分仓同步与统计表(含仓库日汇总)。共享发现不替代仓库的覆盖、时效或发布状态。持久化入口遵循 [Lastleg 指引](../../baiyue-firstleg/lastleg-service/AGENTS.md)与[数据库基线](../../baiyue-firstleg/lastleg-service/src/main/resources/db/lastleg.sql),使用 `DATETIME(3)`、InnoDB 和中文注释;一般实体采用应用生成 `BIGINT` 主键,仓库日汇总采用版本与业务维度复合主键。自营事实不复用客户履约表。
### 通用字段与存储约定
- 每表均有 `id BIGINT NOT NULL PRIMARY KEY`、`created_at DATETIME(3) NOT NULL`、`updated_at DATETIME(3) NOT NULL`;ID 由应用生成,两个时间使用 UTC。下表不重复列出这三个字段,“可空”只适用于明确标记的列,其他列均为 `NOT NULL`。
- `source_account` 为受控配置中的来源账号别名,不存密钥;`customer_code` 为领星货主编码,与平台 `customerId` 无关。账号、货主、仓库、SKU 均保留原值,采用区分大小写的比较规则,例如 `utf8mb4_bin`,不得自动大写或截断。拟定长度为内部容量设计,超长输入进入校验错误,不能默默裁剪。
- 时间点统一 `DATETIME(3)` 存 UTC,业务日用 `DATE`;本期每仓来源时区和业务时区均为已确认的 `Asia/Shanghai`,源时间另存原字符串,业务日期直接沿用有效返回日期。仓库接口的 `timeZone` 不参与本期出库时间转换;不能把 UTC 日期或美国日期写入北京时间业务日。
- 所有商品数量使用 `DECIMAL(20,6)`;库存未知时为 `NULL`,不能默认 `0`。计数使用 `BIGINT`,来源状态使用整数,内部状态使用明确的 `VARCHAR` 枚举。字段含义作为后续 SQL 的中文 `COMMENT`;本期仅维护本服务初始化基线,不另设计测试存量迁移。
- 表间以 ID 逻辑关联,由事务校验引用;不引入跨服务外键。`CHAR(64)` 摘要为规范化内容的 SHA-256 十六进制值;相同业务键比较完整规范化内容,摘要不能替代身份校验。JSON 仅存本节定义的统计字段与范围,不保存地址、电话、签名或原始完整响应。
### 账号共享协调表:`ll_selfop_outbound_discovery_scope`
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `source_account` | `VARCHAR(64)` | 账号级出库目录和详情共享边界 |
| `catalog_from_utc`、`catalog_until_utc` | 各 `DATETIME(3)`,可空 | 已持久化完整目录切片的连续覆盖;不等于任何仓的销量已发布 |
| `next_sequence` | `BIGINT`,默认 `1` | 新发现任务的单调调度序号;重试沿用,只安排任务,不判断对象内容的新旧 |
| `next_observation_sequence` | `BIGINT`,默认 `1` | 每次实际出库详情请求前分配的单调采集代次;与 task 调度序号独立,重试请求也分配新代次 |
| `active_task_id` | `BIGINT`,可空 | 当前共享发现/详情补齐任务 |
| `lease_owner` | `VARCHAR(128)`,可空 | 账号发现执行者 |
| `lease_until_utc` | `DATETIME(3)`,可空 | 账号发现租约到期时刻 |
| `fence_token` | `BIGINT`,默认 `0` | 防止过期发现进程继续写共享暂存或派发任务 |
| `request_budget` | `INT` | 一次调度允许的目录、详情请求批次数,必须为正;BUSINESS 达到后 YIELDED 续跑,不是根累计停采额度 |
| `time_budget_seconds` | `INT` | 一次调度允许占用的执行时长,必须为正 |
| `min_request_interval_ms` | `INT` | 同账号请求最小间隔,按已核准配额配置 |
| `api_budget_json` | `JSON`,可空 | 账号全部采集族共用的计数与保护配置,含统计日、已预留/已确认次数、重试控制和配置摘要;BUSINESS dailyLimit=0 表示不设日次数上限,VALIDATION 保留正值日额度 |
| `api_cooldown_until_utc`、`api_pause_reason` | `DATETIME(3)`、`VARCHAR(64)`,均可空 | 账号全部真实出口共用的冷却截止/固定暂停原因;重启、改任务或切换仓库不清除 |
唯一键 `uk_selfop_discovery_account(source_account)`。目录和详情协调避免同一账号每仓重复抓取;本表的 API 预算/冷却字段同时作为账号全部采集族的请求门禁,库存仍按仓查询并使用独立业务租约,不经共享出库目录,但必须检查同一账号用途对应的额度规则、间隔、授权和冷却。目录按日、密集日按小时切片并恢复,不能把历史首采视为一个长事务。详情请求在有效账号租约下逐次执行;请求前短事务分配采集代次,响应回来后再次校验租约与栅栏,已失效执行者的结果不进入有效暂存。采集代次只防旧结果重放,不代表供应商版本。请求计数由 Lastleg 通过本服务事务预留本次调用及允许的重试次数,Integration 不得超出传入的受控尝试额度并回传实际次数;响应丢失时不盲目退回预留。Integration 不直查 Lastleg 数据库。账号统计日按北京时间固定边界换日,冷却跨日仍保留;单 TEST 与生产各自此表互不相通,跨环境仍靠单一真实出口的运行约束。
### 1. `ll_selfop_sync_scope`:分仓任务族租约及发布指针
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `source_account` | `VARCHAR(64)` | 来源账号别名 |
| `warehouse_code` | `VARCHAR(64)` | 仓库必填;不得使用空值或全仓占位值 |
| `data_family` | `VARCHAR(16)` | `OUTBOUND` 或 `INVENTORY`;按账号+仓库+任务族互斥 |
| `scope_json` | `JSON` | 本仓范围规则,`customerMode=ALL_AUTHORIZED`,库存为全部品质;当前账号全部授权货主均自营,不将当天出现的货主列表当白名单 |
| `scope_hash` | `CHAR(64)` | 仓库、货主范围规则、品质、时区及单位口径的规范化摘要;不包含当天观测到的客户/SKU 集合 |
| `business_timezone` | `VARCHAR(64)` | 本仓报表及调度时区,本期两个仓库均已确认使用 `Asia/Shanghai` |
| `source_timezone` | `VARCHAR(64)`,可空 | 本仓出库源时间时区;本期有效配置固定填写已确认的 `Asia/Shanghai`,未配置不得猜测仓库所在地时区 |
| `schedule_cron` | `VARCHAR(128)` | 本仓、本任务族的调度表达式,按 `business_timezone` 执行 |
| `stale_after_seconds` | `INT` | 本仓、本任务族数据过期阈值,必须为正;库存初始可采用 26 小时,后续按仓调整 |
| `warehouse_policy_ref` | `VARCHAR(128)`,可空 | 未来按仓告警/补货策略的稳定引用;一期为空,不建告警规则表 |
| `initial_from_utc`、`initial_until_utc` | 各 `DATETIME(3)`,可空 | 本仓正式首采固定窗口 `[B,E0)`,截止为 E0,下界 B 按北京时间计算 E0 减 2 个月;重试不重新取当前时间 |
| `discovery_from_utc`、`discovery_until_utc` | 各 `DATETIME(3)`,可空 | 本仓已成功归并并发布的连续出库覆盖,独立于账号目录水位;库存不用 |
| `fact_revision` | `BIGINT`,默认 `0` | 出库事实归并修订号;内容实际变化才增加 |
| `published_fact_revision` | `BIGINT`,默认 `0` | 本仓最后已发布销量版所使用的事实修订号 |
| `rebuild_required` | `TINYINT`,默认 `0` | 事实变化待重建标记;跨仓修订同时置旧、新仓为 `1` |
| `invalidated_at_utc` | `DATETIME(3)`,可空 | 最近一次因事实修订使当前版待重建的时刻 |
| `published_run_id` | `BIGINT`,可空 | 唯一对外可见版本,指向本任务族已发布执行批次 |
| `lease_owner` | `VARCHAR(128)`,可空 | 当前执行者令牌,不使用仅进程内锁 |
| `lease_until_utc` | `DATETIME(3)`,可空 | 租约到期时刻 |
| `fence_token` | `BIGINT`,默认 `0` | 每次取得租约递增,事实提交和发布均校验 |
| `known_order_cursor_id`、`known_order_round_max_id` | 各 `BIGINT`,可空 | 本仓已知订单轮转游标及本轮固定最大内部 ID;不是领星更新游标 |
| `known_order_round_started_at_utc`、`known_order_round_finished_at_utc` | 各 `DATETIME(3)`,可空 | 本仓已知订单核对轮次起止;完成一轮后再重置游标 |
| `history_round_from_utc`、`history_round_until_utc`、`history_slice_cursor_utc` | 各 `DATETIME(3)`,可空 | 本仓历史核对的固定范围及下一待扫描时间片;仅在已确认采集区间内推进 |
| `history_round_finished_at_utc` | `DATETIME(3)`,可空 | 上一轮历史时间片全部完成时刻 |
| `review_request_budget`、`review_time_budget_seconds` | 各 `INT` | 本仓一次复查允许的请求数和时长;统一受账号配额约束 |
| `review_target_days` | `INT`,默认 `7` | 建议每 7 天轮转一遍,属于待配额验证的目标,不保证首次大量详情在单次调度内完成 |
唯一键 `uk_selfop_scope(source_account,warehouse_code,data_family)`;索引 `idx_selfop_scope_lease(lease_until_utc)`。同仓的计划、重试、补采共用租约;两仓独立任务、成败、覆盖、时效与发布。范围、时区或统计口径变化需更新 `scope_hash` 并触发本仓重算,不能把另一仓的版本或覆盖当成本仓结果。已知单、历史切片游标仅在对应复查对象已可靠处理后提交,预算耗尽保存游标留待下次;通过当前仓库归属筛选订单,迁出/迁入单由跨仓修订机制补足,不能只依靠游标是否已经越过该单。
### 2. `ll_selfop_sync_task`:一次计划或人工请求
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `task_no` | `VARCHAR(64)` | 服务端生成的稳定业务任务号,例如 `LXCOL-20261012-XXXXXXXX`;重试沿用,独立于内部 ID,不含凭据,用于页面检索与详情链接 |
| `execution_purpose` | `VARCHAR(16)` | `BUSINESS` 或 `VALIDATION`,由服务端环境模式及受控入口决定;子任务继承,验证不能归并/发布或推进正式水位 |
| `validation_root_task_id` | `BIGINT`,可空 | 验证会话唯一预算根任务;根指向自身、子任务指向同一根,正式任务为空。该引用只合并验证预算,不改变业务上游关系 |
| `validation_budget_json` | `JSON`,可空 | 仅验证根保存冻结的页数/pageSize/详情/真实请求/截止时间上限及累计扣减、停止原因;所有验证请求先锁该根原子扣额度,重启不重置,不新增预算表 |
| `source_account` | `VARCHAR(64)` | 来源账号,用于共享发现与分仓任务统一检索 |
| `task_role` | `VARCHAR(24)` | `ACCOUNT_DISCOVERY` 或 `WAREHOUSE_SYNC` |
| `scope_id` | `BIGINT`,可空 | 分仓任务必须引用分仓作用域;仅账号共享发现为空 |
| `warehouse_code` | `VARCHAR(64)`,可空 | 分仓任务必填且等于 `scope.warehouse_code`;仅共享发现为空 |
| `discovery_scope_id` | `BIGINT`,可空 | 共享发现必填,引用账号发现协调器 |
| `upstream_task_id` | `BIGINT`,可空 | 出库分仓任务引用已持久化目录的共享发现切片;库存无此上游 |
| `discovery_sequence` | `BIGINT`,可空 | 共享任务及分仓子任务继承的调度顺序;重试保持不变,不用于事实覆盖判定 |
| `observation_baseline` | `BIGINT`,可空 | 共享任务建立时已分配的最大详情采集代次,分仓子任务继承;用于识别旧任务恢复期间出现的其他任务新观测 |
| `request_key` | `VARCHAR(128)` | 共享发现包含切片/计划槽位;分仓任务包含仓库、任务族及切片或计划槽位;人工附请求幂等键 |
| `request_hash` | `CHAR(64)` | 请求类型、查询范围、范围摘要的指纹;同键不同请求拒绝 |
| `task_type` | `VARCHAR(24)` | `SCHEDULED`、`MANUAL_RETRY`、`BACKFILL`、`RECHECK`、`REBUILD`、`VALIDATION`;验证只由有界联调入口创建,不由自动计划创建;`RECHECK` 实际重读单据,跨仓事实修订按确定性键创建两仓 `REBUILD` |
| `planned_at_utc` | `DATETIME(3)`,可空 | 计划时点;人工请求可空 |
| `query_from_utc`、`query_until_utc` | 各 `DATETIME(3)`,可空 | 本次出库扫描区间;库存全量不填时间过滤 |
| `query_scope_json` | `JSON` | 不变的账号、货主、仓库、品质和查询条件快照;按单补查在此记录单号 |
| `routing_manifest_json` | `JSON`,可空 | 共享切片目录完整标记、每仓对象键集合/数量和详情就绪状态;各对象记录 `effectiveStageId`,需新代次复查时记录 `refreshTaskId`,完成后指向新有效暂存;不改写旧载荷 |
| `scope_hash` | `CHAR(64)` | 建任务时的业务范围摘要 |
| `status` | `VARCHAR(20)` | `WAITING_SOURCE`、`READY`、`RUNNING`、`RETRY_WAIT`、`SUCCEEDED`、`DEAD`、`SAMPLE_DONE`;后者仅验证用途正常结束,不是正式成功且不自动续跑;仅本仓上游未齐时等待 |
| `apply_state` | `VARCHAR(20)` | 正式出库分仓归并为 `NOT_STARTED`、`APPLYING`、`BLOCKED`、`APPLIED`;账号发现、库存、纯重建及所有验证任务为 `NOT_APPLICABLE`,与调度状态分开 |
| `affected_warehouse_codes_json` | `JSON`,可空 | 本次归并可能改变的仓集合,包含原仓与新仓;首段事实写入前登记,扩大集合须先取得新增仓租约并锁 scope,不能写完再补 |
| `apply_started_at_utc`、`apply_completed_at_utc` | 各 `DATETIME(3)`,可空 | 首段事实提交时刻、全部必需对象可靠归并完毕时刻;失败/`DEAD` 不填完成时间 |
| `attempt_count` | `INT`,默认 `0` | 已创建的执行尝试数 |
| `next_retry_at_utc` | `DATETIME(3)`,可空 | 有限退避后下一次可执行时刻 |
| `requested_by` | `VARCHAR(128)`,可空 | 人工请求的平台操作人,仅内部审计 |
| `result_published_run_id` | `BIGINT`,可空 | 正式分仓成功结果实际对应的已发布统计版;合并构建/无变化复用也必须填写,账号发现及所有验证任务为空,不引用未发布候选 |
| `progress_json` | `JSON`,可空 | 与可恢复检查点一起提交的有界当前进度,含尝试序号、扫描轮次、阶段、单位和可靠完成量/总量;支持无 run 的上游等待状态,结构见页面支撑合同 |
| `progress_updated_at_utc` | `DATETIME(3)`,可空 | 最后一次可靠业务检查点提交时刻,与执行心跳分开;首次尚未提交为空 |
唯一键 `uk_selfop_task_no(task_no)`、`uk_selfop_task_request(source_account,request_key)`;索引 `idx_selfop_task_poll(status,next_retry_at_utc,id)`、`idx_selfop_task_upstream(upstream_task_id,warehouse_code)`、`idx_selfop_task_apply(apply_state,id)`、`idx_selfop_task_scope_list(scope_id,created_at,id)`。任务列表以固定仓库和任务族的 scope 检索后排序分页。分仓出库子任务按共享切片+仓库幂等创建;一个仓为零单也须有目录完整性结论才能推进本仓覆盖。重试新建执行记录,但共享出库切片和已验证详情保留;库存不同尝试不能复用旧页拼装新快照。`APPLYING/BLOCKED` 且影响仓集合包含本仓的任务构成本仓所有销量发布的屏障,包括其他计划任务与 `REBUILD`;失败、租约释放、重试耗尽或 `DEAD` 都不能清除屏障。只有该逻辑任务全部必需对象完成归并后才转 `APPLIED`;不通过改状态放弃半批事实。登记/扩大影响仓集合、完成归并和销量发布检查均须锁相关出库 scope,防止检查与首段提交竞态。库存采集与发布不受该销量屏障阻塞。
### 3. `ll_selfop_sync_run`:执行尝试、覆盖证明和版本头
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `task_id` | `BIGINT` | 本次执行请求 |
| `execution_purpose` | `VARCHAR(16)` | 从 task 冻结继承;验证 run 不得作为日销量/库存快照的正式版本头 |
| `scope_id` | `BIGINT`,可空 | 仅账号共享发现为空;分仓执行必须有作用域 |
| `source_account` | `VARCHAR(64)` | 来源账号 |
| `warehouse_code` | `VARCHAR(64)`,可空 | 分仓执行必填;账号共享发现为空 |
| `run_role` | `VARCHAR(24)` | `ACCOUNT_DISCOVERY` 或 `WAREHOUSE_SYNC` |
| `upstream_task_id` | `BIGINT`,可空 | 分仓出库计算引用的共享切片及其稳定暂存集合 |
| `attempt_no` | `INT` | 同一请求从 `1` 起递增 |
| `scope_hash` | `CHAR(64)` | 本版本的业务范围摘要 |
| `scope_snapshot_json` | `JSON` | 分仓版只含本仓货主/品质、固定首采 `[B,E0)`、时区、调度与过期阈值;账号发现保存待路由仓集合,不冒充业务版 |
| `observed_dimensions_json` | `JSON`,可空 | 本次实际出现的仓库/货主集合及数量摘要,仅作采集证据;例如已观测 70 货主不等于固定授权范围 |
| `fence_token` | `BIGINT` | 本尝试持有的栅栏版本 |
| `base_published_run_id` | `BIGINT`,可空 | 执行开始时的上一可见版本 |
| `reference_sales_run_id` | `BIGINT`,可空 | 库存构建开始时固定的本仓已发布销量版,用其不可变 SKU 维度生成出库已知商品占位;没有已发布销量版时为空,不读取半归并订单事实 |
| `status` | `VARCHAR(24)` | `FETCHING`、`VALIDATED`、`ROUTED`、`FACTS_APPLIED`、`BUILDING`、`READY`、`PUBLISHED`、`REUSED`、`YIELDED`、`FAILED`、`ABORTED`、`SAMPLE_DONE`;正式共享发现成功终态为 `ROUTED`,正式预算耗尽为 `YIELDED`;验证正常停止为 `SAMPLE_DONE`,限流等错误为 FAILED,不自动恢复;`REUSED` 自身不是可查统计版 |
| `last_phase` | `VARCHAR(24)` | 失败/中止前所在阶段,支持判断是否已经归并事实 |
| `fetch_started_at_utc`、`fetch_finished_at_utc` | 各 `DATETIME(3)`,可空 | 实际采集区间;完成前结束时间为空 |
| `inventory_daily_sample_date` | `DATE`,可空 | 仅选为本仓当日代表库存观测的正式库存 run 填写;北京时间日期,不是出库覆盖日期,选择规则见每日库存观测留存 |
| `inventory_sample_rule_version` | `VARCHAR(32)`,可空 | 被选中日样本固定为 `DAILY_SCHEDULED_FIRST_V1`,未选中为空;与采集区间、范围摘要一起解释观测口径 |
| `build_started_at_utc` | `DATETIME(3)`,可空 | 本尝试真正进入统计候选构建的时刻;用于计算构建耗时及首采合并间隔,不使用可能早数小时的任务创建时间替代 |
| `finished_at_utc`、`published_at_utc` | 各 `DATETIME(3)`,可空 | 尝试终止时刻及真正切换发布指针的时刻 |
| `superseded_at_utc` | `DATETIME(3)`,可空 | 本版本被新发布指针替代的时刻;发布新版本时在同一事务中首次填写旧版本此字段,重试不能重置,当前版为空;用于派生旧销量版保留期限,不代表删除原始事实 |
| `page_count` | `INT`,默认 `0` | 已完整获取页数 |
| `source_total_count` | `BIGINT`,可空 | 上游可靠返回的总数;未返回则为空 |
| `unique_object_count` | `BIGINT`,默认 `0` | 规范化去重后对象数 |
| `progress_snapshot_json` | `JSON`,可空 | 本尝试终止时冻结的有界阶段与计数摘要;历史尝试不连接任务后续更新的当前进度。活动尝试读取 task 中同一尝试序号的检查点 |
| `heartbeat_at_utc` | `DATETIME(3)`,可空 | 当前执行者最后一次有效心跳时刻;与租约及栅栏一起判断运行健康,不代表业务进度推进 |
| `coverage_status` | `VARCHAR(16)` | `UNKNOWN`、`PARTIAL`、`COMPLETE`;分页完整与数值有效性分别判断 |
| `covered_from_utc`、`covered_until_utc` | 各 `DATETIME(3)`,可空 | 销量版本实际连续覆盖区间,包含已验证的历史覆盖;不是仅本次增量窗口 |
| `complete_day_from`、`complete_day_until` | 各 `DATE`,可空 | 已完整覆盖自然日的半开区间;首日不足全天时排除首日,当前未结束日不纳入 |
| `applied_fact_revision`、`calculated_fact_revision` | 各 `BIGINT`,可空 | 本尝试归并后的事实修订号及计算所基于的修订号;库存可空 |
| `warehouse_daily_ready` | `TINYINT`,默认 `0` | 仓库每日汇总完成构建及来源行数校验后与 READY 状态同事务设为 `1`;完整空销量版也须通过,未就绪版不可发布、复用或用于经营总览 |
| `error_code` | `VARCHAR(64)`,可空 | 内部可诊断错误分类,不保存认证信息 |
| `error_message` | `VARCHAR(500)`,可空 | 脱敏后的固定错误摘要 |
唯一键 `uk_selfop_run_attempt(task_id,attempt_no)`、`uk_selfop_inventory_daily(source_account,warehouse_code,inventory_daily_sample_date)`;后者允许未选中的 NULL 日期重复,非空日期同账号同仓仅一条。索引 `idx_selfop_run_scope(scope_id,id)`、`idx_selfop_run_status(status,updated_at)`。只有 `BUSINESS` 用途的 `WAREHOUSE_SYNC` 执行记录兼作本仓版本头:库存 `snapshot_run_id`、销量 `sales_run_id` 必须引用同仓对应任务族,不能引用账号发现或验证批次。失败记录不变成成功记录;新尝试有新 ID。
`coverage_status=COMPLETE` 只表示本仓声明范围内扫描完成。账号目录已齐、其他仓已发布,都不能代替本仓详情和归并完成。出库连续区间只能在本仓已有区间被本次完整切片连接后延长;不连续补查不能跨过缺口推进水位。正式完整日统计(包括 7/30 天榜单及自定义日期区间)必须落在本仓 `complete_day_from ≤ 日期 < complete_day_until` 内;当前周期图表可展示已采集量,但须逐日派生完整性,不能直接继承批次的 `COMPLETE`。库存版本的销量覆盖日期列 `complete_day_from/complete_day_until` 为空,选中的库存 run 可以填写 `inventory_daily_sample_date`;库存覆盖由本仓 `scope_snapshot_json` 全部货主/品质及完整分页校验承载,另一仓失败不阻止本仓成功发布。
### 4. `ll_selfop_sync_stage`:待验证事实与修订证据
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `run_id` | `BIGINT` | 首次写入该暂存对象的执行尝试 |
| `stage_batch_id` | `BIGINT` | 出库取共享发现 `task.id`,便于断点续取;库存取本次 `run.id`,严禁跨尝试混页 |
| `last_fetch_run_id` | `BIGINT` | 最后一次补齐该对象的执行尝试,保留重试来源 |
| `observation_sequence` | `BIGINT`,可空 | 该完整出库详情实际请求的采集代次;有效出库对象必填,目录待补对象和库存为空;不能填 task 调度序号 |
| `warehouse_code` | `VARCHAR(64)` | 对象返回的当前仓库,用于路由;缺失则进入身份错误,不猜仓 |
| `previous_warehouse_code` | `VARCHAR(64)`,可空 | 路由时从已有单据识别的原仓库;归并前再校验,跨仓时通知两仓 |
| `object_type` | `VARCHAR(16)` | `OUTBOUND_ORDER` 或 `PRODUCT_STOCK` |
| `object_key_hash` | `CHAR(64)` | 规范化业务键摘要;原业务键保留在载荷中并核对 |
| `first_page_no` | `INT` | 首次发现页码 |
| `observed_at_utc` | `DATETIME(3)` | 实际接收该对象的时刻 |
| `content_hash` | `CHAR(64)` | 完整规范化对象摘要 |
| `payload_json` | `JSON` | 目录阶段可先存订单头,详情齐后补完整 SKU;`VALID` 后不再修改;库存保存产品库存字段 |
| `before_payload_json` | `JSON`,可空 | 订单归并事务内记录归并前的完整规范化事实;新增单为空 |
| `validation_status` | `VARCHAR(16)` | `PENDING`、`VALID`、`INVALID` |
| `validation_code` | `VARCHAR(64)`,可空 | 字段缺失、冲突、未知时区等原因 |
唯一键 `uk_selfop_stage_object(stage_batch_id,object_type,object_key_hash)`;索引 `idx_selfop_stage_history(object_type,object_key_hash,stage_batch_id)`、`idx_selfop_stage_observation(object_type,object_key_hash,validation_status,observation_sequence)`、`idx_selfop_stage_route(stage_batch_id,warehouse_code,validation_status)`。出库详情只在账号共享集合保存一次,各仓通过对象集合引用,不重新调用全部详情。出库对象键为账号+货主+发货单号;库存为账号+货主+仓库+SKU+品质。目录补齐详情是 `PENDING→VALID` 的一次转换;完整对象同键同内容重复返回只记一次,同批同键不同完整内容须建立新的复查代次并阻止相关仓切片发布,不能修改已有 `VALID` 载荷或最后一页覆盖前一页。恢复旧任务时逐对象核对本账号已持久化有效详情的最大采集代次;若其他 task 的观测已超过所复用详情的代次,或待补详情对象在本任务 `observation_baseline` 之后已被其他 task 观测,必须按原 task+对象+已见新代次幂等创建 `RECHECK`,实际重读后更新原路由清单的有效暂存引用。不能静默跳过对象后把原切片算作完成。库存若出现已确认真实分批次粒度,先修订来源键,不擅自把冲突数量相加。
标准化订单载荷包含原状态、原时间、源时区、仓库、SKU、实际数量与业务单位;库存载荷包含四类数量、品质和来源唯一键。保留已归并批次的前后载荷与执行记录用于修订追溯,不把它们当作采集结束即可删除的临时缓存。
### 5. `ll_selfop_outbound_order`:最后确认的单据事实
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `source_account`、`customer_code`、`warehouse_code` | 各 `VARCHAR(64)` | 来源账号、货主、仓库 |
| `delivery_no` | `VARCHAR(128)` | OMP 发货单号,采用列表及详情同一身份 |
| `source_no` | `VARCHAR(128)`,可空 | WMS 来源出库单号,缺失不以内部 ID 回退展示 |
| `source_status` | `INT` | 原始状态,`100` 已出库、`99` 已取消;不改造成内部状态码 |
| `source_created_time`、`source_outbound_time` | 各 `VARCHAR(64)`,可空 | 原时间字符串;不覆盖为拉取时间 |
| `source_timezone` | `VARCHAR(64)`,可空 | 解析采用的源时区 |
| `outbound_at_utc` | `DATETIME(3)`,可空 | 已核准的实际出库时间点;非出库状态可空 |
| `business_date` | `DATE`,可空 | 已核准业务时区下的出库日 |
| `business_timezone` | `VARCHAR(64)` | 该事实当前归属仓所用业务时区,迁仓后重新确定业务日 |
| `sales_eligible` | `TINYINT`,默认 `0` | 仅已出库、时间与全部数量有效时为 `1` |
| `revision_no` | `BIGINT`,默认 `1` | 该单内容变更次数,不等于拉取次数 |
| `content_hash` | `CHAR(64)` | 订单头及完整 SKU 明细规范化摘要 |
| `last_run_id` | `BIGINT` | 最后一次确认或修订的执行尝试 |
| `last_observation_sequence` | `BIGINT` | 最后接受的完整详情采集代次;仅防旧观测重放,不按 task 调度序号拒绝较晚实际读取的事实,不冒充领星源版本号 |
唯一键 `uk_selfop_order(source_account,customer_code,delivery_no)`;索引 `idx_selfop_order_daily(source_account,warehouse_code,sales_eligible,business_date,id)`、`idx_selfop_order_run(last_run_id)`。日计算按仓覆盖全部货主,索引将固定账号/仓库/有效状态放在业务日期前,不以可选货主阻断默认日期范围;最终执行计划须按后文验收。`delivery_no` 的源唯一性在首批样本中确认;若同货主跨仓确实重复,须明确单据稳定身份后修订键,不能直接覆盖。
### 6. `ll_selfop_outbound_item`:整单替换的实际出库明细
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `order_id` | `BIGINT` | 对应外部出库事实 |
| `sku_code` | `VARCHAR(128)` | 原始 `productSku` |
| `product_name` | `VARCHAR(255)`,可空 | 来源商品名称,仅作展示 |
| `actual_quantity` | `DECIMAL(20,6)` | 完整列表 `qty`,缺项整单补取时用详情 `actualDeliveryQty`;必须为非负有效数值 |
| `quantity_unit` | `VARCHAR(32)` | 已核准业务单位;未确认单位不发布正式数量统计 |
唯一键 `uk_selfop_order_sku(order_id,sku_code)`;索引 `idx_selfop_item_sku(sku_code,order_id)`。所选整单数量来源中同 SKU 的有效行先按相同单位合计,再整单替换;包裹层数量仅核对,不再次累加。新旧内容相同时不替换、不增加修订号;数量缺失、解析失败或单位冲突保留在暂存并拒绝该批归并,不能用零替换旧事实。
### 7. `ll_selfop_inventory_snapshot`:产品库存全量批次明细
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `snapshot_run_id` | `BIGINT` | `INVENTORY` 执行尝试;发布后该批明细不可修改 |
| `source_account`、`customer_code`、`warehouse_code` | 各 `VARCHAR(64)` | 来源账号、货主、仓库 |
| `sku_code` | `VARCHAR(128)` | 原始产品 SKU |
| `product_quality` | `SMALLINT` | 来源品质,当前 `0` 正品、`1` 次品;全量均保存,不套用整箱库存 |
| `product_name` | `VARCHAR(255)`,可空 | 来源名称 |
| `physical_quantity`、`available_quantity`、`frozen_quantity`、`transit_quantity` | 各 `DECIMAL(20,6)`,可空 | 分别对应 `stkQty`、`useStkQty`、`freezeQty`、`transitQty`;未知为空 |
| `source_unique_key` | `VARCHAR(512)`,可空 | 原 `uniqueKey4tenant`,只作来源证据 |
| `observed_at_utc` | `DATETIME(3)` | 所在页实际采集时间;缺行占位取本批采集完成时间 |
| `quality_status` | `VARCHAR(16)` | `VALID`、`UNKNOWN`、`INVALID`;`STALE` 按批次年龄实时派生 |
| `quality_reason` | `VARCHAR(64)`,可空 | `SOURCE_ROW_MISSING`、`FIELD_MISSING`、`NEGATIVE_QUANTITY` 等 |
唯一键 `uk_selfop_stock_snapshot(snapshot_run_id,source_account,customer_code,warehouse_code,sku_code,product_quality)`;索引 `idx_selfop_stock_lookup(source_account,customer_code,warehouse_code,sku_code,product_quality,snapshot_run_id)`。实际查询必须传本仓 `whCode`;不传品质过滤,取得本仓产品库存所有品质并保留零库存。落表再次确认仓库等于本次分仓作用域,拒绝混入其他仓。正式可售指标仅筛 `product_quality=0` 的有效 `available_quantity`。
本仓完整分页后,将本批行与本仓上一完整快照、构建开始固定的 `reference_sales_run_id` 维度对照,不读取正在归并的订单或可变 SKU 集合。已知但缺失的货主/SKU/品质补一条 `UNKNOWN` 且四类数量均为空的行;只从该已发布销量版发现的 SKU 至少建立正品候选。未知新品质保留原值并不纳入正品指标。仅要求本仓完整分页成功才能发布;单个源数值异常可作为 `INVALID` 行随本仓完整批次发布,但该行不计算可信可售天数。另一仓失败或本仓未完成的销量归并不阻断本仓库存发布,也不能让本仓成功掩盖另一仓过期。旧数量只通过本仓旧版本查询。
### 8. `ll_selfop_sku_daily_sales`:完整销量版本的稀疏日事实
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `sales_run_id` | `BIGINT` | `OUTBOUND` 执行尝试,同时是计算版本 |
| `source_account`、`customer_code`、`warehouse_code` | 各 `VARCHAR(64)` | 统计范围 |
| `sku_code` | `VARCHAR(128)` | 原始产品 SKU |
| `business_date` | `DATE` | 已核准业务日 |
| `outbound_quantity` | `DECIMAL(20,6)` | 有效出库实际件数之和,非负 |
| `outbound_order_count` | `BIGINT` | 该维度去重发货单数,非负 |
| `quantity_unit` | `VARCHAR(32)` | 同一统计维度内一致的业务单位 |
唯一键 `uk_selfop_daily_version(sales_run_id,source_account,customer_code,warehouse_code,sku_code,business_date)`;索引 `idx_selfop_daily_window(sales_run_id,business_date,customer_code,sku_code)`。固定版本已经唯一绑定来源账号与仓库,默认全货主范围查询先按该版本和日期限缩;查询仍须校验账号/仓库权限,不能用省略索引列代替范围校验。精确货主+SKU 的日趋势使用前述唯一键;不同时保留未经测量证明必要的多套相近宽索引。
每版只包含该仓,从固定事实快照重算本仓全部已采集历史,形成完整查询视图;不能将另一仓的日期或剩余贡献拼入本仓版本。可只保存发生出库的日行;缺日是否能补零由本仓覆盖与本版本 SKU 观察依据共同决定,具体见下文图表规则。本仓去重订单总数不能相加各 SKU 的订单数。
“完整销量版本”表示发布的是完整查询视图,不代表首日和今日都已覆盖全天。日表允许保留这些日期的已采集出库量,查询时结合 `sync_run` 的覆盖与固定 profile 派生完整性。今日、近 3 天、本周、近 15 天、本月及自定义区间均读取此日表;区间按日期求和,不新增 3 天表、周表、15 天表或月表,也不在读取时连接可变订单事实。历史修订发布新日版本后,所有受影响范围自然重算;同一旧版本仍保持原结果。
### 8.1 `ll_selfop_warehouse_daily_sales`:同版本仓库日汇总
复合主键为 `sales_run_id,source_account,warehouse_code,business_date`。`observed_quantity DECIMAL(38,6) NULL` 保存 PCS 且非负的有效 SKU 日量之和,无有效行时为 NULL;`invalid_row_count BIGINT` 保存数量为空、负数或单位不为 PCS 的源日行数;`source_row_count BIGINT` 保存全部贡献日行数。创建、更新时间均为 UTC `DATETIME(3)`。表内只保存有源日行的日期,无源行的零销量仍由版本覆盖证明判定。
每批 SKU 日行、对应仓日累加与检查点同事务提交,失败回滚后重试不重复累加。构建结束时核对源行数总和与完整日版本的构建计数,再设置 `warehouse_daily_ready`;发布、复用及总览读取都要求该标记。旧未就绪版本重新构建发布,不补写旧版本或回退旧查询。经营总览趋势及区间合计直接读仓日汇总;SKU 级趋势、列表和榜单仍读原 SKU 日表,完整性与未知值沿用既有覆盖规则。版本淘汰保护和批次清理同时包含本表。
### 9. `ll_selfop_sku_profile`:随发布版本固定的 SKU 维度与观察依据
| 字段 | 类型/可空 | 含义与约束 |
| --- | --- | --- |
| `profile_run_id` | `BIGINT` | 本仓销量或库存候选版本;与对应 daily/snapshot 使用同一 run,发布后该版本全部维度行不可修改 |
| `source_account`、`customer_code`、`warehouse_code` | 各 `VARCHAR(64)` | 观察范围,不能用另一货主或另一仓证明本仓历史 |
| `sku_code` | `VARCHAR(128)` | 原始产品 SKU |
| `product_name` | `VARCHAR(255)`,可空 | 本版本确认的名称,不实时连接其他版本名称 |
| `quantity_unit` | `VARCHAR(32)`,可空 | 本版本已核准单位;未知或两版本单位冲突时不计算混合指标 |
| `first_seen_at_utc` | `DATETIME(3)`,可空 | 本证据族真实观测到本仓商品的最早接收时刻;缺行占位不能生成观察证据 |
| `earliest_evidence_at_utc` | `DATETIME(3)`,可空 | 库存版取真实库存首次观察时刻;销量版从该版有效事实重新求最早已核准出库时间,可随改期、取消或迁仓撤回/后移 |
| `evidence_type` | `VARCHAR(24)` | `INVENTORY_OBSERVATION`、`VALID_OUTBOUND`、`NONE`;不得将销量证据复制成库存真实观察证据 |
| `evidence_run_id`、`last_seen_run_id` | 各 `BIGINT`,可空 | 本证据族依据的真实采集批次及最近观测批次;无证据占位为空,不能使用候选版本构建时间充当观察时间 |
唯一键 `uk_selfop_profile_version(profile_run_id,source_account,customer_code,warehouse_code,sku_code)`;索引 `idx_selfop_profile_evidence(profile_run_id,earliest_evidence_at_utc,customer_code,sku_code)`。本表不再维护可变的全局 SKU 档案;两类任务各构建自己的不可变维度快照,与日事实/库存明细同版发布。库存版只继承上一已发布库存版的真实库存观察依据,并合入本次实际返回行;从销量版借来的已知 SKU 仅作缺行候选,证据为 `NONE`,不能产生或继承出库历史证据。销量版只由本版固定事实快照中的有效出库重新计算维度和历史依据;被取消、改期或迁出的单不能继续贡献旧日期/旧仓证据,无其他依据时移除该证据,而不是永久取历史最小值。
查询只读取指定 `salesRunId`、`inventoryRunId` 下的两份 profile,按账号+货主+仓库+SKU 合并候选,依据取这两个版本中可靠存在证据的最早值。缺某一侧记录视为该侧没有依据,不能连接更新中的档案补齐;新销量版出现而选中库存版没有的 SKU,库存返回 `UNKNOWN`。名称优先采用所选库存版,缺失时用所选销量版;单位冲突返回异常。首见及出库证据仅用于观察门槛,不代表真实上架日或库龄;纯库存零出库 SKU 随真实观测积累满足完整观察期。同一版本对即使后台继续采集、修订或发布,也保持候选与观察依据不变。
### 事务、幂等与发布步骤
以下步骤仅用于 `executionPurpose=BUSINESS` 的正式业务链。执行器领取、断点恢复、事实归并和销量/库存发布都须先检查用途,验证任务仅进入有界暂存/校验流程;不能通过手工重试或自动恢复进入本节,也不能把验证 stage 复用为正式采集已完成证据。
1. **固定首采与建任务**:每仓首次建立固定 `[B,E0)`,按本仓业务时区保存配置快照;新仓不能继承其他仓的起点或成功水位。库存直接按本仓 `whCode` 建任务。出库时间切片、已知单复查和历史重扫进入账号共享发现队列;只有同一共享 task 内的重试和分仓消费者复用其已完成目录/详情。新一轮复查或重扫必须用新的计划槽位/轮次键建立新 task、sequence 并重新请求领星,即使窗口或单号相同也不能永远复用旧 VALID 详情。所有网络请求在数据库事务外执行。
2. **账号目录共享及详情补齐**:账号协调器通过独立租约和栅栏串行执行共享发现。目录按日/小时有界切片,以确定性请求键建任务,分页成功即持久化目录对象,标记完整目录后按返回 `whCode` 形成每仓对象集合并幂等派发子任务。由于上游出库分页没有仓库筛选,路由必须依赖实际返回仓库;身份缺失不能归入任意仓。每次实际详情请求前分配新的 `observation_sequence`,逐次请求并在结果入暂存时再校验栅栏;同一有效详情由各仓共享。请求数或时长预算耗尽时记录 `YIELDED`、保留游标及已完成详情。恢复时先逐对象比较有效采集代次;已被其他任务新观测的对象按上述规则建立新的 `RECHECK`,重新请求并用路由清单引用新暂存,其他对象才继续复用或补齐。不能因旧 task 的调度序号较小而丢弃它后来实际读到的修订,也不能让恢复任务永久复用已过时详情。新详情改变仓库时同步更新路由并登记旧、新仓依赖。账号目录水位在完整目录及路由清单持久化后可推进,但不能推进任何仓的业务水位。
3. **分仓独立就绪与失败**:某仓切片的完整目录和全部必要有效详情引用齐备后,该仓子任务进入 `READY`;零单仓也需有完整目录证明。另一仓缺详情或字段错误,只让另一仓保持 `WAITING_SOURCE`/失败,已就绪仓可继续处理。本期源时间按已确认的 `Asia/Shanghai` 解释,业务日期直接取有效源日期;UTC 字段只作内部时间点标准化,不按仓库 `timeZone` 再转美国时间。归并前再次校验对象采集代次,较旧观测不能覆盖较新已确认事实;遇到旧引用则转实际复查,不静默完成。采集代次仅防本系统重放,上游内容矛盾仍要重新核对;内容相同也更新已接受采集代次,但不增加事实修订号。
4. **本仓有界事实归并及屏障**:分仓任务取得相关仓出库族租约,按有界数量的订单分段开启短事务,校验租约、栅栏、范围和单据修订号。第一段事实提交前锁相关 scope,登记完整已知影响仓集合、`apply_state=APPLYING` 和起始时刻;登记与首段事实提交原子完成。每段先保存旧规范化内容,再更新订单头、整单替换 SKU、增加订单与相关仓事实修订号,设置 `rebuild_required=1`;取消等非有效出库状态保留事实但不贡献销量。段失败仅回滚该段;已提交段保留,任务保持 `APPLYING` 或转 `BLOCKED`,以摘要、实际采集代次和暂存证据恢复。全部必要对象完成后,在锁影响仓 scope 的短事务中将任务置 `APPLIED` 并记完成时刻;此前不声明切片完整、不推进覆盖。任何计划/重建任务都不能绕过影响本仓的半归并屏障发布销量;任务进入 `DEAD` 也只能保留屏障与失败提示,须恢复原逻辑任务完成归并,不能直接清标记。
5. **跨仓修订同时处理两边**:若同一稳定单据从旧仓移至新仓,归并前收集两仓,按 `(source_account,warehouse_code,data_family)` 字典序一次性取得两仓出库租约;任一不可取得即退回重试,不持有一仓等待另一仓。事务中按相同顺序锁两个 scope,再锁订单,复核实际旧仓;如涉及仓集合变化,回滚后重新取得完整集合。事实变更之前先把两仓登记进本任务的影响仓集合,一次短事务保存修订前后证据、迁移单据和明细、按新仓时区重定业务日,同时增加旧/新仓 `fact_revision`、标记两仓待重建并幂等创建两仓重建任务。重建任务在原任务全部归并完成前不得发布,两仓都检查同一个持久化屏障。重复遇到同条变更时按摘要及实际采集代次识别已处理,不重复累加。旧仓必须撤掉原贡献,新仓必须建立新贡献;单纯更新新仓不算完成。
6. **本仓候选销量版**:确认本仓无 `APPLYING/BLOCKED` 的影响任务后,对固定事实修订号建立一致性读快照,重算本仓全部已采集历史,写入新的 `sales_run_id` 和同 run 的 profile;由完整处理的切片计算连续覆盖与完整自然日区间。profile 的出库存在证据从该快照的有效事实重新计算,取消、改期与迁仓必须撤回已失效依据,不取旧 profile 的历史最小值。未变日及全部修订均由完整重算体现。跨仓修改完成后两仓各自重建、各自发布;等待重建的仓仍可查看旧版,但显式标记 `rebuild_required`,不能作为最新可信数据供后续告警或补货消费。
7. **本仓候选库存版**:构建开始固定上一库存版及当时已发布的本仓 `reference_sales_run_id`,按本仓 `whCode` 完整获取产品库存全部货主、全部品质并保留零库存;在本次独立 `run.id` 下生成快照、缺行占位及 profile。出库已知 SKU 仅从固定已发布销量版的维度读取,不能读取半归并事实;库存 profile 只保留真实库存观察依据,不继承销量历史。核对本仓分页完整性和仓库一致性,保存真实采集区间。另一仓失败、本仓销量归并失败均不阻止本仓完整库存版发布;不同库存尝试不得混页,旧快照和旧 profile 不得修改。
8. **本仓原子发布**:短事务锁本仓对应任务族 `sync_scope`,校验租约、栅栏、范围及候选状态,并确认本版 profile 与日事实/库存明细都已构建完成。销量必须再次检查影响本仓的任务不存在 `APPLYING/BLOCKED`,且 `calculated_fact_revision = scope.fact_revision`;该屏障检查与事实写入共用出库 scope 锁,不能只检查修订号相等。同事务切换本仓 `published_run_id`、写新版本发布时间、填写旧版本 `superseded_at_utc`、为已完整承接的任务填写 `result_published_run_id` 并标记成功;销量同时写 `published_fact_revision`、清除待重建标记,并仅推进已连通的完整出库覆盖。库存不检查销量归并屏障,也不清除销量待重建状态。失去租约、落后的计算或半归并销量都不能发布。
9. **恢复、游标与查询**:部分归并先恢复原 task 至 `APPLIED`,全部归并但未发布时重新计算本仓完整版;已发布后响应中断,通过指针和任务状态判定。复查游标随已完成对象/切片可靠处理提交,未完成时保存当前位置。查询必须指定仓库,固定该仓销量与库存发布 ID,并只取这两个 ID 下的日事实、库存及 profile,固定业务日期窗口、覆盖与时区;质量和时效变化独立说明,不使未发布观察改变同版本候选。若未来提供多仓并排查询,响应和分页游标返回 `warehouseVersions` 映射,每仓各含 `salesRunId`、`inventoryRunId`、两个范围摘要和质量状态;不能用一对账号级版本表示所有仓,也不提供合并可售天数。
后续告警/补货只预留按仓消费边界:输入明确 `source_account`、`warehouse_code`、该仓版本、有效数量、时区、过期与待重建状态,再按 `warehouse_policy_ref` 取得该仓规则。一期不建告警或补货规则表,不因不同仓 SKU 相同而合并阈值。
一期用 MySQL 完成以上流程。Top 100、日均量和可售天数从本仓固定销量版及产品库存版计算,不增加三套累计账本;共享发现仅省去重复上游调用,不改变业务分仓。派生旧销量版按后文保留策略管理;原始事实、库存实际观测、暂存修订证据和执行记录不套用该策略,不能为减少日统计副本而删除审计依据。
## 指标定义
### 每日出库销量
```text
SKU 每日出库销量
= 指定来源账号、货主、仓库、SKU、业务日期内
当前有效且已出库的订单 SKU 件数之和
```
统计门槛为已确认的有效出库状态、有效出库时间、明确 SKU 与数量。用户在开发总控对话已确认数量按每个 SKU 的件数,统一单位 `PCS`;SKU 种类去重数与出库件数分开。箱、套、组合商品不得自行换算或凭 SKU 名称拆分,若来源出现与确认口径不符的单位仍须拒绝混算。订单数按出库单去重,不能把同一单多行当多单,也不能把 SKU 订单数相加作为总订单数。
一期默认且必须按仓统计,查询要求 `warehouseCode`;销量和库存使用完全相同的货主与仓库范围。一个仓的库存不能为另一个仓抵扣缺货,不输出全局合并可售天数。跨仓概览留待明确需求后增加,届时也必须保留每仓的版本、时效和规则。不同来源账号或货主的同名 SKU 不自动合并。
### SKU 销量列表与趋势图
#### 时间范围与日粒度
用户已确认按自然周期查看。令 `D` 为本次查询基准时刻 `queryAsOf` 在所选仓库 `businessTimezone` 下的日期;本期两个仓库均已确认使用 `Asia/Shanghai`,不随浏览器、服务器系统时区或仓库资料变化。今日、近 3 天、本周、近 15 天、本月均按北京时间自然日定义,底层统一换算成半开区间,结束日期的下一日零点不计入本范围。
| 界面选项 | `rangePreset` | 日期范围(两端日期均包含) | 图上日期点数 |
| --- | --- | --- | --- |
| 今日 | `TODAY` | `D` 至 `D` | 1 |
| 近 3 天 | `LAST_3_DAYS` | `D.minusDays(2)` 至 `D`,包含今日 | 3 |
| 本周 | `THIS_WEEK` | D 所在周的周一至 D,包含今日 | 1~7 |
| 近 15 天 | `LAST_15_DAYS` | `D.minusDays(14)` 至 `D`,包含今日 | 15 |
| 本月 | `THIS_MONTH` | D 所在月的 1 日至 D,包含今日 | 1~31 |
| 自定义 | `CUSTOM` | 用户选定的 `startDate` 至 `endDate` | 按实际日期数;结束日期不晚于 D |
例如 D 为 2026-10-12(周一),近 3 天为 10/10~10/12,本周只有 10/12,近 15 天为 9/28~10/12,本月为 10/1~10/12。这些按钮切换的是**查询范围**;趋势始终为每天一个点,3 天不合成一个点、周不改成一个周总点、月不改成固定 30 天。今日只有一个日累计点和对应累计数值;一期不生成小时曲线,不推算两次同步之间的销量。
日期边界使用 `LocalDate` 加减日/月及已核准的 `ZoneId` 换算,不能把一天硬算成 24 小时;覆盖比较使用转换后的真实时间区间。自定义历史查询只展示已采集历史,超出首采下界或存在缺口时保留未覆盖日期,不因查询动作自动补采或修改同步水位。
#### 列表和图表交互
1. 先选仓库,再选上述范围;默认激活“今日”是界面设计默认值。可按货主、SKU 筛选;来源账号+货主+仓库+SKU 构成一条商品记录,同名 SKU 不跨货主合并。
2. SKU 列表展示货主、SKU、商品名称、单位、所选日期范围、区间出库量及数据状态。候选来自固定销量版和库存版的 SKU profile 并集,不能只列有出库商品而漏掉有库存零销量商品;未查询到的陌生 SKU 返回明确无记录。列表保留覆盖不足/待观察条目,不把它们默认为零销量。
3. 点击一条 SKU,展示其每日出库趋势和区间累计;横轴为业务日期,纵轴为出库件数或已核准的数量单位,提示中显示日期、数量、完整性与覆盖截止。若选中范围只有一个日期点,正常显示单点和累计卡片。
4. 列表与图表在完整范围内显示正式出库量;范围包含今日或缺失日时,显示“已采集出库量”及未完整提示。默认按该展示数量降序排列,表头支持升降序切换(`salesSort=QUANTITY_ASC/QUANTITY_DESC`),由服务端在分页前排序,空值始终排末,数量相同按 SKU 编码、货主编码升序;这只是当前范围的 SKU 清单排序,不冒充正式热销或滞销榜。
5. 图表必须保留整个所选日期轴。可靠零值显示 0;未知日期留空/断线,不连成平滑曲线掩盖缺口。部分日使用区别样式和文字“未完整”,不只依赖颜色。没有任何可靠值时显示未覆盖提示,不能显示一条全零线。
6. 标注所选仓库、业务时区、销量完整覆盖截至时间、最近成功发布时间及修订待核对状态;若同时展示库存,另外标注实际库存采集区间。每天 08:00、20:00 更新销量,因此“今日”是已采集累计,不是实时值。
#### 完整量、已采集量和缺口
每个日点和每条 SKU 区间汇总都分别返回 `outboundQuantity`(完整口径)、`observedQuantity`(该版已采集口径)、`dataStatus` 及 `reasonCodes`。数量可空;单位或身份异常不能用 0 替代。完整日按以下规则判断,使用本次固定版本对中的可靠存在依据,不连接后续版本档案:
| 日期情况 | 数值和状态 |
| --- | --- |
| 全日覆盖完整,且有有效出库日行 | `outboundQuantity=observedQuantity=日行数量`,`COMPLETE`;无需等 SKU 积累 30 天观察期才展示这一天 |
| 全日覆盖完整、无出库日行,且 SKU 的可靠存在依据不晚于当日零点 | 两个数量均为 0,`COMPLETE` |
| 有有效出库日行,但全日覆盖未完整 | `outboundQuantity=null`;`observedQuantity` 为该日已采集数量,`PARTIAL`;不能标成全日销量 |
| 无出库日行,但当天已证明的采集区间与 SKU 存在依据之后的区间有非空交集 | 可展示 `observedQuantity=0`;不满足正式零值条件时 `outboundQuantity=null`,`PARTIAL` |
| 当天无可靠资料,或尚无本仓 SKU 存在依据 | 两个数量均为空,`UNKNOWN`,原因区分 `SALES_NOT_COVERED`、`SKU_HISTORY_UNKNOWN` 等 |
状态按完整日条件优先判定,其次才考虑部分日。`observedQuantity` 取固定日表中已确认有效的日行数量;零值仅按上表补出,不能把空行无条件当成已采集 0。例如 SKU 到 16:00 才首次被库存观察,而该日销量只证明覆盖到 08:00,又无实际出库行,则当天仍为未知。首见日在日中且无出库,即使全日已扫描,也最多有已采集 0,不能推断全天已有商品。当前预设都包含今日,今日未结束,故整段范围通常为 `PARTIAL`。
区间 `outboundQuantity` 仅在每个日期点均为 `COMPLETE` 时等于各日完整量之和;否则为空。区间 `observedQuantity` 为可用日点已采集量之和,并返回完整/部分/未知日数;全部日点均未知时仍为空,不能把空集合求和变成 0。区间有可靠数据但不全完整时为 `PARTIAL`,完全没有可靠数据时为 `UNKNOWN`。前端用这一区间已采集值展示当前周期累计,不把“正式数值为空”误显示为“没有销量”;也不按已有天数外推整个周期。
`salesCoveredUntil` 表示连续覆盖截止,不等于抓取结束时刻,也不保证已采集行全部早于该水位;旧单复查可能发现水位外事实,因此部分量统一标为“该版本已采集”。今日未同步、首采首日缺口、旧版等待重建及单位异常须分别说明。SKU 图表可用不代表滞销观察门槛通过,库存缺失或过期也不应阻止已有可靠销量图展示。
正式热销榜仍用独立的 7/30 个完整自然日窗口;正式滞销榜、30 天日均量和可售天数仍用完整 30 天窗口。它们各自标明起止日期,不跟随本节图表按钮改变分母或观察门槛。同期/环比、同比、小时趋势、多 SKU 叠线及销量预测未列入一期必交项;后续增加时再明确比较区间与覆盖要求,不能将缺少的前期数据当成零。
### 热销 Top 100
- 默认按最近 30 个完整自然日的出库件数降序排列,允许切换 7 天;数据窗口必须完整。
- 候选是窗口内销量大于零的 SKU,包含当前已经缺货的热销商品。
- 件数相同时,按去重出库单数降序、SKU 编码升序、货主编码升序稳定排序;最多取 100 条,不足时显示实际数量。
- 今日累计单独展示,不参与默认完整日榜单。窗口缺数时展示覆盖不足,不把缺数产生的排名发布成完整榜单。
### 滞销 Top 100
一期将其定义为“当前有可售库存、近期出库较少的 SKU 排行”,不是财务滞销认定,也不是库龄排行。
- 候选要求最新有效库存的可售量大于零、完整 30 天销量窗口,以及 SKU 在窗口起点之前已经存在的可靠依据。单仓排行使用该仓的观测依据,不能用其他仓的历史替代本仓观察期。
- 排序为 30 天出库件数升序、当前可售量降序、SKU 编码升序、货主编码升序;零销量 SKU 优先,最多 100 条。
- 零库存、未知/过期库存、窗口不完整和观察不足的商品不进入正式榜单。新发现但历史不足的商品单列“待观察”,避免新品直接被判滞销。
- 首次接入时,纯库存且零历史出库的 SKU 若没有更早存在的证据,不能直接推断它已经滞销 30 天;随着观测积累再纳入。榜单补充“近 30 天零出库”标记,不伪造最后出库日期。
候选资格只使用本次查询固定的销量版、库存版所对应的不可变 SKU 观察快照。不能连接“最新档案”改变旧版本的名单;被出库改期、取消或迁仓推翻的历史出库依据须随新销量版撤回,不能从旧库存版重新带回。
若以后要按“库龄超过 90 天”判断滞销,需要额外核验批次剩余数量及库龄来源;连续无出库时长则需要连续出库覆盖与商品存在依据,两者不同。一期不通过首次同步日期反推真实库龄,也不把历史窗口内未见出库解释成从未出库。货主库存关注另提供按可售天数筛选和实物占仓提示,不更改本 Top 100 的既有排序语义。
### 日均销量与库存可售天数
```text
近 30 天日均出库量 = 最近 30 个完整自然日出库件数 ÷ 30
库存可售天数 = 最新有效可售库存 ÷ 近 30 天日均出库量
```
分母包含真实零销量日,不只除以发生销售的天数。日均值保留足够精度,最终天数显示一位小数;计算中间值不提前取整。
库存明细及经营总览共用的窗口统计,将实际出库与商品观察期分开:本仓完整、可信的已发布销量覆盖及有效件数单位确认后,按固定版本中该货主、SKU 的实际窗口出库计算;完整窗口内没有出库行可记零,不能将未采齐、未发布、单位异常或缺少覆盖证明的窗口补零。商品最早可靠记录晚于窗口开始或尚无更早记录时,`observationEligible=false` 和观察不足原因仅作为独立提示,不再清空窗口出库、日均或可售天数,也不单独归入待评估。该规则不将观察不足解释为真实库龄或新品滞销,滞销 Top 100 的独立观察资格不变。
| 条件 | 输出 |
| --- | --- |
| 可售量 300 件,近 30 天出库 150 件 | 日均 5 件,可售 60.0 天 |
| 有可售库存,完整窗口销量为零 | 天数为空,显示“近 30 天无出库”,不输出无限大或随意上限 |
| 明确可售量为零 | 天数 0,标记“无可售库存”;销售窗口是否完整仍单独显示 |
| 库存未知、异常或已过期 | 天数为空,说明数据原因;历史观测值可单独查看 |
| 可售量大于零,但销量覆盖不足 | 不输出正式窗口可售天数,显示覆盖不足原因 |
| 完整可信窗口且数量有效,但商品历史观察不足 | 正常显示窗口出库及日均;结合有效可售库存计算天数,另行提示观察不足 |
判定顺序固定为:先检查库存 `UNKNOWN/INVALID/STALE`,不满足可信条件时天数为空;库存有效且明确为零时返回 `0`,同时独立暴露销量覆盖与观察状态;只有有效正库存才继续检查销量窗口、数量单位及分母是否为零。观察期不足仅提示,不阻断完整窗口统计。不能因为数值字段恰为零,就绕过库存异常或过期检查。
这是一项按历史出库速度估算的覆盖天数,不是需求预测。促销、季节性、断货期间损失的需求、采购交期均未建模;已在途库存不加进本期可售量。
### 货主概览与库存关注
本节保留货主维度的后台聚合口径及早前视图说明;当前正式页面已经按页首用户确认收敛为 SKU 中心,不据此重新加入货主选择或货主面板。SKU 字段、关注谓词和质量规则继续有效,新增总览采用[独立图表设计](经营总览图表设计.md)的单仓 SKU 图形与聚合合同。
库存分析以“来源账号+仓库+货主”作为经营查看范围。选择货主后,概览与下方 SKU 明细使用同一对销量/库存版本和固定的 30 个完整自然日窗口;切换今日、本周等销量图表不改变此处的日均分母。货主选项从本仓固定两版 profile 的货主并集取得,显示真实货主编码;货主名称仅在已有核验来源可提供时展示,缺失显示“—”,不凭编码推断主体、不额外调用领星获取选项。当前无已发布业务版本时显示尚无已采集货主,不能宣称授权范围内没有货主。
| 展示层 | 一期内容与口径 |
| --- | --- |
| 货主概览 | SKU 候选数、有实物库存 SKU 数、全品质实物件数、正品可售件数、冻结件数、次品实物件数、近 30 天出库件数和日均出库量;库存与销量分别说明完整性和观测时间 |
| SKU 同屏明细 | 货主编码/名称、SKU/商品名、实物/正品可售/冻结/次品/在途数量、近 30 天出库量、日均出库、预计可售天数、最近已观测出库日期、关注原因及质量状态;同一 SKU 品质库存先聚合再连接销量,避免重复计算出库 |
| 经营关注 | 近 30 天无出库、可售天数超过输入阈值、冻结库存、次品库存、数据不足/待观察;可售天数与库存量均可排序,展示原因及窗口,不只给颜色或笼统的“卖不掉”标签 |
| 下钻 | 点击 SKU 沿现有 daily 接口展示销量趋势;跨销量/库存页面携带仓库及精确商品身份。只读行不为此新建业务操作列;新增经营总览作为第四个入口,图表下钻按独立设计保持筛选与版本 |
实物量为同 SKU 全部已核验品质的 `physical_quantity` 之和;冻结量按各品质 `frozen_quantity` 独立汇总,是实物中的受限数量,不再加到实物总量;次品实物也是实物的一部分。可售量只取正品 `available_quantity`;在途单列,不视为当前占仓。未知品质可保留来源实物,但不推断它是正品或次品。任何数量缺失或异常均按其指标依赖传播,不能把一个数值有效误当该 SKU 所有指标均有效。实物件数仅表示数量,不表示立方米、托盘或库位占用。
完整发布的库存快照中,若某 SKU 仅有品质为次品、质量状态有效且实物/可用/冻结均明确为零的记录,可售量确认为零;不得仅因没有正品行而使全仓可售总量未知。存在正品行但字段缺失或异常、未知品质、缺整个 SKU 的库存行、库存过期或单位不明时,继续按未知处理。该规则同时适用于库存明细、经营总览和库存周转,不把一般的来源缺行统一补零。
关注条件使用下列明确语义;所需库存必须有效且不过期,销售资格沿用所选完整可信窗口及有效数量单位(经营总览及严格下钻的窗口以[经营总览图表设计](经营总览图表设计.md)为准)。标签可并存,不将冻结/次品与其他标签的命中数简单相加:
| `attention` | 判定及输出 |
| --- | --- |
| `ALL` | 不按关注原因过滤,保留未知、零库存及待观察候选 |
| `NO_OUTBOUND_IN_WINDOW` | 全品质实物量大于零、所选窗口出库资格完整且数量为零;冻结/次品即使正品可售为零也可命中。标签是“有实物库存且所选时段无出库”,不是实际库龄或从未售出 |
| `COVERAGE_GT_THRESHOLD` | 正品可售量和日均出库均大于零、正式可售天数严格大于 `coverageDaysThreshold`;使用未四舍五入的计算值比较。阈值是用户本次查询输入的正整数天,不内置未确认的 90/180 天经营政策 |
| `FROZEN_STOCK`/`DEFECTIVE_STOCK` | 对应有效冻结件数/次品实物量大于零;不依赖销量窗口,单独提示受限或次品占仓 |
| `UNASSESSABLE` | 影响基本出库/可售天数判断的库存异常、过期、销量覆盖不足等实际数据问题;观察不足仅作提示,零销量且数据完整属于“无出库” |
有库存零出库时可售天数仍为空,不当作无穷大,也不混入“超过阈值”的数值排序;零库存返回零天不代表货主优质。`NO_OUTBOUND_IN_WINDOW` 和 `COVERAGE_GT_THRESHOLD` 是不同关注集合,前者不可因分母为零被遗漏。现有 slow Top 100 仍限定正品可售库存大于零,不用它替代上述全品质实物关注。
货主概览的总量来自完整候选集,不能加总当前页或 Top 100。每个指标分别返回正式总量、已知有效小计及未知数量:同一指标任一候选依赖未知,正式总量为空,已知小计明确标为部分资料;完全无可靠行时小计也为空。统计中明确的零值不算未知。库存候选/缺行资格沿用固定 profile 与完整库存快照,数据范围未发布不能输出空集零总量。
货主 30 天出库总量以该货主在固定日版本中的有效出库及完整仓级销售覆盖判断;单个 SKU 尚无足够库存观察期,不阻断库存明细及总览的实际出库、日均及可售天数,观察状态另行提示。货主日均等于完整 30 天总出库量除以 30,不平均各 SKU 日均。若销量窗口不完整,正式总量/日均为空,仍可列该窗口已采集出库小计;不能将缺日按零补齐。SKU 种类数按货主+SKU 去重,不把多个品质计为多个 SKU。
无出库和超过阈值分别展示命中 SKU 数、命中 SKU 的全品质实物件数及其占本货主实物总件数的比例。该占比仅描述“命中 SKU 的实物件数占比”,不是空间占比或真实库龄占比;超过阈值的判定用正品可售天数,计数对象仍为 SKU。分子和分母使用同一版本、货主和全品质实物口径。存在库存未知、正库存商品判断未知或范围不完整时,正式命中量及比例为空,只列“已确认命中小计+待判断 SKU 数/可知的待判断实物量”;不能只在合格样本中算出看似完整的低风险比例。分母为零时比例为空并显示无实物库存。未输入阈值时“超过阈值”指标为空并提示未设置查询阈值;不影响其他概览值。
同一货主的 SKU 可售天数不直接平均,也不输出“总件数÷总日均量”作为货主整体可售天数,防止热门小件掩盖积压大件。最近已观测出库日期只取固定销量版中有效正出库的最大业务日期;没有日行时返回空并说明“已采集历史内未见出库”,同时保留历史覆盖起点,不推算窗口之前的最后出库日或真实库龄。缺货、近期补货、促销和战略备货尚未建模,当前关注项仅供内部业务复核,不自动定级或分摊成本。
### 每日库存观测留存
本期必须积累每日库存观测,继续复用 `ll_selfop_inventory_snapshot`、库存 profile 和 run,不复制一套 SKU 日库存明细。新增的 `inventory_daily_sample_date` 与 `inventory_sample_rule_version` 在 run 中指定每天唯一代表批次,为以后计算平均库存保留确定依据:
1. `DAILY_SCHEDULED_FIRST_V1` 只选择本仓当日计划库存任务中第一个完整分页校验并成功发布的 `BUSINESS` 库存 run。其计划槽位日、实际扫描开始日及结束日须同为北京时间 D;开始日与结束日由真实 `fetch_started_at_utc/fetch_finished_at_utc` 转换,不用发布或任务创建时间伪造观测日。人工临时任务、验证样本及旧日补跑不自动成为日代表。
2. 库存发布事务在同一库存 scope 锁/栅栏下检查当日是否已选中;未选中才一次性写入该日期及规则版本,日标记、run 发布状态及库存发布指针原子提交,唯一键防止重试/并发重复选择。同日后续成功快照仍可成为最新库存,但不覆盖日代表。完整批次允许个别 INVALID/UNKNOWN 行,代表样本不能挑选更好看的行跨批拼接;受影响 SKU 的该日观测为空并保留原因。
3. 当日无合格观测就是缺口;同日失败后成功可选首次成功批次。跨日扫描、翌日补跑不能回填昨日,也不冒充当日正常计划样本;这些成功批次仍保留真实观测且可按原规则服务当前库存。已发布结果恢复只核验原事务的日标记和发布结果,不按恢复先后另选日代表,也不提供给未标记旧版事后补填日期的常规路径;若原事务未提交,恢复时重做完整发布事务及资格判断。
4. 数量未变化也须保存当天的真实库存观测与时间,不能引用昨日批次制造今日样本。所选 run 的 snapshot、profile、scope 范围、采集区间和日标记整体留存,受依赖保护;不套用旧销量计算副本的 7 天淘汰规则。日标记需与批次发布/结果落账一起恢复,不能出现选中未发布样本。
5. 当前每日建议 08:00 的快照仅是一次日内观测,不是日终库存或精确时间加权平均。未来同一 SKU 的期间数量周转可定义为“期间出库件数÷同期每日同口径实物观测均值”,但须先核验全窗口日样本、商品身份/品质/单位和范围一致性;缺日、异常日不补零、不用当前库存反推,也不通过缩小分母天数假装完整 30 天。第一次拉两个月出库只补销量历史,不补出两个月历史库存。
本期交付日代表的确定性选择、持久化和查询可用索引,并在数据采集页显示近 30 个完整日的日样本数、缺口日期及数据积累起点;首采前或观察不足如实显示。正式周转率、日库存曲线和任意历史时点重建不属于本轮新增页面范围,不能把可售天数倒数更名为周转率。
### 后续内部评级与成本分摊
后续按仓库、货主及可解释的 SKU 明细形成内部经营评级,再研究成本分摊。当前不接对外收费客户、不触发 Finance 账单,也不生成“优秀/加价”最终结论。以下条件需要独立确认和验收后再实施:
| 后续能力 | 数据与规则前提 |
| --- | --- |
| 真实库龄 | 核验领星 SKU 批次库存的 `stockAge`、上架日期和剩余在库量,按仓/货主/SKU/品质对齐产品库存;确认起算、退货/移仓/重新上架是否重置以及缺失处理。公开接口和未验证边界见[联调手册](领星WMS接口联调手册.md#后续库龄数据入口)。库龄是现存各批剩余货的年龄,不取 SKU 最早入库日代替全部库存,不用首次同步日期反推 |
| 库龄分布 | 库龄段按仓配置;同 SKU 的不同批次按剩余数量分别落段,未知库龄量单列。加权平均库龄、超龄件数及占比须包含覆盖说明;阶段边界和正式阈值尚未确认,不能直接照用领星 `stockAgeStatus` 作为内部评级 |
| 正式期间周转 | 明确同期销量与平均实物库存的可比口径;每日样本积累充足、缺口及范围变更处理通过验收后再启用。它是数量运营指标,不冒充基于金额与成本的财务周转率 |
| 占仓规模与成本归属 | 件数不是空间;核验 SKU 尺寸/体积、实际托盘或库位占用及其时间,明确货主对应的内部经营主体/成本中心和有效期。不同货主/商品不可仅凭件数或 SKU 天数简单平均排名 |
| 内部评级与成本分摊 | 确认评价周期、仓库阈值、库龄/速度/空间指标权重、观察期、新品/季节性/战略备货例外、人工复核、规则版本和生效日期;结果保留输入版本与判定原因。阈值、费率和调整周期尚未确认,不擅自设置加价档位或追溯起算日 |
## 调度可靠性与运行维护
采用服务内调度+MySQL 持久化任务即可。不能只依赖 Java 进程中的布尔锁:分仓任务按账号+仓库+数据任务族持有独立租约,覆盖计划、重试和人工补采;账号共享出库发现另有账号级协调器,避免重复调用。任务槽位唯一键不能替代跨槽位互斥。租约定期续期,事实归并和版本发布都校验持有者与栅栏版本,防止过期工作线程覆盖新结果。
服务重启后恢复未完成任务并补做遗漏的出库扫描窗口。库存任务若错过计划点,只能补采“现在”的库存并记录缺失日期,不能把今天的数量伪装成昨天的快照。当前产品库存接口读取的是当前库存;如后续需补历史快照,须先确认对应历史接口及字段,不能声称当前查询已经补齐历史可售库存。
建议临时网络错误、限流和服务端错误采用指数退避并设置次数上限;认证或签名错误停止盲目重试,保留可诊断错误。按账号统筹请求配额,避免新单、旧单和库存任务分别重试形成请求风暴。分页大小、批量数和速率使用实际确认的供应商限制,不把某个接口上限套到全部接口。
此前真实串行联调在请求起始最小间隔 1.1 秒时仍出现过一次 HTTP429,有限退避后恢复。因此账号级限流和退避不能留到后续迭代,1.1 秒也不是已确认可持续速率;实际配额与其他调用方的共享影响仍待核实。今后的测试与正式恢复分别遵守[生产首采与测试请求边界](#生产首采与测试请求边界),不能继续按旧探针自动重试限流。
必要运维信息包括任务成败、最后完整覆盖时间、库存年龄、异常 SKU 数、待复查单数、重试次数和连续失败次数。任务故障提示属于一期数据运行维护;库存告警、补货通知属于后续业务功能。人工重试与历史重采集应有独立权限、范围限制和审计记录,同一请求重复执行不得重复计数。
## Admin 菜单与采集页面
### 菜单和页面职责
自营业务在原销量分析、库存分析、数据采集之上新增 **经营总览**,共四个正式页面。原三个页面已有模块实现,源码、部署和验收仍分别核对;本轮设计新增总览,不重建已有模块。页面统一进入自营分析模块,货主保留为商品归属和统计键。新增菜单接入需同时完成路由、页面、服务端合同及权限,不用空占位冒充完成。
| 菜单 | 建议路由 | 页面功能 |
| --- | --- | --- |
| 自营业务 → 经营总览 | `/self-operated/overview` | SKU 中心的单仓指标带、出库趋势、库存消化天数分布、近 30 日热销/最长可售天数/无出库库存 Top 10,支持精确下钻现有页面。页面、接口与验收唯一见[经营总览图表设计](经营总览图表设计.md) |
| 自营业务 → 销量分析 | `/self-operated/sales` | SKU 出库销量列表与单 SKU 日趋势;提供“SKU 销量/热销 Top 100/滞销 Top 100”页签。SKU 销量采用五种自然周期及自定义范围;榜单页签使用各自固定完整日口径,不沿用图表日期按钮 |
| 自营业务 → 库存分析 | `/self-operated/inventory` | 以仓库、SKU、关注条件及排序查询,SKU 同屏展示全品质实物、正品可售、冻结、次品、在途、近 30 天出库/日均与可售天数;货主只显示归属,无独立货主面板。接收总览的分桶/榜单精确条件;未知、观察期与品质口径独立显示 |
| 自营业务 → 数据采集 | `/self-operated/collection` | 分仓任务运行概览、首采覆盖、采集任务列表、阶段进度、失败原因和历史尝试;授权人员可以恢复允许重试的任务 |
建议前端模块为 `apps/admin-web/src/modules/self-operated/`,模块路由由现有 `app/router/index.ts` 装配,菜单由 `core/navigation/adminNavigation.ts` 维护。上述路由名和一级分组属于本方案建议;接入时同步核对[平台菜单与权限契约](../平台客户层级与权限契约.md),不把文档中的建议表述为侧栏已经上线。
销量、库存页面先选仓库,SKU 为主要搜索条件;不再显示来源账号键或独立货主选择,具体商品下钻仍携带货主+SKU 身份。跨页窗口、榜单、关注条件与版本按[总览下钻合同](经营总览图表设计.md#精确下钻合同)传递并校验,目标页明确显示当前条件。经营页面显示“销量覆盖截至……、库存采集于……”,并有“查看采集状态”入口。新版本发布后提示“有新数据,可刷新”,由用户刷新整个查询上下文;进度轮询不得替换当前图表版本或跳动分页。
库存页默认浏览全仓 SKU 清单,不恢复货主概览;经营图形统一放新增总览页。库存关注阈值只影响本次查询,不保存成评级或加价政策;没有阈值时仍可查看数值和排序。来自总览的条件显示为可清除摘要,不为下钻重建独立货主控件。
销量/库存页点击 SKU 后复用已确认的 1120px 居中趋势弹框,保留日轴、未知断线及部分日标注,不恢复列表下方详情。热销和滞销明细保留排名表,候选不足 100 如实展示;新增总览的 Top 10 图形按独立设计取完整候选,不能改变原 Top 100 语义。所有图形数值复用服务端结果,不在浏览器重新推导可信零值或可售天数。
### 数据采集页怎样判断成功
2026-10-13 用户已确认采集首页和详情图形化方案并交开发总控安排,具体布局、任务关联、阶段数量、预计时间与逐操作验收见[数据采集页面设计](数据采集页面设计.md)。该设计将当前关注任务放在首屏,分仓可用结果和历史记录随后展示,技术明细按需展开;本节继续定义运行事实和成功条件。运行概览按“仓库+出库/产品库存”独立提供环境、模式、启用状态、阶段、最近尝试与成功、当前可用数据、计划、等待和异常,不能全部堆进同一张宽表。当前两个仓库各有两类状态,不跨仓合并销量或库存。验证与正式任务分开标识,验证样本结束不刷新正式最近成功和首采覆盖。即使首次尚无发布版本,也必须能显示配置、首采任务和错误。
**任务执行结果、页面可用数据、业务数据完整性分开显示。** 新尝试失败时,最近成功时间及可用版本保持原值,并明确“本次失败,仍展示上次成功数据”;首次失败则显示“尚无可用数据”。下次计划从服务端有效配置取得,停用时显示“未启用”及原因,不由浏览器硬算下一次 08:00。业务时间和计划统一标注北京时间。
| 页面结论 | 必须满足的依据 |
| --- | --- |
| 采集中/等待上游/待重试 | 当前任务及执行尝试的持久状态;展示阶段、上次进展、已耗时和可用的下次重试时刻 |
| 数据已取完,正在校验/计算/待发布 | 拉取或候选构建完成,但尚未完成本仓结果发布;不得显示“本次同步成功” |
| 本次成功,已发布/无变化,沿用已有结果 | 分仓任务 `SUCCEEDED`,且 `result_published_run_id` 指向真实已发布结果;区分 `PUBLISHED` 与 `REUSED`。历史成功记录不因后来发布新版本而改为失败 |
| 账号目录/共享采集完成 | 目录完成使用持久化的 `directoryComplete`;共享任务终态 `ROUTED` 表示其必需采集及路由均已完成,不能只扫完目录就置此状态。两者都不代替本仓归并、计算及发布成功 |
| 首次最近 2 个月已完成 | 本仓正式已发布连续覆盖完整包含固定首采 `[B,E0)`,范围、时区口径一致且没有未闭合归并/修订阻断;测试样本、单个切片成功或目录完成不能代替首采目标窗口全部完成 |
| 已发布,但部分指标待核对 | 分页和发布成功,但存在数值未知、异常 SKU 或销量观察期不足;单独列出质量状态与数量,不能据此把所有 SKU 判为可信 |
| 失败/等待处理 | 展示脱敏原因、失败阶段、尝试次数、服务端给出的处理建议和是否允许重试;旧版本仍可使用时一并显示其时效 |
| 测试样本结束/限流停止 | 展示验证用途、原会话已用预算、停止原因和是否有未采样范围;不得显示正式同步成功,不自动重试或新建下一会话 |
首采区域展示固定目标区间、已发布连续覆盖截至时间、尚未覆盖范围和进行中的切片;目标是固定的最近 2 个月窗口,不是两个自然月,也不固定为 60 天。日期分片可能动态细化,因此不以“某一天任务完成”或固定日期格数推断首采完成,也不把范围中的未采集日期展示为零销量。库存首采独立判断全量快照是否发布,不等待销量首采全部结束。现有 scope 的目标和覆盖足以判断,无需新增采集分组表。
### 进度怎样展示
进度以**阶段+已可靠完成数量**为主,不提供用时估算的总百分比或未经测量的预计完成时间。各阶段完成量表示已经提交的检查点;详情和归并计数按唯一对象计数,重试 HTTP 次数另列,不能重复累加。当前尝试、逻辑任务累计完成和账号共享阶段必须明确标识。
本轮把基于真实推进速度的**阶段预计剩余时间**纳入页面设计,计算条件、暂停估算和服务端字段见[预计时间设计](数据采集页面设计.md#预计时间先给可靠的阶段估算)。不将当前阶段预计结束当作整个同步完成,也不按尝试总耗时直接推算包含复用对象的速度。
| 阶段 | 展示示例与限制,数字仅为合成示例 |
| --- | --- |
| 获取出库目录 | “账号共享目录,第 2 轮,当前切片已取 3/8 页”;总页数未可靠取得时显示“已取 3 页,总量待确认”。扫描轮次与单轮页数分开,不把两轮请求页数相加除以一轮总页数,不把账号总单量显示成某仓单量 |
| 获取单据详情 | 账号共享阶段显示“来源详情 600/1000 单”;只有本仓完整目录及就绪投影确定后才显示本仓有效详情数量,不把共享分母当本仓分母。已复用的有效详情计入已完成,失败/待核对单另列 |
| 获取产品库存 | “本仓已取 4/6 页,读取 4000 行”;同时记录去重后的库存业务键数,不能把库存行数叫作 SKU 数 |
| 校验与归并 | 显示校验中的原因或“已归并 600/1000 单”;重复键、范围不符、总数变化必须保留为异常证据 |
| 统计计算与发布 | 显示“计算中/待发布”和已耗时;只有真实有界工作量才展示分子分母,不能把一条 SQL 的执行时间模拟成进度 |
| 重试或预算等待 | 显示等待原因、下次重试时间/续跑安排和最后检查点;`YIELDED` 表示让出本轮预算,不等于首采成功或永久失败 |
若展示阶段百分比,只能由本阶段可靠完成量/可靠总量计算,并明确标注阶段名;某阶段达到 100% 后仍展示后续阶段,不能把它当成整个任务 100%。总量变化或不可靠时撤去百分比并给出解释。正常零单/零行仍须完成空页校验及发布,不做除零计算。
任务列表按[页面设计](数据采集页面设计.md#历史记录压缩为六列)提供任务范围、阶段计数、最近进展和执行/发布结果摘要,操作列位于第一列。开始/结束时间、完整性、各阶段、本次和历史尝试、失败原因及发布时间进入原有详情弹框;首屏使用进度图和数量摘要,历史与诊断按需展开。恢复仅在具备权限和服务端资格时呈现。界面使用稳定任务号,不将数据库 ID、版本 ID 或内部状态枚举直接作为业务文案。
页面可见且有活动任务时,设计默认每 10 秒刷新轻量运行元数据;无活动任务放缓至 60 秒,离页/浏览器隐藏时停止,恢复可见后重新读取。轮询失败保留最后读到的数据并标注“状态更新失败”和观测时间,不把任务改判失败;切换仓库中止旧请求,避免旧响应覆盖新仓。首次页面 HTTP 加载错误、任务运行失败和库存数据过期分别呈现。
### 页面交付和视觉边界
页面按[前端原型还原验收规则](../Admin-Client前端原型还原验收规则.md)维护逐操作矩阵:原三页的用户截图及当前弹框承载见模块 `operation-matrix.md`,新增经营总览按[总览设计矩阵](经营总览图表设计.md#图表交互与视觉依据)记录,不将早前“未确认”说明覆盖最新用户确认。未定义的新浮层仍标待确认,不照搬其他业务。样式沿用[Admin 高密度界面规范](../Admin高密度界面规范.md);自动检查、真实接口联调与人工 `1920×1080`、100% 截图分别验收,缺少截图保留待人工确认。
## 平台查询接口和权限建议
采用 `/api/platform/v1/last-leg/self-operated-analytics` 作为业务查询前缀,借助统一 Gateway 和平台登录身份。当前 Controller 已有 daily/hot/slow/inventory/sync-runs 入口,[SyncController](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/sync/SyncController.java)已有 `POST /sync-tasks` 创建/恢复命令,支持 RETRY,但受理响应只有内部任务 ID。尚无 summary、采集概览和逻辑任务只读详情;下表和参数段是最终设计合同,既有入口也须按新合同收敛,不代表已部署可调用:
| 后缀 | 用途 |
| --- | --- |
| `GET /dashboard` | 新增经营总览单次聚合:统一版本下的指标带、全仓日趋势、互斥消化分布和三个 Top 10;准确合同及 inventory-coverage 下钻扩展见[总览设计](经营总览图表设计.md#服务端聚合接口),与 sync-overview 分离 |
| `GET /owners` | 从本仓固定两版 profile 并集分页返回已采集货主编码和可用名称,供销量/库存页面共用选择项;无版本时明确尚无可用资料,不调用领星 |
| `GET /owner-overview` | 必须指定一个 `customerCode`;返回本仓货主固定 30 日经营总量、无出库/超阈值 SKU 和实物件数占比,以及各项未知数与覆盖依据,不输出最终评级 |
| `GET /sku-sales-summary` | 按五种预设或自定义范围分页列出本仓 SKU 的区间累计、完整性和候选数量,供选择商品及列表展示 |
| `GET /sku-daily-sales` | 指定来源账号、仓库、精确货主+SKU,返回所选范围内按日期升序的完整日轴与数量/状态,供单 SKU 趋势图 |
| `GET /hot-skus` | 指定完整时间窗口的热销 Top 100 |
| `GET /slow-skus` | 有库存低销量 Top 100,以及候选不足原因 |
| `GET /inventory-coverage` | SKU 同屏出库与库存明细,含全品质实物、正品可售、冻结、次品、在途、30 日出库/日均、可售天数、最近已观测出库日期、关注原因和质量状态;支持本地关注筛选/排序,默认 20 条,单页最多 100 条 |
| `GET /sync-overview` | 按来源账号返回获授权仓库各任务族的配置、首采目标、覆盖、当前任务、最后成功和当前可用结果;可选单仓过滤。只展示独立仓库状态,不汇总业务数量 |
| `GET /sync-tasks` | 指定仓库的逻辑任务分页,支持任务族、任务类型、状态、创建日期及任务号筛选,返回本任务最新尝试摘要 |
| `GET /sync-tasks/{taskNo}` | 指定仓库任务的范围、阶段进度、结果、失败和上游等待摘要;包含最近尝试,完整尝试历史沿下一接口分页 |
| `GET /sync-runs` | 指定仓库、可选任务号的执行尝试分页,返回阶段计数、错误摘要、采集时间与覆盖证明;保留失败尝试,不仅列出已发布版本 |
| `POST /sync-tasks`,`action=RETRY` | 沿用已有命令入口,补稳定任务号、操作原因及受理结果;经独立写权限和资格校验后,将可恢复的原任务排队重试,不修改目标范围、不覆盖旧尝试、不绕过归并屏障 |
统一只读权限 `platform:self-operated-analytics:list` 包含四个菜单、列表、图形聚合、详情及运行辅助查询;重试使用独立 `platform:self-operated-analytics:sync` 并单独校验和审计。前端按钮显隐不代替服务端资格校验;权限须在 IAM、路由、Controller 和 OpenAPI 同步登记后才能接入。客户账号即使传入有效来源账号也不能访问。经营数据和任务列表/详情查询必须带仓库,服务端根据平台权限及账号/仓库范围校验;仅运行概览允许列出多个获授权仓库的独立状态。当前全部 70 个货主纳入,仍保留货主维度。
查询响应至少包含销量业务日期范围、销量覆盖截至时间、库存观测区间、统计版本、数据质量状态和异常原因;异常条目返回空数值与原因,不能返回看似有效的零。只读明细优先展示真实 SKU、仓库编码和出库单号,缺少业务编号时显示“—”。
库存明细的全品质实物、正品可售、全品质冻结、次品实物、在途、窗口出库、日均出库和可售天数支持表头升降序,与筛选区排序共用服务端规则:对完整筛选结果排序后分页,未知值始终末尾,同值按 SKU、货主稳定排序。出库及日均按所选窗口的正式值排序,不用部分采集小计代替;切换排序保留已查询版本并回到第一页,失败恢复上次成功排序及页码。`observedWindowOutboundQuantity` 返回同一版本、同一日期窗口内已确认单位的出库小计;窗口未采齐时展示“已采集”及具体原因,不用部分小计推算完整窗口日均及可售天数;商品观察不足不阻断完整窗口统计,仅单独提示。列表明确展示北京时间统计区间:普通库存明细默认截至昨天,排除未结束的今天;库存数量仍取当前查询的库存观测版本。发布新数据后提供显式“更新数据”入口取得新版本,总览下钻仍返回总览刷新原图。
经营查询参数统一要求 `sourceAccount`、`warehouseCode`,列表可选 `customerCode`、`sku`。首次查询固定该仓 `salesRunId`、`inventoryRunId` 及各自的 `salesScopeHash`、`inventoryScopeHash`,取得版本后生成服务端基准时刻 `queryAsOf`,一起返回。后续同屏列表翻页、SKU 图表、榜单和库存查询携带同一版本上下文,过期或范围不符时明确拒绝,不静默切换;只有显式刷新或切换仓库才重新取得版本。某一族尚无已发布版本时上下文明确标记该族不可用,后续不能偷偷补入新版本;库存不可用时销量仍可按已有版本展示。运行概览及任务接口采用当前持久状态,不受图表版本上下文限制,不能复用经营查询的全量 load。
查询上下文的设计有效期为 30 分钟,响应返回 `queryExpiresAt`;后续请求超过期限时明确提示刷新整页,不能只给其中一个组件偷偷换版。版本本身另有可查询期限,`queryExpiresAt` 不得晚于所选任一版本的可查询截止;固定统计版本与临时查询上下文不是历史日期保留期限。
SKU 观察依据与候选资格也只取这两个版本的不可变快照,不引用后续更新的档案。两类任务的范围摘要分别校验,不要求库存品质配置与销量配置产生同一个哈希。时间字段返回明确时区,数量保留十进制精度;Long 版本 ID 沿用平台字符串序列化。响应同时给出 `businessTimezone`、销量完整日期范围和库存采集起止,前端不能把观测结束时间当成跨页原子快照时间。库存过期、任务失败等运行状态按实际查询时间另行返回,不因固定 `queryAsOf` 而冻结数据新鲜度。
图表查询的请求与响应合同如下,需同步进 OpenAPI 和调用方 DTO:
| 项目 | 一期约定 |
| --- | --- |
| 预设与日期参数 | 两个 SKU 销量端点均接收 `rangePreset=TODAY/LAST_3_DAYS/THIS_WEEK/LAST_15_DAYS/THIS_MONTH/CUSTOM`。仅 `CUSTOM` 必须同时传 `startDate,endDate`(`yyyy-MM-dd`,均含端点);其他预设不接受这两个参数,冲突或非法枚举明确拒绝 |
| 范围解析 | 服务端按固定 `queryAsOf` 和本仓 `businessTimezone` 解析,返回 `anchorDate`、`rangeStartDate`、`rangeEndDateExclusive`;自定义满足 `startDate ≤ endDate ≤ anchorDate`,结束边界使用 `endDate.plusDays(1)`。时间基准不得晚于实际服务端时间或早于所选版本发布时间;它只是查询范围基准,不表示库存回到过去某时刻 |
| 单次查询长度 | `maxQueryDays` 默认 366 个自然日,为可配置的本地查询保护值,不是领星限制或数据保留期限。两个 SKU 端点均先校验含首尾的日期数,再读取/生成日轴;超过上限明确提示可选范围,不静默裁剪。历史更多时分段查询,不限制后台继续积累历史 |
| 粒度 | 固定 `bucket=DAY`,不开放未实现的小时/周/月聚合参数。日图按完整所选日轴升序返回,不分页截断日期 |
| 首屏与后续 | 首屏 summary 取得上述范围和版本上下文;后续 summary 翻页和 daily 查询可用 `CUSTOM` 加相同已解析起止日期,仍带原上下文。只切换五种范围时复用原 `queryAsOf` 和版本重新解析,不用新的“今天”;跨午夜提示刷新,不能无提示改变日期窗口 |
| summary 筛选与分页 | `customerCode,sku` 可选;`sku` 为原始 SKU 精确过滤,同名跨货主返回独立行。分页采用 `current,size`,默认 `1,20`,`size` 最大 100(本地查询设计值);返回候选总数。排序按前述展示数量降序、空值末尾、SKU/货主升序,排序先作用于完整候选再分页 |
| daily 唯一商品 | `customerCode,sku` 必填并精确匹配固定候选;缺失不能合并同名 SKU,候选不存在则返回明确无记录。返回商品身份、区间汇总及每天一点的 `points` |
| 数值与状态 | 汇总和每个 point 包含 `outboundQuantity,observedQuantity,dataStatus,reasonCodes,quantityUnit`;point 另含 `businessDate` 和当日已证明覆盖的起止(无覆盖可空),汇总含 `completeDayCount,partialDayCount,unknownDayCount`。区间总量必须与同一 SKU daily 的日点按上述规则相加一致 |
| 覆盖与时效 | 返回 `salesCoveredFrom,salesCoveredUntil`、完整自然日范围、销量发布时间、修订待核对状态,以及可用时的库存观测起止;批次完整状态与查询范围完整性分字段,不能互相代替 |
| 其他指标的窗口 | 四个统计页面统一最近3/7/15/30天与自定义,全部不含今日。`hot-skus`、`slow-skus` 页面均传CUSTOM起止日期,总览下钻的CUSTOM日期合同见[经营总览图表设计](经营总览图表设计.md)。普通 `inventory-coverage` 使用 salesWindowDays=1..366(默认30)及含当日的 salesWindowEndDate(默认查询基准昨日且须早于查询当日),返回实际起止和 calculationWindowDays;总览严格下钻改用原图CUSTOM区间,禁止混用两组日期参数,详见同一设计入口 |
调用示例为 `GET .../sku-sales-summary?sourceAccount=demo&warehouseCode=WH-DEMO&rangePreset=THIS_MONTH&current=1&size=20`,账号和仓库仅为占位值。首屏解析本月范围后,选择一条货主/SKU 调用 daily,传相同范围与返回的版本上下文即可得到日趋势。任何范围切换都不触发 Integration 调用,不创建新的领星统计接口;不要把本地预设名、日期范围或业务时区直接透传给外部产品库存接口。
### 货主概览与库存关注接口合同
以下货主端点保留后台只读能力与合同,当前 SKU 页面不请求 owners/owner-overview、不恢复货主控件;库存端点仍供页面与总览下钻使用。复用 `platform:self-operated-analytics:list`,不新增写权限或外部采集动作,沿用同屏版本、北京时间和独立库存时效校验。总览 dashboard、coverageBucket、coverageSelection 及严格下钻时效的新增合同统一见[总览设计](经营总览图表设计.md#服务端聚合接口):
| 项目 | 一期约定 |
| --- | --- |
| 货主选项 | `/owners` 要求 `sourceAccount,warehouseCode` 和相同版本上下文,首次请求可建立上下文;`current,size` 默认 1/20,最多 100。可选 `keyword` 仅作货主编码前缀搜索,转义通配字符;编码区分大小写,先去重/过滤再按编码升序分页,返回 total。名称 `customerName` 可空,不将数据库 ID 回退成名称或业务编码 |
| 概览范围 | `/owner-overview` 要求一个精确 `customerCode`,候选不存在返回无记录;不接受 `sku,attention,current,size,rangePreset` 缩小货主总量。固定 30 日窗口,返回实际起止;可选正整数 `coverageDaysThreshold` 仅用于超阈值关注统计。返回货主身份、SKU 去重数、各类数量、30 日出库/日均及数据质量 |
| 概览总量 DTO | 数量指标采用 `overallTotal,knownSubtotal,unknownSkuCount,dataStatus,reasonCodes`;缺失与部分不得扁平化为 0。货主销量总量按销售覆盖判断,SKU 观察期不足另体现在风险统计,不混为同一个未知原因 |
| 关注占比 DTO | 无出库与超阈值各返回 `matchedSkuCount,matchedPhysicalQuantity,physicalQuantityRatio`;存在影响判断的未知时三个正式值为空,另给 `knownMatchedSkuCount,knownMatchedPhysicalQuantity,unassessableSkuCount,unassessableKnownPhysicalQuantity,reasonCodes`。比例为 0~1,前端转百分比;分母及其状态同时返回。已确认无命中时可为 0,范围未知不能为 0;未设阈值用 `THRESHOLD_NOT_SET` |
| SKU 明细筛选 | `/inventory-coverage` 保留可选精确 `customerCode,sku`,新增单值 `attention`,默认 ALL,枚举按前文。仅 `COVERAGE_GT_THRESHOLD` 必须传正整数 `coverageDaysThreshold`,其他关注可携带阈值用于行标签。所有货主/SKU/关注条件在完整候选上生效,然后统计 total、排序、分页;不得先取全仓 Top 100 再筛货主 |
| SKU 排序 | `sortBy=AVAILABLE_DESC/COVERAGE_DAYS_DESC/PHYSICAL_DESC/OUTBOUND_ASC`,默认 AVAILABLE_DESC;按未舍入值排序,未知末尾;同值按 SKU、货主编码升序稳定排序。零销量的空可售天数不强行变为极大数,另用 NO_OUTBOUND_IN_WINDOW 查看 |
| 明细与标签 | 复用既有数量、覆盖字段,补 `outboundQuantity30d,defectivePhysicalQuantity,lastObservedOutboundDate,attentionReasons` 及各自质量依据;最近出库是日粒度,不冒充精确时间或真实库龄。不存在可靠最后日期时为空,附已采集历史起点/原因;正式值与小计、样本不足分开 |
| 页面一致性 | 概览命中 SKU 与同版本/阈值的全量筛选结果一致;不能用本页求和代替概览。前端只渲染服务端公式结果,不自行重算评级/占比或连接不同版本 |
### 采集页面支撑合同
当前 `AnalyticsResponses.Run` 仅有执行/任务内部 ID、状态、覆盖、部分时间和失败摘要,且列表只读已有的分仓 run;账号共享阶段、尚未生成 run 的等待/排队任务不能完整展示。源码已有逐页/逐详情的 `routingManifestJson` 检查点,但 run 的公开计数主要完成后才写,不能直接当作实时进度。需补齐运行专用 DTO 与查询,只读配置、scope、task、run 及有界进度投影,不扫描日销量、库存明细、订单明细或 stage 原始载荷;没有已发布数据时仍应成功返回配置和任务状态。运行列表统一 `current,size`,默认 `1,20`、上限 100,按创建时间、稳定任务号/尝试序号排序分页;日期过滤使用北京时间边界,运行列表回传其查询快照上界以避免新任务插入导致翻页重复。
| 对象 | 必需字段及语义 |
| --- | --- |
| 仓库运行概览 | `warehouseCode,warehouseName,dataFamily,executionEnabled,publicationEnabled,disabledReason,nextScheduledAt,observedAt,sharedUpstream`;仓库名称缺失时展示仓库业务编码,配置来自服务端实际生效值。目录未完成、分仓子任务尚不存在时,也从账号协调器投影该仓关联且获授权可见的 `sharedUpstream` 任务号、状态、阶段、目录完成标记、进度和错误;标注账号共享范围,不伪造分仓任务 |
| 数据与覆盖 | `initialFrom,initialUntil,initialCoverageStatus,salesCoveredFrom,salesCoveredUntil,lastSuccessfulTaskNo,lastSucceededAt,lastPublishedAt,inventoryObservedFrom,inventoryObservedUntil,dataFreshness,rebuildRequired,revisionReviewStatus`;尚无数据时字段明确为空。最后成功允许是复用旧版,不能因此刷新库存观测时间或假造新发布时间 |
| 逻辑任务 | `taskNo,taskType,dataFamily,warehouseCode,queryFrom,queryUntil,taskStatus,applyState,attemptCount,latestAttemptNo,resultPublicationState,nextRetryAt,allowedActions,actionBlockReason`;原始状态供机器判断,界面转换为中文。任务详情和重试均校验任务实际归属与请求仓库一致 |
| 单次尝试 | `attemptNo,status,phase,startedAt,finishedAt,phaseStartedAt,progressUpdatedAt,heartbeatAt,executionHealth,elapsedSeconds,errorCode,failureSummary,retryable`;心跳过期显示执行状态待确认,不由前端直接改为失败或成功 |
| 阶段进度 | `progressScope,countBasis,attemptNo,scanRound,phase,completed,total,totalKnown,unit,sliceFrom,sliceUntil,stageStartedAt,stageFinishedAt`;`progressScope` 区分账号共享和本仓,`countBasis=TASK_CUMULATIVE/ATTEMPT_ONLY` 区分逻辑累计与本次尝试。出库已验证详情复用后的完成量采用前者,库存新尝试采用后者并从零开始;HTTP 请求次数另计,不作为已完成对象数。单位只取 PAGE/ORDER/INVENTORY_ROW/BUSINESS_KEY 等固定枚举,`total` 未知为空,阶段数量不可跨单位求和,归并完成和发布完成分别表达 |
| 质量摘要 | `validationState,invalidCount,unknownCount,pendingReviewCount,countUnit,summaryAsOf`;异常数量属于库存行、业务键、订单或 SKU 须明确单位。未计算时为空并说明,不能默认零;可售等正式指标仍遵守各自资格 |
| 日库存积累 | `sync-overview` 为每仓提供 `inventoryDailySamples`:`ruleVersion,firstSampleDate,windowStartDate,windowEndDateExclusive,sampleDayCount,missingDates`,窗口固定为北京时间截至昨日的 30 个完整日。仅查选中 run 元数据,不扫描库存明细;日样本数不代表每个 SKU 数量都有效,未启动及缺口明确展示 |
| 验证与账号保护 | `environmentLabel,collectionMode,executionPurpose,validationSessionNo,budgetLimits,budgetConsumed,deadlineAt,stopReason,accountCooldownUntil,accountPauseReason`;消费预算含未确认请求的保守预留,已确认真实 HTTP 次数另列。页面重试资格不得绕过验证会话终止、环境独占、根期限或账号冷却;日额度只约束 VALIDATION,BUSINESS 显示请求消耗而非剩余次数额度 |
task 的 `progress_json` 只保存有限阶段的上述检查点、质量计数及计数范围,建议序列化上限 8 KiB,不保存单号/SKU 大集合;run 终止时保存同样有界的 `progress_snapshot_json`,供失败/完成尝试审计,不改写旧尝试。新尝试初始化或覆盖 task 当前进度前,须在持有有效租约/栅栏的事务中先冻结旧 run 的最后可靠摘要;进程崩溃接管也执行该顺序,再按新尝试的恢复策略初始化计数,不能先清空旧证据。当前尝试与历史尝试不能共用一份不断变化的进度。本仓详情完成数来自持久化路由/详情就绪计数的更新投影,不能每次轮询展开共享完整 manifest。共享任务展示账号级阶段;本仓任务展示本仓就绪数量,已知单复查等无目录阶段的任务按实际处理对象声明单位与范围,不套用账号目录总数。
分页写入、详情通过校验及事实分段归并后更新可靠计数;检查点与对应可恢复业务提交保持一致,不在 HTTP 返回但尚未提交时先增加已完成量。计数以检查点覆盖量设置或幂等推进,禁止重试时无条件加一。可将频繁的小检查点有界合并提交,设计目标为待提交检查点最多积累 10 秒,提交后的投影即刻可读;页面再按 10 秒轮询并显示观测时间,不承诺严格实时。心跳可独立更新但必须校验当前执行者/栅栏。耗时阶段无新检查点时保留原计数并更新心跳,不能伪造推进;新尝试从其真实可恢复检查点开始,明确标注尝试序号。不得让长构建事务把全部进度直至结束才一起提交。
页面重试沿 `POST /sync-tasks`,请求为 `action=RETRY,requestKey,warehouseCode,taskNo,reason`,来源账号仍由服务端有效配置核验;沿已有 `requestKey` 实现幂等,不再并列增加另一个重试 URL 或第二套幂等键。服务端从 `taskNo` 读取不变目标范围,仅允许有资格恢复的分仓任务进入待执行状态,返回原任务号及受理状态;将当前命令使用的 `taskId` 引用统一收敛为业务任务号,直接调用方、OpenAPI 与测试同步调整。重复请求同键同载荷返回同一受理结果,同键异载荷拒绝;成功响应只表示排队受理,不能提示采集成功。处理中的任务、范围变化、上游未齐或配置/权限缺失分别返回明确阻断原因,不创建并行同范围采集。库存重试建立新尝试并从第一页全读;出库按已有可靠检查点恢复,保持半归并屏障。审计记录操作人、原因和原任务,修复后的 `DEAD` 也须沿原逻辑任务恢复。账号共享上游的恢复由已有协调器处理,页面不通过伪装单仓任务绕过账号级范围校验。既有 BACKFILL/RECHECK/REBUILD 等运维命令继续受各自后端合同控制;扩大历史范围、改计划和改密钥不纳入一期页面操作。
Integration 仅提供 `/internal/integrations/lingxing/...` 内部查询,沿用内部服务身份校验,不经公网 Gateway。平台查询和内部接口在开发时同步登记 OpenAPI、调用方 DTO 和直接合同测试;IAM 权限与网关实际路由一并核对。
供开发对话采用的内部接口约定如下,当前五类入口均已存在于 Integration 源码,本对话未重跑其完整实现验收。固定前缀 `/internal/integrations/lingxing/wms`,由 Lastleg 发起并携带内部服务令牌;来源账号配置由服务端决定,不从请求接收 App Key 或 Secret。
| POST 后缀 | 请求 DTO | 返回与限制 |
| --- | --- | --- |
| `/warehouses/query` | `current,size` | 规范化仓库目录;外部 `page/pageSize` 映射为内部统一分页字段,`timeZone` 仅作仓库元数据,不覆盖已确认的北京时间口径 |
| `/outbound-orders/query` | 现有 `startTime,endTime,current,size` | 保留当前已实现摘要,用 `whCode` 路由;不新增虚假的外部仓库过滤 |
| `/outbound-orders/locate` | `deliveryNo,current,size`;不接收时间、状态 | 仅传外部同名单号与分页参数,返回规范化摘要及分页元数据;Lastleg 读完所需页,核验原货主+精确单号唯一匹配,再用当前仓库查详情。无匹配/多匹配进入待核对,不认定取消 |
| `/outbound-orders/detail` | `warehouseCode,customerCode,deliveryNo` 全部必填 | 转为外部 `whCode,customerCode,deliveryNo`;返回身份、状态、源时间、完整实际 SKU 明细,不带面单和联系方式 |
| `/product-inventory/query` | `warehouseCode` 必填,`current,size` | 转为外部 `whCode`;固定保留零库存、读取全部品质和该仓全部货主,不开放任意 SKU/数量过滤冒充全量 |
以上接口都只取一页或一单;多页编排和完成性判断归 Lastleg,不能在一个 Integration HTTP 请求中阻塞直到历史首采或全部仓库完成。内部合同还须传递经服务端核验的任务用途、验证会话、允许尝试额度及截止时间,并返回本次实际 HTTP 次数/限流信息,避免内部重试绕过预算;直接内部调用同样受环境出口限制,缺失必要控制上下文不得无限请求。扩展时沿用标准 `ApiResponse`,错误与空数据分开;统计所需数值采用 BigDecimal,外部字符串空值保留未知含义。
## 查询性能与数据增长
### 当前源码核对与开发交接
以下为 2026-10-12 本轮只读查看当前工作区得到的实现现状,区别于后文目标;未执行构建、部署、SQL 或压测。开发对话须在交付前按最终源码再次核对,不把本节发现当成已经完成的修复。
| 当前实现 | 证据入口 | 需收敛的内容 |
| --- | --- | --- |
| 保存单头、整单 SKU 明细和修订载荷,并构建日销量 | [OutboundFactService](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/outbound/OutboundFactService.java)、[OutboundCandidateBuilder](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/outbound/OutboundCandidateBuilder.java) | 事实与日统计两层已在源码体现;真实落库和完整周期采集仍须运行验收 |
| 读 Mapper 按版本/仓库拉取全部历史日行,没有日期或 SKU 条件;Service 统一 load 加载两版 profile、全仓正品库存和日行,再在 Java 计算/分页;sync-runs 也调用此 load | [AnalyticsReadMapper](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsReadMapper.java)、[AnalyticsQueryService](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsQueryService.java) | 按端点拆读路径,将日期与精确 SKU 过滤、区间聚合和列表排序分页下推到数据库;任务记录查询只读必要元数据,不加载整个统计集。仅加索引不能修复缺少过滤条件的 SQL |
| daily 接口可遍历全部候选和日期,返回分页日行;尚无 summary、五种 rangePreset、双数量状态及查询期限合同 | [PlatformAnalyticsController](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/controller/PlatformAnalyticsController.java)、[AnalyticsQuery](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsQuery.java)、[AnalyticsResponses](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsResponses.java) | 补 summary;daily 改精确单 SKU、日期升序、不截断日轴;请求/响应按本方案统一,库存接口固定 30 日窗口,避免沿用通用参数造成混合口径 |
| 日候选在单个事务内加载整仓有效订单、历史观测及明细到 Java;明细查询已按 500 个 order_id 分批,但会继续全部保存在内存,并逐行写日表/profile | [OutboundCandidateBuilder](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/outbound/OutboundCandidateBuilder.java) | 保留一致性与发布屏障,改为数据库聚合或有界处理、批量写入;不能把 IN 参数分批误认为整个构建已经有界。测量事务时长、堆内存、锁与发布耗时 |
| SQL 基线仍为货主前置的日窗口/订单计算索引,尚无本轮新增构建/替代时间和任务结果版本字段 | [Lastleg SQL 基线](../../baiyue-firstleg/lastleg-service/src/main/resources/db/lastleg.sql) | 按本方案索引、`build_started_at_utc`、`superseded_at_utc`、`result_published_run_id` 及 `REUSED` 状态同步实体、Mapper、初始化基线、契约和测试;30 分钟上下文及 7 天派生旧销量版策略仍需实现 |
| Admin 尚未注册自营菜单/页面,后端 Run 响应缺少业务任务号、阶段计数、进展及心跳;尚无采集概览/逻辑任务只读详情,已有 POST 受理命令仍返回内部 ID | [Admin 根路由](../../apps/admin-web/src/app/router/index.ts)、[AnalyticsResponses](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsResponses.java)、[SyncController](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/sync/SyncController.java) | 按采集页面支撑合同增加 `task_no`、有界进度字段、运行查询索引与专用 DTO/接口;收敛已有重试命令,落实正式菜单、页面、权限和独立页面验收,不复用经营数据 load 来轮询 |
| 首采计划与范围重建仍为一个月;没有整轮验证硬上限和跨环境共同账号锁 | [首采计划](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/sync/SelfopSchedulePlanner.java)、[任务服务](../../baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/sync/SelfopTaskService.java)、[WMS 客户端](../../baiyue-firstleg/integration-service/src/main/java/com/baiyue/integration/provider/lingxing/LingxingWmsClient.java) | 首采与范围重建统一两个月;实现任务用途、验证根累计预算、真实尝试额度、账号预算/冷却及两条发布保护。生产独占真实出口,新的真实验证须在这些限制落实后进行 |
当前查询源码已做到读固定统计版、不连接可变订单事实、不回源领星,并且按同版 profile 合并候选、单独筛选正品库存;这些做法继续保留。主要性能差距是读取范围、Java 全量遍历与后台构建规模,尚不能认定查询速度或长期容量达标。
### 页面查询路径
页面只查询本地已发布版本。后台抓取领星、保存单据、计算候选与前台读报表分开执行;源 API 较慢或正在首采,不应让一次页面请求等待领星。前台继续读取上一完整版本,同时显示其覆盖、更新时间和待重建状态。
| 查询 | 一期读路径与约束 |
| --- | --- |
| 货主选项与概览 | 选项从固定两版 profile 去重货主、按编码过滤分页;概览在 SQL 中先限定本仓版本及一个货主,再聚合 30 日销量与按品质归并的 SKU 库存,按统一关注条件返回整体/已知小计及未知计数,不连接原始订单、不逐 SKU 查询 |
| 单 SKU 日趋势 | 精确账号、仓库、货主、SKU 与 `salesRunId` 读取日表;结合两版 profile 和覆盖,最多补 366 个日期点,不联查出库单头/明细 |
| SKU 区间清单 | 固定两版 profile 业务键并集去重 → 本销量版、所选日期内日行按货主+SKU 聚合 → 左连接候选并判断零/未知 → 对完整候选稳定排序 → 最后分页 |
| 热销 Top 100 | 读取本销量版完整 7/30 日的日行,聚合后按约定排序、取前 100;不能先取 100 个 SKU 再计算销量 |
| 滞销 Top 100 | 先聚合完整 30 日日量,再连接本库存版正品有效可售量和固定观察依据,筛选资格后排序取前 100 |
| 库存可售天数列表 | 本库存版与 30 日日销量分别按同一货主+SKU 粒度准备后连接,全品质实物与正品可售分列;对本货主/SKU 完整候选先算关注条件、筛选、排序再分页,默认 20、最多 100 条。最近已观测出库日期限定同一日版本及相同货主/SKU,不能每行回查原单 |
不为每个 SKU 单独发起 SQL,不从浏览器循环请求几千个 SKU,也不为区间清单生成“所有 SKU × 366 天”的巨大补零结果。区间状态日数可依据连续覆盖区间、SKU 存在边界和实际日行分组计算;仅单 SKU 趋势展开日期轴。候选总数直接从同范围的 profile 并集计算,不为计算列表总数重复关联订单事实。每页响应只携带本页区间值;点击 SKU 后再取其日曲线。
库存品质必须在连接前区分,同一 SKU 的正品/次品不能把日销量连接成两份后再求和。未知值判定沿用前文规则,不用统一 `COALESCE(...,0)` 换取实现方便。请求的 SQL 数量应是固定的少量批量查询,不随 SKU 数线性增加;权限查询与业务统计查询分开计数。
### 索引与读取范围
日趋势复用日表唯一键;全货主区间查询使用 `idx_selfop_daily_window(sales_run_id,business_date,customer_code,sku_code)`。库存及 profile 先复用以对应版本 ID 开头的唯一键。后台按仓重算使用 `idx_selfop_order_daily(source_account,warehouse_code,sales_eligible,business_date,id)`,再通过 `order_id` 连接明细。不在查询日期条件上包裹 `DATE()` 或时区转换函数,直接使用已保存的 `business_date` 范围与明确版本条件。
这是按实际查询条件选择的索引设计,不能仅凭“已经建了联合索引”认定速度达标。MySQL 联合索引的列顺序会影响可用前缀,特别是默认不传货主时,货主前置的索引不能等价替代“版本+日期”路径;索引原则见 [MySQL 8.0 联合索引说明](https://dev.mysql.com/doc/refman/8.0/en/multiple-column-indexes.html)。按实际 SQL 的执行计划与扫描量调整,不预先增加多套相近的宽索引;新增索引必须计入批量构建写入成本。
全仓列表需要对范围内所有候选求和和排序,翻到第 1 页不表示数据库只读 20 行。其扫描规模应受“本仓单一版本+所选日期+候选集合”限制,不随原始订单累计条数或旧统计版本数一起放大;单 SKU 查询则应受该 SKU 在所选范围的日行数限制。默认 MySQL 完成查询,不新增 Redis、搜索库或周/月汇总表。若代表性压测仍不达标,先定位实际扫描、临时表和排序瓶颈,再决定是否为固定版本结果增加有界缓存;缓存不能省略账号/仓库/货主范围、版本、日期和权限校验,过期及待重建状态仍实时判断。
### 后台构建和历史版本
一期继续采用固定事实修订号生成本仓完整日版本,保持前文发布屏障和原子切换;**全历史重算发生在后台,不在每次页面查询时发生**。每仓同一时刻只构建一个销量候选,按数据库聚合与有界批量写入处理,不把整仓全部订单加载进 Java 内存。
正常逻辑批次就绪后构建,不按每页、每条订单触发全历史重算。历史首采/补采按完整自然日推进:当天目录完整、订单校验及分仓逻辑归并完成并通过屏障后,立即进入候选构建和发布,不再等待固定 30 分钟间隔。完整列表订单每批最多 100 单在同一事务保存有效暂存、连续观测序号和任务检查点,避免逐单重写整天清单;缺项、较新修订和已存在的恢复引用仍逐单核验。中断只从已提交检查点续跑,不因批量处理放宽租约、暂停、完整性或发布屏障。修订后的必要重建和失败恢复按任务优先级及退避执行,不为凑够间隔跳过修订。事实修订号、覆盖、范围、时区与 profile 依据均未改变时,不重复生成相同销量版;即使销量为零,只要覆盖推进仍需发布新的覆盖结论。库存每天的真实观测版本不因数量未变而跳过。
无变化任务须在核验后以 `SUCCEEDED` 结束,将 `result_published_run_id` 指向复用的已发布版本,本次执行以 `REUSED` 终结且不成为统计版本头。合并构建承接的多个任务,只有新版本实际包含各自已归并事实及应声明的覆盖后才分别成功,均记录同一结果版本;负责发布的 run 为 `PUBLISHED`,其他复用尝试为 `REUSED`。尚未完整承接或仍处于 `APPLYING/BLOCKED` 的任务不能提前成功。此结果落账与版本关联必须可恢复,避免完成构建后任务永久未结束、重复恢复或永久保护旧输入。
完整日版本会随历史增长而扩大,因此区分**业务历史**和**旧计算副本**:查几个月前的销量,通常读取当前版本中该日期的数据,并不需要保留每次计算的旧副本。以下为可配置的设计默认,后续实现不得把“删除旧销量副本”扩展成删除真实单据或库存观测:
| 对象 | 一期保留与淘汰约定 |
| --- | --- |
| 当前销量版与上一发布版 | 始终保留其日表和销量 profile,至少两版用于查询和恢复;只有一个版时保留该版 |
| 更早的已发布销量版 | 被替代后至少保留 7 天;到期且无活动任务依赖时,才可分批淘汰该版 daily 与销量 profile。run 头、版本摘要、事实修订号及审计证据保留 |
| 活动任务输入 | 未完成任务实际使用的 `base_published_run_id`、库存候选的 `reference_sales_run_id` 等输入受保护。引用登记与清理资格检查须用同一 scope 锁/事务协调,不能刚开始构建就删掉输入 |
| 历史来源引用 | 已终结任务和已发布库存所记录的历史 run ID 仅为来源记录;库存已物化自己的 snapshot/profile,不因此永久保留每一个旧销量副本 |
| 原单、明细、修订证据和真实库存观测 | 不应用 7 天销量副本规则;库存 snapshot 与库存 profile、stage 前后载荷及执行头均不自动删除,保留策略后续按实际容量和审计要求制定 |
| 日库存代表证据 | `inventory_daily_sample_date` 非空的 run 及其 snapshot/profile/范围摘要整体保留;后续日均库存、库龄核验或成本分析引用须受保护,不因库存已被新批次替代而淘汰日代表,不用新采集替换历史日 |
| 失败/未发布候选 | 仍计入容量监控;须确认逻辑任务已终结、无恢复或引用需求,再制定清理规则,不能仅凭未发布或失败就自动删除 |
旧销量版对外可查询截止为 `superseded_at_utc + 7 天`,当前版无此截止;到期后即使因恢复保护仍存有派生行,也明确返回版本不可用。查询上下文最多有效 30 分钟,并取其自身期限与各版可查询截止的较早值。一次接口必须先建立短只读一致性事务(明确采用一致快照,例如 `REPEATABLE READ`),在同一快照中校验上下文/版本并读取所需表;不能事务外校验后再开事务读表,也不能只设置 `readOnly` 就假定所有 SQL 同快照。这样已受理的请求不因并发分批淘汰读到半份数据,新请求则按到期规则拒绝。
此处只定义设计和验收,尚未启用任何清理任务。正式启动长期同步前应完成保留策略、引用保护和容量验证,清理/SQL 执行仍按环境授权规则处理。磁盘测算包括原单/明细、每版日行、库存快照、重复复查产生的 stage、失败候选及所有索引,不能只按新增订单数估算。若全历史构建时间或写入量成为瓶颈,再评估复制未变日与仅重算受影响日的候选构建优化;仍须生成完整可查询版本,不能改成页面临时拼接可变增量。
### 性能验收目标
**以下是交给开发的初始目标,当前源码尚未收敛上述优化,本轮也未取得压测结果,不是已达到的性能承诺。** 用一个仓库 1 万个货主+SKU 候选、每个销量版最多 366 万条稀疏日行(366 日上界)、同时保留当前版和至少两个旧版的合成数据进行读路径测试;包含零销量、未知、热门 SKU 集中出库和两仓数据隔离。该规模是容量测试档位,不把实测 6,635 条库存记录直接认定为 SKU 数。
在记录 CPU、内存、磁盘、MySQL 版本/配置及同机其他服务负载后,以 10 个并发只读用户、无业务结果缓存、稳定运行至少 5 分钟测量:
| 请求 | 初始目标 |
| --- | --- |
| 单 SKU 日趋势,最多 366 点 | HTTP 响应 P95 ≤ 500 ms |
| 货主选项分页 | HTTP 响应 P95 ≤ 500 ms,不调用领星 |
| 运行概览、采集任务列表/详情、尝试分页 | HTTP 响应 P95 ≤ 500 ms;首次无业务数据也可读取,仅查运行元数据 |
| 五种常用范围的 SKU 清单、7/30 日榜单、货主概览、带关注筛选/排序的库存可售天数分页 | HTTP 响应 P95 ≤ 1 s |
| 366 日自定义范围 SKU 清单 | HTTP 响应 P95 ≤ 2 s |
| 所有请求 | 正常合法查询无超时/错误;同时报告 P50、P95、P99、SQL 次数、数据库耗时和响应大小 |
首次读取与预热后的结果分开报告;增加单用户基线、后台候选构建同时运行、深分页和多次刷新场景,不能只测反复命中一个热 SKU。首屏和末页必须排序一致;构建期间继续满足查询目标且只读完整旧版。后台另测同等级首采数据与增长档位的数据库构建耗时、扫描量、写入量和磁盘增量,上游详情采集耗时单独记录,不能用“请求领星较慢”解释本地报表慢。
对代表性只读 SQL 在获授权的性能环境保存 `EXPLAIN`,再执行 `EXPLAIN ANALYZE SELECT ...` 记录实际扫描与迭代耗时;后者会真正执行查询,不在共享环境随意压测。[MySQL 8.0 执行计划说明](https://dev.mysql.com/doc/refman/8.0/en/explain.html)。验收应证明:图表查询未访问 order/item/stage,外部 API 调用数为 0;固定版本和日期条件有效;没有逐 SKU N+1;增加旧版本和原始订单后,不退化成扫描全部版本或原单明细。不以执行计划中出现排序本身判失败,必须结合实际扫描量、耗时及临时表使用判断。
## 实施顺序与验收
| 顺序 | 工作与完成标准 |
| --- | --- |
| 0 已完成的联调依据 | 使用[接口联调手册](领星WMS接口联调手册.md)中的请求和证据;出库样本、详情、仓库发现及产品库存全分页均已只读验证 |
| 1 完整 Integration 适配 | 核对已有五类接口、DTO、合法空页归一化、OpenAPI、内部鉴权及调用方;产品库存请求强制指定仓库,出库目录账号共享后分仓;以代码/合同测试/真实 HTTP 各自证据验收 |
| 2 Lastleg 持久化与手工任务 | 按本方案核对已有本服务基线、实体、Mapper、租约和批次,补齐差距;验收可手工触发的单仓采集、候选版、原子发布和失败恢复。源码、实际部署和运行结果分别记录 |
| 3 首采和增量 | 正式生产首采固定最近 2 个月;按时间片抓出库目录和全部必要详情,每仓产品库存全量。测试可跑有界样本、受控人工完整库存/短窗口出库及离线合成流程;完整分页/覆盖证明后再发布和开放对应指标,补齐修订轮转 |
| 4 自动调度 | 每仓持久化 08:00/20:00 出库、每日库存计划及错过任务恢复;账号级配额统一协调,仓库成败独立 |
| 5 指标、图表契约和权限 | 仓库必选,实现五种已确认范围及自定义日期的 SKU 区间清单、逐日趋势与缺口状态;按日表读路径和索引设计查询,正式榜单和可售天数保留各自完整日口径。同屏固定本仓版本、时间基准和范围,校验独立时效与平台权限 |
| 5a 货主经营补充 | 新增本地货主选项/概览,扩展 SKU 同屏字段与关注筛选/排序,按完整候选统计件数占比及未知范围;补 run 日代表字段、唯一约束、发布选择与证据留存,采集页呈现日观测积累。真实库龄、正式周转、最终评级及成本分摊不在本包实现 |
| 6 Admin 菜单与页面 | 保持原三页当前 SKU 中心布局,新增经营总览第四个入口及 dashboard 聚合、精确下钻;按总览设计和逐操作矩阵验收图形。采集页保留任务号、检查点、受控重试和各类空态/失败,核对菜单/路由/服务端权限一致 |
| 7 验收交付 | 完成下表离线、故障及上述性能/版本保留验收,再在获授权环境完成真实采集、页面对账及覆盖核验;提供命令结果、批次、采集时段、响应时间和未决项,不能用本轮只读联调替代落库或性能验收;人工完成页面及截图确认 |
下一开发对话可从第 1 步开始,无需重新询问产品类型、凭据名称、全部货主范围、是否分仓、来源时区或数量单位;北京时间和 SKU 件数 `PCS` 已确认。每一步交付应列明新增代码入口、Schema/OpenAPI、通过的检查和真实验证边界。更正期限及实际分页/字段边界仍须核验,不能提前承诺正式日统计完整准确。
最低可执行验收矩阵如下。数值全部为合成测试数据;真实对账样本保存脱敏证据,不提交客户原始记录。
| 场景/输入 | 必须得到的结果 | 证据方式 |
| --- | --- | --- |
| 产品正品实物 120、冻结 20、可用 100、在途 30;次品 8,另有整箱库存 10 箱 | 正次品分别保存;可售量为 100,不再扣冻结,不加在途、次品或箱数 | DTO/SQL 集成断言;同 SKU 页面核对 |
| 同仓同货主 A 库存 3,000、30 日出库 300;B 库存 30、出库 30,均正品有效且观察完整,查询阈值 90 | 日均分别 10/1,可售 300/30 天;超阈值只命中 A。命中 SKU 实物 3,000、货主实物 3,030,占比 3,000÷3,030;不能按出库量小优先认定 B 更积压 | 合成 SQL/DTO 数值及排序测试 |
| 全仓热销榜前 100 无货主 B,但 B 有 3 个有销量 SKU;库存列表多页 | 筛 B 后热销候选仍有 3 个;B 概览与 B 的全量结果一致,不随页码改变,不从全仓前 100 或当前页二次筛选 | 过滤顺序、跨页与概览一致性测试 |
| 正品实物 120(冻结 20)、次品实物 8、可售 100、在途 30 | SKU/货主实物为 128、可售 100;冻结不叠加,次品不重复相加,在途不算占仓;品质连接不能复制销量。另测全冻结正品可售为零仍显示实物和冻结关注 | 品质聚合及受限库存测试 |
| 有实物库存且完整 30 日零出库;另一个正库存 SKU 观察不足,第三个库存未知 | 前者 NO_OUTBOUND_IN_WINDOW、天数为空,不冒充超过阈值;仅观察不足者按完整窗口实际销量评估,未知库存者保留待判断。库存或销量存在实际缺口时总量/占比为空,已确认小计仍展示 | 零分母、资格与未知传播测试 |
| 无阈值、阈值 0/负数、实际可售 90.04 天显示 90.0 天且阈值 90 | 未设置不计算超阈值指标;非法值拒绝;90.04 按未舍入值命中。没有任何实物库存时占比为空,不给优秀评级 | 入参、精度及无库存测试 |
| 同日计划库存先失败后成功,再有一次成功;另测并发发布和发布后恢复 | 选择同日第一次成功发布 run 为日代表,同日后续不覆盖;账号/仓/日唯一,失败及 VALIDATION 无日标记,恢复不重复标记 | 发布事务、幂等与唯一键测试 |
| 昨日计划跨午夜扫描或今日补跑;另测今日与昨日数量完全相同 | 不回填昨日或冒充今日正常日样本;数量相同仍保留真实今日观测。缺口留空不沿用旧值;日代表 run/snapshot/profile 不受销量版 7 天清理规则影响 | 时区边界、缺口和保留合同测试 |
| 两个月出库已齐,但库存仅积累 2 个日代表;最后出库未在已采集历史中出现 | 页面显示库存样本 2/30 及缺口,不输出正式期间周转率;最后出库为空并说明查询历史边界,不填 60 天或首次同步日充当库龄 | 采集页与经营页合同验收 |
| 同单同 SKU 两行实出 2、3,包裹重复列出 5,预报 6 | 出库销量为 5,订单数 1 | 聚合测试;重复执行结果不变 |
| 单 O1 为 A=2、B=1,单 O2 为 A=3,随后确认取消 O1 | 保存 2 个单头、3 条 SKU 明细;原日量 A=5、B=1,仓级订单总数为 2;取消后撤回 O1 两项贡献,新版 A=3,旧版不变 | 事实保存、修订和日聚合一致性测试 |
| 同单跨页、重试、重复批次返回 | 事实不重复,日量不重复,内容未变不增加修订号 | 唯一键及任务幂等测试 |
| 上一轮 VALID 详情后,下一轮同单已被取消/修改 | 新轮次发起真实新请求并读取变化;仅同一 task 内重试可复用已完成详情 | 请求次数及轮次/sequence 测试 |
| 旧日 A 仓 5 件修订成新日 B 仓 3 件 | A 仓旧贡献归零,B 仓新贡献 3,两仓修订和发布均可追溯;重试不再加 3 | 前后事实、两仓发布版本断言 |
| 已出库 5 件的详情明确改为取消 | 撤销旧贡献;仅分页缺行、详情不存在或权限失败不得当成取消 | 状态修订与失败样本测试 |
| 当前取消单仍返回实际数量 1,但出库时间为空 | 不计入销量;可按单号无时间/状态重新定位,不证明该单曾经已出库 | 本轮取消样本及离线状态用例 |
| 出库第一页请求 size=100,返回 records=[]、total=0、pages=0、current=1、size=10 | 按已验证完整空结果规则归一化成功;非空页或元数据缺失仍拒绝 | 本轮边界负例及 DTO 合同测试 |
| 10/12 08:00 首采目标从 8/12 08:00 起 | 实际完整采集发布后覆盖近 30 天日期窗口;8/12 首日仍不完整,10/12 今日仅累计,SKU 观察资格另验;未完成任务不能因目标范围足够就显示完整 | 时间边界/覆盖区间测试 |
| 基准日为 2026-10-12(周一),分别选择五种范围 | 今日 1 个点;近 3 天为 10/10~10/12;本周只有 10/12;近 15 天为 9/28~10/12;本月为 10/1~10/12 | 固定时钟的范围解析与 API 合同测试 |
| 基准日为 2027-01-01;另选 2028-02-28~2028-03-01 | 本周从 2026-12-28 开始,不从元旦重置;自定义闰年范围有 3 个日点,结束上界为 3/2 | 跨年、闰年和本月首日/月末边界测试 |
| CUSTOM 10/10~10/12;另传缺失日期、倒置起止、未来日期或预设与日期混用 | 正确范围返回 3 个日点、结束上界 10/13;非法组合明确拒绝,不猜测采用哪组参数 | 请求 DTO 与 OpenAPI 合同测试 |
| `maxQueryDays=366`,请求 366 天和 367 天的合法历史日期范围 | 前者通过长度校验,数值按覆盖规则决定;后者在查询/生成日轴前明确拒绝,不生成海量空点、不静默截断 | 日期范围上限合同测试 |
| 合成同 SKU 三天数量为完整日 2、完整零销量日 0、今日已采集 3 | 日点正式量为 2/0/null,已采集量为 2/0/3;区间正式量 null、已采集合计 5、状态 PARTIAL;列表和图表相同 | 日桶、汇总守恒、前端数据断言 |
| 所选日期完全未覆盖;另测中间缺一天 | 全无资料时合计为空、图表为空点;中间缺日保留空点和缺口,不补 0,不把其余已采集总和标完整 | 空集、缺日与图表功能验收 |
| 全天覆盖且实际出库 4 件的新 SKU;另测首次库存观察在当日 16:00、销量只到 08:00 的无出库 SKU | 前者当天可完整显示 4,无需等 30 天;后者正式量和已采集量均未知,不能补 0;在 SKU 存在之后取得覆盖才可出现部分日 0 | SKU 证据与覆盖交叉测试 |
| 同名 SKU 在货主 A 出库 2、货主 B 出库 5 | 列表保留两行;daily 必须指定货主,分别显示 2 和 5,缺货主拒绝,不能返回 7 | 候选集合与商品身份测试 |
| 两仓在同一时刻查询,仓库资料分别为纽约/洛杉矶,浏览器或服务器使用美国时区 | 两仓都按 `Asia/Shanghai` 解析预设、显示相同业务日期;仍保留各自版本与覆盖,不读取仓库所在地时区改变归日 | 两仓固定时钟、环境时区与配置测试 |
| 返回时间为 `2026-10-12 00:30:00`;另测北京时间 23:59:59 与次日 00:00:00 | 源字符串不改,业务日为 10/12,UTC 时间点为 10/11 16:30Z;午夜前后分属相邻业务日且不漏单/重复,不额外转美国时间 | 时间字段、日桶和切片边界断言 |
| 打开列表后发布新版本并跨午夜,再翻页、选 SKU 或切范围 | 沿用原 queryAsOf 和版本,不自动滚动“今日”;显式刷新后整体取得新日期、新版本;库存过期状态仍按实际查询时刻判断 | 同屏版本、日期冻结与动态时效测试 |
| 今日/近 15 天/CUSTOM 间切换,库存 300、完整 30 天销量 150 | 可售天数始终为 60.0,榜单仍按独立完整日窗口;图表切换不调用领星 | 指标口径与上游调用次数断言 |
| 两版 profile 含零销量 SKU;库存同时有正品和次品;清单候选超过一页 | 候选不遗漏,品质不造成销量倍增;先聚合完整候选、稳定排序再分页,页内排行与全候选结果一致 | SQL 结果、分页和候选合同测试 |
| 日表保留多个旧版本,后台正在构建;查询默认全货主近 31 天及精确单 SKU | 只读指定已发布版和所选日窗,不回查 order/item/stage、不调用领星、无逐 SKU SQL;满足对应时延目标 | 执行计划、调用计数和并发性能报告 |
| 旧销量版达到 7 天淘汰期,另有活动库存构建引用它 | 活动输入受保护,当前及上一版不删;已发布库存的历史来源引用不永久阻止后续淘汰;原单/明细/库存实际观测和审计证据不受影响 | 保留策略、引用与范围测试;仅在获授权环境执行清理 |
| 查询开始后派生旧版清理启动;另测超过 30 分钟的上下文 | 受理请求在同一一致快照内完成,无半表;过期上下文明确要求刷新,不返回空列表或静默换版;发布重试不重置旧版替代时间 | 一致性事务、并发清理与过期合同测试 |
| 销量版可用、库存无已发布版或已经过期;随后库存新发布 | 原上下文仍能显示原销量图;库存明确不可用/过期,新版须刷新后纳入,不能使翻页 SKU 候选悄然变化 | 版本缺失、候选冻结与查询降级状态测试 |
| 很早创建、最近一个月出库;美国仓库处于不同季节时段 | 不因创建早而漏单;始终直接按已返回的北京时间归日,不因美国冬夏时间再次加减时差 | 列表筛选、固定北京时间用例与页面对账 |
| 库存 A 仓成功,B 仓第 2 页失败 | A 可发布,B 保持旧版本并显示失败/过期;B 不被 A 的成功时间刷新 | 受控 HTTP 故障注入、两仓指针断言 |
| B 仓返回行数和各页 total 均为 5,741,但唯一键只有 5,740 | 该尝试不得发布;新尝试从第一页重读,成功后才发布,不拼页、不靠去重掩盖缺行 | 本轮真实分页反例、整批重试合同测试 |
| 账号出库目录一页失败;或某仓最后一条详情失败 | 目录失败时相关仓不推进覆盖;仅某仓详情失败时该仓不得宣称完整,其他完整仓按约定独立处理 | 批次覆盖及任务状态断言 |
| 出库任务归并一半后失败,另一重算任务随后执行 | 未完成归并屏障阻止相关仓任何销量候选发布;续完原任务后才解除,任务转 DEAD 不能解除屏障;库存只引用已发布销量维度,仍可独立发布 | 分段事务、跨任务恢复及跨仓屏障断言 |
| 旧采集任务让出预算,新任务确认订单后,旧任务恢复并读到不同内容 | 不按旧任务序号静默丢弃新观测;建立新复查代次重新确认,旧暂存不得倒灌 | 可控请求顺序、修订及恢复测试 |
| 正常数值 0、空字符串、已知 SKU 缺行 | 分别为接口报告零、UNKNOWN、UNKNOWN;不统一填零 | 解析测试、快照质量标记 |
| 四类数值均有效,但不满足尚未核准的组成等式 | 保留四个来源值,不因自拟恒等式标 INVALID;可保留待核验诊断 | 字段映射与质量规则测试 |
| 某 SKU 负数,但全页结构和身份均有效 | 保留异常值及 INVALID,停止该 SKU 和受影响汇总的可信指标;不截成零 | 快照与指标测试 |
| 事实已提交、统计尚未发布时进程退出 | 重启后根据持久化任务与事实重建完整候选,旧报表始终可查,无半版数据 | 服务故障恢复测试 |
| 租约到期后旧工作线程试图提交/发布 | 栅栏校验拒绝旧线程;新任务结果不被覆盖 | 并发事务测试 |
| 第一页查旧版本,期间新版本发布,再翻第二页 | 同一仓的第二页仍使用首屏版本对;版本不可用时明确过期,不静默切换 | API 游标/版本测试 |
| 未发布采集新增更早 SKU 证据,或后续版本将历史出库改期/迁仓 | 旧版本候选不变;新销量版撤回失效出库证据,不从库存版复活;只保留仍有效的本仓依据 | SKU 版本快照、候选及证据撤回测试 |
| A 仓时效 26 小时、B 仓配置 12 小时,均距上次采集 13 小时 | A 新鲜、B 过期;预警接口将来沿同一仓作用域,不互相抵扣 | 配置与时间可控测试 |
| 可用 300、完整 30 天销量 150;另测零销量/零库存 | 日均 5、可售 60.0 天;零销量说明无出库,零库存标记无可售量 | 数值断言与手算对照 |
| 有效零库存但销量覆盖不足;另测过期库存数值为零 | 前者天数 0 并显示覆盖不足;后者天数为空并标过期,不能把过期零值当有效零库存 | 指标优先级交叉用例 |
| 新发现库存 SKU 无历史依据,或零销量日覆盖缺失 | 不进入正式滞销榜,不伪造完整 30 天 | 观察期与覆盖测试 |
| 客户账号、无权限平台账号或未配置仓库访问 | 拒绝;列表/详情同一范围,写入权限独立 | 内部 Token、平台权限、范围测试 |
| BUSINESS 当日/根请求数超过此前额度,或每轮请求批次用完 | 前者持续采集且计数保留;后者保存检查点并 YIELDED 自动续跑;独占许可/根期限到期、真实 429、认证失败及未知结果保护仍能阻止外发,不影响 VALIDATION 样本硬上限 | 计数、批次恢复与用途隔离测试 |
| 测试累计达到 10 次分页/20 次详情/40 次实际请求额度或 5 分钟任一上限 | 整个原验证会话停止;双扫描、子任务、失败请求和重试共用预算,进程重启不能续跑到全量 | 离线计数/重启测试,真实测试仅小样本 |
| 测试只读完部分库存,或目录超过详情预算;另测空页完整 | 验证只暂存校验,不归并事实、不推进目录/仓库覆盖、不发布、不执行缺行占位;空页也不转正式版本 | 用途门禁及两条发布拒绝断言 |
| 任一接口返回 429,或两个采集族同时等待请求 | 账号级停止新请求,持久冷却,测试会话不自动恢复;不让库存、详情或适配器重试绕过,请求消耗及冷却重启不清零,VALIDATION 日额度仍有效 | 离线模拟限流,不向领星做压力测试 |
| 验证遇 429 后将账号交还生产;或验证根失败而子任务仍待执行 | 交接保留已用额度及未到期冷却,不立即恢复请求;失败 run 与根 DEAD 保留,子任务不再出网,不能通过普通重试复活会话 | 状态交接记录、根任务门禁测试 |
| 生产开始正式采集而测试仍持有旧配置 | 启动前确认所有非生产真实出口已关闭且无在途请求;不同数据库租约不能作为环境互斥通过依据 | 配置与运行进程核对,独占窗口交接记录 |
| 首次采集尚无销量/库存版本,或执行配置尚未启用 | 采集页可读取有效配置、首采范围与任务;图表显示未采集,停用显示原因,不返回整页错误或全零曲线 | 运行专用接口、页面联调 |
| 目录已结束/详情 100%,但仍校验、计算或等待发布 | 分阶段展示实际计数,不能显示本次同步成功;正常零单也须校验发布,未知总量不生成百分比 | 检查点合同、阶段页面核对 |
| 账号目录仍在扫描、分仓子任务尚未生成 | 概览通过关联共享任务显示真实阶段与错误;共享总数不冒充本仓总量,不显示无任务即成功 | 协调器元数据查询与页面联调 |
| 首采一个切片已发布,其余仍等待;或两仓只有一仓成功 | 显示本切片成功及首采目标窗口未全部完成;仓库各自显示执行结果、覆盖和可用结果,库存可独立完成 | 覆盖断言、分仓运行概览 |
| 上次成功后新尝试失败,或发布成功但部分库存数量未知 | 前者显示失败及旧数据的真实时效;后者显示发布成功和质量问题,成功时间、发布时间、观测时间不混用 | 状态交叉用例与页面对账 |
| 中途重启/预算让出后续跑,或重试重复提交 | 检查点可恢复且不重复计数,尝试历史保留;任务号稳定;同幂等键仅受理一次,受理不等于采集成功 | 故障恢复、重试权限与幂等测试 |
| 图表打开期间轮询发现新发布、轮询断网、切仓旧请求迟到 | 新发布仅提示整页刷新;断网保留状态并标观测时间;旧仓响应不覆盖新仓,后台隐藏时停止轮询 | 前端状态测试与人工操作 |
| 只读人员进入采集详情,或无同步权限直接 POST 重试 | 列表权限可读详情,写操作服务端拒绝;存在权限但不满足状态资格也给出阻断原因 | 路由/按钮/Controller/IAM 一致性核对 |
真实验收必须选择领星「一件代发出库单」和「产品库存」页面,使用相同仓库与货主筛选,在相近时刻核对至少正品有库存、零库存、次品、冻结和在途样本;不得用整箱页面截图作为产品库存通过证据。每仓至少保存一次完整分页计数、唯一业务键数、批次起止和页面对账结果;源码测试、HTTP 对账和页面证据分别报告。
开发阶段运行受影响服务的 Maven 测试;新增 Mapper 执行 `node scripts/mapper-scan-contract.mjs`,新增/修改接口执行 `node --test scripts/gateway-route-contract.test.mjs`,同步静态 OpenAPI、直接消费者和权限。新增 Lastleg 子域再执行该服务指引规定的模块依赖检查。Schema 落入本服务最新初始化基线,不设计测试存量兼容。本文只设计表结构;执行 SQL、部署和共享环境操作仍按仓库授权规则处理。
## 开发准入与正式启用门槛
**2026-10-12 评审结论:有条件通过,可继续后端开发并收敛已有源码;正式统计启用仍须逐项验收。** 服务归属、WMS 自研系统接口、全部自营范围、按仓库存采集、十表结构和后端交付边界已可作为开发输入。SKU 图表的五种自然周期范围已获用户确认;列表、日趋势、区间汇总、部分日/未知值、同屏版本及本次查询性能合同一并纳入开发输入。已有实现仍有整版读入 Java、图表 DTO/端点未收敛等差距,见源码核对表。本次评审还补齐半批事实的发布屏障、SKU 观察依据的版本化、旧任务恢复后的修订复核、库存校验依据及零库存判定优先级;实现与测试必须包含这些规则,不能只完成正常采集路径。
北京时间来源与业务日已由用户确认,当前两仓 `sourceTimezone=businessTimezone=Asia/Shanghai`,08:00/20:00 调度亦采用北京时间。每日 08:00 库存、26 小时库存过期、30 天主榜/7 天辅助、48 小时增量重叠和 7 天复查目标仍是**可配置的设计默认值**,不是供应商保证或新增用户确认。开发可按默认值实现;正式开启任务时逐仓记录配置。未来变更统计时区仍须更新范围摘要并重新核验覆盖,不能因当前配置相同取消分仓边界。
| 阶段/门槛 | 当前准入与完成依据 |
| --- | --- |
| 后端编码 | 在现有 Integration 五类适配和 Lastleg 源码上继续收敛:补齐五种图表接口合同、查询下推、后台有界构建、索引及版本保留规则;补运行专用接口、检查点和重试合同,分仓任务、持久化、发布与指标逐项验收。Admin 页面按本文交接,业务告警与补货留待后续 |
| 正式销量:时间与数量 | 来源及统计时区已由用户确认采用北京时间,无需再等待时区选择。已验证多 SKU 详情及一个样本的起止秒包含关系;实现仍需验证北京时间午夜归日、UTC 内部存储和每仓同单页面对账,以及同 SKU 多行、实出不等于预报、部分出库及单位。用户确认与代码/页面验收分别记录 |
| 正式库存:数量与范围 | 只读按仓全页及重试已通过,Java 落库和页面对账未完成。A 仓两种页大小均一致;B 仓出现过重复/缺键,完整重读后恢复。须实现唯一键数/总数校验、整批重试和旧版保留,再核对正品有库存/零库存、次品、冻结、在途与空值 |
| 分页完整性 | 固定源时间窗口 372 单,size=124/125 各 3 页集合及摘要一致;尚无同筛选页面导出核对。库存重复反例已经出现。重复扫描只提供观测证据,`COMPLETE` 表示本次声明范围按约定扫描及验证完成,不代表原子快照或永久稳定排序 |
| 修订的可观察性 | 已验证当前取消单详情99、正常及取消单可无时间/状态按单号定位。尚未验证“曾出库再取消”的前后事实、实际改仓时旧三标识的行为、历史保留和回溯更正范围。失败/查无不得撤销原销量,未闭合修订范围必须显示待核对状态 |
| 定时任务与吞吐 | 正式生产首采两个月,实际规模需在受控采集中记录;旧一个月样本约 6.1 万单不代表新规模。启用前确认账号配额及唯一真实出口,计入全部接口、复核和重试;落实账号冷却、请求消耗记录、1.2 秒间隔、切片恢复和后台续跑;BUSINESS 不设每日/根累计请求次数上限。已知单持续增长时重新计算轮转周期,不长期盲用 7 天承诺 |
| 测试与生产分工 | 测试默认 OFF;VALIDATION 达到页/详情/真实请求/时长上限即停止且不可发布。TEST BUSINESS 仅执行受控人工完整库存/短出库窗口及其派生任务,保留有限独占期限、根截止时间、累计请求计数、1.2 秒间隔与完整发布校验,取消每日/根累计请求次数额度,不自动计划或首采两个月;截断或失败不得发布,已验证样本不得转正式。生产最近两个月首采仍另按正式发布流程启用,部署与采集执行分别留证 |
| 图表能力 | 五种自然周期和自定义日期、每日点数、区间与日图守恒、零值/未知区分、SKU 身份、跨午夜上下文、独立榜单及可售天数口径通过合同验收;页面完成后再验证筛选联动、缺口提示和单点显示。后端接口完成不能替代页面验收 |
| 货主经营评估 | 货主选项/概览及同屏 SKU 指标、积压查询阈值、先筛选后分页、全品质实物与正品可售区别、风险未知传播、每日代表观测选择/留存及缺口展示通过离线验收;用途仅为内部评估,不输出未经确认的优劣评级或加价结果 |
| 菜单与采集页面 | “自营业务”四个入口按各自设计接入,总览新增图形/聚合/下钻完成离线验收;采集页首采覆盖、用途、阶段计数、发布与质量、分仓失败、重试及权限分别验收。未取得人工截图时不得宣称页面视觉通过 |
| 查询速度与容量 | 完成代表性数据、无业务缓存、10 并发及后台构建期间的性能验收,记录执行计划、P95/P99、扫描量和磁盘增长;30 分钟上下文与旧销量版保留/引用保护通过测试。本文目标未实测,不能按建索引或只读 API 联调成功直接判定通过 |
| 业务发布与交付 | 离线合同、分段归并屏障、版本冻结、幂等、故障恢复、客户身份拒绝访问及真实落库对账全部通过;交付批次、时段、每仓版本和未决项。联调 HTTP 成功、源码测试通过、部署和业务验收分别记录 |
上述来源验证可以与编码并行推进,但真实账号始终遵守环境独占和整轮验证预算;没有通过的正式启用门槛保持关闭或明确显示待核验,不把缺口折算成零销量、零库存或完整榜单。两个月目标实际完整采集后可以覆盖近 30 天日期窗口,纯库存零出库 SKU 的观察期仅作库存明细与总览提示,滞销榜仍须积累独立观察期;测试样本和仅配置目标范围均不能代替正式覆盖。
并行开发时,建议本对话继续维护这份主方案与联调手册,另一个后端开发对话负责 Integration、Lastleg、SQL 基线文件、OpenAPI 和离线验收,避免同时改写主文档。可以先实现完整后端链路与合成数据测试;真实销量覆盖和正式榜单发布保留启用门槛。当前两个时区字段直接配置已确认的 `Asia/Shanghai`,不得使用仓库所在地时区二次转换;自动调用速率不得把本轮低频成功当作配额确认。未授权时不执行 SQL、不部署、不启用真实定时任务;这不影响先完成代码。
后续库存预警按仓使用本期的有效可售库存、日均销量和数据新鲜度,再增加该仓阈值、去重和通知记录。补货还需要该仓采购/运输交期、安全库存、目标覆盖天数、在途可信度和最小采购量;一期不预置未经确认的公式或自动采购动作。
## 相关工程契约
- [后端项目指引](../../baiyue-firstleg/AGENTS.md)
- [统一认证与服务通信契约](../统一认证与服务通信契约.md)
- [测试环境单机部署与配置契约](../测试环境单机部署与配置契约.md)
- [平台客户层级与权限契约](../平台客户层级与权限契约.md)
- [AI 工程执行与协作约束](../AI工程执行与协作约束.md)