feat(selfop): add warehouse dashboard and exact inventory drilldowns

This commit is contained in:
2026-10-13 00:45:39 +08:00
parent a84a9b730e
commit a41c271a67
27 changed files with 827 additions and 48 deletions
@@ -21,6 +21,39 @@ info:
重试沿既有POST sync-tasks,action=RETRY/requestKey/warehouseCode/taskNo/reason,使用独立sync权限并最终复核资格。
security: [{bearerAuth: []}]
paths:
/api/platform/v1/last-leg/self-operated-analytics/dashboard:
get:
summary: 单仓固定版本经营总览
description: >-
平台独立只读权限platform:self-operated-analytics:list。一次一致快照固定销量和库存版本。
默认LAST_15_DAYS仅改变趋势及区间出库,日均、互斥分桶和三个Top10固定至昨日30完整日。
不接受货主、SKU、分页、windowDays、topN或其他额外筛选。仓级趋势不套单SKU观察期。
库存严格下钻须携带原版本和drilldownFrom=DASHBOARD;截止时间由服务端根据库存真实观测重算。
parameters:
- {$ref: '#/components/parameters/sourceAccount'}
- {$ref: '#/components/parameters/warehouseCode'}
- {$ref: '#/components/parameters/rangePreset'}
- {$ref: '#/components/parameters/startDate'}
- {$ref: '#/components/parameters/endDate'}
- {$ref: '#/components/parameters/bucket'}
- {$ref: '#/components/parameters/salesRunId'}
- {$ref: '#/components/parameters/inventoryRunId'}
- {$ref: '#/components/parameters/salesScopeHash'}
- {$ref: '#/components/parameters/inventoryScopeHash'}
- {$ref: '#/components/parameters/queryAsOf'}
responses:
'200':
description: 统一ApiResponse;版本或参数错误为业务失败,不降级成空图。
content:
application/json:
schema:
type: object
properties:
code: {type: string}
message: {type: string}
data: {$ref: '#/components/schemas/Dashboard'}
'401': {description: 缺少可信认证身份}
'403': {description: 非平台身份或缺少独立只读权限}
/api/platform/v1/last-leg/self-operated-analytics/sku-sales-summary:
get:
summary: SKU五周期或历史累计清单
@@ -149,6 +182,10 @@ paths:
summary: 正品库存与固定30日可售天数
description: '固定30天,不接受图表周期。可售仅品质0可用量,不扣冻结或加在途次品。先UNKNOWN/INVALID/STALE,再有效零库存为0,再校验正库存单位、窗口、观察期;零销量天数为空。 权限platform:self-operated-analytics:list。'
parameters:
- {$ref: '#/components/parameters/coverageBucket'}
- {$ref: '#/components/parameters/bucketRuleVersion'}
- {$ref: '#/components/parameters/coverageSelection'}
- {$ref: '#/components/parameters/drilldownFrom'}
- {$ref: '#/components/parameters/attention'}
- {$ref: '#/components/parameters/sortBy'}
- {$ref: '#/components/parameters/coverageDaysThreshold'}
@@ -338,6 +375,14 @@ components:
securitySchemes:
bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT}
parameters:
coverageBucket:
name: coverageBucket
in: query
description: 与非ALL attention和coverageSelection互斥,须配套固定规则版本;SQL分页前采用与总览相同互斥谓词。
schema: {type: string, enum: [DAYS_0_30, DAYS_30_60, DAYS_60_90, DAYS_GT_90, NO_OUTBOUND_30D, NO_AVAILABLE, UNKNOWN]}
bucketRuleVersion: {name: bucketRuleVersion, in: query, description: 不带coverageBucket时禁止单独传入, schema: {type: string, enum: [COVERAGE_DISTRIBUTION_V1]}}
coverageSelection: {name: coverageSelection, in: query, description: 正式有限正可售天数候选,在SQL分页前过滤,与非ALL attention及coverageBucket互斥, schema: {type: string, enum: [FINITE_POSITIVE]}}
drilldownFrom: {name: drilldownFrom, in: query, description: 必须固定原版本,服务端在真实库存有效期与查询到期较早者阻断并要求刷新总览;不增加权限, schema: {type: string, enum: [DASHBOARD]}}
keyword: {name: keyword, in: query, schema: {type: string, minLength: 1, maxLength: 64}, description: 区分大小写的货主编码字面前缀,先过滤去重再分页}
attention: {name: attention, in: query, schema: {type: string, default: ALL, enum: [ALL, NO_OUTBOUND_30D, COVERAGE_GT_THRESHOLD, FROZEN_STOCK, DEFECTIVE_STOCK, UNASSESSABLE]}}
sortBy: {name: sortBy, in: query, schema: {type: string, default: AVAILABLE_DESC, enum: [AVAILABLE_DESC, COVERAGE_DAYS_DESC, PHYSICAL_DESC, OUTBOUND_30D_ASC]}, description: 完整候选先筛选后按未舍入值排序分页,未知末尾,同值SKU和货主升序}
@@ -370,6 +415,99 @@ components:
customerCodeRequired: {name: customerCode, in: query, required: true, schema: {type: string, minLength: 1, maxLength: 64}}
skuRequired: {name: sku, in: query, required: true, schema: {type: string, minLength: 1, maxLength: 128}}
schemas:
DashboardContext:
allOf:
- {$ref: '#/components/schemas/Context'}
- type: object
properties:
fixedWindowStartDate: {type: string, format: date}
fixedWindowEndDateExclusive: {type: string, format: date}
fixedWindowDays: {type: integer, enum: [30]}
inventoryFreshUntil: {type: string, format: date-time, nullable: true}
inventoryDrilldownExpiresAt: {type: string, format: date-time, nullable: true}
DashboardMetric:
type: object
properties:
value: {type: number, nullable: true}
knownSubtotal: {type: number, nullable: true}
unit: {type: string, enum: [PCS, PCS_PER_DAY, SKU]}
dataStatus: {type: string, enum: [COMPLETE, PARTIAL, UNKNOWN]}
unknownSkuCount: {type: string, nullable: true}
reasonCodes: {type: array, items: {type: string}}
outboundQuantity: {type: number, nullable: true}
observedQuantity: {type: number, nullable: true}
DashboardKpis:
type: object
properties:
intervalOutboundQuantity: {$ref: '#/components/schemas/DashboardMetric'}
averageDailyOutboundQuantity30d: {$ref: '#/components/schemas/DashboardMetric'}
physicalQuantity: {$ref: '#/components/schemas/DashboardMetric'}
availableQuantity: {$ref: '#/components/schemas/DashboardMetric'}
physicalSkuCount: {$ref: '#/components/schemas/DashboardMetric'}
noOutboundSkuCount30d: {$ref: '#/components/schemas/DashboardMetric'}
CoverageDistribution:
type: object
description: 数值桶按未舍入交叉乘积判断;七桶互斥守恒。库存版未发布时计数为空,不把销量候选伪造成库存总量。
properties:
bucketRuleVersion: {type: string, enum: [COVERAGE_DISTRIBUTION_V1]}
candidateSkuCount: {type: string, nullable: true}
excludedZeroPhysicalSkuCount: {type: string, nullable: true}
dataStatus: {type: string, enum: [COMPLETE, PARTIAL, UNKNOWN]}
reasonCodes: {type: array, items: {type: string}}
unknownReasons: {type: object, additionalProperties: {type: string}}
buckets:
type: array
minItems: 7
maxItems: 7
items:
type: object
properties:
bucketCode: {type: string, enum: [DAYS_0_30, DAYS_30_60, DAYS_60_90, DAYS_GT_90, NO_OUTBOUND_30D, NO_AVAILABLE, UNKNOWN]}
skuCount: {type: string, nullable: true}
knownPhysicalQuantity: {type: number, nullable: true}
unknownPhysicalSkuCount: {type: string, nullable: true}
DashboardRankingItem:
type: object
properties:
customerCode: {type: string}
sku: {type: string}
productName: {type: string, nullable: true}
value: {type: number}
unit: {type: string, enum: [PCS, DAY]}
outboundQuantity30d: {type: number, nullable: true}
skuOutboundOrderCount: {type: string, nullable: true}
physicalQuantity: {type: number, nullable: true}
availableQuantity: {type: number, nullable: true}
frozenQuantity: {type: number, nullable: true}
defectivePhysicalQuantity: {type: number, nullable: true}
averageDailyOutboundQuantity: {type: number, nullable: true}
inventoryCoverageDays: {type: number, nullable: true}
DashboardRanking:
type: object
description: eligibleCount来自完整候选;items最多10项,不用Top100数量冒充全量合格数。
properties:
windowStartDate: {type: string, format: date}
windowEndDateExclusive: {type: string, format: date}
calculationWindowDays: {type: integer, enum: [30]}
eligibleCount: {type: string, nullable: true}
unassessableCount: {type: string, nullable: true}
items: {type: array, maxItems: 10, items: {$ref: '#/components/schemas/DashboardRankingItem'}}
dataStatus: {type: string, enum: [COMPLETE, PARTIAL, UNKNOWN]}
reasonCodes: {type: array, items: {type: string}}
Dashboard:
type: object
properties:
context: {$ref: '#/components/schemas/DashboardContext'}
kpis: {$ref: '#/components/schemas/DashboardKpis'}
salesTrend:
type: object
properties:
summary: {$ref: '#/components/schemas/SalesSummary'}
points: {type: array, maxItems: 366, items: {$ref: '#/components/schemas/Point'}}
coverageDistribution: {$ref: '#/components/schemas/CoverageDistribution'}
hotSkus: {$ref: '#/components/schemas/DashboardRanking'}
longCoverageSkus: {$ref: '#/components/schemas/DashboardRanking'}
noOutboundSkus: {$ref: '#/components/schemas/DashboardRanking'}
OwnerOption:
type: object
properties: