Compare commits
No commits in common. "main" and "codex/local-3001-integration" have entirely different histories.
main
...
codex/loca
@ -5,10 +5,6 @@ NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB_NAME?schema=public
|
||||
|
||||
# 员工工时花名只读人员配置来源(可选;开发环境配置后会合并正式人员配置)
|
||||
SUPPLY_CHAIN_PRODUCT_ARCHIVE_READ_DATABASE_URL=postgresql://READ_USER:READ_PASSWORD@READ_HOST:5432/READ_DB
|
||||
SUPPLY_CHAIN_PRODUCT_ARCHIVE_READ_DATABASE_SSL_REJECT_UNAUTHORIZED=true
|
||||
|
||||
# 产品全量档案的聚水潭 SQL Server 只读来源
|
||||
JST_REPORT_DB_HOST=
|
||||
JST_REPORT_DB_PORT=1433
|
||||
@ -18,39 +14,6 @@ JST_REPORT_DB_USER=
|
||||
JST_REPORT_DB_PASSWORD=
|
||||
JST_REPORT_DB_ENCRYPT=true
|
||||
JST_REPORT_DB_TRUST_SERVER_CERTIFICATE=true
|
||||
SUPPLY_CHAIN_LIMIT_SNAPSHOT_DIR=
|
||||
# 换货规则表 app.jst_sku_replace_rule 与 rpt 视图使用独立授权;留空时复用上面的主机、库和账号。
|
||||
JST_SKU_REPLACE_RULE_SCHEMA=app
|
||||
JST_SKU_REPLACE_RULE_DB_USER=
|
||||
JST_SKU_REPLACE_RULE_DB_PASSWORD=
|
||||
# 停产下架链接日快照最大导入延迟(小时);超时保留已有链接。
|
||||
JST_DISCONTINUATION_LINK_MAX_AGE_HOURS=26
|
||||
# 数据库链接快照仅用于诊断;低于已有链接该比例时拒绝覆盖。
|
||||
JST_DM_MIN_EXISTING_COVERAGE_RATIO=0.5
|
||||
|
||||
# 停产下架服务器 Chrome / RPA。
|
||||
JST_RPA_BROWSER_URL=http://127.0.0.1:9222
|
||||
JST_RPA_PROFILE_DIR=
|
||||
JST_RPA_CHROME_EXECUTABLE=
|
||||
JST_RPA_HEADLESS=true
|
||||
JST_RPA_DEBUG_PORT=9222
|
||||
JST_RPA_PYTHON=/usr/bin/python3
|
||||
# 聚水潭账号密码登录,仅配置在服务器私有环境文件,禁止提交真实值。
|
||||
JST_RPA_LOGIN_ID=
|
||||
JST_RPA_LOGIN_PASSWORD=
|
||||
JST_RPA_ATTEMPTS=3
|
||||
JST_RPA_FALLBACK_BATCH_SIZE=10000
|
||||
# 限价流程历史快照首次构建的追溯起点;后续只按更新时间增量同步。
|
||||
LIMIT_PRICE_HISTORY_SOURCE_START=2026-01-01T00:00:00
|
||||
# 物流规则和省份占比快照目录;正式站建议指向 shared/freight-config,本地留空。
|
||||
FREIGHT_CONFIG_DATA_DIR=
|
||||
# 产品限价页面已确认记录来源库(限价历史抽屉展示来源记录时使用)。
|
||||
PROD_ARCHIVE_DB_HOST=
|
||||
PROD_ARCHIVE_DB_PORT=5432
|
||||
PROD_ARCHIVE_DB_NAME=
|
||||
PROD_ARCHIVE_DB_USER=
|
||||
PROD_ARCHIVE_DB_PASSWORD=
|
||||
PROD_ARCHIVE_DB_SSL=true
|
||||
|
||||
# OA 新品流程看板数据源。默认 mysql;切换到 JSReportReplica 时设为 mssql。
|
||||
OA_BOARD_SOURCE_DRIVER=mysql
|
||||
@ -111,15 +74,11 @@ JST_PLANNING_SQLSERVER_TRUST_SERVER_CERTIFICATE=true
|
||||
JWT_SECRET=replace-with-random-secret
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# 钉钉登录(OAuth 应用;只用于登录授权和用户信息获取)
|
||||
DINGTALK_LOGIN_CLIENT_ID=
|
||||
DINGTALK_LOGIN_CLIENT_SECRET=
|
||||
# 钉钉登录
|
||||
DINGTALK_CLIENT_ID=
|
||||
DINGTALK_CLIENT_SECRET=
|
||||
DINGTALK_CALLBACK_ORIGIN=
|
||||
|
||||
# 钉钉开放平台 API(通讯录、待办、审批等服务端调用)
|
||||
DINGTALK_API_CLIENT_ID=
|
||||
DINGTALK_API_CLIENT_SECRET=
|
||||
|
||||
# 钉钉待办:OA 阶段超时预警,以及预计出货日前 15 天的品牌策划主管提醒。
|
||||
DINGTALK_TODO_ENABLED=false
|
||||
DINGTALK_TODO_DETAIL_BASE_URL=
|
||||
@ -128,9 +87,6 @@ DINGTALK_TODO_ESCALATION_ENABLED=false
|
||||
DINGTALK_TODO_ESCALATION_DAYS=3
|
||||
# DINGTALK_AGENT_ID=
|
||||
|
||||
# 清货池多包标记钉钉通知:填写在职员工目录中的姓名,默认推送卷帘大将。
|
||||
SUPPLY_CHAIN_CLEARANCE_COMBO_NOTIFICATION_RECIPIENT=卷帘大将
|
||||
|
||||
# 由壹点通知中心(仅服务端使用,项目密钥禁止提交或暴露给浏览器)
|
||||
YYD_NOTIFICATION_CENTER_URL=https://sc.jiansun.vip/notification-center
|
||||
YYD_NOTIFICATION_PROJECT_CODE=furniture_platform
|
||||
@ -212,9 +168,6 @@ ALIBABA_1688_IMAGE_SEARCH_TAGS=4306497
|
||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_ENABLED=false
|
||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_BASE_URL=https://example.com
|
||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_KEY=replace-with-material-library-api-key
|
||||
# 检测报告查询(不填则复用上面素材平台的 BaseUrl / API Key;都没有时「是否有检测报告」显示为空)
|
||||
MATERIAL_LIBRARY_CERTIFICATE_REPORT_API_BASE_URL=
|
||||
MATERIAL_LIBRARY_CERTIFICATE_REPORT_API_KEY=
|
||||
|
||||
# 产品全量档案以图搜图(模型文件放在仓库外,不提交到 Git)
|
||||
IMAGE_SEARCH_MODEL_PATH=
|
||||
|
||||
11
furniture-3d-system/.gitignore
vendored
11
furniture-3d-system/.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/node_modules.pnpm-mixed
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
@ -16,7 +15,6 @@
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/.next-*/
|
||||
/.next-oa-preview*/
|
||||
/.next-oa-webpack/
|
||||
/out/
|
||||
@ -76,12 +74,3 @@ local-packages/npm-cache/cache/_logs/
|
||||
|
||||
# 脚本监控 Agent 本机凭据
|
||||
agents/script-monitor/config.json
|
||||
|
||||
# 下载中心本地运行时产物
|
||||
.download-center/
|
||||
|
||||
# 限价每日快照(本机/生产运行产物,不入库)
|
||||
/data/limit-price-snapshots/
|
||||
|
||||
# 产品档案对账诊断报告
|
||||
/data/archive-reconcile-diagnostics/
|
||||
|
||||
@ -1,257 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"generatedAt": "2026-10-26T08:52:26.453Z",
|
||||
"sourceFile": "各省发货占比.xlsx",
|
||||
"totalShipment": 10983200,
|
||||
"rowCount": 31,
|
||||
"rows": [
|
||||
{
|
||||
"province": "安徽",
|
||||
"shipmentCount": 426181,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.038803,
|
||||
"region": "一区",
|
||||
"regionShare": 0.214
|
||||
},
|
||||
{
|
||||
"province": "江苏",
|
||||
"shipmentCount": 800646,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.072897,
|
||||
"region": "一区",
|
||||
"regionShare": 0.214
|
||||
},
|
||||
{
|
||||
"province": "上海",
|
||||
"shipmentCount": 325564,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.029642,
|
||||
"region": "一区",
|
||||
"regionShare": 0.214
|
||||
},
|
||||
{
|
||||
"province": "浙江",
|
||||
"shipmentCount": 797004,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.072566,
|
||||
"region": "一区",
|
||||
"regionShare": 0.214
|
||||
},
|
||||
{
|
||||
"province": "北京",
|
||||
"shipmentCount": 290861,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.026482,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "福建",
|
||||
"shipmentCount": 467880,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.0426,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "广东",
|
||||
"shipmentCount": 1820992,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.165798,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "河北",
|
||||
"shipmentCount": 424406,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.038641,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "河南",
|
||||
"shipmentCount": 546529,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.04976,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "湖北",
|
||||
"shipmentCount": 378469,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.034459,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "湖南",
|
||||
"shipmentCount": 386336,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.035175,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "江西",
|
||||
"shipmentCount": 271676,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.024736,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "山东",
|
||||
"shipmentCount": 626419,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.057034,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "天津",
|
||||
"shipmentCount": 115475,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.010514,
|
||||
"region": "二区",
|
||||
"regionShare": 0.485
|
||||
},
|
||||
{
|
||||
"province": "广西",
|
||||
"shipmentCount": 397325,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.036176,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "贵州",
|
||||
"shipmentCount": 242963,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.022121,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "黑龙",
|
||||
"shipmentCount": 166557,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.015165,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "吉林",
|
||||
"shipmentCount": 135735,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.012358,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "辽宁",
|
||||
"shipmentCount": 257754,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.023468,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "山西",
|
||||
"shipmentCount": 177480,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.016159,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "陕西",
|
||||
"shipmentCount": 281956,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.025672,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "四川",
|
||||
"shipmentCount": 532179,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.048454,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "云南",
|
||||
"shipmentCount": 341852,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.031125,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "重庆",
|
||||
"shipmentCount": 222288,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.020239,
|
||||
"region": "三区",
|
||||
"regionShare": 0.251
|
||||
},
|
||||
{
|
||||
"province": "甘肃",
|
||||
"shipmentCount": 144419,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.013149,
|
||||
"region": "四区",
|
||||
"regionShare": 0.05
|
||||
},
|
||||
{
|
||||
"province": "海南",
|
||||
"shipmentCount": 148380,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.01351,
|
||||
"region": "四区",
|
||||
"regionShare": 0.05
|
||||
},
|
||||
{
|
||||
"province": "内蒙",
|
||||
"shipmentCount": 166636,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.015172,
|
||||
"region": "四区",
|
||||
"regionShare": 0.05
|
||||
},
|
||||
{
|
||||
"province": "宁夏",
|
||||
"shipmentCount": 47493,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.004324,
|
||||
"region": "四区",
|
||||
"regionShare": 0.05
|
||||
},
|
||||
{
|
||||
"province": "青海",
|
||||
"shipmentCount": 39031,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.003554,
|
||||
"region": "四区",
|
||||
"regionShare": 0.05
|
||||
},
|
||||
{
|
||||
"province": "西藏",
|
||||
"shipmentCount": 441,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.00004,
|
||||
"region": "未分区",
|
||||
"regionShare": 0.0002471046689489402
|
||||
},
|
||||
{
|
||||
"province": "新疆",
|
||||
"shipmentCount": 2273,
|
||||
"totalShipment": 10983200,
|
||||
"shipmentShare": 0.000207,
|
||||
"region": "未分区",
|
||||
"regionShare": 0.0002471046689489402
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,18 +0,0 @@
|
||||
[Unit]
|
||||
Description=Furniture JST logistics rule daily snapshot
|
||||
Wants=network-online.target furniture-jst-rpa-browser.service
|
||||
After=network-online.target furniture-jst-rpa-browser.service
|
||||
Conflicts=furniture-discontinuation-daily.service plm-barcode-sync.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/opt/furniture-3d-system-prod/current/furniture-3d-system
|
||||
Environment=NODE_ENV=production
|
||||
Environment=TZ=Asia/Shanghai
|
||||
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
||||
Environment=JST_RPA_BROWSER_URL=http://127.0.0.1:9222
|
||||
Environment=FREIGHT_CONFIG_DATA_DIR=/opt/furniture-3d-system-prod/shared/freight-config
|
||||
EnvironmentFile=/opt/furniture-3d-system-prod/shared/.env
|
||||
ExecStart=/usr/bin/env node node_modules/tsx/dist/cli.mjs scripts/sync-jst-logistics-rules.ts
|
||||
TimeoutStartSec=30min
|
||||
Nice=5
|
||||
@ -1,11 +0,0 @@
|
||||
[Unit]
|
||||
Description=Run JST logistics rule snapshot daily
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 09:30:00 Asia/Shanghai
|
||||
Persistent=true
|
||||
AccuracySec=30s
|
||||
Unit=furniture-jst-logistics-snapshot.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@ -1,19 +0,0 @@
|
||||
[Unit]
|
||||
Description=Furniture supply-chain daily limit price snapshot
|
||||
Wants=network-online.target furniture-jst-rpa-browser.service
|
||||
After=network-online.target furniture-jst-rpa-browser.service furniture-jst-logistics-snapshot.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/opt/furniture-3d-system-prod/current/furniture-3d-system
|
||||
Environment=NODE_ENV=production
|
||||
Environment=TZ=Asia/Shanghai
|
||||
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
||||
Environment=JST_RPA_BROWSER_URL=http://127.0.0.1:9222
|
||||
Environment=FREIGHT_CONFIG_DATA_DIR=/opt/furniture-3d-system-prod/shared/freight-config
|
||||
Environment=SUPPLY_CHAIN_LIMIT_SNAPSHOT_DIR=/opt/furniture-3d-system-prod/shared/limit-price-snapshots
|
||||
Environment=LIMIT_SYNC_SKIP_EXCEL=1
|
||||
EnvironmentFile=/opt/furniture-3d-system-prod/shared/.env
|
||||
ExecStart=/usr/bin/env node scripts/run-limit-price-daily-snapshot.mjs
|
||||
TimeoutStartSec=2h
|
||||
Nice=10
|
||||
@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=Run furniture supply-chain daily limit price snapshot
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 09:30:00 Asia/Shanghai
|
||||
Persistent=true
|
||||
RandomizedDelaySec=60
|
||||
AccuracySec=30s
|
||||
Unit=furniture-limit-price-snapshot.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@ -1,109 +0,0 @@
|
||||
# 1688代发异常事项:退货地址推送接口
|
||||
|
||||
## 交付与启用
|
||||
|
||||
本接口供外部系统按 SPU 推送退货地址。正式站发布包含该接口的版本并配置独立密钥后,对方即可按本文调用。
|
||||
|
||||
部署人员在生产运行环境配置 `DROPSHIP_RETURN_ADDRESS_API_KEY`,使用独立随机密钥(建议至少32个随机字节),由双方通过安全渠道交接;密钥不进入 Git、对接文档、URL 或普通日志。未配置时接口返回503。无需提供数据库账号,无需平台用户登录。
|
||||
|
||||
## 请求
|
||||
|
||||
- 方法:`POST`
|
||||
- 正式地址:`https://cp.jiansun.vip/api/external/supply-chain/dropship-return-addresses`
|
||||
- 本地联调:`http://localhost:3001/api/external/supply-chain/dropship-return-addresses`
|
||||
- 请求头:`Content-Type: application/json`、`x-api-key: <对接密钥>`
|
||||
- 每批1–100条;单条推送也使用 `items` 数组。
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"spu": "DF-C1YMJ0009",
|
||||
"returnAddress": "收件人:张三;电话:138xxxxxxxx;地址:江苏省……"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
| --- | --- | --- |
|
||||
| `spu` | 是 | 1–120位字母、数字、下划线或连字符;自动去除首尾空格并转为大写 |
|
||||
| `returnAddress` | 是 | 去除首尾空格后1–500字;收件人、电话、详细地址合并为一段文字 |
|
||||
|
||||
仅接收上述字段。额外传入发票类型、安装方式等字段,该条会失败,不会改动这些字段。
|
||||
|
||||
调用示例(密钥由调用方运行环境注入):
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
'https://cp.jiansun.vip/api/external/supply-chain/dropship-return-addresses' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header "x-api-key: ${DROPSHIP_RETURN_ADDRESS_API_KEY}" \
|
||||
--data '{"items":[{"spu":"DF-C1YMJ0009","returnAddress":"收件人:张三;电话:138xxxxxxxx;地址:江苏省……"}]}'
|
||||
```
|
||||
|
||||
## 写入规则
|
||||
|
||||
1. 一SPU一退货地址,同SPU下所有SKU共用;按档案中的DF代发范围核验,不受当月业绩筛选影响。
|
||||
2. SPU必须已存在于产品档案。没有维护记录时自动创建维护记录;不会创建产品档案。
|
||||
3. 新地址覆盖该SPU原有地址(包括人工填写的地址),以数据库实际处理顺序为准。
|
||||
4. 只更新退货地址和维护人/时间,保留发票类型、整装/拆装;新增维护记录时其他字段使用原有空值默认值。
|
||||
5. 空地址拒绝,不能通过这个接口清空地址。重复推送同一地址返回 `UNCHANGED`,不重复写入或变更时间。
|
||||
6. 同批重复SPU(含大小写/首尾空格差异)的重复项全部失败,其余条目继续处理;每批按输入顺序返回结果。
|
||||
7. 每条独立保存,某条失败不撤销已经成功的条目。实际地址变化写入1688代发异常事项操作日志,标记“外部退货地址同步”,包含前后值与请求编号。
|
||||
8. 当前页面默认只显示有业绩SPU;无业绩产品推送成功后,切换“全部SPU”查看。
|
||||
|
||||
## 响应
|
||||
|
||||
HTTP200表示批次已处理,**仍须检查 `data.failedCount` 和每条 `status`**。
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 2,
|
||||
"successCount": 1,
|
||||
"failedCount": 1,
|
||||
"updatedCount": 1,
|
||||
"unchangedCount": 0,
|
||||
"results": [
|
||||
{
|
||||
"index": 1,
|
||||
"spu": "DF-C1YMJ0009",
|
||||
"status": "UPDATED",
|
||||
"updatedAt": "2026-10-24T04:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"spu": "DF-MISSING",
|
||||
"status": "FAILED",
|
||||
"code": "SPU_NOT_FOUND",
|
||||
"message": "产品档案中不存在该代发 SPU"
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"requestId": "服务端请求编号",
|
||||
"timestamp": "2026-10-24T04:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`index` 从1开始,对应请求数组顺序;`UPDATED` 表示新增或更新成功,`UNCHANGED` 表示地址已一致。两种均计入 `successCount`。时间使用UTC ISO8601格式。
|
||||
|
||||
| 每条失败码 | 含义与处理 |
|
||||
| --- | --- |
|
||||
| `INVALID_ITEM` | 字段格式、地址为空/过长或包含额外字段;修正数据后重推 |
|
||||
| `DUPLICATE_SPU` | 同批存在重复SPU;去重后重推 |
|
||||
| `SPU_NOT_FOUND` | 不存在对应DF代发档案;核对SPU或等待档案同步后重推 |
|
||||
| `WRITE_FAILED` | 保存失败;重试该条,持续失败时附 `meta.requestId` 联系管理员 |
|
||||
|
||||
| HTTP状态 | 含义 |
|
||||
| --- | --- |
|
||||
| 200 | 批次已处理,检查逐条结果 |
|
||||
| 400 | 非法JSON、缺少items、空批次、超过100条或多余顶层字段;整批不写入 |
|
||||
| 401 | 缺少密钥或密钥错误;整批不写入 |
|
||||
| 503 | 服务端未配置对接密钥;整批不写入 |
|
||||
| 500 | 系统错误;提供请求编号排查 |
|
||||
|
||||
网络超时或500时可能已有部分条目保存。可重推相同地址;如果期间出现更新,应重新取得上游最新地址再推送,避免旧批次覆盖新地址。调用方不应并发推送同一SPU。
|
||||
@ -1,101 +0,0 @@
|
||||
# 1688 Dropship Integration
|
||||
|
||||
## Scope
|
||||
|
||||
The module at `/supply-chain/new-products/1688-dropship-data` includes the DF
|
||||
product table, date and multi-select column filters, real-performance ordering,
|
||||
and daily/weekly/monthly trend details with prior-month comparisons.
|
||||
|
||||
It reads the shared product archive and daily sales fact table. The shared
|
||||
`barcode + statDate` key, unmatched source barcodes, synchronization lock,
|
||||
staging, and atomic publication are preserved. No second daily table or
|
||||
archive foreign key is introduced.
|
||||
|
||||
## Field Contract
|
||||
|
||||
| Display | Source / calculation |
|
||||
| --- | --- |
|
||||
| Sales amount | `sales_amount` -> daily `salesAmount` |
|
||||
| Real performance | `real_performance` |
|
||||
| After-sale amount | `after_sale_amount` |
|
||||
| After-sale cost rate | After-sale amount / real performance; blank for zero denominator |
|
||||
| Gross profit | Real performance - clear sales cost - send freight |
|
||||
| SKU operator | DF barcode/day distinct `ider`, latest nonempty value in selected period |
|
||||
| Total cost | Latest available daily `unitCost + estimatedFreight` in selected period; missing components are displayed as zero |
|
||||
| Unit cost | Latest available daily DF cost from `goods_count = 1` observations; the existing product cost excludes freight; missing values are displayed as zero |
|
||||
| Estimated freight | Average `send_freight` for the same DF barcode/day where `goods_count = 1`; the stored value remains null when no quantity-one observations exist and the page displays zero |
|
||||
|
||||
The archive's existing `shipAmount` continues to use `clear_goods_cost`.
|
||||
It must not be substituted for the dropship module's `salesAmount`.
|
||||
Cost ties use quantity-one frequency, total frequency, then descending unit cost. The page displays total cost before unit cost and estimated freight.
|
||||
|
||||
## Deployment Prerequisites
|
||||
|
||||
Code publication is not production deployment or database/data synchronization.
|
||||
No credentials, database dumps, local accounts, or local environment files are
|
||||
included in this change.
|
||||
|
||||
1. Confirm the target PostgreSQL identity, backup, and migration readiness using
|
||||
the repository deployment/database workflow. Apply migration
|
||||
`20260912120000_add_daily_sales_dropship_fields` after the existing mainline
|
||||
daily-sales migration. Do not replay the old local prototype's daily-table
|
||||
creation migrations against the mainline database.
|
||||
2. Generate the Prisma client and build the application with the target's
|
||||
existing environment configuration.
|
||||
3. Run the existing `npm run sync:supply-chain-product-archive-daily --` command
|
||||
with `--backfill`, `--apply`,
|
||||
`--confirm=sync-supply-chain-product-archive-sales-performance-daily`,
|
||||
`--expected-database-identity=...`, and
|
||||
`--expected-source-database-identity=...` using verified identities.
|
||||
The upstream requires read access to the configured
|
||||
`JST_REPORT_DB_SCHEMA.dws_sn_sales_full_info_di`; credentials remain in the
|
||||
target environment's `JST_REPORT_DB_*` variables.
|
||||
4. Check the completed sales sync run, historic DF `salesAmount` completeness,
|
||||
module view permission `newProduct1688DropshipData`, images, filters, totals,
|
||||
and trend comparisons. The existing daily schedule then maintains the data.
|
||||
|
||||
The migrations add `skuOperator` (empty by default), `unitCost` (nullable),
|
||||
`estimatedFreight` (nullable), and `salesAmount` (nullable). Historical facts need a backfill; the ordinary
|
||||
seven-day refresh does not populate all older dates. Until backfilled, unknown
|
||||
sales totals display as `--`, not zero or shipped amount.
|
||||
|
||||
Filtering uses the existing cache service with user-scoped keys and a 24-hour
|
||||
TTL. Multi-process deployments should provide the existing shared Redis cache.
|
||||
The module uses its own authenticated DF-only image route under
|
||||
`/api/supply-chain/1688-dropship-data/source-image`. Its view permission does not
|
||||
require granting access to the full product archive; the shared image service
|
||||
and its source/URL restrictions are retained.
|
||||
Rollback can restore the prior application version while retaining the additive
|
||||
columns; do not drop the daily table or discard its history.
|
||||
|
||||
## Validation And Review
|
||||
|
||||
Independent review conclusion: `passed with warnings`. Architecture and
|
||||
root-cause handling passed, including the corrected image-permission boundary.
|
||||
The existing visual implementation was retained; target-environment screenshots
|
||||
and real-data acceptance were not performed in this integration.
|
||||
|
||||
Final integration includes shared-mainline update `5156ead4`, retaining its
|
||||
separate dropship-issues entry and permissions. After merging, 20 focused Jest
|
||||
suites (171 tests), TypeScript checking, relevant ESLint, and the production
|
||||
build passed. The build retains two unrelated broad-file-pattern warnings in
|
||||
the existing OCR route and DWG wrapper.
|
||||
|
||||
The integration was checked with focused Jest suites for the page, trends,
|
||||
filters, permissions, image route, service/repository mapping, and shared daily
|
||||
sync; TypeScript checking; changed-file ESLint; and a production build using
|
||||
isolated placeholder configuration (no live database). An in-memory PostgreSQL
|
||||
compatibility replay confirmed that the additive migration retains existing
|
||||
DF/non-DF facts and the original primary key. Image-route tests exercise the
|
||||
real request/auth wrapper, including module-only access and denied requests.
|
||||
|
||||
The repository-wide `db:verify-master-data:code` and
|
||||
`db:verify-master-data:truth` audits still fail on existing baseline issues.
|
||||
Independent comparison with clean shared-mainline commit `38259cc4` found no
|
||||
new blocking findings: code-audit finding content is unchanged apart from
|
||||
schema line offsets, and the truth-audit JSON is identical. These global audit
|
||||
failures are not presented as passing checks.
|
||||
|
||||
No target deployment, live target database migration/backfill, or live-data
|
||||
browser validation of the integrated version is included in this publication.
|
||||
The existing local test application and its prototype database are unchanged.
|
||||
@ -1,78 +0,0 @@
|
||||
# 产品全量档案同步性能治理
|
||||
|
||||
## 范围与业务规则
|
||||
|
||||
本次治理覆盖源库集中取数、匹配索引、事务批量写入、来源快照、断点续跑与日销优先调度。保留字段来源、负责人优先级、只填空值、首次来源初始化不记变更日志等既有规则。
|
||||
|
||||
公司 SQL Server 只读,使用个人账号。验证写入只发生在本机 PostgreSQL 55432 的独立测试 schema;public 业务表只读。本轮没有生产配置、定时器或生产数据变更,没有提交、推送、main 合并或部署。
|
||||
|
||||
## 来源读取
|
||||
|
||||
- 一轮先固定档案 id、条码、SKU、供应商、仓库及初始图片状态,再集中读取价格、公共库存、清仓、库存监控、供应商、仓库、图片七类来源。最多两个来源同时读取,每个来源尝试独占一个连接池,池大小为 1。
|
||||
- 请求通过 OPENJSON 传入,不受原每 200 条重复查询限制。库存监控仍按原始目标批次匹配和选最新记录,保留条码优先、SKU 唯一兜底的口径。
|
||||
- 图片保留 DF 全量刷新、nonDF 仅补当前无图档案的范围。主视图 jst_skumap 按 shop_id、shop_i_id、shop_sku_id 三列主键每页 50,000 条读取编码,再按命中的主键精确读取图片;另外两张图片表分别以哈希匹配读取请求编码,并读取 DF 范围;重复命中由最新非空选择去重。使用 COMPRESS 与 UTF-16 JSON 无损传输,跨页、跨表选择最新非空图片,保留时间的亚毫秒顺序,不截断图片。
|
||||
- 每个来源的成功结果独立原子保存,带 SHA-256、条数和行结构校验。重启复用已完成来源,失败来源重新读取;来源未读完不发布部分结果。图片三张表还会分别保存已完成结果,失败后只重读未完成的表;主表内部分页尚不持久化中间页。
|
||||
- 每次请求有完整传输的墙钟限时:图片 600 秒,其他来源 300 秒。PRODUCT_ARCHIVE_SNAPSHOT_QUERY_TIMEOUT_MS 可统一覆盖,范围 1,000~600,000 毫秒。超时取消请求,以 ETIMEOUT 进入原有 30 秒、120 秒、300 秒退避重试;认证、权限、结构错误不重试。
|
||||
- 每批写前重新读取当前人员与图片,保留人工负责人保护;条码、SKU、供应商或仓库改变时拒绝旧快照。原先已有图的 nonDF 档案中途被清空,也要求新的 run key,避免使用未请求对应图片的旧快照。
|
||||
- 各来源在不同时间读取,不构成跨公司库与应用库的同一时刻事务快照。后续新 run key 会重新读取来源。
|
||||
|
||||
## 匹配、写入与调度
|
||||
|
||||
- 来源数组建立索引,共享图片索引跨批复用。保留原 find 的首条命中与原始来源顺序。
|
||||
- 每批最多 200 档案,在一个事务中按 id 排序锁定当前行,更新变化字段、字段来源、来源记录和逐字段日志。日志失败时整批回滚。来源日期传值、明确 null、未传三种状态保持原行为。
|
||||
- Service 计算计划,Repository 负责数据访问与事务;Repository 只通过参数接收 planner,不在运行时调用 Service。
|
||||
- PostgreSQL advisory lock 命名空间 90710:3 为写入轮次,4 为日销意向,5 为全量任务排他。日销先登记意向,后台当前批完成后让行。同库重复全量任务返回 SKIPPED_RUNNING;连接中断后拒绝继续写。
|
||||
- 主补全与 JST 基础档案按批让行。负责人流程与生产类型阶段以整阶段持锁。JST 某项写入失败时停止启动新项,等待同批已发出写入结束后再释放锁,不保存失败批次进度。
|
||||
- 匹配 mapper 保留原映射职责;类型、索引、快照、编排和事务已按职责拆分。没有新增界面或视觉样式。
|
||||
|
||||
## 运行与续跑
|
||||
|
||||
入口:scripts/sync-supply-chain-product-archive-enrichment.ts。保留 --apply、固定确认词、目标库及来源身份门禁。
|
||||
|
||||
- 默认 run key 为北京时间日期;相同配置重启继续已提交批次,已完成阶段跳过。
|
||||
- 跨天续跑使用原 run key,例如 --run-key=2026-10-21。
|
||||
- 同日需要重新读取最新来源时使用新 key,例如 --run-key=2026-10-21-second,保留原断点。
|
||||
- 目标库、来源身份、关键代码或批次参数变化会拒绝旧断点;损坏文件不会静默按空来源继续。
|
||||
- 断点经临时文件、fsync、原子 rename 保存。批次提交后、保存前中断会重放该批,通过比较当前值避免重复记录未变化字段。
|
||||
- 默认目录:本地 D:\ChanPing\.local-services\checkpoints\product-archive-sync;生产发布结构下 shared/product-archive-sync-checkpoints。可用 PRODUCT_ARCHIVE_SYNC_CHECKPOINT_DIR 指定。
|
||||
- ARCHIVE_SOURCE_SNAPSHOT 输出来源读取与复用状态,ARCHIVE_PICTURE_PAGE 输出主表扫描及命中进度,ARCHIVE_SYNC_BATCH 与 ARCHIVE_SYNC_PHASE 输出批次和阶段计时。
|
||||
- 本机 Node、Jest、TypeScript、ESLint 都通过 .local-services/scripts/invoke-node-hidden.ps1 启动。
|
||||
|
||||
## 验证记录(2026-10-21)
|
||||
|
||||
全部写入位于本机独立副本 archive_full_1792578871629。基础来源真实读取 81,070 条,副本从 75,527 档案增至 81,074 条。随后覆盖全部档案,对修改前 mapper/applyPatch 构建的独立 oracle 逐批核对档案业务字段、来源业务字段和逐字段日志。排除 id、自动更新时间、同步时间等每轮必然不同的元数据。
|
||||
|
||||
- 全部 81,074 档案通过;比较 1,092,416 条来源记录及 15,290 条新增逐字段日志,结果一致。
|
||||
- 首、中、末三个 200 条批次的六类来源共 18 项新旧取数对比通过。
|
||||
- 图片主表 24,581,427 行按主键读至表尾,和独立 COUNT_BIG 结果一致;命中并精确回查 64,382 行,三表完成后得到 32,944 个最新非空图片编码。
|
||||
- 13 组、97 项相关测试通过;TypeScript、源码 ESLint、diff --check 通过;3001 健康接口 database=connected、api=running。
|
||||
- 在真实本机 PostgreSQL 验证日志失败整批回滚、日销在后台批次间优先、失败释放锁及同库全量排他。测试也覆盖后表失败复用主表、来源快照损坏、断点恢复、超时取消和 JST 失败等待已发出写入结束。
|
||||
|
||||
### 分阶段计时
|
||||
|
||||
以下是同一独立副本的分阶段实测,包含真实公司库读取与本地写入。部分来源读取在前置阶段完成,并在完整比对时复用,故这是分阶段累计,不是一次连续正式站整轮耗时。为了便于评估,累计值单独加上已提前读取的六类来源时间,且未扣除生产并发取数可能节省的重叠时间;旧逻辑 oracle 比对开销不计入执行成绩。
|
||||
|
||||
| 阶段 | 实测 |
|
||||
| --- | ---: |
|
||||
| 基础商品来源 81,070 条 | 173.81 秒 |
|
||||
| 基础档案写入(更新 75,523,新增 5,547) | 270.33 秒 |
|
||||
| 负责人流程首次应用(变更 2,317) | 51.36 秒 |
|
||||
| 提前读取的六类非图片来源,分别累计 | 204.30 秒 |
|
||||
| 三表图片完整取数 | 806.17 秒 |
|
||||
| 共享数据、补全匹配和写入(复用来源,扣除 oracle 比对) | 81.08 秒 |
|
||||
| 产品类型实际同步 | 39.68 秒 |
|
||||
| 分阶段累计 | 27.11 分钟 |
|
||||
|
||||
图片主表单独耗时 787.83 秒,三表总图片来源 806.17 秒。完整补全验证实际墙钟 5.77 分钟,其中旧逻辑比对 4.42 分钟。
|
||||
|
||||
另一个 1,000 档案旧新写入试验中,匹配加写入从 44.13 秒降至 0.92 秒,约 48 倍;该局部试验不含源库,不能作为整轮加速倍数。
|
||||
|
||||
### 适用限制与收口
|
||||
|
||||
公司库负载和网络有波动。先前相同来源的读取曾明显变慢,不能据本地累计值承诺正式站固定耗时。完整生产成绩需正式发布后实测。负责人和产品类型阶段以整阶段让行;图片恢复以单表完成为边界,未完成的主表仍需重读。增量镜像、公司库索引与 DDL 不在本次范围。
|
||||
|
||||
本轮未提交、推送、合并 main 或部署,生产写入为 0。提交状态:可提交;main 标记:可合并 main。源码、测试及本说明共 31 个文件,登记为 awaiting_review。
|
||||
|
||||
规范结论:passed with warnings,这是自审,不是独立复审。架构职责保持;无视觉改动;修复重复扫描、线性查找、逐条事务和失败后重读已完成来源的原因。警告为尚无连续生产整轮计时及上述阶段让行限制。
|
||||
|
||||
审计证据:D:\ChanPing\.local-services\audits\archive-sync-performance-20261021\complete-review-result.json。
|
||||
@ -1,54 +0,0 @@
|
||||
# 产品全量档案图片索引操作说明
|
||||
|
||||
全量档案本地图片索引只读取 `SupplyChainProductArchive.image`。上游「我司 SKU 命中」继续使用现有独立图搜服务。
|
||||
|
||||
## 更新规则
|
||||
|
||||
- 有图片且没有索引:建立索引。
|
||||
- 图片地址变化、旧索引失效、模型版本或向量规格变化:重新索引。
|
||||
- 图片为空或地址已经变化:旧向量退出本地搜索。任务会把旧记录标为 `STALE`。
|
||||
- 下载或计算失败:保存 `FAILED` 和原因,六小时后再次尝试;图片地址再次变化则立即处理。
|
||||
- 图片内容相同、地址不同:复用向量并更新地址与状态,避免反复处理同一张图片。
|
||||
- 下载、计算期间图片改变或产品删除:提交前锁定并核对档案行,丢弃过期结果,下一轮处理最新图片。
|
||||
- 所有索引任务使用同一 PostgreSQL 会话锁,避免手动补索引、同步触发与定时任务并发计算。
|
||||
- 正常增量任务不会重新下载全部已就绪图片;同一 URL 下替换了图片内容时,使用完整内容核验命令。
|
||||
|
||||
## 本地命令
|
||||
|
||||
在主应用目录执行,模型沿用现有 `IMAGE_SEARCH_MODEL_PATH` 或 `.local-services/models/dinov2-small`。
|
||||
|
||||
```bash
|
||||
# 每次最多处理 200 条缺失、换图或到期重试记录
|
||||
npm run image-search:index
|
||||
|
||||
# 补齐当前全部待处理记录,并立即重试历史失败
|
||||
npm run image-search:index -- --limit=0 --retry-now
|
||||
|
||||
# 下载所有带图档案,核验同一 URL 下的图片内容变化
|
||||
npm run image-search:index -- --all --limit=0
|
||||
```
|
||||
|
||||
命令持续输出已处理、成功、复用、失败、跳过、过期结果丢弃和失效条数;失败行输出条码与原因。最后输出本轮结果及待处理数量。失败仍会进入后续重试,本轮失败数必须单独关注,`remaining` 只计算已到重试时间的记录。
|
||||
|
||||
## 生产启用
|
||||
|
||||
代码必须先进入被授权的 `origin/main`,再经统一部署入口发布。安装定时器与首次补索引按用户授权执行,不能把本地开发完成等同于生产已启用。
|
||||
|
||||
安装入口:`scripts/install-product-archive-image-index-timer.sh`。它要求现有生产模型文件齐全,并让任务显式使用共享模型目录。当前应用启动命令已注入模型路径,无须为本功能重写应用环境。
|
||||
|
||||
- 服务:`furniture-product-archive-image-index.service`。
|
||||
- 定时器:`furniture-product-archive-image-index.timer`,每五分钟检查,单次最多处理 200 条,失败六小时后重试。
|
||||
- 两个现有全量同步服务分别增加本功能自己的 `image-index.conf`,同步成功后异步启动索引服务。
|
||||
- 前台保存、导入及其他来源的图片变化由五分钟定时检查覆盖;索引下载与推理不阻塞保存请求。
|
||||
- 通过 `PRODUCT_ARCHIVE_IMAGE_INDEX_LIMIT` 可在安装时调整单次处理上限。
|
||||
- 脚本和日志通过发布目录与 systemd 管理,不手工修改发布目录。
|
||||
|
||||
```bash
|
||||
bash scripts/install-product-archive-image-index-timer.sh
|
||||
systemctl status furniture-product-archive-image-index.timer --no-pager
|
||||
journalctl -u furniture-product-archive-image-index.service -n 50 --no-pager
|
||||
```
|
||||
|
||||
首次补齐历史缺口可等待定时任务分批完成;需要一次补完时,手动索引命令必须指向共享模型路径。运行中的另一索引任务会返回 `LOCKED`,稍后重试。
|
||||
|
||||
验收应核对有图档案与索引的关联,而不是只看 `READY` 行数:当前地址一致、状态 `READY`、模型版本与 768 维向量均符合要求才是可用索引。历史 404 或不可读取地址不能靠索引任务生成图片,需要按条码定位源图片问题。
|
||||
@ -24,30 +24,6 @@
|
||||
|
||||
只做生产/目标库地基核查时,优先在远端应用目录执行 `npm run db:foundation:audit:production-readonly`。该命令不事务回放治理迁移 SQL,只通过只读迁移状态和质量查询生成 `.master-data-audits/foundation-production-readonly-current/manifest.json`,适合把脱敏 summary 或 manifest 交回主线收口。拿到 manifest 后,用 `npm run db:foundation:verify-production-readonly -- --manifest <manifest.json>` 做机器验收;只有 `accepted: true` 才能把这份证据计入生产级地基收口。
|
||||
|
||||
## 聚水潭 RPA 登录凭据
|
||||
|
||||
- 本地可在 `furniture-3d-system/.env.jst-rpa-auth` 保存
|
||||
`JST_RPA_LOGIN_ID` 和 `JST_RPA_LOGIN_PASSWORD` 两项。该文件被 `.env*`
|
||||
忽略规则排除,禁止提交 Git。
|
||||
- 正式部署脚本会自动发现该文件,上传到目标机 `incoming` 临时目录,再安全合并进
|
||||
`/opt/furniture-3d-system-prod/shared/.env`;正式 `.env` 和临时文件均保持
|
||||
`600`,临时文件在部署结束后删除。
|
||||
- 使用其他安全位置时,可给部署入口显式传
|
||||
`-JstRpaAuthFile <凭据文件绝对路径>`。未提供文件时会保留正式机已有值,不会被空值覆盖。
|
||||
- 部署完成后可执行
|
||||
`node_modules/.bin/tsx scripts/jst-shop-goods-rpa.ts --action=login`
|
||||
验证登录态,不执行链接导出。
|
||||
|
||||
## 限价每日快照
|
||||
|
||||
- 生产安装入口:`furniture-3d-system/scripts/install-limit-price-snapshot-systemd-timer.sh`。
|
||||
- systemd 名称:`furniture-limit-price-snapshot.service` / `furniture-limit-price-snapshot.timer`。
|
||||
- 默认每天中国标准时间 `09:30` 执行,可在安装时通过 `LIMIT_PRICE_SNAPSHOT_CALENDAR` 覆盖。
|
||||
- 快照持久目录默认 `/opt/furniture-3d-system-prod/shared/limit-price-snapshots`,安装器会把该路径写入生产 `.env` 的 `SUPPLY_CHAIN_LIMIT_SNAPSHOT_DIR`,避免部署切换版本后丢失。
|
||||
- 任务先更新产品档案、限价、物流规则并完整对账正式产品全量档案,全部成功后才原子切换 `latest.json`。
|
||||
- 安装器会检查数据库和聚水潭报表库环境变量;生产 `.env` 更新后需要重启正式应用进程。
|
||||
- 安装、启用定时器和首次生产快照属于生产操作,需要单独授权。
|
||||
|
||||
## 花名册同步
|
||||
|
||||
- 花名册同步属于当前 `furniture-3d-system` 自有能力,不依赖 `shujuku2` 或旧入口机。
|
||||
@ -58,10 +34,6 @@
|
||||
- 定时频率:每天 `03:00` 和 `15:00`。
|
||||
- 源库连接信息只允许放在生产本地 `/opt/furniture-3d-system-prod/shared/employee-directory-sync.env`,不能写入 Git 或共享文档。
|
||||
- 生产应用内部接口密钥继续从 `/opt/furniture-3d-system-prod/shared/.env` 的 `HR_SYNC_SECRET` 读取。
|
||||
- 人员信息表的钉钉组织同步使用 `furniture-3d-system/scripts/install-dingtalk-directory-sync-timer.sh`,生产 systemd 名称为 `furniture-dingtalk-directory-sync.service` / `furniture-dingtalk-directory-sync.timer`。
|
||||
- 钉钉同步每天 `03:00` 执行一次,读取生产共享 `.env` 中的 `DINGTALK_API_CLIENT_ID` 和 `DINGTALK_API_CLIENT_SECRET`,并自动更新人员信息;正式页面仍保持只读。钉钉网页登录使用独立的 `DINGTALK_LOGIN_CLIENT_ID`、`DINGTALK_LOGIN_CLIENT_SECRET`,两组凭据不能混用。
|
||||
- 人员信息的每日更新口径:钉钉新人自动新增一行;在职人员按钉钉更新部门/负责人;人工覆盖(manualOverride)的行保持不变;**离职人员不做删除,只在页面把「在职状态」变为离职**(方案 B:先比对上一轮在册且本轮抓取范围内消失的人,再逐个调 `user/get` 确认 60121 用户不存在才标记;抓取人数明显缩水时整轮跳过并写入告警)。
|
||||
- 首次部署后需在正式服务器执行该安装脚本,并手动执行一次 `npm run sync:dingtalk-directory:apply` 完成首次填充;后续由 systemd 每日执行。
|
||||
|
||||
## BOM 成本参考镜像
|
||||
|
||||
@ -78,19 +50,6 @@
|
||||
- 准确度回测:`npm run backtest:bom-cost-reference -- --sample-size=30`,只读最新成功镜像,不访问上游;样本限制 20 至 50 个,任一可比较 SKU 超过固定 10% 误差门槛即失败。
|
||||
- 迁移、真实同步、安装/启用定时器和生产回测都必须另行获得生产操作授权;部署代码本身不代表这些动作已经执行。
|
||||
|
||||
## 商品 SABC 月度核算
|
||||
|
||||
- 核算命令:`npm run run:product-sabc -- --year=2026 --month=8 --mode=APPLY --confirm=run-product-sabc`;试算(只算不写库)用 `--mode=PREVIEW`,末尾加 `--skip-if-exists` 可跳过已生成月份。
|
||||
- 8 月人工表对账命令:`npm run compare:product-sabc-xlsx`,默认读取本机下载的人工工作簿;生产执行前应先完成产品全量档案日销售数据同步。
|
||||
- 生产定时器安装脚本:`furniture-3d-system/scripts/install-product-sabc-timer.sh`。
|
||||
- 生产 systemd 名称:`furniture-product-sabc-monthly.service` / `furniture-product-sabc-monthly.timer`。
|
||||
- 默认调度:每月 3 日中国标准时间 `03:00`,附带最多 10 分钟随机延迟;服务生成上一个自然月的数据,已生成过则跳过(不覆盖管理员的重算结果)。
|
||||
- 生成即冻结:每次生成会把整行明细写入 `SupplyChainProductArchiveSabcResult.snapshot`,页面、筛选下拉、导出和经营总览都只读取该快照。生成之后档案的销售状态、人员字段等变化都不会再改变已生成月份的数据。
|
||||
- 重新生成(页面「执行核算」)仅限管理员(`ADMIN` / `SUPER_ADMIN`);其他账号只能查看冻结结果。本地一键刷新 `node scripts/local-sync-archive.mjs` 在每月 3 日前会跳过 SABC 步骤,需要立刻重算时用 `--force-sabc`。
|
||||
- SABC 的销售收款总金额、真实业绩、发货件数、商品毛利额和售后金额统一读取 `SupplyChainProductArchiveSalesPerformanceDaily`,与产品全量档案页面保持同一月度口径。
|
||||
- 首次安装生成权限为 `600` 的 `/opt/furniture-3d-system-prod/shared/product-sabc-timer.env`,默认 `PRODUCT_SABC_TIMER_MODE=APPLY`;如需先观察可临时改为 `PREVIEW`(只试算不写库、不冻结)。
|
||||
- SABC 自动任务只读取现有 `DATABASE_URL`;上游日销售同步任务继续使用 `JST_REPORT_DB_*`,凭据不写入定时器配置。安装、迁移、首次 APPLY 和生产核算都需要单独授权。
|
||||
|
||||
## 操作日志保留策略
|
||||
|
||||
- 清理脚本:`npm run db:cleanup-operation-logs` 默认只做 dry-run;只有显式传入 `--apply --confirm=cleanup-operation-logs` 才会删除数据。
|
||||
@ -101,15 +60,6 @@
|
||||
- 安装脚本会在 `/opt/furniture-3d-system-prod/shared/operation-log-retention.env` 保存权限为 `600` 的数据库身份门禁和非敏感参数;身份未填写或保留天数不是 30 天时拒绝启用。
|
||||
- 首次启用前必须先在生产执行 dry-run,核对候选数量、数据库身份和 systemd 落点;生产安装、首次 `--apply` 和后续代码部署均需另行获得授权。
|
||||
|
||||
## 停产需求确认超时收口
|
||||
|
||||
- 收口脚本:`furniture-3d-system/scripts/finalize-production-stop-confirmations.ts`,把超过确认截止时间仍未反馈的停产需求按「默认同意停产」落库,并给未反馈的确认人记 `TIMEOUT` 票。
|
||||
- 生产安装入口:`furniture-3d-system/scripts/install-production-stop-confirmation-finalize-timer.sh`。
|
||||
- 生产 systemd 名称:`furniture-production-stop-confirmation-finalize.service` / `furniture-production-stop-confirmation-finalize.timer`,默认每 10 分钟检查一次(北京时间),附带最多 1 分钟随机延迟;可用 `PRODUCTION_STOP_FINALIZE_CALENDAR` 覆盖调度。
|
||||
- 收口逻辑幂等:只处理「已过截止时间且仍待确认」的需求,重复执行不会重复收口;失败由 systemd 在 5 分钟后自动重试。
|
||||
- 运行日志:`journalctl -u furniture-production-stop-confirmation-finalize -n 100 --no-pager`,每次运行输出本次收口的需求数量。
|
||||
- 首次安装与启用:在项目根执行 `bash scripts/install-production-stop-confirmation-finalize-timer.sh`(需 root,脚本写入 `/etc/systemd/system/` 并 `enable --now`);安装/启用定时器属于生产操作,需要另行获得授权。
|
||||
|
||||
## 明确排除
|
||||
|
||||
- `192.168.58.20` 不是当前 `furniture-3d-system` 的生产部署目标。
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
# 铺货与业绩快照定时更新
|
||||
|
||||
本机 3001 的 Windows 计划任务 `Furniture-DistributionSnapshots-3001` 每天北京时间 09:00 顺序更新铺货明细、真实业绩。任务使用 `+08:00` 开始边界;错过时间后在机器可运行时补执行,重复触发不并发。电脑需开机、用户登录且公司报表库与本地 PostgreSQL 可连接。此任务未配置到正式站。
|
||||
|
||||
本机入口在 `D:\ChanPing\.local-services\scripts\run-distribution-snapshots.vbs`,依次调用隐藏 PowerShell、统一 Node 无窗口入口和个人凭据注入器。凭据仅运行时读取 `LOCAL_SECRETS.md` 的公司数据库节,不进入源码或日志。所有公司库操作均为 SELECT。
|
||||
|
||||
仓库入口 `scripts/sync-distribution-snapshots.ts` 验证来源和目标身份、持有任务锁,串行执行两个同步 CLI。日志及每次前后计数、含索引物理字节数、业绩统计区间报告写到 `D:\ChanPing\.local-services\logs\distribution-snapshots`。失败返回非零退出码,可从计划任务 LastTaskResult 和阶段日志定位;不自动报告成功。
|
||||
|
||||
铺货先读取目标管理、人员、店铺映射,字段一次补齐后写入连接专属临时表。自然键去重后核对实际条数、三类来源,较上一版减少超过 20% 时拒绝发布,等待人工核查。通过后在一个事务中替换正式表,失败保留上一版。最终替换阶段持有表锁,页面查询可能等待;临时表和会话锁会在进程退出后自动释放。没有新增长期全量副本,没有反复 UPDATE 数百万行。
|
||||
|
||||
组合补齐:`rpt.jst_comboProductInfo` 与 `rpt.jst_comboProductItem` 只提供完整套装编码、组成条码和数量。同步从三张 skumap 中同时匹配档案条码及包含档案条码的组合编码,每个原始套装规格仅保存一次。轻量组成关系写入 `SupplyChainDistributionComboItem`,与铺货规格在同一事务发布;读取视图 `SupplyChainDistributionListingResolved` 将套装规格关联到组成条码。页面按条码及平台、店铺ID、铺货ID去重,直接铺货与多个套装共享同一链接时计一次,组件数量不乘链接数。链接状态保持 skumap/目标管理原口径,不使用组合头 enabled 替代。关系缺失、组成为空或发布校验失败时保留上一批完整快照。
|
||||
|
||||
页面支持搜索组合编码/名称,并在链接明细的条码列查看“直接铺货”“组合套装”或两者共有及具体套装数量。Excel 铺货页保留原始货号和规格,新增独立“组合关系”工作表,避免重复保存/导出整份宽数据。业绩匹配可识别通过套装覆盖的子条码链接;只使用业绩源已经提供的子条码金额,不将套装金额复制或按数量自动分摊。
|
||||
|
||||
首次补齐可在两个同步 CLI 上显式增加 `--reuse-direct-snapshot`:先在本地数据库中将已发布的档案直接规格复制到临时表,再从三张源表读取组合规格,整体校验后统一替换。直接规格原有同步时间与内容保留,便于精确比较组合净增量。此选项不用于每日任务;定时任务默认完整刷新直接与组合规格。
|
||||
|
||||
业绩沿用独立版本发布:当月截至昨天以内源库最新可用日,按条码、店铺、铺货 ID 汇总;成功后保留当前和上一成功版本。每月 1 日当月无完整统计日时保留前一版并报出失败,页头继续显示实际统计周期。铺货与业绩不能作为一个跨库事务提交:铺货成功但业绩失败时,铺货保留新版本,业绩保留上一成功版,任务整体显示失败。
|
||||
|
||||
手动运行和定时执行使用相同入口,不并发补数。此文档不授权部署。
|
||||
|
||||
正式站经独立授权后,先核实目标库、来源库身份和磁盘余量,再通过统一部署入口应用三个迁移。服务器从公司库只读生成自己的快照,不复制整个本地数据库。个人凭据沿用 shared/marketing-sales-personal.env,目标和来源身份门禁放在权限为 600 的 shared/distribution-snapshots.env,分别为 DISTRIBUTION_EXPECTED_DATABASE 和 DISTRIBUTION_EXPECTED_SOURCE(host:port/database 格式)。来源连接的其他 JST_REPORT_DB_* 配置沿用 shared/.env。
|
||||
|
||||
生产安装入口 scripts/install-distribution-snapshots-timer.sh 校验身份及依赖后,启用 furniture-distribution-snapshots.timer,每天 Asia/Shanghai 09:00 执行;错过的调度补执行。对应 service 通过 scripts/run-distribution-snapshots.sh 运行同一串行同步入口,日志在 journalctl -u furniture-distribution-snapshots,前后条数与空间报告在 shared/logs/distribution-snapshots。手动首次执行使用 systemctl start --no-block furniture-distribution-snapshots.service;任务锁防止并发,失败状态需核查报告和日志。本机任务与生产任务独立,安装生产任务不会停用本机任务。
|
||||
@ -1,33 +0,0 @@
|
||||
# DIY 柜门图案方向修正:请核对并替换旧图片
|
||||
|
||||
状态:待发布的更新日志文案。后续正式发布时录入系统“更新日志管理”并发布,本文档本身不会向用户推送通知。
|
||||
|
||||
## 更新日志标题
|
||||
|
||||
DIY 柜门图案方向已修正,请核对并替换旧图片
|
||||
|
||||
## 更新日志摘要
|
||||
|
||||
DIY 柜门图案曾出现左右镜像,现已修正。如果此前使用过 DIY 截图、AI 白底图或场景效果图,请对照柜门原图核对方向;发现镜像的图片请重新出图,并替换已使用的旧图片。
|
||||
|
||||
## 已修正的内容
|
||||
|
||||
DIY 柜门图案在 3D 画面中曾左右镜像,部分图案、文字和动物的朝向可能与原图相反。现已修正柜门贴图方向,横竖切换后也按原图方向显示。
|
||||
|
||||
## 用过旧图片的同事需要处理
|
||||
|
||||
1. 核对修复前生成、下载或发布的 DIY 截图、AI 白底图和场景效果图,重点检查文字及图案的左右方向。
|
||||
2. 如发现镜像,请打开原保存方案,刷新页面后对照柜门原图确认方向,重新截图或生成 AI 图片。
|
||||
3. 将店铺商品图、详情页、方案资料及其他已使用位置中的错误图片替换为核对后的新图片。
|
||||
|
||||
## 已保存的方案和旧图片
|
||||
|
||||
已保存方案可以直接打开并重新出图,无需重新搭建,也不需要为这次显示方向修正重新上传柜门原图。
|
||||
|
||||
旧截图及已生成的 AI 图片不会自动改正,已下载或发布的文件也不会自动替换。请核对重新生成的图片后再使用;原本方向正确的图片可继续保留。
|
||||
|
||||
## 后续发布登记
|
||||
|
||||
- 这条说明需要作为用户可见更新日志发布,仅提交本文档不能代替发布通知。
|
||||
- 发布时使用系统现有“更新日志管理”,覆盖使用 DIY 的同事;如不能准确筛选这批人员,按全员可见发布。
|
||||
- 发布后确认普通使用者可看到标题、摘要及上述替换说明。不要把“修正 3D 预览”写成“旧图片已自动更新”。
|
||||
@ -1,46 +0,0 @@
|
||||
# 2026-10-28 限价运费计算阻塞网页线程
|
||||
|
||||
## 故障与证据
|
||||
|
||||
生产版本 `83a3138fe9704d61f13de9f665779e783931c57d` 的网页进程长时间占用一个 CPU 核心,端口 3001 的监听队列达到 512,本机健康检查无法响应,公网返回 502。穿透进程持续正常,内存和磁盘有余量,故障时没有检出矿工。
|
||||
|
||||
重启前的 CPU 采样主要落在 Unicode 格式转换、字符串替换和去空格。入口访问日志显示,部署后 `/api/supply-chain/limit-management/products` 出现长耗时;17:13 至 19:29 之间有 338 次限价计算请求,其中一次成功请求耗时 397.882 秒。请求时间包含等待与其他请求争用的时间,不能当作单次函数的纯计算耗时。
|
||||
|
||||
故障链路:
|
||||
|
||||
`限价商品列表 / 限价导出 -> calculateRegionFreightsForArchiveRows -> buildFreightCalculation -> 每个快递的每个省份调用 selectLatestApplicableRate -> 重扫并规范化整批费率`
|
||||
|
||||
10 月 28 日新增的区域和全国运费计算,把原来用于单个商品的全表扫描放大到整页商品。算法的重复扫描量随「商品数 × 快递数 × 29 个省份 × 全部费率行数」增长,而且原整批循环没有让出网页线程。并发打开或刷新列表会进一步积压请求;入口 502 和数据库事务超时是下游症状。
|
||||
|
||||
## 隔离复现
|
||||
|
||||
只读取得费率、物流规则和商品字段快照,在本机离线运行,不向生产网页接口重放旧重计算。上游费率视图有 75,630 行;复现的 200 行限价页中有 159 行能进行运费计算,共需 4,465 行费率。
|
||||
|
||||
同一快照、同一计算时间、相同输入与输出顺序,本机 Node.js 24.22.1 的一次带规范化调用计数的对比(计数会影响耗时):
|
||||
|
||||
| 项目 | 修复前 | 修复后 |
|
||||
| --- | ---: | ---: |
|
||||
| 计算耗时 | 16,056 ms | 124 ms |
|
||||
| 字符串规范化调用 | 64,631,978 | 16,030 |
|
||||
| 完整结果 SHA-256 | `a34b18ccfd50ae6053b26d7ca2513fa101368cadfdbee3befaf7f752c360efc6` | 相同 |
|
||||
|
||||
这是离线计算对比,不代表生产端到端请求耗时,也不包含数据库和网络时间。原始商业数据与 CPU 采样只保存在私有排错目录,不进入 Git。
|
||||
|
||||
另用本机独立 HTTP 子进程调用实际批量服务,数据访问以同一只读快照代替,不插入字符串计数。旧版计算用时 5,012 ms,期间并发健康请求最长等待 5,019 ms;修复版计算用时 143 ms,8 次健康请求均为 200,最长等待 29 ms。两次完整服务返回值摘要均为 `ef8a73cec474260611eb74c073d22b32c721b1881644db7ceff22cc83e41d489`。
|
||||
|
||||
## 修复边界
|
||||
|
||||
- 纯计算层按规范化后的快递和省份建立费率索引,并按原来的生效时间和规则 ID 排序。保留有效期、重量/体积/最长边、计泡、取整、失效提示和财务计算规则。
|
||||
- `createFreightCalculator` 在同一批次复用费率索引与发货占比;新的批次重新使用自己的费率快照和计算时间,不增加跨请求的陈旧缓存。
|
||||
- 批量服务超过 8 ms 计算时间片后用 `setImmediate` 让出线程,让健康检查和其他请求有执行机会。
|
||||
- 新增复杂度、报价一致性、有效期和批量让出线程的回归测试。本次生产修复保留现有运费计算公式及基线期望,不带入开发线上尚未发布的运费功能和公式变化。
|
||||
|
||||
不修改数据库结构或业务数据、页面视觉、SSH 密钥、密码、共享账号、全局 Node.js、穿透配置或其他项目服务。
|
||||
|
||||
## 发布核查
|
||||
|
||||
2026-10-29 的真实页面验证发现额外的运行时兼容问题:单商品运费能返回,大页触发让出线程时抛出 `RangeError: Maximum call stack size exceeded`。在生产私有 Node.js 22.23.4 的独立子进程中,仅加载当前 Next.js 16.1.6 的 `fast-set-immediate.external.js` 并调用 `node:timers/promises.setImmediate()` 即可复现;调用栈反复进入框架的 `patchedSetImmediatePromise`,与数据库记录无关。
|
||||
|
||||
统一的 `yieldToEventLoop` 改用 `node:timers` 的回调接口包装 Promise。它仍然把执行机会交还事件循环,不修改框架、全局计时器或其他项目;新增子进程测试加载实际 Next.js 补丁,覆盖生产运行环境,避免只用模拟计时器漏掉这类问题。
|
||||
|
||||
发布需按当前生产部署入口说明执行,保留上一版本,核对目标主机、生产提交、PM2、端口和日志。重点验证真实的限价列表和限价导出,同时并行请求健康检查;不能只验证健康检查或本地测试就认定生产修复完成。其他项目的进程 PID 和重启次数应保持不变。
|
||||
@ -1,35 +0,0 @@
|
||||
# 2026-10-29 限价排序与 DNS 延迟
|
||||
|
||||
## 两种独立的等待
|
||||
|
||||
生产版本 `66db2a26` 已修复运费重复扫描和 Next.js 计时器兼容问题,继续检查时仍出现两类延迟,不能混为同一个业务故障。
|
||||
|
||||
- 本机网页进程:限价排序函数在价格、时间相同的商品之间,反复调用带 `zh-CN` 和 `numeric: true` 参数的 `String.localeCompare`。每次比较都重新准备排序规则,整页取数时需要给五万多条候选商品排序,阻塞网页线程约 3 秒。
|
||||
- 公网检查:分阶段记录 DNS、TCP、TLS 和首字节耗时,慢请求中 DNS 本身耗时约 5.01 秒;同次请求后续连接和网页响应只占约 0.09 秒。保持域名、TLS 验证与目标服务不变,仅诊断时用 `curl --resolve` 指定当前地址后,30 次请求均返回 200,最长约 0.234 秒。该 DNS 等待发生在检查机器上,不能据此认为每位用户的浏览器都有相同延迟。
|
||||
|
||||
当前主机的两个上游 DNS 直接查询都观察到少量超时,本地 DNS stub 的 AAAA 查询也有超时。证据不足以仅凭这些现象断定双宽带负载均衡是唯一原因。本次不改共享主机的 DNS、路由器、网卡、FRP 或其他项目配置。
|
||||
|
||||
## 排序对比与修复范围
|
||||
|
||||
在生产私有 Node.js 22.23.4 的独立进程中,只读取得 54,381 条销售商品候选和 13,537 条货号限价,使用同一批内存数据对比:
|
||||
|
||||
| 指标 | 原比较方式 | 复用 Intl.Collator |
|
||||
| --- | ---: | ---: |
|
||||
| 排序耗时 | 3,318 ms | 212 ms |
|
||||
| 完整有序商品 ID SHA-256 | `8d83f84e3252953e0729d320da3a43ede88fca7cffce6e2f906c9f857a62ff10` | 相同 |
|
||||
|
||||
这是隔离计算耗时,不是整页网络时间。取数约 700 ms,不计入上表。
|
||||
|
||||
打开真实限价页面的同时采样 100 次本机健康请求,全部返回 200,但最长等待 3,076 ms,其中 4 次超过 1 秒。这与隔离复现的同步排序耗时吻合;无用户操作时的 30 次本机检查最长约 9 ms。
|
||||
|
||||
产品档案 Repository 内复用同一个 `Intl.Collator('zh-CN', { numeric: true })`,用于限价列表及筛选候选值的中文数字比较。价格优先、时间优先、条码数字顺序、相同比较值的稳定顺序、空值位置、筛选范围及分页契约保持原样。不增加跨请求业务数据缓存,不修改 SQL、数据库结构、业务数据或页面视觉。
|
||||
|
||||
回归验证覆盖中文与数字/前导零/大小写/全半角的顺序一致性、大批次不重复初始化排序规则、筛选候选值去重与分页。发布后还需在真实页面翻页/刷新并并发检查本机健康响应,核对其他项目进程和凭据指纹。
|
||||
|
||||
## 继续定位查询参数准备
|
||||
|
||||
第一轮上线后,连续翻页和打开筛选仍出现过约 2.1 秒的等待。仅对本项目网页进程短时启用函数采样,发现主线程热点包含 Prisma 的查询模板参数展开函数 `td / Hn / ca / od`,并伴随对象分配和垃圾回收。采样完成后已恢复原 Node.js 启动参数。
|
||||
|
||||
隔离只读查询使用 54,395 条销售商品中的 54,291 个货号,对比 ORM 的逐项 `IN` 参数与 PostgreSQL 的单个 `text[]` 参数:原查询约 958 ms,数组参数约 202 ms;两者均返回 9,552 条,完整货号与限价结果摘要均为 `801e325b6d702880dc7d386f0903b1be073655407bfc43b08a7cdbbf7713df1e`。CPU 时间约为 1,657 ms / 285 ms,包含独立测试进程的各线程。
|
||||
|
||||
限价列表及负责人限价候选值改用参数化 `= ANY($1::text[])`,仍只查询已有筛选候选对应的货号;保留空集合短路、未定价商品、非空价格条件与原始小数精度。没有拼接货号进入 SQL,不扩大读取范围,不修改数据库结构或记录,不新增缓存。新增超过 PostgreSQL 标量参数上限的批次、数据范围、SQL 参数绑定及空价格候选值回归测试。
|
||||
@ -1,163 +0,0 @@
|
||||
# 类目配置取数与筛选逻辑
|
||||
|
||||
> 用途:给需要接入类目配置数据的开发人员使用。本文描述当前代码和数据库的真实口径(2026-09-24 更新:合并为单表展示结构)。
|
||||
>
|
||||
> 重要:本文不包含数据库连接串、账号、密码。连接信息统一见《README.md》。
|
||||
|
||||
## 1. 数据源与责任边界
|
||||
|
||||
- **权威数据源**:本系统 `DATABASE_URL` 指向的 PostgreSQL,schema `public`,共 2 张表(见第 2 节)。数据通过类目配置页面人工维护 + Excel 导入,**没有外部同步来源**。
|
||||
- **业务作用**:
|
||||
1. 维护「二级类目 → 一级类目」的唯一归属(产品全量档案类目列的规范值来源);
|
||||
2. 维护每个类目的 4 档规模阈值(爆款/热卖款/动销款/低动销款 × 业绩区间、日销区间)与「分类」归属。
|
||||
- **人工维护原则**:直接写表会绕过唯一键校验和页面权限,接入方一律只读。
|
||||
|
||||
### 1.1 代码依据
|
||||
|
||||
- Prisma 模型:`prisma/schema.prisma` 中 `SupplyChainProductArchiveCategoryConfig`、`SupplyChainProductArchiveCategoryScaleRow`。
|
||||
- 页面:`src/app/supply-chain/database/category-config/`(单表展示,数据源即规模行表)。
|
||||
- 服务:`supply-chain-product-archive-category-scale-row.service.ts`(页面主数据源,含类目清单同步)、`supply-chain-product-archive-category-config.service.ts`(类目主表维护)。
|
||||
|
||||
## 2. 物理表清单与分工
|
||||
|
||||
| 表名 | 角色 | 当前行数量级 |
|
||||
| --- | --- | --- |
|
||||
| `"SupplyChainProductArchiveCategoryScaleRow"` | **类目规模行(页面当前数据源)**:一行 = 二级类目 × 规模标签,自带一级类目、分类、阈值 | 类目数 × 4 |
|
||||
| `"SupplyChainProductArchiveCategoryConfig"` | **类目主表**:一行一个二级类目的身份记录(唯一性、启停状态),供产品档案同步、类目下拉等消费方使用 | 80+ |
|
||||
|
||||
两表关系:
|
||||
|
||||
```text
|
||||
ScaleRow(页面表:一级/二级/分类/阈值/标签,行=类目×档位,唯一键 categoryL2+scaleLabel)
|
||||
│ 服务写入时自动同步(新增/改行会把一级类目、分类落到主表)
|
||||
▼
|
||||
Config(类目身份表:categoryL2 全表唯一 + status 启停,供外部消费方取类目清单)
|
||||
```
|
||||
|
||||
> 历史说明:旧三表结构中的「分类档位模板表」(`SupplyChainProductArchiveCategoryGroupTier`)已于 2026-09-24 迁移(20260924160000)删除,其数据已按类目展开并入规模行表;旧接口 `/api/supply-chain/category-config/group-tiers/*` 已下线。
|
||||
|
||||
## 3. 表字段字典
|
||||
|
||||
### 3.1 `"SupplyChainProductArchiveCategoryScaleRow"`(类目规模行,页面数据源)
|
||||
|
||||
| 字段 | 类型/约束 | 业务含义 | 取值规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `TEXT` 主键 | 行 ID | 系统生成 |
|
||||
| `categoryL1` | `TEXT NOT NULL DEFAULT ''` | 一级类目(≤160 字符) | 页面/导入必填 |
|
||||
| `categoryL2` | `TEXT NOT NULL` | 二级类目名(≤160 字符) | 与 `scaleLabel` 联合唯一 |
|
||||
| `categoryGroup` | `TEXT NOT NULL DEFAULT ''` | 分类(如 电脑桌/书桌、其他沙发类) | 空 = 未分类 |
|
||||
| `scaleLabel` | `TEXT NOT NULL` | 规模标签 | 只能是 爆款/热卖款/动销款/低动销款 |
|
||||
| `performanceStart` | `DECIMAL(14,2)` NOT NULL | 业绩起点(元) | |
|
||||
| `performanceEnd` | `DECIMAL(14,2)` 可空 | 业绩终点(元) | **NULL = 不设上限** |
|
||||
| `dailySalesStart` | `DECIMAL(12,2)` NOT NULL | 日销起点(件/天) | 支持小数 |
|
||||
| `dailySalesEnd` | `DECIMAL(12,2)` 可空 | 日销终点(件/天) | NULL = 不设上限 |
|
||||
| `sortOrder` | `INTEGER` | 档位顺序:爆款1 → 热卖款2 → 动销款3 → 低动销款4 | 判定从高档往低档取第一个命中 |
|
||||
| `createdAt` / `updatedAt` | `TIMESTAMP(3)` | 系统字段 | |
|
||||
|
||||
约束:`UNIQUE(categoryL2, scaleLabel)`——一个类目每个标签只有一行;索引:`categoryL2`、`categoryGroup`。
|
||||
|
||||
### 3.2 `"SupplyChainProductArchiveCategoryConfig"`(类目主表)
|
||||
|
||||
| 字段 | 类型/约束 | 业务含义 | 取值规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `TEXT` 主键 | 配置记录 ID | 系统生成 |
|
||||
| `categoryL2` | `TEXT` 唯一 | 二级类目名(≤160 字符) | 全表唯一 |
|
||||
| `categoryL1` | `TEXT NOT NULL` | 一级类目(≤160 字符) | |
|
||||
| `categoryGroup` | `TEXT NOT NULL DEFAULT ''` | 分类 | 空 = 未分类 |
|
||||
| `scaleLabel` | `TEXT NOT NULL DEFAULT ''` | 【预留未使用】 | 阈值数据在 ScaleRow 表 |
|
||||
| `performanceStart` / `performanceEnd` | `DECIMAL(14,2)` 可空 | 【预留未使用】 | 同上 |
|
||||
| `dailySalesStart` / `dailySalesEnd` | `DECIMAL(12,2)` 可空 | 【预留未使用】 | 同上 |
|
||||
| `status` | `TEXT`,`ACTIVE` / `INACTIVE` | 启用/停用;停用的不进类目选项 | 默认 `ACTIVE` |
|
||||
| `createdAt` / `updatedAt` | `TIMESTAMP(3)` | 系统字段 | |
|
||||
|
||||
索引:`categoryL1`、`categoryGroup`、`status`;`categoryL2` 唯一。
|
||||
|
||||
## 4. 其他线程取数方式
|
||||
|
||||
### 4.1 只读 SQL(直连数据库)
|
||||
|
||||
```sql
|
||||
-- 4.1.1 页面同款数据:全部规模行(一级类目、二级类目、分类、4 档阈值)
|
||||
SELECT "categoryL1", "categoryL2", "categoryGroup", "scaleLabel",
|
||||
"performanceStart", "performanceEnd", "dailySalesStart", "dailySalesEnd"
|
||||
FROM "SupplyChainProductArchiveCategoryScaleRow"
|
||||
ORDER BY "categoryL2" ASC, "sortOrder" ASC;
|
||||
|
||||
-- 4.1.2 某个类目的 4 档阈值
|
||||
SELECT "scaleLabel", "performanceStart", "performanceEnd", "dailySalesStart", "dailySalesEnd"
|
||||
FROM "SupplyChainProductArchiveCategoryScaleRow"
|
||||
WHERE "categoryL2" = '住宅家具 - 书桌'
|
||||
ORDER BY "sortOrder" ASC;
|
||||
|
||||
-- 4.1.3 类目身份清单(供下拉/同步用,只取启用的)
|
||||
SELECT "categoryL1", "categoryL2", "categoryGroup"
|
||||
FROM "SupplyChainProductArchiveCategoryConfig"
|
||||
WHERE "status" = 'ACTIVE'
|
||||
ORDER BY "categoryL1" ASC, "categoryL2" ASC;
|
||||
|
||||
-- 4.1.4 某分类下的全部类目及其阈值
|
||||
SELECT "categoryL2", "scaleLabel", "performanceStart", "performanceEnd"
|
||||
FROM "SupplyChainProductArchiveCategoryScaleRow"
|
||||
WHERE "categoryGroup" = '电脑桌/书桌'
|
||||
ORDER BY "categoryL2" ASC, "sortOrder" ASC;
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 表名/字段名**必须带双引号**保持大小写。
|
||||
- 终点列为 `NULL` 表示不设上限,不要写成 0。
|
||||
- 停用(`INACTIVE`)类目历史仍在 Config 表里,统计按 `status` 过滤。
|
||||
- 商品/档案行上的类目与分类是写入时的快照,改配置不回刷历史数据。
|
||||
|
||||
### 4.2 代码内取数(同仓库服务)
|
||||
|
||||
```ts
|
||||
// 规模行(页面同款数据:一行=类目×档位,含一级类目与分类)
|
||||
import { supplyChainProductArchiveCategoryScaleRowService } from
|
||||
'@/server/services/supply-chain-product-archive-category-scale-row.service'
|
||||
await supplyChainProductArchiveCategoryScaleRowService.list({ page: 1, pageSize: 100 })
|
||||
await supplyChainProductArchiveCategoryScaleRowService.listAll({ categoryGroup: '电脑桌/书桌' })
|
||||
|
||||
// 类目身份清单(产品全量档案同步、类目下拉在用)
|
||||
import { supplyChainProductArchiveCategoryConfigService } from
|
||||
'@/server/services/supply-chain-product-archive-category-config.service'
|
||||
await supplyChainProductArchiveCategoryConfigService.list({ page: 1, pageSize: 100, status: 'ACTIVE' })
|
||||
|
||||
import { supplyChainProductArchiveCategoryConfigRepository } from
|
||||
'@/server/repositories/supply-chain-product-archive-category-config.repository'
|
||||
await supplyChainProductArchiveCategoryConfigRepository.findActiveByCategoryL2('住宅家具 - 书桌')
|
||||
```
|
||||
|
||||
### 4.3 走 API(需要登录 + 供应链权限)
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `/api/supply-chain/category-config/scale-rows` | GET / POST | 规模行列表 / 新增(写入时自动同步类目主表) |
|
||||
| `/api/supply-chain/category-config/scale-rows/{id}` | PATCH / DELETE | 规模行编辑 / 删除 |
|
||||
| `/api/supply-chain/category-config/scale-rows/export` · `/import` | GET / POST | 导出(筛选跟随当前搜索/分类/标签) / 流式导入(表头:一级类目、二级类目、分类、规模标签、业绩起终点、日销起终点) |
|
||||
| `/api/supply-chain/category-config/scale-rows/groups` | GET | 去重分类列表(筛选项) |
|
||||
| `/api/supply-chain/category-config` · `/{id}` | GET/POST/PATCH | 类目身份维护(供应商新品推荐下拉也走这里) |
|
||||
| `/api/supply-chain/category-config/export` · `/import` | GET / POST | 类目身份导出 / 导入 |
|
||||
|
||||
权限:查询走类目节点「查看」,导出走「下载」,写入走「编辑」;角色 `SUPER_ADMIN/ADMIN/SUPERVISOR` 或业务角色 `MANAGEMENT`。
|
||||
|
||||
## 5. 现有消费方(改动前必读)
|
||||
|
||||
| 消费方 | 取数点 | 依赖 |
|
||||
| --- | --- | --- |
|
||||
| 产品全量档案(DF/聚水潭同步) | `jst-sku-product-archive-sync.service.ts`、`jst-sku-product-archive-full-sync.service.ts` | `findActiveByCategoryL2`:仅启用类目作为规范值 |
|
||||
| 供应商新品推荐类目下拉 | `/api/supply-chain/supplier-recommended-products/category-options` | Config `service.list`,仅 `ACTIVE` |
|
||||
| 类目配置页面 | 第 4.3 节接口 | — |
|
||||
|
||||
改 `status`、`categoryL2` 名称、删除类目前先确认不影响以上消费方。
|
||||
|
||||
## 6. 接入前检查清单
|
||||
|
||||
- [ ] 表名/字段名带双引号保持大小写。
|
||||
- [ ] 方向:`categoryL2`(二级)→ `categoryL1`(一级);`categoryGroup`(分类)是独立维度,不是一级类目,空 = 未分类。
|
||||
- [ ] 终点列 `NULL` = 不设上限,不要当 0 处理。
|
||||
- [ ] 档位判定顺序:爆款 → 热卖款 → 动销款 → 低动销款,从高到低取第一个命中区间。
|
||||
- [ ] 阈值数据取 `ScaleRow` 表;Config 表上的 `scaleLabel`/阈值列为预留未使用。
|
||||
- [ ] 停用(`INACTIVE`)配置历史仍在 Config 表里,统计按 `status` 过滤。
|
||||
- [ ] 商品/档案行上的类目、分类是写入时快照,改配置不回刷历史数据。
|
||||
- [ ] 页面写操作会双写两表(规模行 + 类目主表同步),外部只读不写就不会产生不一致。
|
||||
@ -1,99 +0,0 @@
|
||||
# 内容视频流程看板取数与筛选逻辑
|
||||
|
||||
> 用途:给需要接入「新品-视频进度(内容部)」(钉钉内容视频流程)数据的开发人员使用。本文描述当前代码和数据库的真实口径(2026-10-08 首版)。
|
||||
>
|
||||
> 重要:本文不包含数据库连接串、账号、密码。该页面数据**不存在本系统 PostgreSQL**,全部来自企划 MySQL 的同步快照。
|
||||
|
||||
## 1. 数据源与责任边界
|
||||
|
||||
- **页面直接数据源**:企划 MySQL(连接变量 `JST_PLANNING_DB_*`,生产为生产服务器本机实例)的 `planning_database` 库,快照表 `oa_video_flow_board_snapshot`。
|
||||
- **原始数据源**:钉钉 OA 审批镜像库的 `ding_Workflow` / `ding_WorkflowScheme` / `ding_WorkflowTask` 三张表,按流程编码过滤:
|
||||
- 内容视频流程 `PROC-AE9FF3E1-A442-4E6A-85CF-0F2B86409673`(登记在 `oa-board.constants.ts` 的 `OA_BOARD_PROCESS_CODES.video`)。
|
||||
- **同步方式**:`scripts/sync-oa-video-flow-board.ts --apply`(即 `npm run sync:oa-video-flow:apply`,服务 `oa-video-flow-board.service.ts`)全量拉取该流程全部实例与表单字段,按"一条流程实例 = 一行"写入快照表,并用 `sync_version` 清理已消失的实例。生产由 systemd 定时器(`install-oa-board-sync-timer.sh` 安装的 `furniture-oa-board-sync.timer`)每 15 分钟在主看板、品牌策划看板之后顺序执行。
|
||||
- **责任边界**:镜像库与钉钉侧的数据质量不由本系统负责;本系统保证"镜像库 → 快照 → 页面"这一段。下游一律**只读快照表**,不要直连 ding_ 三张源表(源表结构随钉钉变化,无稳定性承诺)。
|
||||
- **与新品主链的关系**:内容视频流程**不参与** OA 新品看板主链的项目聚合、总状态与时效矩阵(其表单无开款编号/关联单号,仅有 SPU)。本表是独立流程维度的进度数据。
|
||||
|
||||
### 1.1 代码依据
|
||||
|
||||
- 同步与快照读写的唯一实现:`src/server/services/oa-video-flow-board.service.ts`(建表 DDL 在 `ensureSnapshotTable`,自动建表)。
|
||||
- 源库连接复用:`oa-board-sync.service.ts` 导出的 `openOaBoardSourceMysqlConnection` / `openOaBoardSourceSqlServer`(跟随 `OA_BOARD_SOURCE_DRIVER`)。
|
||||
- 页面接口:`GET /api/intake/oa-board/video-flow`(参数 `keyword` / `demandType` / `brand` / `status` / `page` / `pageSize`,需登录态)。
|
||||
- 页面组件:`src/app/intake/oa-board/OaBoardVideoFlowPanel.tsx`(Tab 参数 `tab=video-flow`)。
|
||||
|
||||
## 2. 物理表(MySQL · planning_database)
|
||||
|
||||
| 物理表 | 用途 | 键与索引 |
|
||||
| --- | --- | --- |
|
||||
| `oa_video_flow_board_snapshot` | 最新快照,一行 = 一条内容视频流程实例 | `row_key` 唯一;`spu` / `status` / `stage_label` / `demand_type` / `brand` / `created_date` 有索引 |
|
||||
|
||||
同步任务自动建表(`CREATE TABLE IF NOT EXISTS`),引擎 InnoDB,字符集 utf8mb4。
|
||||
|
||||
## 3. 字段字典
|
||||
|
||||
| 字段 | 类型 | 含义 | 取值规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `row_key` | `VARCHAR(191)` 唯一 | 钉钉流程实例 ID(processInstanceId),幂等拉取键 | 源表主键,稳定不变 |
|
||||
| `business_id` | `VARCHAR(128)` | 流程发起编号 | 形如 `202610071758000088167` |
|
||||
| `title` | `VARCHAR(255)` | 流程标题 | `XX提交的内容视频和图片申请流程` |
|
||||
| `submitter_name` | `VARCHAR(255)` | 提交人姓名 | 从标题提取;`originator_user_id` 存钉钉用户 ID 兜底 |
|
||||
| `status` | `VARCHAR(32)` | 流程状态 | `RUNNING` / `COMPLETED` / `TERMINATED` |
|
||||
| `demand_department` | `VARCHAR(255)` | 需求部门 | 表单字段「需求部门」 |
|
||||
| `demand_type` | `VARCHAR(255)` | 需求类型 | 如 淘系-逛逛视频 / 抖音-千川素材视频 / 安装视频 / AI视频 等 13 种 |
|
||||
| `task_type` | `VARCHAR(255)` | 任务类型 | 如 布景拍摄 / 二次剪辑 |
|
||||
| `install_video_category` | `VARCHAR(255)` | 安装视频分类 | 仅安装视频类需求有值(如 B档) |
|
||||
| `brand` | `VARCHAR(255)` | 品牌 | 表单字段「品牌」 |
|
||||
| `scene_category` | `VARCHAR(255)` | 场景类别 | 如 卧室 / 客厅 / 玄关 |
|
||||
| `spu` | `VARCHAR(64)` | SPU | 大写规范化;约 3% 行无值(账号维护等非产品需求) |
|
||||
| `product_id` | `VARCHAR(128)` | 商品ID | 表单字段「商品ID」,可能为"暂无" |
|
||||
| `sample_barcodes` | `TEXT` | 样品条码 | 表单「样品明细」表格中商品编码列,多值分号分隔 |
|
||||
| `content_owner` | `VARCHAR(255)` | 内容部负责人 | 表单字段 |
|
||||
| `planning_supervisor` | `VARCHAR(255)` | 品牌策划主管 | 表单字段 |
|
||||
| `script_director` | `VARCHAR(255)` | 编导 | 流程推进到该环节时才填,未填为 NULL |
|
||||
| `cameraman` | `VARCHAR(255)` | 摄像 | 同上 |
|
||||
| `editor` | `VARCHAR(255)` | 剪辑 | 同上 |
|
||||
| `retoucher` | `VARCHAR(255)` | 修图 | 可选环节 |
|
||||
| `cover` | `VARCHAR(255)` | 封面 | 同上 |
|
||||
| `stage_label` | `VARCHAR(64)` | 当前阶段 | 推断规则见第 4 节 |
|
||||
| `acceptance_result` | `VARCHAR(255)` | 验收结果 | 表单「运营/策划/客服验收结果」 |
|
||||
| `score` | `VARCHAR(64)` | 评分 | 表单「评分」 |
|
||||
| `created_date` | `DATETIME` | 流程发起时间(北京时间) | 源表 createAt |
|
||||
| `finished_date` | `DATETIME` | 流程完成时间 | RUNNING 为 NULL |
|
||||
| `duration_days` | `DECIMAL(10,2)` | 总时效(天) | 发起 → 完成;未完成计至同步时刻 |
|
||||
| `last_source_updated_at` | `DATETIME` | 源表最近更新时间 | 增量拉取判据 |
|
||||
| `sync_version` | `VARCHAR(64)` | 本次同步批次标识 | 全表同批;用于清理已消失实例 |
|
||||
| `synced_at` | `TIMESTAMP` | 本行最近同步时间 | 数据新鲜度判据(`MAX(synced_at)`) |
|
||||
|
||||
## 4. 阶段(stage_label)推断规则
|
||||
|
||||
源表只有 activityId 没有节点名,首版按表单字段的推进顺序推断(钉钉流程设计中角色字段按环节顺序逐步填写):
|
||||
|
||||
```text
|
||||
status = COMPLETED → 已完结
|
||||
status = TERMINATED → 已终止
|
||||
验收结果或评分已填 → 验收/评分
|
||||
否则取已填角色的最后一个:
|
||||
编导 → 编导制作 → 摄像 → 摄影拍摄 → 剪辑 → 剪辑制作
|
||||
→ 修图/封面 → 封面/修图制作
|
||||
一个角色都没填 → 需求发起
|
||||
```
|
||||
|
||||
若后续拿到钉钉流程节点名清单,可升级为 activityId 级精确映射(预留位:`oa-board.constants.ts` 的 `OA_BOARD_ACTIVITY_MAPPING_SEED.video` 当前为空数组)。
|
||||
|
||||
## 5. 接入示例(一律只读)
|
||||
|
||||
```sql
|
||||
-- 按 SPU 拉该产品的全部视频流程
|
||||
SELECT * FROM oa_video_flow_board_snapshot WHERE spu = 'SJ0159';
|
||||
|
||||
-- 只看进行中的
|
||||
SELECT * FROM oa_video_flow_board_snapshot WHERE status = 'RUNNING';
|
||||
|
||||
-- 增量拉取(幂等键 row_key)
|
||||
SELECT * FROM oa_video_flow_board_snapshot
|
||||
WHERE last_source_updated_at > :上次拉取时间;
|
||||
|
||||
-- 取数前先看新鲜度(生产最多滞后 15 分钟)
|
||||
SELECT MAX(synced_at) FROM oa_video_flow_board_snapshot;
|
||||
```
|
||||
|
||||
关联口径:与产品数据对齐用 `spu` 或 `sample_barcodes` 中的条码;本表**没有开款编号**,不要与主看板的 `OPENING:` 维度直接关联。
|
||||
@ -1,188 +0,0 @@
|
||||
# BOM 成本预估 v2(agent 方案)设计文档
|
||||
|
||||
> 日期:2026-09-16
|
||||
> 状态:设计稿,待评审
|
||||
> 前置阅读:`docs/项目总览.md`、`src/server/services/bom-costing/agent/`(已建成的引擎)
|
||||
> 关联会话结论:1688 以图搜图锚定为过渡方案(双重失败已实测证伪),终局为 agent 估算 + 公司数据
|
||||
|
||||
## 1. 一句话定位
|
||||
|
||||
**粘贴竞品链接 → 几分钟后得到一张诚实的成本预估卡。**
|
||||
|
||||
原始需求从未变过。v2 的一切设计决策都回到这句话:数字 + 构成故事 + 诚实的区间,工序全部退到幕后。
|
||||
|
||||
## 2. v1 的教训(为什么 v2 长这样)
|
||||
|
||||
| v1 的问题 | 根因 | v2 的对策 |
|
||||
|---|---|---|
|
||||
| 工序暴露给用户(逐图泳道、五阶段时间线、重算模式、复核流) | 工程美德当成了产品 | 一根进度条 + 一张卡;审计链存在但默认隐藏 |
|
||||
| 27 个 SKU 全量核算(用户等十几分钟才见结果) | 首结果时间没有被设计 | 首批 3 个精算先出结果,其余按需点算,结果缓存 |
|
||||
| 十几分钟等待 | 按 SKU 串行/并发全量跑 | case 级一次运行,2-3 分钟 |
|
||||
| 复杂度随精度承诺膨胀 | 用流程堆精度 | 用数据+校准逼近精度(±10% 是"尽量"的校准目标) |
|
||||
| 1688 锚定顶替估算 | 上线压力下的权宜 | 1688 永远是后台交叉验证信号 |
|
||||
|
||||
**必须保留的教训**:用户会锚定数字。区间 + 置信度 + 不确定点是答案的一部分,不是脚注——这是"给一个数"和"给一个诚实的数"的差别。
|
||||
|
||||
## 3. 设计总纲(五条,裁决一切分歧)
|
||||
|
||||
1. **预估,不是核算**:输出是带区间与置信度的假设生成器,供决策筛选,永不承诺报价级精度。
|
||||
2. **agent 主导分析,代码承载确定性**:看什么图、怎么拆结构、查什么价格由 agent 决定;一切数值计算(汇总、区间合成、损耗)由确定性代码完成。模型永不算数、永不编价。
|
||||
3. **价格只认公司数据**:历史 BOM 镜像是唯一定价依据(检索→分类中位→cohort→损耗四级回退);1688 行情仅作后台交叉验证,永不成为展示价。
|
||||
4. **前端来源中性(不可触碰)**:界面永不出现 1688 字样/报价/链接;一切序列化出口经过 `src/lib/bom-costing/public-estimate-text.ts`。
|
||||
5. **实测闸门**:agent 流程未经回测与真实案例对比验证(偏差分布达标)前,不替换任何线上行为。
|
||||
|
||||
## 4. 用户形态
|
||||
|
||||
### 4.1 默认入口:预估卡 + 全量 SKU 按需核算
|
||||
|
||||
**覆盖率是需求,等待时间是体验问题**——所有 SKU 都要能出结果,但采用"先出一批、其余按需点算"的渐进交付:
|
||||
|
||||
```
|
||||
粘贴链接 → [进度条:抓取中 → 分析中 → 出预估](2-3 分钟)→
|
||||
|
||||
┌────────────────────────────────────────┐
|
||||
│ 源氏木语 白蜡木实木衣柜 │
|
||||
│ 预计出厂成本 ¥260 – 380(首批 3 个 SKU)│
|
||||
│ 置信度:中等 │
|
||||
│ 板材 ¥180± · 五金 ¥25± · 加工+人工 ¥60± · 包材 ¥15± │
|
||||
│ 依据:8 张商品图 + 公司历史 BOM 12 条 │
|
||||
│ 不确定:板厚未确认;铰链数量按两门推定 │
|
||||
└────────────────────────────────────────┘
|
||||
SKU 列表(27 个):
|
||||
[¥260.29 已核算] (3) 原木色 0.9米两门 ← 首批自动精算
|
||||
[¥375.48 已核算] (7) 原木色 1.0米 ← 首批自动精算
|
||||
[¥406.77 已核算] (18) 原木色 1.96米 ← 首批自动精算
|
||||
[点击核算] (4) 原木色 0.9米/高2.2米 ← 用户点击后单独运行
|
||||
[点击核算] ... ← 已算结果缓存,不重跑
|
||||
────────────────────────────────────────
|
||||
商品图片(保留现有形式:主图/SKU图/详情图 分类浏览 + 大图预览)
|
||||
```
|
||||
|
||||
- **首批自动精算**:默认 3 个代表性 SKU(跨度原则:最小/最大/中间,或按材质/结构差异选),2-3 分钟出卡——用户立刻有结果。
|
||||
- **其余 SKU 按需点算**:未核算的 SKU 显示占位按钮,用户点击触发单 SKU agent 运行(复用现有 per-SKU runner,1-2 分钟/个);**结果持久缓存**,重复点击不重跑。
|
||||
- 进度状态只有三个:抓取中 / 分析中 / 出预估(含失败态与重试按钮)。
|
||||
- **渐进展开**:点构成 → BOM 行明细;点行 → 证据(哪张图、哪条镜像参考行)。审计链存在,默认折叠。
|
||||
- **商品图片保留现有展示形式**(主图/SKU 图/详情图分类 + 逐图状态 + 大图预览):用户需要直接查看竞品链接内容,图片是判断依据,不是工程噪声。
|
||||
|
||||
### 4.2 深度模式(保留现有工作台)
|
||||
|
||||
需要全量 27 SKU 拆解、人工核对、证据溯源的用户,从预估卡一键进入现有工作台。v2 验证前工作台不动;验证后它降级为二级入口,不再是默认。
|
||||
|
||||
## 5. 架构:case 级 agent 运行
|
||||
|
||||
### 5.1 运行编排(新,核心增量)
|
||||
|
||||
交付模型:**首批自动精算 + 其余按需点算**。现有 per-SKU runner 是按需点算的执行体,case 级编排负责首批选择与结果汇总。
|
||||
|
||||
```
|
||||
阶段一:runBomCostingAgentEstimateForCase(caseId)(首批)
|
||||
1. 取案例简报(标题/渠道/已解析品类 skill)+ 归档素材 + SKU 清单(含尺寸)
|
||||
2. 代码侧选首批 SKU(默认 3 个:最小/最大/中间,或按材质/结构差异;
|
||||
单 SKU 商品即为它自己)
|
||||
3. 逐个运行现有 per-SKU agent 循环(结构行 + 查价)
|
||||
4. 代码侧汇总出 EstimateCard:卡面五行/区间/置信度 + SKU 列表状态
|
||||
(已核算 3 / 待核算 24)
|
||||
|
||||
阶段二:按需点算(用户驱动)
|
||||
用户点击未核算 SKU → 单 SKU agent 运行(现有 runner)→
|
||||
结果持久缓存 → 卡面/SKU 列表实时更新
|
||||
```
|
||||
|
||||
- 首批预算:≤24 轮 / ≤32 次模型调用 / ≤8 分钟(per-SKU 预算 × 3 + 编排开销)。
|
||||
- **结果缓存**:per-SKU 结果按 (caseId, skuId, 引擎版本) 持久化;点击已核算 SKU 直接展示缓存,不重跑。SKU 重算 = 显式刷新动作(换引擎版本或用户主动重跑)。
|
||||
- 失败语义:首批中单个 SKU 失败 → 该 SKU 标记失败可单独重试,不拖垮整卡;结构成功但价格饥饿 → 出结果但区间放宽 + 不确定点说明(PARTIAL 是合法产物)。
|
||||
- 并发上限与幂等:同一 SKU 的点算请求可去重合并(进行中重复点击返回同一运行)。
|
||||
|
||||
### 5.2 输出契约:EstimateCard(case 级)
|
||||
|
||||
```ts
|
||||
interface BomEstimateCard {
|
||||
caseId: string
|
||||
title: string
|
||||
skus: Array<{ // 全量 SKU 清单 + 状态
|
||||
externalSkuId: string
|
||||
name: string
|
||||
status: 'ESTIMATED' | 'PENDING' | 'FAILED' | 'RUNNING'
|
||||
estimate?: BomCostingAgentEstimate // 已核算时:行/区间/置信度/evidence
|
||||
cachedAt?: string
|
||||
}>
|
||||
firstBatchSkuIds: string[] // 首批自动精算的 SKU
|
||||
breakdown: Array<{ // 卡面五行(由已核算 SKU 的行汇总;随点算增长)
|
||||
category: 'BOARD'|'STEEL'|'ACCESSORY'|'PACKAGING'|'PROCESSING'|'LABOR'
|
||||
total: number | null // null = 该组无价,卡面显示"待补"
|
||||
}>
|
||||
totalRange: { min: number; max: number } // 当前已核算 SKU 的区间包络
|
||||
confidence: number // 已核算 SKU 的最小置信度
|
||||
openQuestions: string[] // 卡面"不确定"(已核算 SKU 的并集)
|
||||
usage: { rounds; modelCalls; elapsedMs }
|
||||
}
|
||||
```
|
||||
|
||||
原则:明细(行级)保留在各 SKU 的 estimate 里供展开;卡面只消费 breakdown/totalRange/confidence/openQuestions,并随按需点算增量更新。
|
||||
|
||||
### 5.3 价格供给(核心依赖,最大缺口)
|
||||
|
||||
首测"定价饥饿"的根治项——把 v1 场景服务的回退链包装成 agent 工具:
|
||||
|
||||
| 优先级 | 工具 | 包装自 |
|
||||
|---|---|---|
|
||||
| P0 | 尺寸感知镜像检索(分类/材质词/尺寸/参考 SKU 多级过滤) | `findReferenceCandidates` |
|
||||
| P0 | 分类画像中位价 | `fillCategoryProfileFallbacks` |
|
||||
| P1 | 同类 cohort 报价 | `resolveCohortQuote` |
|
||||
| P1 | 历史损耗率 | `applyHistoricalLossFactorFallbacks` |
|
||||
| P2 | AI 补估(置信度上限 0.75 + 标记复核) | 现有 estimator |
|
||||
|
||||
工具输出纪律:摘要级(无供应商/店铺/链接),带 sourceRowId 供 evidence 引用,单位明确。**1688 工具维持现状**(只透出状态/参考价/区间/可比数四个中性字段)。
|
||||
|
||||
### 5.4 数据与知识资产接入(全部保留,形态不变)
|
||||
|
||||
| 资产 | v2 中的角色 | 接入形态 |
|
||||
|---|---|---|
|
||||
| 历史 BOM 镜像(每日 MSSQL 同步) | 唯一价格依据 | 工具(5.3) |
|
||||
| 品类 Skill(5 个)+ 结构先验 | agent 脑中的专家清单:必备组件、证据纪律、校验规则 | 提示词注入(`renderBomCostingAgentSkillKnowledge`,已建成) |
|
||||
| 材质目录 | 材质身份唯一权威 | 工具(已接线) |
|
||||
| 商品抓取/归档/抠图 | 看图取证的素材 | 工具 + mission 组装(已接线;抠图模型待切 remove-background) |
|
||||
| 1688 行情 | 后台交叉验证信号 | 工具(已接线,中性摘要) |
|
||||
|
||||
### 5.5 校准闭环(±10% "尽量"的实现方式)
|
||||
|
||||
- **回测**:历史 BOM 镜像抽 20-50 个真实 SKU,同一规则/agent 重算 vs 真实成本,产出误差分布(复用 `backtest:bom-cost-reference` 基建)。
|
||||
- **shadow 对比**:agent 结果与现行引擎结果对同一案例并排落库(工程域),持续记录偏差(`report:bom-cost-shadow-divergence` 同款模式)。
|
||||
- **阈值语义**:±10% 是回测循环追的校准目标(按品类分段看分布),不是单案例门禁。偏差大 → 调工具/知识/提示词 → 再回测。
|
||||
- 未过门槛 → agent 流程保持旁路,线上仍是现行方案。
|
||||
|
||||
## 6. 前端形态
|
||||
|
||||
- **新入口**:`/bom-costing` 默认视图替换为预估卡流(粘贴框 → 卡 → SKU 列表 → 图片浏览)。
|
||||
- **商品图片**:保留现有展示形式(主图/SKU 图/详情图分类 tab + 大图预览 + 逐图状态),用户需要直接查看竞品内容;它从"处理进度件"回归"内容主体"。
|
||||
- **SKU 列表**:已核算显示价格,未核算显示"点击核算"按钮(运行中显示进度态);点击结果写缓存并即时更新卡面。
|
||||
- **组件**:新 `BomEstimateCard`(平台组件体系 + 白曜石规范),状态只有 加载/成功/失败(可重试)。
|
||||
- **现有工作台**:不动,作为深度模式从卡进入。
|
||||
- **移动端**:卡、SKU 列表与图片浏览响应式。
|
||||
- 红线复检:卡面文案经过 `publicEstimateWarningText` 清洗;evidence 展开层同样不得出现来源敏感词。
|
||||
|
||||
## 7. 落地顺序(每步可独立验证)
|
||||
|
||||
| 步骤 | 内容 | 验收 |
|
||||
|---|---|---|
|
||||
| ① | 价格回退工具(P0 两个) | 对已知案例重跑,未定价行显著下降 |
|
||||
| ② | 首批编排 + EstimateCard 契约 + 按需点算与结果缓存 | 单案例 2-8 分钟出首批卡;点击未核算 SKU 增量出结果且缓存生效 |
|
||||
| ③ | 预估卡前端(含 SKU 列表与现有图片浏览)+ 三态进度 | 真人走通 粘贴→出卡→点算其他 SKU→看图核对 全流程 |
|
||||
| ④ | 回测 + shadow 接入,按品类出基线报告 | 误差分布报告(对照 ±10% 目标) |
|
||||
| ⑤ | 实测样本扩到 5-10 案例,按品类定门槛 | 达标 → 翻转默认入口;未达标 → 继续迭代 |
|
||||
|
||||
## 8. 红线(重申)
|
||||
|
||||
1. 前端永不出现 1688 相关内容;一切文本出口过脱敏契约。
|
||||
2. agent 引擎在过实测闸门前保持旁路(不替换线上行为)。
|
||||
3. 区间 + 置信度 + 不确定点是答案的一部分,任何展示形态不得省略。
|
||||
4. 单价只来自工具结果;总额只由代码汇总。
|
||||
5. 现有工作台在 v2 过门槛前不删不改语义。
|
||||
|
||||
## 9. 当前资产清单(截至本文日期)
|
||||
|
||||
已建成:agent 契约/工具层/循环/网关适配/skill 知识渲染/per-SKU runner(27 测试)、镜像与材质只读仓储、1688 中性摘要工具、板材开料工具、shadow 报表、trial 试跑脚本、生产真实案例(源氏木语 27 SKU)对照数据。
|
||||
|
||||
待建:价格回退工具(5.3 表)、case 级编排、EstimateCard 契约与前端、回测接入。
|
||||
|
||||
外部依赖:1688 新 key(当前过期,影响搜图与 A/B 实测,不影响 ①② 开发)。
|
||||
@ -78,13 +78,9 @@
|
||||
# 是否启用 OA 看板钉钉待办
|
||||
DINGTALK_TODO_ENABLED=false
|
||||
|
||||
# 钉钉开放平台 API 应用配置(待办、审批等服务端调用)
|
||||
DINGTALK_API_CLIENT_ID=
|
||||
DINGTALK_API_CLIENT_SECRET=
|
||||
|
||||
# 钉钉网页登录使用独立 OAuth 应用配置,不与 API 应用混用
|
||||
DINGTALK_LOGIN_CLIENT_ID=
|
||||
DINGTALK_LOGIN_CLIENT_SECRET=
|
||||
# 钉钉应用基础配置
|
||||
DINGTALK_CLIENT_ID=
|
||||
DINGTALK_CLIENT_SECRET=
|
||||
DINGTALK_CALLBACK_ORIGIN=
|
||||
|
||||
# 待办跳转回系统的基础地址
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 供应链产品全量档案字段来源对照表
|
||||
|
||||
更新时间:2026-10-16
|
||||
更新时间:2026-08-10
|
||||
|
||||
## 1. 合并与追踪规则
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
| `sales7d` | 7日日销 | | | `rpt.pm_stockMonitor.sevenAvgSale` | 按条码匹配最新记录 |
|
||||
| `sales15d` | 15日日销 | | | `rpt.pm_stockMonitor.fifteenAvgSale` | 按条码匹配最新记录 |
|
||||
| `purchasePrice` | 采购价 | `rpt.jst_sku.purchase_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空 |
|
||||
| `costPrice` | 成本价(聚水潭) | `rpt.jst_sku.cost_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空。DF 条码自 2026-10-16 起同样取本来源(原 `rpt.dws_sn_sales_full_info_di.goods_cost` 为金额口径,已停用) |
|
||||
| `costPrice` | 成本价(聚水潭) | `rpt.jst_sku.cost_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空 |
|
||||
| `costPriceSecondary` | 成本价(新品) | | `external.product_catalog_wide_v1.cost_price` | | 按档案条码精确匹配 `barcode_business_key`,只读取 `catalog_source = INTAKE_READY` 的新品行;匹配不到或来源为空时留空 |
|
||||
|
||||
## 3. JST_SKU 第一批字段覆盖率
|
||||
|
||||
@ -1,180 +0,0 @@
|
||||
# 停产下架服务器运行与上游数据依赖
|
||||
|
||||
更新日期:2026-10-26
|
||||
|
||||
## 1. 服务器每日入口
|
||||
|
||||
服务器入口:
|
||||
|
||||
```bash
|
||||
node scripts/run-supply-chain-discontinuation-daily.mjs
|
||||
```
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. 生成 Prisma Client。
|
||||
2. 同步虚拟云仓库存快照。
|
||||
3. 生成编码维度通知和库存预警。
|
||||
4. 通过服务器 Chrome 从聚水潭“店铺商品管理”导出完整链接并生成链接维度快照。
|
||||
5. 生成未下架监控与乐捐快照。
|
||||
|
||||
生产安装:
|
||||
|
||||
```bash
|
||||
sudo bash scripts/install-supply-chain-discontinuation-linux-timer.sh
|
||||
```
|
||||
|
||||
默认使用 systemd,每日北京时间 10:30 执行,`Persistent=true` 会在服务器恢复后补跑。
|
||||
服务名默认为 `furniture-discontinuation-daily`。
|
||||
|
||||
RPA 使用的 Python 必须显式可执行:
|
||||
|
||||
- 正式机默认 `/usr/bin/python3`,安装器会检查可执行权限并写入
|
||||
`JST_RPA_PYTHON=/usr/bin/python3`。
|
||||
- 如果正式机使用虚拟环境或其他路径,安装前设置
|
||||
`DISCONTINUATION_PYTHON_BIN=/明确路径/python3`。
|
||||
- 每日同步在连接聚水潭页面、开始导出前先执行 `python3 --version` 预检;
|
||||
路径不存在、不可执行或版本命令失败时立即终止,不会等到导出完成后才发现。
|
||||
|
||||
## 2. 数据源与生成责任
|
||||
|
||||
| 数据 | 服务器读取来源 | 应用侧生成方式 | 是否依赖个人电脑 |
|
||||
| --- | --- | --- | --- |
|
||||
| 商品状态、组合商品、库存 | `JianSun_Old.JianSun`、`JSReportReplica.rpt` | 服务端 SQL 只读,应用生成本地快照 | 否 |
|
||||
| 虚拟云仓库存 | `JianSun_Old.JianSun.dbo.jst_virtualstockStocksInfo` | 服务端脚本读取,应用生成本地日快照 | 否 |
|
||||
| 替换规则(PM) | `JSReportReplica.rpt.pm_replacementManage` | 服务端 SQL 只读 | 否 |
|
||||
| 替换规则(聚水潭) | `JSReportReplica.rpt.jst_sku_replace_rule` | 读取最新 `snapshot_date` | 否 |
|
||||
| 供应商剩余库存 | `JianSun_Old.JianSun.dbo.plm_productDiscontinuation` | 服务端 SQL 只读 | 否 |
|
||||
| 链接完整快照 | 聚水潭“店铺商品管理”前台导出 | 服务器 Chrome + RPA,分批导出后落本地链接快照 | 否(需服务器浏览器登录态) |
|
||||
| 链接数据库快照(诊断) | `JianSun_Old.JianSun.dbo.dm_jst_shop_goods_link_daily` | 只用于比对和排障,不作为每日全量写入源 | 否 |
|
||||
| 订单与订单明细 | `JSReportReplica.rpt.jst_Orders`、`jst_orderitem`、`jst_qmcust_orderlist*` | 服务端 SQL 只读 | 否 |
|
||||
| 产品档案、负责人、申诉审核状态 | 应用 PostgreSQL | 应用自身写入 | 否 |
|
||||
|
||||
## 3. 数据库链接快照仅用于诊断
|
||||
|
||||
`dm_jst_shop_goods_link_daily` 的真实字段包括:
|
||||
|
||||
- `run_date`
|
||||
- `shop_i_id`
|
||||
- `shop_sku_id`
|
||||
- `sku_id`
|
||||
- `online_status`
|
||||
- `stock_sync_status`
|
||||
- `imported_at`
|
||||
|
||||
该表缺少大量完整导出链接,不能作为链接维度最终数据源。2026-10-26 对比结果:
|
||||
|
||||
- 数据库快照:52,639 行,按条件过滤后匹配通知仅 1,868 条。
|
||||
- 聚水潭完整导出:246,480 条,全部匹配通知。
|
||||
- 数据库快照缺少 99% 以上完整链接,只能用于差异诊断。
|
||||
|
||||
诊断脚本带覆盖率保护:如果数据库快照结果不足已有链接的 50%,会拒绝覆盖。
|
||||
|
||||
可配置:
|
||||
|
||||
```text
|
||||
JST_DISCONTINUATION_LINK_MAX_AGE_HOURS=26
|
||||
```
|
||||
|
||||
手动核对:
|
||||
|
||||
```bash
|
||||
node_modules/.bin/tsx scripts/sync-supply-chain-discontinuation-links-from-dm.ts \
|
||||
--date=YYYY-MM-DD \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## 4. 上游任务归属
|
||||
|
||||
已确认此前完整链接由个人 Mac 的 launchd 每日 09:00 启动 RPA 生成,随后写入数据库。
|
||||
数据库日快照不是完整当前链接。
|
||||
|
||||
新的服务器方案:
|
||||
|
||||
- Linux 安装器同时安装 `furniture-jst-rpa-browser.service`。
|
||||
- Chrome 使用持久化 profile,并通过本机 CDP 端口供 RPA 使用。
|
||||
- 每日入口从服务器 Chrome 导出完整聚水潭数据。
|
||||
- 正常整单导出最多尝试 3 次,任一次成功立即结束;只有全部失败后才按每批
|
||||
10,000 个编码降级导出,避免页面偶发失败时立即产生大量分批记录。
|
||||
- 首次部署优先把已登录的聚水潭 profile 放到 `JST_RPA_PROFILE_DIR`,或在服务器图形/VNC 环境完成一次登录。
|
||||
- 服务器私有环境文件可配置 `JST_RPA_LOGIN_ID` 和
|
||||
`JST_RPA_LOGIN_PASSWORD`;持久登录态失效时 RPA 会自动填写账号密码登录页。
|
||||
凭据只能保存在权限为 `600` 的服务器共享 `.env`,禁止提交到 Git。
|
||||
- 自动登录遇到验证码、短信验证或密码错误时会明确失败并保留已有链接,不覆盖完整快照。
|
||||
|
||||
首次登录示例:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop furniture-jst-rpa-browser.service
|
||||
sudo env JST_RPA_HEADLESS=false \
|
||||
JST_RPA_PROFILE_DIR=/opt/furniture-3d-system-prod/shared/jst-rpa-profile \
|
||||
node scripts/launch-jst-rpa-browser.mjs
|
||||
```
|
||||
|
||||
在图形界面登录聚水潭后停止该进程,再启动 systemd 浏览器服务。
|
||||
|
||||
如需允许登录态失效后自动重新登录,在服务器私有环境文件
|
||||
`/opt/furniture-3d-system-prod/shared/.env` 中配置以下两项后重启每日服务:
|
||||
|
||||
```text
|
||||
JST_RPA_LOGIN_ID=<聚水潭登录账号>
|
||||
JST_RPA_LOGIN_PASSWORD=<聚水潭登录密码>
|
||||
```
|
||||
|
||||
不要把这些值写入仓库、聊天记录、普通日志或 systemd 命令行参数。
|
||||
正式部署入口会自动读取本机被忽略的
|
||||
`furniture-3d-system/.env.jst-rpa-auth`,并在发布时合并上述两项;也可通过
|
||||
`-JstRpaAuthFile` 指定其他权限受限的凭据文件。部署结束后临时凭据会被删除。
|
||||
配置后可只验证登录,不执行导出:
|
||||
|
||||
```bash
|
||||
node_modules/.bin/tsx scripts/jst-shop-goods-rpa.ts --action=login
|
||||
```
|
||||
|
||||
## 5. 重跑保护
|
||||
|
||||
通知:
|
||||
|
||||
- 按 `(barcode, statDate)` 原地 upsert,保持既有通知 ID 不变。
|
||||
- 同日重跑成功后,当天通知集合必须与最新计算结果完全一致;不再命中的旧通知
|
||||
会删除,其旧链接按外键级联删除。
|
||||
- 通知、链接清理和库存预警写入位于同一数据库事务。计算或写入失败时整体
|
||||
回滚,原有通知和链接保持不变。
|
||||
- 当天停产/退市商品整体为 0 时也走同一清理事务,不保留上一轮通知。
|
||||
|
||||
乐捐:
|
||||
|
||||
- 同日重跑不再先删除全部当天记录。
|
||||
- 已进入 `PENDING_REVIEW`、`APPROVED`、`REJECTED`、`RETURNED` 的记录保留。
|
||||
- 仅清理不再命中当前结果且仍为 `PENDING_APPEAL` 或 `LEFT_EMPLOYEE` 的旧记录。
|
||||
- 候选 ID 虽在事务外读取,最终 `DELETE` 会再次校验数据库中的最新状态;候选
|
||||
读取后刚提交的申诉不会被误删。
|
||||
- 清理和 upsert 位于同一数据库事务,失败时整体回滚。
|
||||
|
||||
链接导入:
|
||||
|
||||
- 旧链接删除、新链接解析和写入使用同一事务。
|
||||
- 最终有效写入为 0 时默认回滚并保留旧链接快照。
|
||||
- 只有业务明确确认当天正常零链接时,才可给导入命令追加
|
||||
`--allow-zero-links`,允许清空当天链接。
|
||||
|
||||
## 6. 文件交付
|
||||
|
||||
- 每日入口:`scripts/run-supply-chain-discontinuation-daily.mjs`
|
||||
- Linux 安装器:`scripts/install-supply-chain-discontinuation-linux-timer.sh`
|
||||
- 完整链接 RPA:`scripts/jst-shop-goods-rpa.ts`
|
||||
- 服务器浏览器:`scripts/launch-jst-rpa-browser.mjs`
|
||||
- 链接数据库诊断:`scripts/sync-supply-chain-discontinuation-links-from-dm.ts`
|
||||
- 通知重跑保护:`src/server/repositories/supply-chain-discontinuation-notice.repository.ts`
|
||||
- 乐捐重跑保护:`src/server/services/supply-chain-discontinuation-penalty-sync.ts`
|
||||
|
||||
## 7. 本地验证结果
|
||||
|
||||
2026-10-26 使用完整聚水潭导出验证:
|
||||
|
||||
- 完整导出:246,480 条链接。
|
||||
- 链接落库后共 246,480 条,涉及 304 个负责人。
|
||||
- 同日重跑通知后链接数量和通知 ID 未变化。
|
||||
- 未下架监控首次同步生成 516 条。
|
||||
- 未下架监控第二次同步仍为 516 条,无重复。
|
||||
- 自动化测试覆盖链接条件过滤、去重、新鲜度拒绝、通知/乐捐状态保护。
|
||||
@ -1,256 +0,0 @@
|
||||
# 停产下架管理数据源
|
||||
|
||||
更新时间:2026-10-20
|
||||
|
||||
## 1. 当前状态
|
||||
|
||||
停产下架管理已按新口径重新接入数据源:
|
||||
|
||||
- 通知下架-编码维度已接入并生成每日快照。
|
||||
- 通知下架-链接维度已接入并生成每日快照。
|
||||
- 库存预警已接入,读取当日 `ProductInventorySnapshot`。
|
||||
- 未下架监控与申诉已接入,读取聚水潭订单头及订单明细生成月度乐捐明细。
|
||||
- 页面、页签、表头、筛选、分页和导出能力已保留。
|
||||
- 四个页签均支持导出当前筛选结果:编码维度、链接维度、库存预警、未下架监控与申诉分别使用独立字段和文件名。
|
||||
- 页面查询接口只读取本地快照,不再实时访问 SQL Server。
|
||||
- 每日生成入口按北京时间生成当天快照;同日重跑只替换当天数据,历史日期不动。
|
||||
|
||||
## 2. 数据流边界
|
||||
|
||||
页面和查询接口只读本地落库结果,不直接访问外部库:
|
||||
|
||||
```text
|
||||
外部权威数据源
|
||||
-> 每日 10:30 同步/计算任务
|
||||
-> 本地快照及业务表
|
||||
-> 停产下架页面 API
|
||||
-> 页面筛选、展示和导出
|
||||
```
|
||||
|
||||
本地承接表:
|
||||
|
||||
| 数据类别 | 本地表 |
|
||||
| --- | --- |
|
||||
| 虚拟云仓库存 | `SupplyChainVirtualInventorySnapshot` |
|
||||
| 库存预警(按商品编码 + 仓库) | `ProductInventorySnapshot` |
|
||||
| 通知下架-编码维度 | `SupplyChainDiscontinuationNotice` |
|
||||
| 通知下架-链接维度 | `SupplyChainDiscontinuationNoticeLink` |
|
||||
| 未下架订单和乐捐 | `SupplyChainDiscontinuationPenalty` |
|
||||
|
||||
## 3. 权威数据源
|
||||
|
||||
### 3.1 简尚库存
|
||||
|
||||
来源:`JianSun.dbo.jst_inventoryInfo_view`
|
||||
|
||||
页面使用“公有可用数”口径:
|
||||
|
||||
```text
|
||||
公有可用数
|
||||
= 库存
|
||||
- 虚拟云仓库存
|
||||
- 订单锁定数(包含缺货仓订单占有数)
|
||||
```
|
||||
|
||||
- 虚拟云仓库存来自 `JianSun.dbo.jst_virtualstockStocksInfo`,按商品编码汇总。
|
||||
- 公有可用数小于 0 时按 0 处理。
|
||||
- 仓库名称通过 `JianSun.dbo.jst_warehouseInfo` 映射。
|
||||
- `三方-南昌樵舍仓` 作为京喜渠道仓单独汇总,不计入普通简尚库存。
|
||||
- 渠道专用仓库存不计入简尚主库存,单独返回到“渠道专用仓库存”和“专用仓备注”。
|
||||
- 库存预警的“库存所在仓库”只统计实际计入公有可用数且有正库存的普通仓。
|
||||
- 同一商品在多个仓库有库存时拆成多条明细,不合并仓库名称。
|
||||
- 虚拟云仓库存按各仓库存占比分摊到仓库明细,分摊后各行公有可用数合计与原商品汇总值一致。
|
||||
- 虚拟云仓先落到本地 `SupplyChainVirtualInventorySnapshot`,日任务只读本地快照,避免链接服务器偶发超时。
|
||||
- 日常通过 `scripts/sync-supply-chain-virtual-inventory-snapshot.ts` 从 `JianSun.dbo.jst_virtualstockStocksInfo` 同步;链接服务器不可用时,可用 `scripts/import-supply-chain-virtual-inventory-snapshot.ts` 导入页面导出文件作为兜底。
|
||||
|
||||
### 3.2 工厂库存
|
||||
|
||||
来源:`plm_productDiscontinuation`
|
||||
|
||||
- 使用 `remainingStock` 作为供应商剩余库存量。
|
||||
- 按 `skucode` 去重,空条码忽略;同一商品编码存在多条记录时,以 `createTime DESC, Id DESC` 最新创建的一条为准。
|
||||
|
||||
### 3.3 商品状态
|
||||
|
||||
商品状态以聚水潭源表 `rpt.jst_sku.other_5` 为准,只纳入实时状态为停产、退市的编码。
|
||||
|
||||
- 本地产品全量档案只用于补充 SKU、成本、名称等资料。
|
||||
- 本地档案同步较慢时,不能用档案中的旧状态决定当天停产/退市结果。
|
||||
- 若源表有、档案暂时没有的新停产编码,使用源表条码、货号和成本兜底进入计算。
|
||||
|
||||
### 3.4 组合商品
|
||||
|
||||
来源:
|
||||
|
||||
- `rpt.jst_comboProductInfo`:组合商品主表。
|
||||
- `rpt.jst_comboProductItem`:组合商品与子商品明细。
|
||||
|
||||
规则:
|
||||
|
||||
- 只检查 `enabled=1` 的组合商品。
|
||||
- 子商品数量 `qty>0` 才参与判断。
|
||||
- 组合中任一子商品为停产/退市,且该子商品的简尚库存 + 工厂库存 = 0,则组合商品进入通知下架。
|
||||
- 支持组合嵌套:若子商品本身也是组合,继续向下递归检查,并对循环引用做保护。
|
||||
- 组合商品自身库存仍按组合编码从简尚库存和工厂库存表读取,用于页面展示。
|
||||
|
||||
### 3.5 可替换商品
|
||||
|
||||
来源:`rpt.pm_replacementManage` 与 `rpt.jst_sku_replace_rule`,两张表按同一套货号继承和条码展开规则合并。
|
||||
|
||||
- `pm_replacementManage` 只读取 `delete=0` 且替换货号非空的数据;聚水潭表按启用/删除字段可用性自动过滤无效行。
|
||||
- 聚水潭替换表使用 `rpt.jst_sku_replace_rule`,只读取最新 `snapshot_date`;原编码取 `sku_id`,替换编码取 `data`,`data` 中逗号分隔的多个候选按顺序保留。
|
||||
- 同一原编码同时存在两张表规则时优先使用聚水潭替换表;聚水潭表不可读取时记录告警,并继续使用 `pm_replacementManage`。
|
||||
- 优先按完整商品编码匹配;完整编码没有规则时,按商品档案货号继承同货号其他条码的替换规则。
|
||||
- 兼容反向录入:若当前停产条码或其货号等于 `replaceSku`,则反向使用对应 `sku` 作为替换来源。
|
||||
- 同一原编码存在多条规则时,取 `createTime` 最新的一条。
|
||||
- 替换目标能在商品档案映射时,必须展开为具体 barcode;同货号有多个条码时优先销售、有库存、最近更新的条码。
|
||||
- 替换目标完全无法映射时,仍展示规则中的替换编码,但是否换图、区别点和价格差允许为空。
|
||||
- 是否换图取 `isSwapping`,1 为是。
|
||||
- 区别点取 `differentPoints`。
|
||||
- 价格差沿用现有页面口径:替换商品成本 - 原商品成本。
|
||||
|
||||
### 3.6 店铺链接
|
||||
|
||||
正式来源:聚水潭“店铺商品管理”完整前台导出(服务器 Chrome + RPA)。
|
||||
|
||||
- 每日编码维度生成后,服务器 Chrome 自动批量查询并导出完整链接。
|
||||
- 默认先执行单次全量导出,连续尝试 3 次;第 1、2 次失败后重置页面再重试,
|
||||
任一次成功立即结束;只有三次都失败后才切换为每批 10,000 个编码的分批导出。
|
||||
- 只保留 `online_status='是'` 且 `stock_sync_status='开启同步'` 的导出记录。
|
||||
- 全部批次成功后,在数据库事务中替换当天链接;失败时保留已有完整链接。
|
||||
- 最终有效写入为 0 时默认回滚;确有业务依据的正常零链接必须显式使用
|
||||
`--allow-zero-links`,避免异常空文件清空当天快照。
|
||||
- 每个平台店铺商品编码独立展示,不合并多个平台店铺商品编码,不加总线上库存。
|
||||
- 运营负责人取 `op_TargetManagement.ider`(ID负责人)。
|
||||
- 部门负责人取 `op_TargetManagement.currCater`。
|
||||
- 利润中心取 `op_TargetManagement.dept`。
|
||||
- 同一商品ID在负责人表中存在多条时,按 `realTime DESC, Id DESC` 取最新一条。
|
||||
- 最终结果落到本地 `SupplyChainDiscontinuationNoticeLink`,页面不直接读取 Excel 或大表。
|
||||
|
||||
链接维度页面字段顺序:
|
||||
|
||||
1. 统计时间
|
||||
2. 平台
|
||||
3. 店铺名称
|
||||
4. 商品ID
|
||||
5. 平台店铺商品编码
|
||||
6. 线上商品编码
|
||||
7. 线上库存
|
||||
8. 运营负责人
|
||||
9. 部门负责人
|
||||
10. 利润中心
|
||||
11. 处理说明
|
||||
12. 可替换商品编码
|
||||
13. 区别点
|
||||
14. 是否换图
|
||||
15. 价格差
|
||||
16. 渠道专用仓库存
|
||||
|
||||
前 10 项来自聚水潭完整导出和负责人表;后 6 项关联同一系统商品编码的编码维度快照。
|
||||
|
||||
历史数据源差异留档:
|
||||
|
||||
- 聚水潭前台导出:234,656 行、234,641 个唯一链接。
|
||||
- legacy 链接结果:779,412 行。
|
||||
- 完全匹配:234,337 个链接。
|
||||
- legacy 多余:545,075 个链接。
|
||||
- 仅使用 `jst_skumap` 主表:447,558 行。
|
||||
- 主表与前台精确匹配:182,276 个链接。
|
||||
- 主表漏掉前台链接:52,380 个。
|
||||
- 主表多出前台链接:265,282 个。
|
||||
- `jst_skumap.enabled=1` 无法完整复刻前台“库存同步状态 + 店铺链接是否上架”的当前视图。
|
||||
|
||||
`dm_jst_shop_goods_link_daily` 仅用于差异诊断:
|
||||
|
||||
- 2026-10-26 快照共 52,639 行,`imported_at` 为当天 10:18。
|
||||
- 同日聚水潭完整导出为 246,480 条;数据库快照匹配通知后只有 1,868 条,缺少 99% 以上完整链接。
|
||||
- 因此该表不能作为链接维度最终数据源。
|
||||
|
||||
数据库快照诊断命令:
|
||||
|
||||
```bash
|
||||
pnpm exec tsx scripts/sync-supply-chain-discontinuation-links-from-dm.ts --date=YYYY-MM-DD
|
||||
```
|
||||
|
||||
只做只读核对时加 `--dry-run`。脚本默认在结果不足已有链接的 50% 时拒绝覆盖。
|
||||
|
||||
Linux 每日任务(北京时间 10:30):
|
||||
|
||||
```bash
|
||||
sudo bash scripts/install-supply-chain-discontinuation-linux-timer.sh
|
||||
```
|
||||
|
||||
任务顺序为“虚拟云仓库存快照 → 编码维度通知快照与库存预警快照 →
|
||||
聚水潭完整链接导出 → 未下架监控与乐捐快照”。
|
||||
|
||||
每日任务启动时会先按当前 `prisma/schema.prisma` 重新生成 Prisma Client,
|
||||
并输出各数据源阶段日志,避免加载到旧客户端或无法判断卡在某一张外部表。
|
||||
系统不再依赖个人 Mac;服务器使用独立的 `furniture-jst-rpa-browser.service`
|
||||
和持久化 Chrome 登录态。
|
||||
RPA Python 路径由 `JST_RPA_PYTHON` 明确指定;Linux 安装器默认
|
||||
`/usr/bin/python3`,可用 `DISCONTINUATION_PYTHON_BIN` 覆盖。同步动作会在连接
|
||||
浏览器和开始导出前预检 Python,路径或执行权限有误时立即失败。
|
||||
正常整单导出次数默认由 `JST_RPA_ATTEMPTS=3` 控制,分批兜底大小默认由
|
||||
`JST_RPA_FALLBACK_BATCH_SIZE=10000` 控制。
|
||||
聚水潭账号密码只从服务器私有 `.env` 的 `JST_RPA_LOGIN_ID` 和
|
||||
`JST_RPA_LOGIN_PASSWORD` 读取;持久登录态有效时不会重复提交登录,
|
||||
失效时才自动填写登录页。凭据不得写入仓库或日志。
|
||||
|
||||
只补库存预警、不重跑通知和链接时:
|
||||
|
||||
```bash
|
||||
pnpm exec tsx scripts/generate-supply-chain-inventory-alert-snapshot.ts
|
||||
```
|
||||
|
||||
服务与定时器日志通过 `journalctl -u furniture-discontinuation-daily` 查看。
|
||||
|
||||
> 预策不作为任何数据源或参考流程;其数据正确仅因为每天人工导入了聚水潭前台文件。
|
||||
|
||||
### 3.7 订单
|
||||
|
||||
- 其他平台订单头:`jst_Orders`
|
||||
- 其他平台订单明细:`jst_orderitem`
|
||||
- 淘系订单头:`jst_qmcust_orderlist`
|
||||
- 淘系订单明细:`jst_qmcust_orderlist_item`
|
||||
|
||||
订单只用于“未下架监控与申诉”和乐捐计算,不参与通知编码或库存预警:
|
||||
|
||||
- 订单明细通过 `shop_sku_id` 与通知链接维度的平台店铺商品编码匹配。
|
||||
- 仅统计当月有通知下架的链接。
|
||||
- 排除订单状态 `Sent`(已发货)、`Delivering`(发货中)、`Split`(被拆分)、`Merged`(被合并)。
|
||||
- 仅当订单付款日期达到该链接首次通知下架日期后的第 2 天(含),才进入未处理明细;不按具体时分判断。
|
||||
- 页面默认统计日期为北京时间当天,可手动切换历史日期。
|
||||
- 未下架监控按“任务生成日期”归档,并实时计算到当前时间;通知后第 2 天起的所有后续订单都计入,不只是第 2 天当天。
|
||||
- 每日任务固定处理三天前的通知队列:23 日统计 20 日通知、22 日起流入的订单;24 日统计 21 日通知、23 日起流入的订单。
|
||||
- 同一订单命中同一平台店铺商品编码只计一笔;同一订单命中两个链接计两笔。
|
||||
- 乐捐唯一键为“内部订单号 + 平台店铺商品编码”,重复来源数据不会重复计费。
|
||||
- 运营可对自己的待申诉明细上传凭证并发起申诉;有业务管理操作权限的人员可审核通过、驳回或打回。
|
||||
- 申诉和审核均支持按链接批量处理:同一平台店铺商品编码下符合条件的订单一次覆盖。
|
||||
- 审核通过后原乐捐记录保留,但页面展示与月度汇总按 0 元计算,实现自动减免。
|
||||
- 负责人离职或无法匹配到员工目录在职记录时,状态显示“人员已离职”,乐捐金额按 0 元计算且不能申诉。
|
||||
- “人员已离职”可作为申诉状态筛选;筛选后可批量向部门负责人的钉钉个人工作通知推送处理提醒。
|
||||
- 未下架监控页顶部显示本月有效乐捐合计(¥10/单),审核通过的明细不计入合计。
|
||||
- 未下架监控支持个人列显示/拖拽排序、紧凑展示和筛选字段配置。
|
||||
|
||||
## 4. 业务口径
|
||||
|
||||
- 通知下架:商品状态为停产或退市,且简尚库存 + 工厂库存 = 0。
|
||||
- 京喜渠道判断:普通简尚库存、工厂库存和渠道专用仓库存均为 0,仅 `三方-南昌樵舍仓` 有库存时,处理说明为“仅京喜下架”;存在有效替换款时为“仅京喜替换”。
|
||||
- 南昌樵舍仓无库存或其他仓库仍有库存时,是否通知及替换判断继续沿用原逻辑;需要进入通知时,处理说明显示“全渠道下架”或“全渠道替换”。
|
||||
- 组合通知:组合商品任一子编码为停产/退市且合计库存为 0,整个组合进入通知下架。
|
||||
- 库存预警:商品状态为停产或退市,且公有可用数 + 工厂库存为 1 至 5;0 库存只进入通知下架。
|
||||
- 多仓商品先按商品编码判断是否进入预警,再拆成仓库明细;工厂库存没有仓库来源时单独展示为仓库为空的一条明细。
|
||||
- 通知后订单监控:付款日期达到该链接首次通知下架日期后的第 2 天(含)。例如 20 日通知,22 日全天的订单都计入。
|
||||
- 排除订单状态:已发货、发货中、被合并、被拆分。
|
||||
- 乐捐:按“订单 + 链接”组合计算,每组 10 元;同一订单命中两个链接计两笔。
|
||||
- 乐捐按月累计,次月 1 日开启新账期,历史明细保留。
|
||||
- 运营人员只能查看自己的订单和申诉数据。
|
||||
|
||||
## 5. 接入约束
|
||||
|
||||
- 新同步任务负责写入本地快照,页面 API 不承担跨库实时计算。
|
||||
- 编码维度、链接维度和库存预警必须使用同一统计日的库存口径。
|
||||
- 历史通知数据一经生成不得覆盖,每日任务只新增当日结果。
|
||||
- 同一统计日允许重跑并替换当天快照,其他日期保持不变。
|
||||
- 统计日期统一使用北京时间。
|
||||
- 页面查询不得直接访问任一外部数据源。
|
||||
@ -1,212 +0,0 @@
|
||||
# 清货管理(2026-10-20)
|
||||
|
||||
## 1. 页面与入口
|
||||
|
||||
- 页面:`/supply-chain/management/clearance`
|
||||
- 菜单:供应链中心 > 经营管理 > 清货管理
|
||||
- 五个页签:清货池、清货任务、清货分数、清货复盘、清货配置
|
||||
|
||||
## 2. 数据与边界
|
||||
|
||||
页面和接口只读取本地 PostgreSQL 快照/业务表,不实时访问 SQL Server。
|
||||
|
||||
| 数据 | 本地表 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 清货池 | `SupplyChainClearancePoolSnapshot` | 产品全量档案的月度快照;包含 `periodKey`、`generatedAt`;只有经管重新生成时才会更新 |
|
||||
| 清货池生成记录 | `SupplyChainClearancePoolGeneration` | 每个清货月份只有一条经管生成记录,保存生成时间、生成人和商品数 |
|
||||
| 清货任务 | `SupplyChainClearanceTask` | 按完整月份保存任务量、折扣、原因、审核状态;同月调整记录独立保留 |
|
||||
| 流转记录 | `SupplyChainClearanceTaskEvent` | 提交、通过、驳回、调整、运营确认移除 |
|
||||
| 复盘快照 | `SupplyChainClearanceReviewSnapshot` | 月度整体/运营中心/部门负责人/优先级/折扣维度 |
|
||||
| 销售出库进度 | `SupplyChainProductArchiveSalesPerformanceDaily` | 按条码汇总清货月份内 `shipQty`;页面对已确认任务实时覆盖进度,脚本持久化到任务 `clearedQty` |
|
||||
| 时间窗口 | `SupplyChainClearanceApplicationWindow` | 每个清货月份包含次月任务制定窗口和次月折扣调整窗口;清货周期始终为一个完整自然月 |
|
||||
| 清货池库存 | 聚水潭库存、PLM 清货表 | 经管生成时实时读取源表,计算结果落到清货池月度快照;页面不直接查询源表 |
|
||||
|
||||
清货池月度生成:
|
||||
|
||||
- 只有 `clearanceReview.edit` 权限的经管可以在「清货配置 > 时间窗口」中同时选择是否生成清货池。
|
||||
- 清货池页面标题说明会读取当前月份的生成记录,展示生成时间、生成人和生成方式;未通过配置生成或没有生成记录时不追加该说明。
|
||||
- 经管选择 `YYYY-MM` 清货月份后,系统按当前产品全量档案补充商品资料,并从聚水潭库存和 PLM 清货表重新计算库存。
|
||||
- 简尚总库存 = 大货仓实体库存 + 前置仓库存 + 专用仓库存;库存直接读取上游最新视图 `JianSun_Old.JianSun.dbo.jst_inventoryInfo_view`,四类仓库统一使用公有可用数 `qty - order_lock`。大货、前置、专用仓保留负数参与汇总,例如缺货仓的负库存会冲减大货仓;代发仓负数按 0。
|
||||
- 大货仓:仓库类型为 `成品专用仓-真实库存` 或 `成品发货仓-真实库存`,剔除新外贸仓、张小飞代发仓、济南外贸仓、南昌樵舍仓和 `简尚-运营` 开头的专用仓。
|
||||
- 前置仓:仓库名称包含 `南昌樵舍仓`。
|
||||
- 专用仓:仓库名称以 `简尚-运营` 开头。
|
||||
- 代发仓:仓库类型为 `成品代发仓-虚拟库存`,负数参与汇总,且结果小于 `77777`。
|
||||
- 简尚总库存 = 大货仓 + 前置仓 + 专用仓 - 最新虚拟云仓库存;云仓库存完全抵扣后为 0 的商品不进入清货池。
|
||||
- 供应商剩余库存读取 `rpt.plm_productDiscontinuation.remainingStock`,同一商品取 `state=2/3` 中 `updateTime, Id` 最新的一条。
|
||||
- 工厂总库存 = 供应商剩余库存 + 代发仓;已移除贴单发货仓。
|
||||
- 简尚 + 工厂总库存 = 简尚总库存 + 工厂总库存;清货池只写入和展示总库存大于 0 的商品。
|
||||
- 两清协议按 `rpt.plm_productDiscontinuation` 判断:存在任意状态记录、但没有 `state=2/3` 的有效库存记录时显示「工厂库存未统计」;PLM 完全无记录或已有有效库存记录时留空。
|
||||
- 清货池「日销」= 产品全量档案 `sales3d × 0.5 + sales7d × 0.3 + sales15d × 0.2`,结果保留两位小数,不取采购日销 `dailySales`;旧数据仅存在 7 日销时回退到 `sales7d`。
|
||||
- 清货池「最近入库时间」在简尚总库存为 0 时显示「简尚无库存」;工厂仍有库存时总库存和工厂库存照常保留,入库天数仍按真实采购入库日期计算。
|
||||
- 清货池生成排除二级分类为空和「其他 - 福利品」的商品,并单独排除编码 `GBGJ-MPWL01N126V01`。
|
||||
- 优先级规则和固定任务规则的条件值从当前清货池读取候选并支持多选;仅「包含 / 不包含」使用自由文本输入,「为空 / 不为空」无需填写值。
|
||||
- 清货池「停产时间」按商品编码关联 `rpt.plm_productDiscontinuationDetail_flow` 与 `rpt.plm_productDiscontinuation_flow`:仅取已完成的停产流程(`flowtype=14`、`flowStatus=1`)、有效停产明细(`prostatus=1`、`state=0`),以最新流程的 `applyTime` 填入年月日;销售状态为「销售」的商品无论是否存在历史停产流程都清空停产时间。
|
||||
- 清货池「利润中心」按产品档案的运营负责人 `opsHead`,在人员信息表 `SupplyChainManagementOwnerConfig.operationOwner` 中精确匹配后取 `profitCenter`;未匹配时留空。
|
||||
- 清货组合匹配只处理产品全量档案的单双包字段原本为「双包」或「三包」且有库存的商品;「非双包」和空值即使出现在组合明细中,也不改写包型,清货商品编码直接填当前商品编码。
|
||||
- 组合读取启用的 `jst_comboProductInfo` 和 `jst_comboProductItem`,并严格按档案包型匹配:双包只取 2 个不同商品编码的组合,三包只取 3 个不同商品编码的组合;1 个、4 个及以上或其他包型组合不参与。
|
||||
- 同一商品编码命中多个组合时只选择一个,且组合编码必须以 `ZH-` 开头并直接包含当前商品编码;`运营组合套装xxx`、`MKH-运营组合套装xxx` 等无法从编码识别组成的组合不使用。具体组合中优先选择所有组成商品数量均为 1 的组合,其次选择总数量更小、存在 A/B 对应件、无异常且编码稳定的组合。例如同时存在 `ZH-...A-2-...B-2` 和 `ZH-...A-1-...B-1` 时,必须选择后者。
|
||||
- 原有单商品明细行不新增、不拆分,`清货商品编码` 只填写选中的完整具体组合编码;不是 `ZH-` 开头或找不到具体组合时,填当前商品编码。
|
||||
- 对选中的组合递归检查组成商品:任一组件状态为停产或退市且总库存小于等于 0 时,组合状态标记「多包异常」,并记录首个异常原因;同月二次折扣调整不清空或重算这些组合字段。
|
||||
- 组合清货总量取所有组成商品「简尚+工厂总库存」的最小值;非组合商品取自身简尚+工厂总库存。组合所有组成商品的简尚库存都大于 0 但数值不一致时标记「多包库存不等」。
|
||||
- 可用天数按 `清货总量 / 日销` 向下取整;日销为 0 时显示「无销量」,日销为空时留空。
|
||||
- 清货池页面和导出中的日销最多保留两位小数;整数不补小数位,可用天数仍使用原始日销精度计算。
|
||||
- 「多包异常」商品禁止填写任务、申请折扣、推送草稿或由经管调整后通过;「多包库存不等」允许填写任务和申请折扣,但任务量上限仍使用组合最小清货总量。
|
||||
- 常规生成完成后,向钉钉个人通知发送一条汇总提醒,只说明「多包异常」和「多包库存不等」已生成及各自数量,不推送商品编码或组合明细;收件人从在职员工目录按姓名匹配,默认「卷帘大将」,可通过 `SUPPLY_CHAIN_CLEARANCE_COMBO_NOTIFICATION_RECIPIENT` 调整。二次折扣调整不重新打标,也不重复发送该通知。
|
||||
- 同一清货月份允许经管不限次数重新生成;再次生成时事务内覆盖该月生成记录和商品快照,未再次生成时数据保持不变。
|
||||
- 生成方式分为「常规生成」和「二次折扣调整」。常规生成重算完整商品池;二次折扣调整必须基于已生成月份,只更新现有商品的大货仓、前置仓、专用仓、代发仓、供应商剩余库存、简尚库存、工厂库存、总库存、日销(7 日销)和可用天数,商品集合、状态、负责人、利润中心、两清协议及其他字段保持不变。
|
||||
- 「清货配置」是紧邻「清货复盘」的独立页面,不再以清货池工具栏弹窗展示,且仅对具备 `clearanceReview.edit` 权限的经管显示;页面包含「时间窗口」「优先级规则」「固定任务规则」「折扣限制」四个子页签。优先级和固定任务规则由经管人工维护并持久化到 `SupplyChainClearanceConfig`,每条规则填写目标值、多个条件以及条件关系(同时满足 `ALL` / 满足任一 `ANY`);条件关系通过点击条件之间的「且 / 或」切换。同一商品命中多条规则时采用序号最小的一条。
|
||||
- 「折扣限制」按商品编码或 SPU 人工维护禁止申请折扣清单,匹配不区分大小写,重复配置自动合并;命中后清货池折扣输入禁用,保存草稿、提交、审核、申请人确认及经管强制推送都会在服务端再次校验,不能通过接口绕过。
|
||||
- 优先级字段是人工自定义的优先级名称,不使用固定的高/中/低字典;规则值和兜底值都由经管自由命名。
|
||||
- 优先级规则可判断销售状态、商品状态、分类、品牌、供应商、单双包、合作状态、负责人、商品编码、货号、各类库存、日销、可用天数、成本价、清货商品编码、清货总量、多包标记、利润中心、两清协议、停产时间和入库天数;支持等于、不等于、包含、不包含、数值比较以及为空/不为空。
|
||||
- 有预设值的文本字段支持勾选多个值;等于和包含在任一选中值命中时成立,不等于和不包含在避开全部选中值时成立。数字字段及自由文本字段保持单值输入。
|
||||
- 优先级规则只在常规生成清货池时计算并写入 `extra.priority`,同时保留命中的规则 ID 和序号;二次折扣调整不重算优先级。未命中任何规则时优先级留空。
|
||||
- 固定任务规则使用安全算术公式,可引用简尚库存、工厂库存、总库存、日销、可用天数、清货总量、成本价、入库天数等数值字段,支持数字、括号和加减乘除;只解析受控字段和运算符,不执行任意代码。公式结果向下取整,字段无数值或计算无效时固定任务量留空。
|
||||
- 优先级和固定任务配置都支持选填兜底值:当所有规则均未命中时,使用人工填写的兜底优先级或兜底公式;留空时保持为空。
|
||||
- 常规生成清货池时把首个命中公式的结果写入 `extra.fixedTaskQty`,同时保留公式、规则 ID 和序号;二次折扣调整不重算。
|
||||
- 页面可通过「清货月份」切换已经生成的月度清货池。
|
||||
|
||||
历史导入和诊断脚本:
|
||||
|
||||
- 当前全量映射:`npm run import:clearance-pool-archive`
|
||||
- 把产品全量档案全部行写入清货池,销售状态取 `productStatus`;
|
||||
- 《10月清货池》38 个业务抬头覆盖到对应字段,其余抬头保存在 `extra` JSONB;
|
||||
- 商品名称和品牌作为商品档案列保留,可在列设置中显示;
|
||||
- 已有快照中的库存值优先保留,其余使用档案库存字段。
|
||||
- 停产/退市手工诊断同步:`npm run sync:clearance-pool:discontinued`。该命令会重写指定日期的清货池快照,只用于单独核对,不纳入清货池库存刷新流程。
|
||||
- 使用 `rpt.jst_sku` 的实时停产/退市状态;
|
||||
- 简尚库存取 `jst_inventoryInfo_view`,按现有规则排除专用仓并扣减虚拟云仓库存;
|
||||
- 工厂库存取 `plm_productDiscontinuation` 中同一商品编码最新创建记录的 `remainingStock`;
|
||||
- 只写入总库存 > 0 的款式。
|
||||
- 本机导入(已有合并表时):`npm run import:clearance-pool -- --file=<csv> --date=YYYY-MM-DD`
|
||||
- 复盘快照导入:`npm run import:clearance-review`
|
||||
- 「清货配置 > 时间窗口」默认选中当前正在查看的清货月份,并提供「不生成」「常规生成或重新生成」「二次折扣调整生成」三个选项。
|
||||
- 当前正式生成只保留产品全量档案字段,以及重新计算的库存、7 日销、可用天数、利润中心和两清协议;清货商品编码、清货批次、优先级、固定任务量等清货专有字段继续留空,等待后续逻辑接入。
|
||||
- 档案基线重建:`npm run reset:clearance-pool:archive -- --date=YYYY-MM-DD --apply`;只更新现有快照字段,不新增或删除商品。
|
||||
- 已处理库存实验命令 `sync:clearance-stock:stock-monitor` 当前暂停,不作为清货池正式库存来源。
|
||||
|
||||
## 3. 任务流转
|
||||
|
||||
清货任务默认列按经管提供抬头展示:统计月、spu、清货商品编码、清货价、成本价、折扣预警、价格类型、运营负责人、部门负责人、利润中心、期初任务量、真实任务量、发货数量、任务清货进度、退款数量、简尚库存、工厂库存、总库存、日销、可用天数、全国运费、清货批次、优先级。任务页只展示经管已推送的 `APPROVED` 明细,因此不再展示任务审核状态;统计月固定为第一列。
|
||||
|
||||
任务计算口径:
|
||||
|
||||
- 清货商品编码映射当前任务月份清货池的 `extra.clearanceCode`。
|
||||
- 成本价取产品全量档案 `costPrice`;如果清货商品编码是组合款,则递归汇总组合明细中所有组成商品的产品档案成本,并按组成数量计算。
|
||||
- 清货价 = 成本价 × 申请折扣;折扣为空时直接展示成本价。
|
||||
- 折扣预警仅在存在折扣时计算:任务清货进度小于 50% 标记「异常」,大于等于 50% 标记「正常」。
|
||||
- 期初任务量取同一申请人该条任务最早一次草稿/提交记录中的任务量,不随审核调整覆盖。
|
||||
- 任务清货进度 = 发货数量 / 真实任务量。
|
||||
- 简尚库存、工厂库存、7 日销取每日更新的产品全量档案;总库存 = 简尚库存 + 工厂库存;可用天数 = 总库存 / 7 日销并向下取整,日销为 0 时显示「无销量」。
|
||||
- 运营负责人、部门负责人、利润中心、清货批次和优先级映射当月清货池数据;发货数量和退款数量来自销售出库日数据;清货价、价格类型和全国运费在接入各自数据源前按上述规则展示或保持为空。
|
||||
|
||||
- 运营在清货池填写并保存任务申请,可以查看清货池全部商品;只有清货经管可以审核申请,审核权限走 `clearanceReview.edit`。申请人确认和正式任务推送分别校验操作/审核权限。
|
||||
- 同一商品、同一期次允许不同运营分别保存自己的任务申请;草稿按申请人隔离,互不覆盖。经管可查看该编码下全部申请;普通运营只能读取和看到本人申请、审核结果,不能看到其他人的申请内容、审核状态或状态计数。
|
||||
- 审核状态流转为 `DRAFT/PENDING 待审核` → `READY 已审核` → `APPROVED 已确认`。经管审核通过后,申请人只看到自己的“已审核”结果;点击“确认无误”后自动写入清货任务明细并切换到清货任务页,无需经管再次推送。未确认的 `READY` 申请仅经管有权限强制推送。任务仍按申请人拆分,`spu负责人` 显示实际申请人。
|
||||
- 清货任务页只做数据展示,不承载审核或调整操作。
|
||||
- 清货池展示任务流程和「全部任务 / 待审核 / 已通过 / 按调整后通过 / 已驳回」审核结果快捷按钮;经管看到全部申请统计,普通运营仅看到本人申请统计。点击后筛选清货池明细,导出时沿用同一结果。清货任务页不展示利润中心汇总、状态分段按钮、重复搜索框或记录类型切换;任务周期、筛选项、刷新、导出和列设置合并为同一行。任务明细每页 50 条,表格下方使用紧凑分页控件展示总条数、本页条数和上一页/下一页。没有任务时仍保留完整表头,只在表体显示原有空态提示。
|
||||
- 清货池任务流程和状态概览并入清货月份栏:任务流程压缩为单行步骤条,销售、退市、停产各自展示编码数量、简尚库存和总库存,长期滞销库存当前显示待配置;窄屏时状态卡片自动换行。指标按整个生成月份汇总,不随筛选条件变化,宽屏展示状态卡片后为筛选和明细释放纵向空间。
|
||||
- 清货池明细最右侧为“审核结果”列。待审核、已通过、按调整后通过、已驳回分别标识;已驳回结果附带驳回原因。经管可点击结果查看申请详情;申请人端仅显示本人结果,并在审核通过时突出显示“确认审核结果”。申请人确认后自动跳转清货任务页;强制推送和自动确认都会刷新清货池、审核计数与任务明细。
|
||||
- 审核弹窗顶部展示商品编码、清货商品编码、SPU、运营负责人、部门负责人、利润中心、清货批次、商品状态、优先级、简尚/工厂/总库存、清货总量、7 日销、可用天数、停产时间和入库天数,并展示同商品编码最近月份的任务量、折扣、完成量和完成率。每条申请可“通过原申请”“调整后通过”或“驳回”;调整后通过必须修改任务量或折扣,调整原因选填,驳回必须填写驳回原因。待审核弹窗不提供强制推送。
|
||||
- 经管强制推送只处理 `READY`(已审核未确认)记录,入口保留在清货池明细下方,一次推送当前月份和任务期次的全部未确认记录;申请人已确认的记录会自动进入清货任务,不在强制推送范围内。
|
||||
|
||||
```text
|
||||
经管选择清货月份并确认次月任务制定、次月折扣调整时间(起止时间)
|
||||
-> 只有在对应时间窗口内,运营才能填写任务量 + 折扣 + 原因
|
||||
-> 三个字段完整后离开输入框自动保存为 DRAFT 草稿
|
||||
-> DRAFT / PENDING 待审核
|
||||
-> 经管审核通过 -> READY 已审核
|
||||
-> 申请本人确认无误 -> APPROVED 已确认,自动进入清货任务
|
||||
-> 或由经管强制推送未确认的 READY 申请 -> APPROVED 已确认
|
||||
-> 经管驳回 -> REJECTED(必须填写原因,仅原申请人可见)
|
||||
-> 运营确认移除:REMOVED
|
||||
-> 经管调整任务量/折扣后重新审核
|
||||
```
|
||||
|
||||
同一月份、同一商品、同一种记录类型只允许一条非 REMOVED 任务。调整窗口开放后,即使已有月初确认任务,也可以为同款创建独立的调整记录,初始任务不覆盖、不删除。
|
||||
|
||||
分数和复盘按商品去重:同一商品存在初始任务和调整记录时,只取最新一期已确认记录参与任务量、完成量和维度汇总,历史记录继续保留用于追溯。
|
||||
|
||||
销售状态商品不允许申请折扣:清货池折扣和折扣原因都固定为空,只允许填写任务量;草稿保存、批量推送、经管调整接口均强制校验,不能通过接口绕过。
|
||||
|
||||
非销售状态商品申请折扣时必须先填写任务量,填写折扣时必须填写原因;没有折扣需求时折扣留空即可,可以只填写任务量。
|
||||
|
||||
草稿单元格在输入过程中只更新本地引用,失焦时立即提交保存,避免逐字重绘整张 50 行明细;保存接口并行校验时间窗口和库存快照,并并行查询本人草稿及有效任务。折扣输入保留 200ms 防抖,保存期间继续修改会在首次保存完成后自动补存最新内容。
|
||||
|
||||
时间窗口规则:
|
||||
|
||||
- 只有 `clearanceReview.edit` 可以配置和确认时间窗口(「清货配置」页签只对经管显示);选择生成时,窗口保存成功后再执行清货池生成。
|
||||
- 未配置、未开始或已结束时,清货池进入只读,运营不能保存草稿,经管不能推送正式任务;后端会强制校验。经管审核和申请人确认审核结果不受该限制。
|
||||
- 清货月份始终是完整自然月,任务和后续折扣调整都归属该清货月份。
|
||||
- 次月任务制定:目标清货月前一个月的最后 7 天。
|
||||
- 次月折扣调整:目标清货月第 8 天起的第一个工作日,自动跳过周末;法定节假日可由经管人工调整。
|
||||
- 「清货配置」页面会按上述规则自动填入建议时间,经管可修改后确认。
|
||||
|
||||
执行进度:
|
||||
|
||||
- 清货任务页实时读取本地销售出库日数据,口径为清货月份内 `shipQty` 合计,仅对 `APPROVED` 任务生效。
|
||||
- 每日销售出库同步完成后会自动回写上月和当月任务进度;也可单独执行 `npm run sync:clearance-progress -- --period=YYYY-MM`,不传月份时同步当月。
|
||||
- 回写内容为 `clearedQty`、`lastProgressAt`;销售源未覆盖目标月份时不覆盖上次进度。
|
||||
- 本地销售出库数据未覆盖目标月份时保留上次持久化进度,页面会显示覆盖到哪一天。
|
||||
|
||||
草稿接口:
|
||||
|
||||
- `POST /api/supply-chain/clearance/tasks/draft`:表内自动保存(同一商品同一记录类型重复保存会更新同一条草稿)。
|
||||
- `POST /api/supply-chain/clearance/tasks/push`:把勾选的 DRAFT 批量转为 PENDING,并写 SUBMIT 流转记录。
|
||||
- `GET/POST /api/supply-chain/clearance/window`:读取指定清货月份的次月任务制定/折扣调整时间;经管保存起止时间并确认。
|
||||
- `POST /api/supply-chain/clearance/tasks/:id/review`:直接通过或驳回。
|
||||
- `POST /api/supply-chain/clearance/tasks/:id/approve-adjust`:待审批任务调整任务量/折扣后通过。
|
||||
- `POST /api/supply-chain/clearance/tasks/:id/adjust`:经管调整已驳回任务后确认。
|
||||
- `POST /api/supply-chain/clearance/generate`:经管常规生成或重新生成清货池;二次折扣调整模式仅刷新库存、日销和可用天数。
|
||||
|
||||
## 4. 三张明细表的通用能力
|
||||
|
||||
清货池、清货任务、清货分数均支持:
|
||||
|
||||
1. 右上角齿轮「显示设置」:与限价管理一致,统一提供紧凑展示、列设置、筛选设置三个入口。
|
||||
2. 个人列设置:搜索、勾选要显示的字段,上下移动字段顺序;列宽直接在明细表头右侧拖动手柄调整,松手后按用户保存到浏览器本地。
|
||||
3. 顶部筛选:在「显示设置 → 筛选设置」中选择要展示的筛选项后,字段以横向下拉框显示;候选值可搜索并直接点击多选,也可粘贴或输入空格、逗号、分号、顿号、换行分隔的批量值后回车应用。点击或批量输入均使用精确等于匹配,避免选择「双包」时误包含「非双包」。同一字段多值按 OR 组合,不同字段按 AND 组合,并以标签展示已选条件、逐项清除或清空全部。清货池筛选在服务端执行,覆盖普通字段、日期、数值和 `extra` JSONB 抬头;任务量、折扣、原因按当前清货月份的动态任务数据筛选,任意人工配置字段的候选值通过 `GET /api/supply-chain/clearance/options` 加载。
|
||||
4. 导出 Excel:清货池提供「导出明细」和「导出明细(含图片)」两个入口,统一放在筛选行右侧。普通明细移除图片列并生成 `清货池-明细` 文件;含图片导出会强制加入图片列、抓取商品图片,并生成 `清货池-含图片` 文件。两者都按当前列顺序、列宽和筛选结果导出,并按每页 5,000 条自动循环读取全部匹配记录,不再截断为首页。
|
||||
5. 横向滚动:明细底部固定显示可拖动的横向滚动条,位于分页操作区上方;同时保留触控板横向滚动。
|
||||
|
||||
清货池默认展示《10月清货池》38 个抬头,任务量、折扣、原因、任务状态固定放在列顺序末尾;销售状态单独作为业务字段展示。
|
||||
清货池顶部不再重复提供商品搜索框,统一通过下方人工配置筛选项查询。
|
||||
|
||||
## 5. 权限
|
||||
|
||||
页面可见:`businessManagement.view`
|
||||
|
||||
| 动作 | 权限节点 |
|
||||
| --- | --- |
|
||||
| 清货池定任务、提交折扣申请、运营确认移除 | `clearanceOperation.edit` |
|
||||
| 经管通过、驳回、调整任务 | `clearanceReview.edit` |
|
||||
| 经管生成或重新生成清货池 | `clearanceReview.edit` |
|
||||
| 查看清货池、任务、分数、复盘 | `businessManagement.view` |
|
||||
|
||||
默认角色:
|
||||
|
||||
- `operation` 角色默认获得清货运营操作权限;
|
||||
- `management` 角色默认获得清货经管审核权限;
|
||||
- 管理员默认拥有全部权限。
|
||||
|
||||
## 6. 当前版本边界
|
||||
|
||||
- 清货任务的 `clearedQty` 已接入本地销售出库汇总;页面实时展示,持久化由 `sync:clearance-progress` 完成,生产定时任务仍需按部署环境配置。
|
||||
- 所有旧导入、重置和库存同步脚本仍会拒绝直接修改已生成月份;更新必须通过经管「清货配置 > 时间窗口」中的生成选项执行。
|
||||
- 清货分数先使用示例权重:完成度 60 + 毛利率 25 + 货值回收 15;历史周期使用复盘快照计算,当月任务只有完成度可计算。
|
||||
- 清货分数档位按完成率上下限区间匹配,上下限均可配置为包含或不包含;上限留空表示无上限,区间不可重叠,旧“最低完成率”配置会自动转换为连续区间。
|
||||
- 清货复盘优先使用 `2026年4月清货复盘.xlsx` 导入的历史快照;没有快照的月份由最新一期已确认任务 + 本地销售出库进度自动生成。
|
||||
- 当月自动复盘暂不计算毛利率、结算毛利率和货值回收率,字段保持为空,待结算口径接入后再补齐。
|
||||
- 清货池导出单次最多 5,000 条,按当前筛选范围导出;超出时在「导出说明」中提示实际总数。
|
||||
- 本机已导入 2026-10-20 全量清货池快照(79,959 个产品,销售 51,619 / 停产 21,010 / 退市 3,548 / 未标记 3,782)。
|
||||
|
||||
## 7. 验证命令
|
||||
|
||||
```bash
|
||||
npm run type-check
|
||||
npm test -- src/server/services/__tests__/supply-chain-clearance.service.test.ts
|
||||
npm test -- src/server/services/__tests__/supply-chain-clearance-review.builder.test.ts
|
||||
npm test -- src/lib/clearance-filters.test.ts
|
||||
npm test -- src/lib/supply-chain-permission-settings.test.ts
|
||||
```
|
||||
|
||||
全仓 `type-check` 当前存在与本次改动无关的历史错误(`node_modules.pnpm-mixed`、`src/hooks/usePerformance.ts` 等);本次新增文件无 TypeScript 报错,ESLint 无 error。
|
||||
@ -1,289 +0,0 @@
|
||||
# 物流到货签收时长看板 · 订单明细每日快照
|
||||
|
||||
## 为什么要做
|
||||
|
||||
看板的全部数据来自远端只读报表视图 `rpt.jst_order_express_details`(外加 `jst_qmcust_orderlist_item` 条码明细和 `SupplyChainProductArchive` 产品档案富化)。
|
||||
该视图是远端对象,一次扫描通常以分钟计,页面每次刷新、翻页、下钻、导出都要重新扫一遍,看板因此很难"高效运转"。
|
||||
|
||||
做法:每天把看板口径的订单明细物化到本地 Postgres,看板优先读本地快照,只有快照覆盖不到时才回落实时数据源。
|
||||
|
||||
## 数据与表
|
||||
|
||||
### 条码/供应商/采购的匹配来源(两套互补的订单表)
|
||||
|
||||
报表库把订单明细拆成了两套**互不重叠**的表,只看一套会漏掉近一半订单的条码:
|
||||
|
||||
| 表 | 覆盖平台 | 规模(实测) |
|
||||
| --- | --- | --- |
|
||||
| `rpt.jst_qmcust_orderlist` / `rpt.jst_qmcust_orderlist_item` | 淘宝天猫、阿里巴巴(1688)、淘工厂、天猫供销 | 408 万 / 576 万行 |
|
||||
| `rpt.jst_Orders` / `rpt.jst_OrderItem` | 京东商城、京东厂家直送、拼多多、头条放心购(抖音)、得物、唯品会、线下、微信视频号、小红书 | 124 万 / 175 万行 |
|
||||
|
||||
看板取条码时会**同时查两张明细表并用 `INNER_ORDER_ID`(即 `o_id`)合并**(`listItemRows`,两表按订单号各发一批查询后合并结果)。修复前只查 `jst_qmcust_orderlist_item`,导致京东/拼多多/抖音/得物/唯品会的订单条码全空;修复后新生成的快照条码匹配率为 **100%**(此前整体 52.3%)。
|
||||
|
||||
> 注:`rpt.jst_salesOutboundInfo`(销售出库单头)可以读取,但只有 `o_id / so_id / l_id / shop_name / io_id / qty` 等出库单头字段,没有 SKU 列,不能单独提供条码。
|
||||
|
||||
纯新增两张表(迁移 `prisma/migrations/20260928120000_add_logistics_arrival_signing_duration_snapshot`):
|
||||
|
||||
| 表 | 作用 |
|
||||
| --- | --- |
|
||||
| `LogisticsArrivalSigningDurationSnapshotRun` | 批次元数据:`snapshotDate`、`windowFrom`、`windowTo`、`status`(RUNNING/COMPLETED/FAILED)、`rowCount`、`message` |
|
||||
| `LogisticsArrivalSigningDurationSnapshot` | 明细行:与看板 09 订单明细一一对应,含条码/供应商/采购/`productItems` 等快照时点已固化的富化结果 |
|
||||
|
||||
要点:
|
||||
|
||||
- 只有 `status = COMPLETED` 的批次对读路径可见,写入中途或失败的批次不会被读到。
|
||||
- 行唯一键 `(snapshotDate, rowKey)`,`rowKey` 与下钻键一致(内部单号 + 线上订单号 + 物流单号 + 签收时间),重复执行用 `skipDuplicates` 幂等。
|
||||
- `(snapshotDate, signTime)` 有索引,按签收日期区间取数走索引。
|
||||
|
||||
## 生成任务
|
||||
|
||||
```bash
|
||||
# 默认:快照日期=当天(Asia/Shanghai),窗口=最近 62 天(覆盖本月与上月),保留最近 2 批
|
||||
npm run sync:logistics-arrival-signing-duration-snapshot -- \
|
||||
--apply --confirm=sync-logistics-arrival-signing-duration-snapshot \
|
||||
--expected-database-identity="<本地库身份>" \
|
||||
--expected-source-database-identity="<远端报表身份>"
|
||||
```
|
||||
|
||||
可调参数:`--snapshot-date=YYYY-MM-DD`、`--window-days`、`--chunk-days`(默认 7,按天分批抓取以控制内存)、`--batch-size`(默认 2000)、`--retention`、`--force`(当天批次已完成时强制重建)。
|
||||
|
||||
编排逻辑在 `src/server/services/logistics-arrival-signing-duration-snapshot.service.ts`:
|
||||
|
||||
1. 当天批次已 `COMPLETED` 且未 `--force` → 直接跳过;
|
||||
2. 标记 `RUNNING` 并清空当天旧行;
|
||||
3. 按 `chunk-days` 分片读远端并写入本地,同时记下真正抓到的最新签收时间 `dataThrough`;
|
||||
4. 全部成功才标记 `COMPLETED`(连同 `dataThrough`),随后按 `retention` 清理更早批次;任一分片失败则标记 `FAILED` 并保留错误信息,读路径会自动继续用上一批快照;
|
||||
5. 收尾关闭远端连接池(一次性任务必须关闭,否则进程挂在事件循环上)。
|
||||
|
||||
生产定时(systemd timer,默认北京时间 03:00):
|
||||
|
||||
```bash
|
||||
sudo bash scripts/install-logistics-arrival-signing-duration-snapshot-timer.sh
|
||||
```
|
||||
|
||||
首次安装会生成 `$BASE/shared/logistics-arrival-signing-duration-snapshot.env` 模板,需要填两个身份校验值(缺省为空会拒绝写入)。可用环境变量覆盖:`LOGISTICS_SNAPSHOT_CALENDAR`、`LOGISTICS_SNAPSHOT_WINDOW_DAYS`、`LOGISTICS_SNAPSHOT_RETENTION`。
|
||||
|
||||
## 读路径规则
|
||||
|
||||
`src/server/repositories/logistics-arrival-signing-duration.repository.ts` 的每个读方法都先判断快照是否可用:
|
||||
|
||||
- 取最近一批 `COMPLETED` 快照;请求区间**完全落在快照的实际覆盖范围**内才读快照(否则回落实时视图,避免返回不完整结果)。覆盖上限 = `min(windowTo - 1, dataThrough 当天)`:
|
||||
- `windowTo` 是跑批当天,当天还没签收完,所以只能信任到它前一天;
|
||||
- 再用批次记录的 `dataThrough`(该批真实最大签收时间)兜底,避免数据源滞后时把不完整的一天当成完整。
|
||||
- 例:一批 10-08 生成、数据只到 10-08 05:30 的快照,对 `10-01~10-08` 不再命中,看板/导出自动改用实时源,不会少算 10-08。
|
||||
- 命中快照时返回值带 `source: 'snapshot'` 与 `snapshotDate`,页面头部显示"快照数据 · 日期";未命中为 `source: 'live'`,显示原来的"数据更新 · 时间";
|
||||
- 快照表还没迁移或还没有任何批次时,`findLatestCompletedRun()` 返回 null(并吞掉异常),整条链路自动等价于改造前行为。
|
||||
|
||||
因此在以下情况仍会走实时数据源(较慢,但结果正确):请求区间超出窗口、任务当天尚未跑完或失败、本地还没生成过快照。
|
||||
|
||||
## 指标口径(整区间精确 vs 样本)
|
||||
|
||||
命中快照时,**整块看板都是整区间精确值**,与 2,000 条样本彻底解耦:
|
||||
|
||||
| 看板块 | 精确口径实现 |
|
||||
| --- | --- |
|
||||
| 经营总览(6 个 KPI) | `aggregateDashboard()` 一次扫描聚合 |
|
||||
| 履约环节拆解(5 环节平均/中位/P90) | 同上,`percentile_cont` 直接给出分位数 |
|
||||
| 发货→签收时效分布 | 同上,4 个区间计数 |
|
||||
| 付款→签收 / 付款→发货 多维经营分析(01–08) | `aggregatePayToSignAnalysis()` / `aggregatePayToSendAnalysis()`,按签收日期 + 采购/供应商/条码(`jsonb_array_elements` 展开 `productItems`)+ 物流公司/发货仓/平台/店铺分组 |
|
||||
| 发货仓 / 物流公司 超时排名 | `aggregateThresholdRankings()` |
|
||||
| 09 订单明细下钻 | `listDrilldownRows()`:按维度条件下钻,**列出全量匹配订单**并给出真实总数与页数 |
|
||||
|
||||
口径一致性由三点保证:筛选条件统一由 `snapshotFilterConditions()` 派生(Prisma 与原始 SQL 共用);比率/取整统一走 lib 的 `buildLogisticsArrivalSigningDurationOverview`、`buildPayToSignMetrics`、`buildPayToSendMetrics`;排序统一走 `comparePayToSignGroups` / `comparePayToSendGroups` / `compareLogisticsArrivalSigningDurationThresholdRows`。每个聚合块都用"把整区间明细喂给样本路径同样函数"的方式做过逐字段比对(MATCH)。
|
||||
|
||||
### 看板顺序与联动链路
|
||||
|
||||
### 履约环节拆解:目标值与看板宽度
|
||||
|
||||
### 经营总览:平均到货时长卡片与异常下钻
|
||||
|
||||
- 签收订单后面新增 **付款→签收平均到货时长** 卡片:主值是该环节平均时长,附行给出 **本月目标**(上月平均 × 98%)与 **目标达成率**,与履约环节拆解同一套口径。
|
||||
- **缺少关键时间 / 时间异常** 两张卡片可点击:点击后用 `{ kind: 'anomaly', value: 'missing' | 'invalid' }` 条件下钻,09 订单明细列出对应订单;再点一次取消。判定口径与总览一致——缺少关键时间 = 四个时间任一为空;时间异常 = 四个时间齐全但至少一个环节时长算不出来(时间顺序异常)。
|
||||
- **自动滚动只保留给经营总览的异常卡片**(卡片上写着"点击看明细"):点它们会把 09 订单明细滚到视口顶部(`detailBoardRef` + `scrollIntoView`,按"下钻签名"去重,不会在翻页时反复滚动)。**经营对象维度(平台/店铺/发货仓/物流/采购/供应商/商品/签收日期)的点选不再自动滚动**——那些看板是连着看的,一滚到底就没法接着看其他维度、还得手动往上拉;下钻筛选本身照常生效,09 明细就在原位置更新。
|
||||
- 为保证"卡片数字 = 点击后的明细条数",**命中快照时经营总览始终按整区间精确统计**(`aggregateDashboard(..., includeStages=false)`,只算总览不算分位数);超出精确上限的大区间里,只有履约环节拆解/时效分布/经营分析/超时排名退回样本,页面脚注会写明。
|
||||
|
||||
### 经营总览视觉层次与看板工具条
|
||||
|
||||
- 经营总览从"7 张卡一张网格"改为**两组网格**:5 张履约指标卡一排(`--sc-kpi-cols`,标准 5 / 紧凑 3 / 展开 2),2 张数据质量卡单独成排(`--sc-kpi-alert-cols`,标准 2 / 展开 1)。7 张卡在常见宽度下不再是 5+2、第二排右侧大片留白。
|
||||
- 卡片排版统一:标签 12px 用 `--sc-muted`,数值 24px 且 `font-variant-numeric: tabular-nums`,用 `margin-top: auto` 贴底、副标题保留 2 行最小高度(`min-height: 2.9em`)。同一行内标签换行不影响数值对齐,卡片等高、数字列对齐。
|
||||
- 绿色强调卡(付款→签收平均到货时长、付款→签收履约率)改为"浅绿底 + 左侧 2px 竖条"(`inset 2px 0 0`),不再整卡大面积着色;点击态的竖条与边框跟着状态色走。
|
||||
- 缺关键时间 / 时间异常改为横向"告警条":左侧图标 + 名称/说明,数值贴右,用低饱和暖色表达"异常"语义;点击下钻、`is-active` 选中态、`aria-pressed` 与 `aria-label`(含条数)都保留。
|
||||
- 修复看板工具条(导出 / 尺寸切换)样式不生效的根因:`BoardTools` 是本文件内的**独立组件**,styled-jsx 按组件注入作用域类,主组件里的 `.sc-logistics-icon-button` / `.sc-logistics-board-tools` / `.sc-logistics-board-size-label` 规则永远匹配不到它的 DOM,四个看板右上角一直是"裸图标"。现在样式放进 `BoardTools` 自己的 `<style jsx>`(深色排名表头用 `:global(.sc-logistics-ranking-board-header)` 适配),并删除主样式块里的死规则。
|
||||
|
||||
- 每个环节多出一列 **目标**:`本月目标 = 上月同环节平均时长 × 98%`(`LOGISTICS_ARRIVAL_SIGNING_DURATION_TARGET_IMPROVEMENT_RATIO`)。基准月取所选区间结束日期所在月的上一个月(跨年正确),基准值由 `aggregateStageAverages()` 用同一套筛选条件跑上个月区间得到;当前平均不高于目标时目标值标绿表示达标,鼠标悬停可看到"目标 = 上月平均 × 98%"的算式。
|
||||
- 目标后面还有 **目标达成率**:该环节时长不超过目标的订单占比。精确口径由 `aggregateStageTargetHits()` 按目标值统计命中订单数,样本口径由 `buildLogisticsArrivalSigningDurationStageAchievement()` 在样本上计算;无目标或无可计算订单时显示 `-`,鼠标悬停显示"命中 / 可计算"明细。环节行的平均/中位/P90/目标/达成率都只显示数值,单位(小时)只在表头语义里体现。
|
||||
- 环节行按 `128px / 弹性条 / 74 / 74 / 86 / 92` 收紧列宽,列表支持横向滚动,避免 P90、目标被截断。
|
||||
- 履约环节拆解与发货→签收时效分布之间加了**可拖动的分隔条**(`role="separator"`,支持左右方向键微调),比例默认 62% 并记在本地,可随时左右拖动调整两个看板的宽度。
|
||||
|
||||
经营对象问题排名各看板按"上级 → 下级"顺序排列,并逐级联动:
|
||||
|
||||
```
|
||||
01 签收日期维度 → 02 平台维度 → 03 店铺维度 → 04 发货仓维度 → 05 物流维度
|
||||
→ 06 采购维度 → 07 供应商维度 → 08 商品维度 → 09 订单明细
|
||||
```
|
||||
|
||||
点击任意一级对象,都会保留更上级条件、丢弃更下级条件,并把这条链路作为筛选条件重新取数:
|
||||
|
||||
- 整区间口径:链路以 `cascade`(JSON 数组,`LogisticsArrivalSigningDurationDrilldownFilter[]`)随 dashboard 请求下发,服务端在 `aggregatePayToSignAnalysis` / `aggregatePayToSendAnalysis` / `aggregateThresholdRankings` 的 WHERE 上追加 `cascadeSqlWhere`,因此下级看板与 09 明细都只展示对应数据;
|
||||
- 样本口径(实时源或超出精确上限):前端用 `logisticsArrivalSigningDurationRowMatchesCascade` 收窄样本后重算;
|
||||
- 店铺维度分组键是"平台 + 店铺",因此平台级联动能精确到该平台下的店铺;
|
||||
- 联动刷新是原位刷新(只有页面筛选变化才显示整块加载态),排名看板标题会显示当前链路与"联动加载中…"提示;
|
||||
- 经营总览、履约环节拆解、时效分布属于页级口径,不随联动链路变化。
|
||||
|
||||
### 经营对象的目标与目标达成率
|
||||
|
||||
02–08 经营对象表(平台 / 店铺 / 发货仓 / 物流 / 采购 / 供应商 / 商品)在原有指标后面追加 **目标** 与 **目标达成率** 两列;01 签收日期维度的「指标 × 日期」矩阵与总计不追加(按日算"上月同日目标"没有业务含义)。
|
||||
|
||||
- **目标 = 该对象上月同环节平均时长 × 98%**,达成率 = `100% − (平均 − 目标) / 目标 × 100%`,与履约环节拆解同一套口径,只是精确到每个经营对象——所以每个对象的目标值不同(例如 08 月各采购平均不同,9 月目标 52.7 / 56.5 / 60.5 …),达成的判断是"相对自己上个月是否改善 2%"。
|
||||
- 数值口径与展示一致:平均、目标都按 1 位小数取值后再算达成率,便于和表里看到的数字对账;没有上月基线的对象(新采购、新店铺、当月新条码)显示 `-`。
|
||||
- 取数:`aggregateObjectBaselines()`(快照仓库)在上月区间一次扫描算出 7 个维度 × 付款→签收 / 付款→发货 两个环节的平均时长,随 dashboard 载荷以 `objectBaselines` 下发;前端用 `withObjectAnalysisTargets()` 按分组键(`JSON.stringify(keyParts)`,与样本路径同一套键)合并,样本口径与整区间口径共用。
|
||||
- 这条聚合约 2–4 秒,因此按「快照日期 + 基准月 + 关键词/排除词」在服务端缓存 10 分钟(这些平均值只在每日快照重建时变化),并与看板其余查询并行执行;查询失败只让这两列显示 `-`,不会把整块看板拖回实时源。
|
||||
- 「经营对象问题排名」导出的 CSV 同步带上这两列。
|
||||
|
||||
### 看板分页
|
||||
|
||||
> 页面顶部的「经营管理 / 物流时效 + 看板标题 + 物流签收明细口径 + 明细条数/更新时间」页头已去掉(用户口径:无需展示,标签页标题已经说明是哪个看板);快照/缓存状态在数据源异常时的黄色提示条里仍会给出。
|
||||
|
||||
### 批量关键词查询
|
||||
|
||||
筛选区的「关键词」支持批量输入(与排除关键词同一套拆分规则):
|
||||
|
||||
- 按中文逗号 `,`、英文逗号 `,`、换行拆分,自动去空、去重,最多 100 个、单个最长 120 字符(原始输入上限 4000 字符);
|
||||
- **多个关键词之间是"或"**:命中任一字段即算匹配。整区间(快照)口径覆盖:内部单号、线上订单号、物流单号、物流公司、店铺、平台、发货仓、收货地址,以及快照时点固化的**条码、商品名称、供应商、采购**(`KEYWORD_FIELDS`)——所以能直接按采购/供应商/条码检索;
|
||||
- 走实时源时(区间超出快照窗口)只覆盖前 8 个报表字段,条码/采购/供应商这三类需要本地快照支持,这是两套口径的已知差异;
|
||||
- 解析规则由 `splitSearchKeywords()` 统一提供,服务端缓存键按关键词集合(排序后)计算,输入顺序不同不会重复缓存。
|
||||
- 输入框与其它筛选控件**等高**(34px 单行高度,多行时框内滚动),"已识别 N 个关键词"显示在标签行右侧并在空间不足时省略,不再把这一格撑成两行高。
|
||||
|
||||
筛选区的三个按钮各管一件事,点下去**立即按新条件查询**(互不重复):
|
||||
|
||||
| 按钮 | 签收日期 | 关键词 | 排除关键词 | 结果 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **重置** | 清空(输入框留空) | 清空 | 清空 | 没有任何筛选条件;空日期由服务端兜底为本月 1 号~昨日(`dateRangeCleared` 让输入框保持空白,不被兜底值回填) |
|
||||
| **恢复默认** | 回到本月 1 号~昨日(显式填回输入框) | 保留 | 恢复系统默认配置(全部勾选) | 默认口径 |
|
||||
| **清空排除** | 回到本月 1 号~昨日(显式填回输入框) | 保留 | 置空 | 默认区间 + 不排除任何关键词 |
|
||||
|
||||
三者过去都只是"改输入、等用户点查询"(重置只清日期与关键词、另两个只改排除词),结果看起来一样容易混;现在各自的语义、界面状态与结果都区分开了,按钮 hover 有对应说明,筛选栏底部也有一行图例。空日期提交时服务端仍按默认区间查询(跨天且当日快照未生成时可能落实时源,较慢但结果正确)。
|
||||
|
||||
**默认签收日期区间是「本月 1 号 ~ 昨日」**(由 `normalizeLogisticsArrivalSigningDurationFilters()` 生成):当日签收数据仍在滚动,且每日快照只覆盖到昨日,用"到昨日"作默认值可以让首屏直接命中快照(整区间精确 + 秒级),不再因为区间含当天而回落到实时源。每月 1 号时会自动退化为"昨日当天"(本月 1 号还没到昨日)。
|
||||
|
||||
筛选控件(签收日期、关键词输入框,以及排除关键词选择器的摘要)都是**受控并与服务端下发的筛选条件同步**:从菜单重新进入(无参数 URL)、重置后查询、浏览器前进/后退时,控件会刷成当前真正生效的条件。这一点很重要——否则会出现"数据已经是默认区间,输入框还停在上次条件"的错觉。
|
||||
|
||||
### 排除关键词选择器
|
||||
|
||||
筛选区的「排除关键词」是**按字段分组**的勾选式选择器(`ExcludeKeywordPicker.tsx`)。关键词只能配置在它要匹配的那个字段上,**不跨字段混用**——这是这一版最重要的口径修正。
|
||||
|
||||
**字段与匹配规则**
|
||||
|
||||
| 字段(配置分组) | 数据列 | 匹配方式 |
|
||||
| --- | --- | --- |
|
||||
| 发货仓 | `warehouseName` | 该字段值包含关键词(不区分大小写),命中即排除 |
|
||||
| 物流公司 | `logisticCompanyName` | 同上 |
|
||||
| 订单标签 | `orderLabels`(JSON 数组文本) | 同上 |
|
||||
| 买家留言 | `buyerMessage` | 同上 |
|
||||
| 卖家备注 | `remark` | 同上 |
|
||||
|
||||
配置与筛选都以「字段 → 关键词」存储(`LogisticsArrivalSigningDurationExcludeKeywords`)。SQL 侧对每个关键词只生成一条 `NOT (该字段 ILIKE '%词%')`;旧版"命中 5 个字段里任意一个就排除"的口径已废弃——那正是「物流」会连带命中订单标签里的 `商家指定物流`、`物流探查` 和买家留言里"物流单号"的根因。
|
||||
|
||||
**选择器交互**
|
||||
|
||||
- **按字段分组**:发货仓 / 物流公司 / 订单标签 / 买家留言 / 卖家备注,每组标题显示"共 N 项 · 已勾选 M 项",右侧索引条按字段跳转(字段没有候选词时不显示该组);
|
||||
- **默认全选**:候选词 = 该字段的默认排除词 ∪ 当前已排除词(旧链接里带的自定义词不会丢),进入看板时全部处于"排除"状态;取消勾选 = 本次不排除该项,确定后立即以当前表单条件重新查询(`requestSubmit()` 在受控值落到 DOM 后再触发);一个都不勾时以 `excludeKeywordsMode=clear` 提交,避免回落到默认排除词;
|
||||
- 选择器内含:搜索、`全部 / 已选项(n)` 页签、`全选 / 反选 / 清空`(作用于当前可见列表,全选带半选态)、`紧凑模式`(四列更密);
|
||||
- **新增候选词仅系统管理员可操作**:底部先选字段、再输入关键词(回车或点「添加」),新词并入**该字段**的默认排除词并立即落库(`PUT …/settings`,服务端 `assertCanManage` 同样校验管理员身份),提示"已保存到「物流公司」默认排除关键词:…";非管理员只看到"需要新增请联系系统管理员";
|
||||
- 页面原先独立的「默认排除关键词」设置卡片已删除,维护入口统一收敛到选择器;`清空排除`(不排除任何词)与 `恢复默认`(回到全部默认排除词)两个按钮保留,只改输入、点查询生效。
|
||||
|
||||
**编码与兼容**
|
||||
|
||||
- 筛选栏摘要按字段给数量(如"已排除 14 项:发货仓 3 · 物流公司 5 · 订单标签 2 · 买家留言 2 · 卖家备注 2"),URL 用 `excludeKeywords=字段:词,词;字段:词` 的紧凑写法;
|
||||
- **老链接的平铺写法按当前配置归位**:`excludeKeywords=词1,词2` 会被逐个对应到配置里它所属的字段(大小写不敏感,`bbff` 会归一到配置里的 `BBFF`),配置里已经没有的词说明它没有字段、直接忽略——**不会再把一个词摊到 5 个字段上**(那会让选择器每组都列出全部关键词)。只有当配置为空、任何词都对应不上时才退回旧口径,避免老链接突然变成"不排除任何词"。
|
||||
- 历史配置(数组)在 `normalizeExcludeKeywords()` 里仍按旧口径挂到全部 5 个字段(那是当年真实的匹配语义);但配置一旦以新结构保存,后续解析都会走"按字段归位"。
|
||||
|
||||
**当前初设配置**(2026-09-30 落库):发货仓=`自动发货仓 / 常州简尚家居有限公司 / 京东云仓`;物流公司=`京喜 / 物流 / 安能 / 自提 / 中铁`;订单标签=`配件 / 1688一件代发`;买家留言=`BBFF / SSDD`;卖家备注=`BBFF / SSDD`。2026-09-01~09-02 实测:不排除 26,296 行 → 按字段排除后 17,506 行(旧口径同样 14 个词是 16,552 行,多出的 954 行正是"标签里带物流、物流公司里没有"的那些)。
|
||||
|
||||
经营对象问题排名里的每个看板都有独立翻页,不再用"显示更多"或无限滚动,也不会因为对象太多被截断:
|
||||
|
||||
- 01 签收日期维度:按日期列翻页(默认 10 列/页,可切 10/20/50/100);
|
||||
- 02–08 采购/供应商/条码/物流公司/发货仓/平台/店铺:按对象行翻页(默认 20 行/页);
|
||||
- 发货仓 / 物流公司 超时排名:按对象行翻页(默认 20 行/页)。
|
||||
- 09 订单明细:翻页控件就在看板内部(不再挂在页面底部);未下钻时按 URL 的 `page/pageSize` 走服务端分页(每页 25/50/100/200,命中快照时显示真实总数"共 N 条筛选结果"),下钻时用本地分页翻全量匹配明细。
|
||||
|
||||
分页条统一由 `BoardPagination.tsx` 渲染(含"共 N 条 · 第 x / y 页"、上/下一页与每页条数),行集合变化(例如切换采购后重新筛选供应商)时自动回到第 1 页。
|
||||
|
||||
**每页条数切换的坑(已修)**:下拉框原先在 `onChange` 里同时调了 `onPageSizeChange(新值)` 和 `onPageChange(1)`。服务端分页(09 订单明细未下钻时)这两次都会触发 `router.push`,而后一次带着**旧的** `pageSize` 构造 URL 并覆盖前一次,表现为"订单明细每页条数选了没反应、又跳回 50"。现在 `BoardPagination` 只通知"每页条数变了",回到第 1 页由调用方负责:服务端分页用 `href(1, { ...filters, pageSize })`,本地分页(各经营对象表、发货仓/物流公司排名、下钻后的 09 明细)在 `setState` 里带 `page: 1`。
|
||||
|
||||
店铺维度表在"店铺"列前额外展示"平台"列:点击平台即把该表筛成该平台的店铺(标题变成"店铺维度 · 天猫",再次点击取消),点击店铺名仍走 09 明细下钻。店铺分组的业务键是"平台 + 店铺",避免同名店铺跨平台互相覆盖;平台列在样本路径(JS 归组)与整区间路径(SQL `GROUPING SETS ((platform, shop))`)都做了同样的处理,并逐字段比对过一致性。
|
||||
|
||||
### 规模上限与样本回落
|
||||
|
||||
SQL 聚合成本与区间行数近似线性(约 5 万行/秒)。当**区间原始行数超过 `LOGISTICS_ARRIVAL_SIGNING_DURATION_EXACT_SCOPE_MAX_ROWS`(11 万条,约 11 天)**时,`getDashboard` 会退回区间内均匀抽取的 2,000 条样本(`listDashboardSampleRows`,5 层并行、按签收时间分层,不再是"取最近一段"),并在底部注释写明原因与"缩小签收日期范围即可得到精确值"。
|
||||
|
||||
返回值上的 `dataScope`(`exact` / `sample`)与 `dataScopeReason`(`live-source` / `range-too-large`)驱动卡片副标题、底部注释与 CSV 导出的口径说明。
|
||||
|
||||
实测(663k 行快照,含 13 个默认排除关键词):
|
||||
|
||||
| 区间 | 原始行数 | 口径 | 首屏耗时 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 天 | 约 1.3 万 | 整区间精确 | 0.55s |
|
||||
| 2 天 | 约 2.6 万 | 整区间精确 | 1.7s |
|
||||
| 5 天 | 约 6.5 万 | 整区间精确 | 3.1s |
|
||||
| 本月 | 约 30 万 | 样本(超上限) | 3.8–5.0s |
|
||||
|
||||
服务端 60s 内重复访问直接命中缓存。要把大区间也做成亚秒精确,方向是在快照写入时就把 4 个时间戳、5 个环节时长落成列,并把多维指标的计数改为位掩码 `GROUP BY`(一次扫描产出全部桶计数),现有文档结论不变、只替换实现。
|
||||
|
||||
### 查询性能优化(2026-10)
|
||||
|
||||
上线后"每次查询生成太慢"的根因有三个,都在快照读路径上:
|
||||
|
||||
1. **整表顺序扫描**:聚合查询原先 `SELECT` 全列(`rowKey`、`productItems`、十几个文本列),
|
||||
Postgres 只能对 900MB 的堆表做 `Seq Scan`(28 天区间实测 `read=106409` 页、6.3 秒)。
|
||||
现在按用途收窄列集合——`FILTERED_TIMELINE_SELECT`(只取 4 个时间戳)用于整区间总览/环节目标,
|
||||
`FILTERED_DIMENSION_SELECT`(时间戳 + 4 个维度列)用于签收日期/平台/店铺/发货仓/物流/超时排名——
|
||||
并新增覆盖索引 `LogisticsArrivalSigningDurationSnapshot_cover_idx`
|
||||
(`(snapshotDate, signTime) INCLUDE (payTime, sendTime, fetchTime, warehouseName, platformName, shopName, logisticCompanyName)`),
|
||||
扫描变成 **Index Only Scan**(同样的 28 天区间 26ms、`Heap Fetches: 0`)。
|
||||
2. **有序集聚合把整行拖进排序**:`percentile_cont(0.5/0.9) WITHIN GROUP` 原先和十几个计数写在同一条语句里,
|
||||
输入行 14 列宽,PG 18 会把它整行送进排序(两个分位数单独就占 4.3 秒,环节分位数叠在一起甚至到分钟级)。
|
||||
现在分位数**按列拆成独立语句**、只投影一个浮点列,并与计数语句并行下发(`Promise.all`)。
|
||||
3. **CTE 表达式被重复内联**:`measured`/`durations` 的 `CASE + EXTRACT` 会在每一处引用被重新计算,
|
||||
同一条聚合里重复十几次。改为 `AS MATERIALIZED` 后只算一次(计数语句从 2.3s 降到 ~0.5s)。
|
||||
|
||||
另外把 **环节目标(上月平均 × 98%)** 也接入 10 分钟内存缓存(此前每次刷新都重跑上月聚合)。
|
||||
|
||||
优化前后(本机 Postgres 18、快照 648,689 行、冷启动、无缓存):
|
||||
|
||||
| 场景 | 优化前 | 优化后 |
|
||||
| --- | --- | --- |
|
||||
| 仓库层 `getDashboard`(28 天,样本口径) | 7.3s | **2.3s** |
|
||||
| 仓库层 `getDashboard`(10 天) | 4.4s | **0.4s** |
|
||||
| 仓库层 `getDashboard`(2 天,精确口径) | 1.7s | **0.3s** |
|
||||
| 浏览器首屏出数(冷启动,含 5.6MB 载荷 + 渲染) | ~7–10s | **2.7s** |
|
||||
|
||||
正确性用 2 天区间做过逐项对账:SQL 聚合与"取全量明细在 JS 里现算"在订单数、平均/中位/P90、
|
||||
异常计数、达成率、时效分布、5 个环节的 可计算单量/平均/中位/P90 上完全一致。
|
||||
|
||||
时间戳在 SQL 里用 `NULLIF(BTRIM(col),'')::timestamp` 直接转型:快照写入的就是远端 `CONVERT(varchar(19), …, 120)` 结果,格式固定;早期版本用正则逐行校验,整区间聚合约慢一个数量级。
|
||||
|
||||
## 运维排查
|
||||
|
||||
```sql
|
||||
-- 批次状态
|
||||
SELECT "snapshotDate", status, "windowFrom", "windowTo", "rowCount", message, "finishedAt"
|
||||
FROM "LogisticsArrivalSigningDurationSnapshotRun" ORDER BY "snapshotDate" DESC;
|
||||
|
||||
-- 当天行数
|
||||
SELECT count(*) FROM "LogisticsArrivalSigningDurationSnapshot" WHERE "snapshotDate" = CURRENT_DATE;
|
||||
```
|
||||
|
||||
- 页面长时间显示"数据更新"而不是"快照数据" → 先看批次是不是 `FAILED`,或窗口是否覆盖了页面的签收日期区间。
|
||||
- 批次 `RUNNING` 卡住(进程被杀)→ 该批次不会发布;直接重跑或加 `--force` 即可(重跑会先清空当天行)。
|
||||
- 需要重建历史窗口 → `--window-days=` 调大 + `--force`。
|
||||
|
||||
## 部署步骤(生产)
|
||||
|
||||
1. 部署新版本代码;
|
||||
2. 执行迁移 `prisma/migrations/20260928120000_add_logistics_arrival_signing_duration_snapshot`(需按生产迁移流程授权、先备份);
|
||||
3. 跑一次手工同步生成第一批快照(`--apply`),确认批次为 `COMPLETED`、行数与窗口符合预期;
|
||||
4. 安装 timer 并核对 `systemctl list-timers`;
|
||||
5. 打开看板确认头部显示"快照数据 · 日期"。
|
||||
|
||||
注意:Prisma Client 重新生成过,运行中的 dev/prod 进程需要重启才能拿到新模型。
|
||||
207
furniture-3d-system/docs/设计规范/苹果风格视觉标准.md
Normal file
207
furniture-3d-system/docs/设计规范/苹果风格视觉标准.md
Normal file
@ -0,0 +1,207 @@
|
||||
# 苹果风格视觉设计标准 (Apple-Style Visual Design Standards)
|
||||
|
||||
> **版本**: 2.0 (2026-01-29)
|
||||
> **状态**: 补充参考标准;当前新平台第一视觉标准以 `白曜石工坊统一视觉标准.md` 为准
|
||||
> **优先级**: 如本文件与白曜石工坊标准冲突,优先执行白曜石工坊标准
|
||||
> **目标**: 打造优雅、统一、沉浸式的用户体验,完全去除"模板化"和"AI味",接近苹果官网的视觉水准。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [核心设计哲学](#1-核心设计哲学)
|
||||
2. [详细视觉规范](#2-详细视觉规范)
|
||||
* [色彩系统](#21-色彩系统)
|
||||
* [排版系统](#22-排版系统)
|
||||
* [布局与间距](#23-布局与间距)
|
||||
* [材质与光影](#24-材质与光影)
|
||||
3. [交互与动画](#3-交互与动画)
|
||||
4. [去AI味专项指南](#4-去ai味专项指南)
|
||||
5. [技术架构与文件结构](#5-技术架构与文件结构)
|
||||
6. [组件开发规范](#6-组件开发规范)
|
||||
|
||||
---
|
||||
|
||||
## 1. 核心设计哲学
|
||||
|
||||
### 1.1 内容至上 (Content is King)
|
||||
* **原则**:界面不仅是容器,更是内容的舞台。UI 应该"退后",让 3D 模型、产品图片和关键数据"向前"。
|
||||
* **执行**:
|
||||
* 背景色主要使用白色或极淡的灰色;当前产品不再提供深浅色主题切换。
|
||||
* **拒绝**无意义的装饰性边框、分割线和阴影。只有在区分层级必要时才使用。
|
||||
* **拒绝**均等分割的网格布局("AI味"典型特征)。使用非对称布局(如:左侧 2/3 展示 3D 模型,右侧 1/3 展示控制面板)。
|
||||
|
||||
### 1.2 物理感与材质 (Materiality & Physics)
|
||||
* **原则**:数字界面应模拟真实世界的物理属性。
|
||||
* **执行**:
|
||||
* **Liquid Glass (毛玻璃)**:仅在覆盖内容时使用(如导航栏、侧边栏、浮动面板)。标准:`backdrop-filter: blur(20px) saturate(180%)`。
|
||||
* **Spring Animation (弹簧动画)**:交互元素(按钮按压、弹窗出现)必须使用弹簧物理模拟,而不是简单的线性过渡。
|
||||
* **光影**:光源应统一来自正上方。阴影应柔和、弥散,模拟环境光遮蔽 (Ambient Occlusion)。
|
||||
|
||||
### 1.3 优雅的排版 (Elegant Typography)
|
||||
* **原则**:排版是信息层级的核心。
|
||||
* **执行**:
|
||||
* **动态字间距 (Dynamic Tracking)**:大标题收紧 (-0.4px),正文标准,小字加宽 (+0.06px)。
|
||||
* **层次分明**:使用字重 (Weight) 和颜色 (Color) 区分层级,而不是单纯靠字号。
|
||||
|
||||
---
|
||||
|
||||
## 2. 详细视觉规范
|
||||
|
||||
### 2.1 色彩系统
|
||||
|
||||
**设计令牌位置**: `src/lib/design-tokens/colors.ts`
|
||||
|
||||
不要使用纯灰 (`#808080`),总是混入微量的蓝色或暖色调。
|
||||
|
||||
| 用途 | Light Mode | Dark Mode | 说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **背景** | `#FFFFFF` | `#000000` | 纯净背景,最大化对比度 |
|
||||
| **次级背景** | `#F5F5F7` | `#1C1C1E` | 用于分组区块,**比系统灰(#F2F2F7)更淡、更优雅** |
|
||||
| **主文本** | `#1D1D1F` | `#F5F5F7` | 接近纯黑但不是纯黑 (Off-black) |
|
||||
| **次文本** | `#86868b` | `#86868b` | 经典的苹果灰,用于说明文字 |
|
||||
| **强调色** | `#008D74` | `#00B896` | 品牌绿,仅用于链接和主按钮 |
|
||||
| **分割线** | `rgba(0,0,0,0.1)` | `rgba(255,255,255,0.1)` | 极细,极淡 |
|
||||
|
||||
### 2.2 排版系统
|
||||
|
||||
**设计令牌位置**: `src/lib/design-tokens/typography.ts`
|
||||
|
||||
我们使用**合法的开源/系统字体栈**,严格避免版权风险:
|
||||
* **西文**: `-apple-system`, `BlinkMacSystemFont`, `Inter`, `Segoe UI`
|
||||
* **中文**: `-apple-system`, `Noto Sans CJK SC`, `Source Han Sans SC` (思源黑体), `Microsoft YaHei`
|
||||
* **代码**: `JetBrains Mono`, `Fira Code`
|
||||
|
||||
| 样式 | 字号 (px) | 行高 (px) | 字间距 (px) | 用途 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **Large Title** | 34 | 41 | -0.4 | 页面主标题 |
|
||||
| **Title 1** | 28 | 34 | -0.36 | 章节标题 |
|
||||
| **Title 2** | 22 | 28 | -0.26 | 子章节标题 |
|
||||
| **Headline** | 17 | 22 | -0.41 | 强调文本 (Semibold) |
|
||||
| **Body** | 17 | 25 | -0.41 | 正文内容 |
|
||||
| **Caption 1** | 12 | 16 | 0 | 说明文字 |
|
||||
|
||||
### 2.3 布局与间距
|
||||
|
||||
**设计令牌位置**: `src/lib/design-tokens/spacing.ts`
|
||||
|
||||
* **基础网格**: 4px
|
||||
* **大留白原则**:
|
||||
* 模块间距至少 `48px` 或 `64px`。
|
||||
* 卡片内部 Padding 至少 `24px`。
|
||||
* **流体布局**:
|
||||
* 容器宽度随视口变化,最大宽度限制 (Max-width: 1440px)。
|
||||
* **全宽突破**: Hero Banner 等关键元素应占据全宽。
|
||||
|
||||
### 2.4 材质与光影
|
||||
|
||||
* **Liquid Glass**:
|
||||
* Light: `rgba(255, 255, 255, 0.72)` + Blur 20px
|
||||
* Dark: `rgba(22, 22, 23, 0.72)` + Blur 20px
|
||||
* **阴影 (Shadows)**:
|
||||
* Subtle: `0 2px 8px rgba(0, 0, 0, 0.04)` (静态元素)
|
||||
* Standard: `0 4px 12px rgba(0, 0, 0, 0.08)` (悬浮/交互)
|
||||
* Elevated: `0 8px 24px rgba(0, 0, 0, 0.12)` (模态框)
|
||||
|
||||
---
|
||||
|
||||
## 3. 交互与动画
|
||||
|
||||
**设计令牌位置**: `src/lib/design-tokens/animations.ts`
|
||||
|
||||
所有交互必须有物理反馈。
|
||||
|
||||
* **按钮按压**: Scale 0.98, Duration 100ms
|
||||
* **微交互 (Hover)**: Scale 1.02, Duration 200ms
|
||||
* **页面过渡**: Duration 500ms, Curve `cubic-bezier(0.16, 1, 0.3, 1)`
|
||||
* **Spring 参数**: 使用 `spring(mass: 1, stiffness: 100, damping: 15)` 模拟真实回弹。
|
||||
|
||||
---
|
||||
|
||||
## 4. 去AI味专项指南
|
||||
|
||||
"AI味" = 死板、重复、缺乏人情味、过度依赖默认组件。
|
||||
|
||||
### 4.1 拒绝"Lorem Ipsum"
|
||||
* ❌ "Product Name 1", "This is a description."
|
||||
* ✅ "Eames Lounge Chair", "由模压胶合板和皮革制成,经典的设计符号。"
|
||||
* **要求**:所有占位文本必须是**真实场景数据**。
|
||||
|
||||
### 4.2 拒绝"均等分布"
|
||||
* ❌ 4个功能点,每个占 25% 宽度,图标+标题+描述,完全一样。
|
||||
* ✅ 1个核心功能占 60% 宽度配大图,其余 3 个功能以列表形式在侧边展示。打破对称。
|
||||
|
||||
### 4.3 拒绝"默认图标"
|
||||
* ❌ 使用实心块状图标。
|
||||
* ✅ 优先使用文字。如果必须用图标,使用细线条风格 (Stroke width 1.5px-2px)。
|
||||
|
||||
### 4.4 拒绝"彩虹渐变"
|
||||
* ❌ 高饱和度彩虹渐变背景。
|
||||
* ✅ 纯色、微渐变或高质量实拍图/3D渲染图。
|
||||
|
||||
---
|
||||
|
||||
## 5. 技术架构与文件结构
|
||||
|
||||
本项目基于 **Next.js 16 + Tailwind CSS + React 19** 构建。
|
||||
|
||||
### 5.1 目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── lib/
|
||||
│ └── design-tokens/ # 核心视觉配置
|
||||
│ ├── typography.ts # 字体定义
|
||||
│ ├── colors.ts # 颜色定义
|
||||
│ ├── spacing.ts # 间距定义
|
||||
│ └── animations.ts # 动画定义
|
||||
├── components/
|
||||
│ └── ui/ # 基础原子组件
|
||||
│ ├── Button.tsx # 遵循规范的按钮
|
||||
│ ├── Card.tsx # 遵循规范的卡片
|
||||
│ ├── Typography.tsx # 文本组件封装
|
||||
│ └── ...
|
||||
└── app/
|
||||
└── globals.css # CSS 变量映射 (自动从 tokens 生成)
|
||||
```
|
||||
|
||||
### 5.2 迁移指南
|
||||
|
||||
如果你在代码中看到旧的 CSS 类(如 `text-gray-500`),请替换为语义化的设计令牌类:
|
||||
* ❌ `text-gray-500` -> ✅ `text-(--color-label-secondary)` (或封装好的 Tailwind 类)
|
||||
* ❌ `bg-white` -> ✅ `bg-(--color-background-primary)`
|
||||
* ❌ `shadow-lg` -> ✅ `shadow-apple-lg`
|
||||
|
||||
---
|
||||
|
||||
## 6. 组件开发规范
|
||||
|
||||
### 6.1 Button
|
||||
* **形状**: 高度 36px/44px,Pill shape (999px) 或 Rounded (8px)。
|
||||
* **质感**: Primary (纯色无边框), Secondary (浅灰背景)。禁止复杂渐变。
|
||||
|
||||
### 6.2 Card
|
||||
* **圆角**: 统一 `20px` 或 `24px`。
|
||||
* **边框**: Light Mode 下通常无边框或极淡 `1px` 边框。
|
||||
* **内容**: 内部 Padding 至少 `24px`。
|
||||
|
||||
### 6.3 Input
|
||||
* **样式**: 移除默认边框,使用浅灰背景 (`#F5F5F7`),聚焦时出现 Focus Ring。
|
||||
* **高度**: 至少 `44px`。
|
||||
|
||||
### 6.4 Slider
|
||||
* **组件来源**: 统一使用 `src/components/ui/Slider.tsx`,禁止直接使用原生 `input[type="range"]`。
|
||||
* **轨道样式**: 高度 `6px`,圆角 `999px`,未填充色 `#D4DCE6`,填充色使用品牌色 `#008D74`(深色场景可切换 `#00B896`)。
|
||||
* **滑块样式**: 尺寸 `16px`,白色描边 `2px`,投影 `0 1px 3px rgba(0, 0, 0, 0.25)`。
|
||||
* **状态规范**: 禁用态降低不透明度并禁用交互光效;拖动态允许轻微放大(建议 `scale(1.08)`)。
|
||||
* **可访问性**: 必须提供 `aria-label`,并保持键盘可调节。
|
||||
|
||||
### 6.5 响应式
|
||||
* 使用 `useBreakpoint` 或 Tailwind 的响应式前缀 (`md:`, `lg:`)。
|
||||
* 支持 iPhone 刘海屏安全区域 (`safe-area-inset`).
|
||||
|
||||
---
|
||||
*遵循此标准,我们将构建出一个不仅好用,而且令人愉悦的家具展示系统。*
|
||||
|
||||
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
# 限价与破价判定规则(业务确认版)
|
||||
|
||||
> 适用范围:限价管理 · 破价订单审核(`/supply-chain/management/custom-table` → 破价订单审核)
|
||||
> 本文件为业务口径的唯一来源;规则变更请改本文件并注明生效时间。
|
||||
|
||||
## 一、限价与判定口径
|
||||
|
||||
1. **判定基准**:以「商家实收」为准;`商家实收 < 限价 × 数量` 即判定破价。
|
||||
2. **实付款与平台承担**:实付款 + 平台承担(红包 / 消费券 / 平台券)= 商家实收;凭证必须同时包含「实付款」和「数量」。
|
||||
3. **出资方字典**
|
||||
- 商家承担:店铺优惠(官方立减、单品直降、店铺券)、政府补贴
|
||||
- 平台承担:红包、消费券、平台券、平台优惠、新品礼金
|
||||
- 买家承担:淘金币
|
||||
- 待定:首单礼金(等与运营确认后补录)
|
||||
4. **限价维度**:按**货号**限价——同一货号下所有规格/条码共用同一套限价。
|
||||
5. **限价时间版本**:按订单付款时点生效的限价判定(限价调整前后分别取当时版本),不做"用最新限价回算历史单"。
|
||||
6. **成本变动规则(限价维护侧)**:成本价上涨 5% 以内不动限价;成本价下调不变限价。
|
||||
7. **非负责人限价**:2026-10-07 00:00 前的 ID 价格不判定破价(此后按规则判定)。
|
||||
8. **老链接分界**:上架时间早于 2026-10-07 00:00 的链接(老链接)破价申诉通过;之后的按限价流程结束日期判定(上架时间需早于流程结束日期),并核对店铺款式编码 / 商品编码。
|
||||
9. **负责人触发规则**:非运营发起的流程,前两个月全公司运营同价(不归属个人);两个月后按销量触发负责人(销量高者为负责人)。
|
||||
10. **数据快照**:破价单在生成时保存金额快照;退款/售后状态单独记录,退款不等于免责(仍需判断是否已调价)。
|
||||
11. **离职人员**:订单负责人已离职 → 自动标记「人员已离职」,不判定、不计乐捐。
|
||||
12. **是否有检测报告**:取素材平台「检测报告」接口(`/api/external/certificate-reports`,按 **SPU** 精确匹配,只认有报告文件的记录),进程内缓存 10 分钟;
|
||||
接口未配置或读取失败时该列显示为空、筛选候选值为空。配置项:`MATERIAL_LIBRARY_CERTIFICATE_REPORT_API_BASE_URL/API_KEY`,
|
||||
不填则复用素材平台 `MATERIAL_LIBRARY_WHITE_BACKGROUND_API_*`。
|
||||
13. **手工换货单**:聚水潭订单明细出现「被替换行(`item_status=Replaced`、数量归 0)」+「换入行(`item_ext_data` 含 `item_source=手工换货/手工添加`)」时按换货单处理:
|
||||
- 判定按**换出行原编码**的限价执行(客户在平台上购买的那个编码);
|
||||
- 成交数量与商家实收取**换入行**(换货不改变订单成交金额);换入行金额抓不到时回退订单主表商家实收 → 订单主表买家实付,都取不到才按 0(由运营申诉说明);
|
||||
- 换货行配对成功后按原编码生成一条,不再按换入编码单独生成,避免同一订单重复生成两条破价单;
|
||||
- 配对优先按 `sku_uuid` 精确匹配,剩余行仅在一对一时顺序配对;复杂结构(多对多)暂不强行配对,相关行按各自编码现状处理。
|
||||
|
||||
## 二、申诉与审核
|
||||
|
||||
1. **申诉类型**(4 组)
|
||||
- 链接时间:老链接,上架时间早于限价流程(凭证须含链接 ID + 创建时间)/ 京喜换主体 · 老店转新店
|
||||
- 优惠 / 活动:平台优惠 · 红包 · 券(非商家承担)/ 参与秒杀 · 百补 · 大促等平台活动(凭证须含活动开始与截止时间,且活动价未破价)/ 拼多多多件折扣(99折)
|
||||
- 流程申请:已走新品限价及限价调整(必填流程号,系统到「新品限价及限价调整」流程校验,查不到不允许提交)
|
||||
- 其他原因(须在备注中详细说明)
|
||||
2. **申诉范围**:链接时间 / 流程申请类支持**链接级一次性申诉**(默认勾选,覆盖该链接下全部破价订单,后续新单自动继承);优惠 / 活动类按订单申诉。
|
||||
3. **审核要点仅审核人可见**;申诉人只看到凭证要求与示例图。
|
||||
4. **凭证校验**:必填校验 + 识别提示(识别结果仅提示,人工一键打回);后续增加图片查重(hash / 相似度)与关键字段(订单号、链接 ID)一致性校验。
|
||||
5. **乐捐**:驳回计 ¥10/单(离职不计);配套审计链(数据快照 + 申诉材料 + 审核记录),扣款执行交财务确认,保留异议通道。
|
||||
6. **举证期限**:待与运营确认后设置(避免事后无法追溯凭证)。
|
||||
|
||||
## 三、权限(由管理员统一管理)
|
||||
|
||||
路径:用户管理后台 → 权限配置 → 经营管理。限价管理相关的查看 / 导出 / 申诉都由管理员在这里开通。
|
||||
|
||||
| 能力 | 权限节点 | 动作 |
|
||||
| --- | --- | --- |
|
||||
| 页面与数据查看(限价明细、破价订单审核) | 限价管理 | 可见 |
|
||||
| 导出(导出明细 / 导出明细(含图片)/ 破价订单导出 / 乐捐明细) | 限价管理 | 下载 |
|
||||
| 申诉 · 审核 · 生成 · 导入 | 限价管理 | 操作 |
|
||||
| 规则配置(页面可见 + 保存参数) | 限价管理 · 规则配置 | 可见 / 操作 |
|
||||
|
||||
- 没有「下载」权限时页面不展示导出入口,服务端导出接口同样鉴权(下载中心任务按「限价管理 · 下载」校验)。
|
||||
- 没有「操作」权限时:不显示申诉/审核按钮,服务端也会拒绝申诉、审核、生成、导入。
|
||||
- 申诉保留一条兜底:破价单的负责人本人,即使没有「操作」权限,也可以对本人负责的订单发起申诉(审核仍需「操作」)。
|
||||
- 没有「规则配置」权限时,规则配置页签不显示、接口不可调用。
|
||||
|
||||
## 四、生成与运行
|
||||
|
||||
1. **按天增量**:限价逻辑上线后,破价订单**按天增量生成**(每天只生成最新一天的破价单并入库),**历史数据不再重复回算**;破价订单审核页每天展示最新破价单。
|
||||
2. **数据来源**:聚水潭报表库(`jst_Orders` + `jst_OrderItem` / 天猫奇门 `jst_qmcust_orderlist` + `_item`),取 `seller_income_amount`、`buyer_paid_amount`、`qty`、`shop_i_id`、`shop_sku_id`、`pay_date`、退款状态,以及换货识别字段 `item_status`、`item_ext_data`(`item_source`、`sku_uuid`)。报表库查询较慢,逐日增量任务建议按天分片拉取。
|
||||
3. **限价来源**:钉钉流程(仅取**已完结**),共 4 个模板,同步脚本 `scripts/sync-limit-workflows.mjs`:
|
||||
- 「新品限价及限价调整」→ 货号取「货号(非商品编码)」,限价取「期望日常限价」;
|
||||
其中的「调整限价明细表」子表(申请类型=调整原货号限价)只填「调整后限价」,该列同样作为限价取值
|
||||
(2026-10-16 修复,此前这类调价会被漏掉);
|
||||
- 「新品限价流程」→ 明细取系统产品限价页面(按流程链接 SPU 关联)。页面数据来自正式库
|
||||
`external.product_catalog_wide_v1`(限价字段:`expected_daily_limit_price` / `non_spu_owner_limit_price`),
|
||||
本地库该视图为空时退回本地 `IntakeNonIntegratedProduct(LimitRow)`;
|
||||
- 「各中心特殊限价需求申请」→ 货号取「货号」,限价取「最低售价」;
|
||||
- 「GB特殊限价需求申请」→ 货号取「货号」,限价取「期望日常限价」(2026-10-16 纳入)。
|
||||
- 「产品限价页面」已确认(CONFIRMED)的限价全量纳入(2026-10-16):来源正式库
|
||||
`external.product_catalog_wide_v1`,给没有限价流程的货号兜底(如 CY012801=268.6);
|
||||
同货号与流程限价冲突时**以流程为准**,页面价只在流程没覆盖时生效。
|
||||
- 「限价期初数据」(业务整理的存量限价 Excel,2026-10-16 纳入):放在期初数据目录
|
||||
(`LIMIT_SYNC_INITIAL_DIR`,默认 `<app>/期初数据`;目录为空时用仓库自带
|
||||
`scripts/data/limit-sync/期初数据/限价期初数据*.xlsx`),取目录里最新一份,
|
||||
按「货号 + 负责人限价 + 更新日期」写入,作为**最低优先级**兜底(如 MKHDNZ0409260600468=279)。
|
||||
|
||||
优先级:**限价流程 > 产品限价页面(已确认)> 限价期初数据**;低优先级只在货号没有更高优先级限价时生效。
|
||||
**例外(2026-10-16 确认)**:期初数据的「更新日期」**晚于**流程完成时间时,以期初数据为准(谁新用谁);
|
||||
期初比流程旧则维持上面的优先级。
|
||||
**非负责人限价一律按最新口径计算**(2026-10-16 确认):负责人限价 × 1.05,且不低于 +10 元;
|
||||
期初数据表 / 产品限价页面里的历史「非负责人限价」(倍数 1%~15% 不统一)不采用,
|
||||
限价明细页、导出文件、破价判定三处都用同一公式。
|
||||
写入规则:同货号多流程取**审核通过时间最新**的一条;限价为 0 或空的明细剔除;「申请与SPU负责人同价 / 活动限价」不覆盖负责人限价,只作为价格授权;流程「货号」列若填的是商品编码,用商品编码匹配产品档案换算为货号;无货号时同样用商品编码匹配。页面限价的时间按**北京时间墙钟**存(与流程完成时间一致)。
|
||||
|
||||
4. **覆盖率**:暂不做未匹配清单与告警(后续优化)。
|
||||
|
||||
5. **正式站同步(服务器端)**:`scripts/sync-limit-workflows.mjs` 已改成服务器可运行,连接信息全部取
|
||||
服务器 `shared/.env` 里的既有变量(`DATABASE_URL` / `JST_REPORT_DB_*` / `PROD_ARCHIVE_DB_*`),
|
||||
不再依赖 Mac 本地路径:
|
||||
|
||||
```bash
|
||||
# 手动跑一次(正式站目录下,root 或能读 shared/.env 的账号)
|
||||
cd /opt/furniture-3d-system-prod/current/furniture-3d-system
|
||||
sudo -E bash -lc 'set -a; . /opt/furniture-3d-system-prod/shared/.env; set +a; \
|
||||
LIMIT_SYNC_OUT_DIR=/opt/furniture-3d-system-prod/shared/limit-workflow-sync/导出结果 \
|
||||
LIMIT_SYNC_INITIAL_DIR=/opt/furniture-3d-system-prod/shared/limit-workflow-sync/期初数据 \
|
||||
node scripts/run-limit-workflow-sync-production.cjs'
|
||||
|
||||
# 安装每日定时器(systemd,与营销日销同一套样板):每天北京时间 07:30
|
||||
sudo bash scripts/install-limit-workflow-sync-timer.sh
|
||||
systemctl list-timers --all 'furniture-limit-workflow-sync*' --no-pager
|
||||
journalctl -u furniture-limit-workflow-sync.service -n 200 --no-pager
|
||||
```
|
||||
|
||||
- 写库是**全量重建 + 单事务**:成功才提交;失败自动回滚,正式库保留上一版限价与授权。
|
||||
- 输出目录 `LIMIT_SYNC_OUT_DIR`(默认 `<app>/导出结果`)里的 `限价流程_完整明细表.xlsx` 每日覆盖,
|
||||
旧版本进 `历史版本/`,默认保留最近 20 份(`LIMIT_SYNC_HISTORY_KEEP`)。
|
||||
- `--skip-excel` / `LIMIT_SYNC_SKIP_EXCEL=1` 可只写库不导 Excel。
|
||||
- 期初数据更新:把新的 `限价期初数据*.xlsx` 放进 `shared/limit-workflow-sync/期初数据/` 即可,下次同步自动取最新。
|
||||
- `SupplyChainBreachRuleConfig` 不需要灌数:接口读取时用代码内置默认值,首次在「规则配置」保存时落库。
|
||||
|
||||
### 每日聚水潭物流规则快照
|
||||
|
||||
- 正式站采用服务器端 systemd:`npm run install:jst-logistics-systemd`,默认每天 `09:30`(Asia/Shanghai)执行,避开 09:00 的数据库任务,并与 10:30 停产 RPA 任务错开。可通过 `JST_LOGISTICS_CALENDAR` 覆盖。
|
||||
- 开发机示例使用 macOS launchd:`npm run install:jst-logistics-daily`,默认每天 `09:00`。
|
||||
- 正式站快照目录固定为 `$BASE/shared/freight-config`,应用和定时任务共同读取 `FREIGHT_CONFIG_DATA_DIR`,发布切换 release 不会重置。
|
||||
- 本地未配置 `FREIGHT_CONFIG_DATA_DIR` 时继续使用 `data/freight-config`。
|
||||
- 每天最多成功更新一次;若快照的 `generatedAt` 已是北京时间当天,重跑会自动跳过。
|
||||
- 当天确需人工覆盖时执行:在聚水潭 RPA 浏览器保持登录后运行 `npm run sync:jst-logistics-rules -- --force`。
|
||||
- 同步失败时保留上一份可用快照,不写入半成品。
|
||||
- 物流规则同步、停产下架同步和其他聚水潭 RPA 任务不得并发;正式站 unit 已与停产和条码同步任务建立冲突保护。
|
||||
|
||||
## 五、待办
|
||||
|
||||
- [ ] 限价流程数据接入 + 筛选逻辑(生效时间版本、货号维度)
|
||||
- [ ] 非负责人限价 10-07 规则、负责人触发规则(前两个月同价 / 两个月后按销量)落到判定引擎
|
||||
- [ ] 按天增量任务(cron / worker)与失败告警
|
||||
- [ ] 凭证查重(hash / 相似度)、关键字段一致性校验
|
||||
- [ ] 举证期限、乐捐财务对接流程
|
||||
- [ ] 首单礼金 / 新品礼金出资方确认后补录出资方字典
|
||||
@ -74,7 +74,6 @@ const contentSecurityPolicy = [
|
||||
"base-uri 'self'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
`frame-src 'self' ${lodopScriptSources.join(' ')}`,
|
||||
"form-action 'self'",
|
||||
"img-src 'self' data: blob: http: https:",
|
||||
"font-src 'self' data:",
|
||||
@ -86,13 +85,6 @@ const contentSecurityPolicy = [
|
||||
...(isProduction ? ["upgrade-insecure-requests"] : []),
|
||||
].join('; ')
|
||||
|
||||
// 市场选款看板静态页(public/modules/selection-market/)由宿主页 /market-research/selection-market
|
||||
// 以同源 iframe 嵌入:在基础 CSP 上仅放开 frame-ancestors 为同源,其余指令不变。
|
||||
const boardContentSecurityPolicy = contentSecurityPolicy.replace(
|
||||
"frame-ancestors 'none'",
|
||||
"frame-ancestors 'self'"
|
||||
)
|
||||
|
||||
const baseSecurityHeaders = [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
@ -237,7 +229,6 @@ function buildClientStorageEnv() {
|
||||
const clientStorageEnv = buildClientStorageEnv()
|
||||
|
||||
const nextConfig = {
|
||||
devIndicators: false,
|
||||
distDir: process.env.NEXT_DIST_DIR || '.next',
|
||||
// 启用standalone输出,用于Docker部署
|
||||
output: 'standalone',
|
||||
@ -248,7 +239,7 @@ const nextConfig = {
|
||||
},
|
||||
|
||||
// 服务器外部包配置(Next.js 16新配置)
|
||||
serverExternalPackages: ['sharp', 'three', 'gsap', '@huggingface/transformers', 'onnxruntime-node', 'mssql', 'mysql2'],
|
||||
serverExternalPackages: ['sharp', 'three', 'gsap', '@huggingface/transformers', 'onnxruntime-node'],
|
||||
|
||||
// Turbopack配置(Next.js 16默认启用)
|
||||
turbopack: {},
|
||||
@ -391,21 +382,6 @@ const nextConfig = {
|
||||
source: '/(.*)',
|
||||
headers: baseSecurityHeaders,
|
||||
},
|
||||
{
|
||||
// 市场选款看板静态页需要被本平台宿主页同源 iframe 嵌入:
|
||||
// 覆盖基础安全头的 X-Frame-Options DENY / frame-ancestors 'none',仅此路径生效。
|
||||
source: '/modules/selection-market/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN'
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: boardContentSecurityPolicy
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
// Next 静态资源在生产环境可长期缓存;开发环境必须禁用缓存,否则浏览器会继续复用旧 bundle。
|
||||
source: '/_next/static/:path*',
|
||||
|
||||
54
furniture-3d-system/package-lock.json
generated
54
furniture-3d-system/package-lock.json
generated
@ -26,7 +26,6 @@
|
||||
"bcryptjs": "^3.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dockerode": "^4.0.9",
|
||||
"exceljs": "^4.4.0",
|
||||
@ -43,7 +42,7 @@
|
||||
"next": "^16.1.6",
|
||||
"obj2gltf": "^3.2.0",
|
||||
"pg": "^8.17.2",
|
||||
"pinyin-pro": "^3.29.5",
|
||||
"pinyin-pro": "^3.28.1",
|
||||
"postcss": "^8.5.6",
|
||||
"print-js": "^1.6.0",
|
||||
"puppeteer": "^24.40.0",
|
||||
@ -53,12 +52,10 @@
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"server-only": "^0.0.1",
|
||||
"sharp": "^0.34.5",
|
||||
"ssh2": "^1.17.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"three": "^0.182.0",
|
||||
"uuid": "^11.1.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.2"
|
||||
@ -69,7 +66,6 @@
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dockerode": "^4.0.1",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/jest": "^30.0.0",
|
||||
@ -6149,13 +6145,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/crypto-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
|
||||
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/docker-modem": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/docker-modem/-/docker-modem-3.0.6.tgz",
|
||||
@ -8884,13 +8873,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
@ -9355,20 +9337,6 @@
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dockerode/node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
|
||||
@ -15537,9 +15505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pinyin-pro": {
|
||||
"version": "3.29.5",
|
||||
"resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.5.tgz",
|
||||
"integrity": "sha512-Fk5YoRmtdHqJWqAuzYVG/NzvzeCbaG+ZEwbY6Nbps3QAIrvQQEtcm49frcyCM8i2b5najnzPDtYqrHRYTTiuwQ==",
|
||||
"version": "3.28.1",
|
||||
"resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.28.1.tgz",
|
||||
"integrity": "sha512-oqz8ulwRgtUXRi0vbqEfGNly19zpyCxYrjhkk5TibGcgSW6eNwS5woajCXRwqURi8Ehc2yOFTiB4uNoZ+NJOnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
@ -16953,12 +16921,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/server-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
|
||||
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
@ -18870,16 +18832,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
|
||||
"integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
|
||||
@ -3,9 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"worker:ai-image": "node scripts/run-ai-image-worker.mjs",
|
||||
"predev": "npm run prisma:check-client-sync",
|
||||
"dev": "next dev --webpack",
|
||||
"dev": "next dev",
|
||||
"build": "prisma generate && next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint",
|
||||
@ -19,35 +18,11 @@
|
||||
"prisma:studio": "prisma studio",
|
||||
"sync:supply-chain-product-archive": "tsx scripts/sync-supply-chain-product-archive.ts",
|
||||
"sync:supply-chain-product-archive-daily": "tsx scripts/sync-supply-chain-product-archive-sales-performance-daily.ts",
|
||||
"generate:modular-processing-daily-snapshot": "tsx scripts/generate-modular-processing-daily-snapshot.ts",
|
||||
"sync:supply-chain-product-archive-enrichment": "tsx scripts/sync-supply-chain-product-archive-enrichment.ts",
|
||||
"sync:distribution-listings": "tsx scripts/sync-distribution-listings.ts",
|
||||
"backfill:product-archive-designer": "tsx scripts/backfill-product-archive-designer-from-jst-sku-info-manage.ts",
|
||||
"backfill:product-archive-designer:apply": "tsx scripts/backfill-product-archive-designer-from-jst-sku-info-manage.ts --apply",
|
||||
"backfill:product-archive-opshead": "tsx scripts/backfill-product-archive-opshead-from-jst-sku-info-manage.ts --mode=regular",
|
||||
"backfill:product-archive-opshead:apply": "tsx scripts/backfill-product-archive-opshead-from-jst-sku-info-manage.ts --mode=regular --apply",
|
||||
"backfill:product-archive-opshead:regular": "tsx scripts/backfill-product-archive-opshead-from-jst-sku-info-manage.ts --mode=regular",
|
||||
"backfill:product-archive-opshead:regular:apply": "tsx scripts/backfill-product-archive-opshead-from-jst-sku-info-manage.ts --mode=regular --apply",
|
||||
"backfill:product-archive-production-type": "tsx scripts/backfill-product-archive-production-type.ts",
|
||||
"backfill:product-archive-production-type:apply": "tsx scripts/backfill-product-archive-production-type.ts --apply",
|
||||
"sync:supply-chain-product-archive-sales-performance": "tsx scripts/sync-supply-chain-product-archive-sales-performance.ts",
|
||||
"run:product-sabc": "tsx scripts/run-product-sabc.ts",
|
||||
"compare:product-sabc-xlsx": "tsx scripts/compare-product-sabc-xlsx.ts",
|
||||
"seed:local-product-sabc-xlsx": "tsx scripts/seed-local-product-sabc-xlsx.ts",
|
||||
"sync:jst-sku-product-archive": "tsx scripts/sync-jst-sku-product-archive.ts",
|
||||
"snapshot:limit-price": "tsx scripts/generate-limit-price-snapshot.ts",
|
||||
"snapshot:limit-price:daily": "node scripts/run-limit-price-daily-snapshot.mjs",
|
||||
"install:limit-price-snapshot-systemd": "bash scripts/install-limit-price-snapshot-systemd-timer.sh",
|
||||
"import:province-shipment-share": "node scripts/import-province-shipment-share.mjs",
|
||||
"sync:jst-logistics-rules": "tsx scripts/sync-jst-logistics-rules.ts",
|
||||
"snapshot:jst-logistics:daily": "tsx scripts/sync-jst-logistics-rules.ts",
|
||||
"install:jst-logistics-daily": "bash scripts/install-jst-logistics-daily-sync.sh",
|
||||
"install:jst-logistics-systemd": "bash scripts/install-jst-logistics-systemd-timer.sh",
|
||||
"sync:bom-cost-reference": "tsx scripts/sync-bom-cost-reference.ts",
|
||||
"backtest:bom-cost-reference": "tsx scripts/backtest-bom-cost-reference.ts",
|
||||
"report:bom-cost-shadow-divergence": "tsx scripts/report-bom-cost-shadow-divergence.ts",
|
||||
"report:bom-agent-baseline": "tsx scripts/report-bom-agent-baseline.ts",
|
||||
"trial:bom-costing-agent": "tsx scripts/run-bom-costing-agent-trial.ts",
|
||||
"import:yunshan-created-at": "tsx scripts/import-yunshan-product-created-at.ts",
|
||||
"import:product-archive-drawing-media": "tsx scripts/import-product-archive-drawing-media.ts",
|
||||
"db:verify-master-data": "node scripts/verify-master-data-foundation.js",
|
||||
@ -85,7 +60,6 @@
|
||||
"materials:sync-standard": "tsx scripts/sync-standard-finishes.ts",
|
||||
"materials:sync-standard:apply": "tsx scripts/sync-standard-finishes.ts --apply",
|
||||
"sync:jst-sku-stock": "tsx scripts/sync-jst-sku-stock-products.ts",
|
||||
"sync:product-inventory-snapshot": "tsx scripts/sync-product-inventory-snapshot.ts",
|
||||
"image-search:model:prepare": "tsx scripts/prepare-product-archive-image-search-model.ts",
|
||||
"image-search:index": "tsx scripts/index-product-archive-images.ts",
|
||||
"repair:barcode-print:historical": "tsx scripts/repair-barcode-print-source-rows.ts",
|
||||
@ -93,24 +67,12 @@
|
||||
"sync:oa-board:apply": "tsx scripts/sync-oa-board.ts --apply",
|
||||
"sync:oa-brand-planning": "tsx scripts/sync-oa-brand-planning-board.ts",
|
||||
"sync:oa-brand-planning:apply": "tsx scripts/sync-oa-brand-planning-board.ts --apply",
|
||||
"sync:oa-video-flow": "tsx scripts/sync-oa-video-flow-board.ts",
|
||||
"sync:oa-video-flow:apply": "tsx scripts/sync-oa-video-flow-board.ts --apply",
|
||||
"sync:oa-art-flow-raw": "tsx scripts/sync-oa-art-flow-raw.ts",
|
||||
"sync:oa-art-flow-raw:apply": "tsx scripts/sync-oa-art-flow-raw.ts --apply",
|
||||
"import:clearance-pool": "tsx scripts/import-clearance-pool-snapshot.ts",
|
||||
"import:clearance-pool-archive": "tsx scripts/import-clearance-pool-archive.ts",
|
||||
"reset:clearance-pool:archive": "tsx scripts/reset-clearance-pool-to-archive.ts",
|
||||
"sync:clearance-pool:discontinued": "tsx scripts/sync-clearance-pool-snapshot.ts",
|
||||
"sync:clearance-stock:stock-monitor": "tsx scripts/sync-clearance-stock-from-stock-monitor.ts",
|
||||
"sync:clearance-progress": "tsx scripts/sync-clearance-progress.ts",
|
||||
"import:clearance-review": "tsx scripts/import-clearance-review-snapshots.ts",
|
||||
"cache:oa-board:local": "tsx scripts/cache-oa-board-local-preview.ts",
|
||||
"import:oa-board-activity-mappings": "tsx scripts/import-oa-board-activity-mappings.ts",
|
||||
"import:oa-board-node-rules": "tsx scripts/import-oa-board-node-rules.ts",
|
||||
"report:oa-board-activities": "tsx scripts/report-oa-board-activities.ts",
|
||||
"sync:employee-directory": "tsx scripts/sync-employee-directory.ts",
|
||||
"sync:employee-directory:apply": "tsx scripts/sync-employee-directory.ts --apply",
|
||||
"sync:dingtalk-directory:apply": "tsx scripts/sync-dingtalk-directory.ts --apply",
|
||||
"sync:external-jsreport": "tsx scripts/sync-external-readonly-to-jsreport.ts",
|
||||
"sync:external-jsreport:apply": "tsx scripts/sync-external-readonly-to-jsreport.ts --apply",
|
||||
"db:verify-master-data:truth": "node scripts/verify-master-data-truth-sources.js",
|
||||
@ -163,7 +125,6 @@
|
||||
"bcryptjs": "^3.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dockerode": "^4.0.9",
|
||||
"exceljs": "^4.4.0",
|
||||
@ -180,7 +141,7 @@
|
||||
"next": "^16.1.6",
|
||||
"obj2gltf": "^3.2.0",
|
||||
"pg": "^8.17.2",
|
||||
"pinyin-pro": "^3.29.5",
|
||||
"pinyin-pro": "^3.28.1",
|
||||
"postcss": "^8.5.6",
|
||||
"print-js": "^1.6.0",
|
||||
"puppeteer": "^24.40.0",
|
||||
@ -190,12 +151,10 @@
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"server-only": "^0.0.1",
|
||||
"sharp": "^0.34.5",
|
||||
"ssh2": "^1.17.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"three": "^0.182.0",
|
||||
"uuid": "^11.1.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.2"
|
||||
@ -229,7 +188,6 @@
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dockerode": "^4.0.1",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/jest": "^30.0.0",
|
||||
@ -252,20 +210,5 @@
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@prisma/engines",
|
||||
"cpu-features",
|
||||
"esbuild",
|
||||
"onnxruntime-node",
|
||||
"prisma",
|
||||
"protobufjs",
|
||||
"puppeteer",
|
||||
"sharp",
|
||||
"ssh2",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "HomeQuickLink" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL DEFAULT '',
|
||||
"url" TEXT NOT NULL,
|
||||
"iconKey" TEXT NOT NULL DEFAULT 'link',
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"updatedBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "HomeQuickLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "HomeQuickLink_enabled_sortOrder_idx" ON "HomeQuickLink"("enabled", "sortOrder");
|
||||
@ -1,6 +0,0 @@
|
||||
-- Keep all existing barcode/day facts and the mainline shipAmount definition.
|
||||
-- NULL salesAmount explicitly marks history requiring a source backfill.
|
||||
ALTER TABLE "SupplyChainProductArchiveSalesPerformanceDaily"
|
||||
ADD COLUMN "skuOperator" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "unitCost" DECIMAL(24,4),
|
||||
ADD COLUMN "salesAmount" DECIMAL(24,4);
|
||||
@ -1,19 +0,0 @@
|
||||
-- Operations-maintained checklist per dropship (DF) SPU on the 1688 dropship
|
||||
-- issues page. Read-only columns always come live from the product archive;
|
||||
-- only the two operations-maintained fields live here.
|
||||
CREATE TABLE "SupplyChainDropshipIssue" (
|
||||
"id" TEXT NOT NULL,
|
||||
"spu" VARCHAR(120) NOT NULL,
|
||||
"invoiceType" VARCHAR(16) NOT NULL DEFAULT '',
|
||||
"returnAddress" TEXT NOT NULL DEFAULT '',
|
||||
"updatedByName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SupplyChainDropshipIssue_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainDropshipIssue_spu_key" ON "SupplyChainDropshipIssue"("spu");
|
||||
CREATE INDEX "SupplyChainDropshipIssue_updatedAt_idx" ON "SupplyChainDropshipIssue"("updatedAt");
|
||||
|
||||
ALTER TYPE "OperationLogAction" ADD VALUE IF NOT EXISTS 'SUPPLY_CHAIN_DROPSHIP_ISSUE_WRITE';
|
||||
@ -1,15 +0,0 @@
|
||||
ALTER TABLE "FinishedProductProgressItem"
|
||||
ADD COLUMN "sourceOpeningCode" TEXT,
|
||||
ADD COLUMN "sourceProductId" TEXT,
|
||||
ADD COLUMN "sourceSkuId" TEXT,
|
||||
ADD COLUMN "sourceGroupKey" TEXT,
|
||||
ADD COLUMN "sourceSupplierCode" TEXT,
|
||||
ADD COLUMN "sourceImportedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "supplierCode" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE UNIQUE INDEX "FinishedProductProgressItem_factory_source_key"
|
||||
ON "FinishedProductProgressItem" ("sourceOpeningCode", "sourceProductId", "sourceSkuId");
|
||||
CREATE INDEX "FinishedProductProgressItem_sourceGroupKey_idx"
|
||||
ON "FinishedProductProgressItem" ("sourceGroupKey");
|
||||
CREATE INDEX "FinishedProductProgressItem_sourceImportedAt_idx"
|
||||
ON "FinishedProductProgressItem" ("sourceImportedAt");
|
||||
@ -1,4 +0,0 @@
|
||||
-- 1688 dropship issues page: operations now maintain whether the SPU ships
|
||||
-- assembled (整装, drawing not required) or knocked down (拆装, drawing required).
|
||||
ALTER TABLE "SupplyChainDropshipIssue"
|
||||
ADD COLUMN "assemblyType" VARCHAR(16) NOT NULL DEFAULT '';
|
||||
@ -1,21 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "BomCostingAgentSkuEstimate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"caseId" TEXT NOT NULL,
|
||||
"skuId" TEXT NOT NULL,
|
||||
"externalSkuId" TEXT NOT NULL DEFAULT '',
|
||||
"engineVersion" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"estimate" JSONB NOT NULL,
|
||||
"usage" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BomCostingAgentSkuEstimate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "BomCostingAgentSkuEstimate_skuId_engineVersion_key" ON "BomCostingAgentSkuEstimate"("skuId", "engineVersion");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "BomCostingAgentSkuEstimate_caseId_idx" ON "BomCostingAgentSkuEstimate"("caseId");
|
||||
@ -1,22 +0,0 @@
|
||||
CREATE TABLE "SupplyChainManagementOwnerConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"spu" TEXT NOT NULL,
|
||||
"itemNumber" TEXT NOT NULL,
|
||||
"productCode" TEXT NOT NULL,
|
||||
"operationOwner" TEXT NOT NULL DEFAULT '',
|
||||
"departmentOwner" TEXT NOT NULL DEFAULT '',
|
||||
"generalOwner" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SupplyChainManagementOwnerConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainManagementOwnerConfig_spu_itemNumber_productCode_key"
|
||||
ON "SupplyChainManagementOwnerConfig"("spu", "itemNumber", "productCode");
|
||||
CREATE INDEX "SupplyChainManagementOwnerConfig_spu_idx"
|
||||
ON "SupplyChainManagementOwnerConfig"("spu");
|
||||
CREATE INDEX "SupplyChainManagementOwnerConfig_itemNumber_idx"
|
||||
ON "SupplyChainManagementOwnerConfig"("itemNumber");
|
||||
CREATE INDEX "SupplyChainManagementOwnerConfig_productCode_idx"
|
||||
ON "SupplyChainManagementOwnerConfig"("productCode");
|
||||
@ -1,9 +0,0 @@
|
||||
DROP INDEX IF EXISTS "SupplyChainManagementOwnerConfig_spu_itemNumber_productCode_key";
|
||||
DROP INDEX IF EXISTS "SupplyChainManagementOwnerConfig_spu_idx";
|
||||
DROP INDEX IF EXISTS "SupplyChainManagementOwnerConfig_itemNumber_idx";
|
||||
DROP INDEX IF EXISTS "SupplyChainManagementOwnerConfig_productCode_idx";
|
||||
|
||||
ALTER TABLE "SupplyChainManagementOwnerConfig"
|
||||
DROP COLUMN IF EXISTS "spu",
|
||||
DROP COLUMN IF EXISTS "itemNumber",
|
||||
DROP COLUMN IF EXISTS "productCode";
|
||||
@ -1,8 +0,0 @@
|
||||
ALTER TABLE "Department"
|
||||
ADD COLUMN "directoryManagerDingtalkUserIds" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];
|
||||
|
||||
ALTER TABLE "EmployeeDirectory"
|
||||
ADD COLUMN "managerDingtalkUserId" TEXT;
|
||||
|
||||
CREATE INDEX "EmployeeDirectory_managerDingtalkUserId_idx"
|
||||
ON "EmployeeDirectory"("managerDingtalkUserId");
|
||||
@ -1,10 +0,0 @@
|
||||
ALTER TABLE "SupplyChainManagementOwnerConfig"
|
||||
ADD COLUMN "sourceEmployeeId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainManagementOwnerConfig_sourceEmployeeId_key"
|
||||
ON "SupplyChainManagementOwnerConfig"("sourceEmployeeId");
|
||||
|
||||
ALTER TABLE "SupplyChainManagementOwnerConfig"
|
||||
ADD CONSTRAINT "SupplyChainManagementOwnerConfig_sourceEmployeeId_fkey"
|
||||
FOREIGN KEY ("sourceEmployeeId") REFERENCES "EmployeeDirectory"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -1,6 +0,0 @@
|
||||
ALTER TABLE "SupplyChainManagementOwnerConfig"
|
||||
ADD COLUMN "manualOverride" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "isHidden" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
CREATE INDEX "SupplyChainManagementOwnerConfig_isHidden_updatedAt_idx"
|
||||
ON "SupplyChainManagementOwnerConfig"("isHidden", "updatedAt");
|
||||
@ -1,3 +0,0 @@
|
||||
ALTER TABLE "SupplyChainManagementOwnerConfig"
|
||||
ADD COLUMN "department" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "profitCenter" TEXT NOT NULL DEFAULT '';
|
||||
@ -1,42 +0,0 @@
|
||||
CREATE TABLE "Dropship1688SourcingTask" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sourceArchiveId" TEXT NOT NULL,
|
||||
"sourceSpu" TEXT NOT NULL DEFAULT '',
|
||||
"sourceBarcode" VARCHAR(100) NOT NULL,
|
||||
"sourceCostPrice" DECIMAL(24,4),
|
||||
"sourceSkuOperator" TEXT NOT NULL DEFAULT '',
|
||||
"sourceDateFrom" DATE NOT NULL,
|
||||
"sourceDateTo" DATE NOT NULL,
|
||||
"purchaserUserId" TEXT NOT NULL,
|
||||
"purchaserName" TEXT NOT NULL,
|
||||
"supplierName" TEXT NOT NULL DEFAULT '',
|
||||
"quoted_price_tax_included" DECIMAL(24,4),
|
||||
"invoiceTaxPoint" DECIMAL(8,4),
|
||||
"sourcingMode" TEXT NOT NULL DEFAULT '',
|
||||
"jianshangBarcode" TEXT NOT NULL DEFAULT '',
|
||||
"acceptedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Dropship1688SourcingTask_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "Dropship1688SourcingTask_sourceBarcode_purchaserUserId_key"
|
||||
ON "Dropship1688SourcingTask"("sourceBarcode", "purchaserUserId");
|
||||
|
||||
CREATE INDEX "Dropship1688SourcingTask_sourceArchiveId_idx"
|
||||
ON "Dropship1688SourcingTask"("sourceArchiveId");
|
||||
|
||||
CREATE INDEX "Dropship1688SourcingTask_purchaserName_idx"
|
||||
ON "Dropship1688SourcingTask"("purchaserName");
|
||||
|
||||
CREATE INDEX "Dropship1688SourcingTask_sourceSpu_idx"
|
||||
ON "Dropship1688SourcingTask"("sourceSpu");
|
||||
|
||||
CREATE INDEX "Dropship1688SourcingTask_sourceSkuOperator_idx"
|
||||
ON "Dropship1688SourcingTask"("sourceSkuOperator");
|
||||
|
||||
CREATE INDEX "Dropship1688SourcingTask_purchaserUserId_acceptedAt_idx"
|
||||
ON "Dropship1688SourcingTask"("purchaserUserId", "acceptedAt");
|
||||
|
||||
CREATE INDEX "Dropship1688SourcingTask_acceptedAt_idx"
|
||||
ON "Dropship1688SourcingTask"("acceptedAt");
|
||||
@ -1,32 +0,0 @@
|
||||
CREATE TABLE "ModularProductProcessingRuleSet" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL DEFAULT '模块化商品备货规则',
|
||||
"columns" JSONB NOT NULL DEFAULT '[]',
|
||||
"updatedById" TEXT,
|
||||
"updatedByName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ModularProductProcessingRuleSet_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ModularProductProcessingRuleRow" (
|
||||
"id" TEXT NOT NULL,
|
||||
"ruleSetId" TEXT NOT NULL,
|
||||
"values" JSONB NOT NULL DEFAULT '{}',
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ModularProductProcessingRuleRow_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "ModularProductProcessingRuleSet_updatedAt_idx"
|
||||
ON "ModularProductProcessingRuleSet"("updatedAt");
|
||||
CREATE INDEX "ModularProductProcessingRuleRow_ruleSetId_sortOrder_idx"
|
||||
ON "ModularProductProcessingRuleRow"("ruleSetId", "sortOrder");
|
||||
|
||||
ALTER TABLE "ModularProductProcessingRuleRow"
|
||||
ADD CONSTRAINT "ModularProductProcessingRuleRow_ruleSetId_fkey"
|
||||
FOREIGN KEY ("ruleSetId") REFERENCES "ModularProductProcessingRuleSet"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -1,3 +0,0 @@
|
||||
-- Store the quantity-one freight estimate separately from the existing total freight fact.
|
||||
ALTER TABLE "SupplyChainProductArchiveSalesPerformanceDaily"
|
||||
ADD COLUMN "estimatedFreight" DECIMAL(24,4);
|
||||
@ -1,32 +0,0 @@
|
||||
ALTER TABLE "SupplyChainManagementOwnerConfig"
|
||||
ADD COLUMN "secondaryDepartment" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
WITH RECURSIVE department_tree AS (
|
||||
SELECT "id", "name", "parentId", 0 AS depth
|
||||
FROM "Department"
|
||||
WHERE "externalId" = '1'
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT child."id", child."name", child."parentId", parent.depth + 1
|
||||
FROM "Department" child
|
||||
INNER JOIN department_tree parent ON child."parentId" = parent."id"
|
||||
), employee_ancestors AS (
|
||||
SELECT employee."id" AS "employeeId", department."id" AS "departmentId", department."parentId"
|
||||
FROM "EmployeeDirectory" employee
|
||||
INNER JOIN "Department" department
|
||||
ON department."externalId" = employee."primaryDepartmentExternalId"
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT ancestor."employeeId", parent."id", parent."parentId"
|
||||
FROM employee_ancestors ancestor
|
||||
INNER JOIN "Department" parent ON parent."id" = ancestor."parentId"
|
||||
)
|
||||
UPDATE "SupplyChainManagementOwnerConfig" config
|
||||
SET "secondaryDepartment" = department_tree."name"
|
||||
FROM employee_ancestors
|
||||
INNER JOIN department_tree
|
||||
ON department_tree."id" = employee_ancestors."departmentId"
|
||||
AND department_tree.depth = 3
|
||||
WHERE config."sourceEmployeeId" = employee_ancestors."employeeId";
|
||||
@ -1,67 +0,0 @@
|
||||
ALTER TABLE "SupplyChainProductArchive"
|
||||
ADD COLUMN "sabcRating" VARCHAR(1) NOT NULL DEFAULT '',
|
||||
ADD COLUMN "sabcUpdatedAt" TIMESTAMP(3);
|
||||
|
||||
CREATE TABLE "SupplyChainProductArchiveSabcResult" (
|
||||
"id" TEXT NOT NULL,
|
||||
"archiveId" TEXT NOT NULL,
|
||||
"barcode" VARCHAR(100) NOT NULL,
|
||||
"periodStart" DATE NOT NULL,
|
||||
"periodEnd" DATE NOT NULL,
|
||||
"eligible" BOOLEAN NOT NULL,
|
||||
"sabcRating" VARCHAR(1) NOT NULL DEFAULT '',
|
||||
"category" TEXT NOT NULL DEFAULT '',
|
||||
"performanceScale" TEXT NOT NULL DEFAULT '',
|
||||
"salesScale" TEXT NOT NULL DEFAULT '',
|
||||
"finalScale" TEXT NOT NULL DEFAULT '',
|
||||
"profitLabel" TEXT NOT NULL DEFAULT '',
|
||||
"averageDailySales" DECIMAL(24,8),
|
||||
"feeRatio" DECIMAL(20,8),
|
||||
"grossMargin" DECIMAL(20,8),
|
||||
"shipAmount" DECIMAL(24,4) NOT NULL,
|
||||
"realPerformance" DECIMAL(24,4) NOT NULL,
|
||||
"shipQty" BIGINT NOT NULL,
|
||||
"grossProfit" DECIMAL(24,4) NOT NULL,
|
||||
"afterSaleAmount" DECIMAL(24,4) NOT NULL,
|
||||
"status" VARCHAR(24) NOT NULL DEFAULT 'CALCULATED',
|
||||
"reason" TEXT NOT NULL DEFAULT '',
|
||||
"calculatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "SupplyChainProductArchiveSabcResult_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "SupplyChainProductArchiveSabcRun" (
|
||||
"id" TEXT NOT NULL,
|
||||
"periodStart" DATE NOT NULL,
|
||||
"periodEnd" DATE NOT NULL,
|
||||
"mode" VARCHAR(16) NOT NULL,
|
||||
"status" VARCHAR(24) NOT NULL,
|
||||
"sourceRows" INTEGER NOT NULL DEFAULT 0,
|
||||
"eligibleRows" INTEGER NOT NULL DEFAULT 0,
|
||||
"taggedRows" INTEGER NOT NULL DEFAULT 0,
|
||||
"comparedRows" INTEGER NOT NULL DEFAULT 0,
|
||||
"mismatchRows" INTEGER NOT NULL DEFAULT 0,
|
||||
"errorMessage" TEXT,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"completedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "SupplyChainProductArchiveSabcRun_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainProductArchiveSabcResult_barcode_periodStart_periodEnd_key"
|
||||
ON "SupplyChainProductArchiveSabcResult"("barcode", "periodStart", "periodEnd");
|
||||
CREATE INDEX "SupplyChainProductArchiveSabcResult_periodStart_periodEnd_idx"
|
||||
ON "SupplyChainProductArchiveSabcResult"("periodStart", "periodEnd");
|
||||
CREATE INDEX "SupplyChainProductArchiveSabcResult_sabcRating_periodEnd_idx"
|
||||
ON "SupplyChainProductArchiveSabcResult"("sabcRating", "periodEnd");
|
||||
CREATE INDEX "SupplyChainProductArchiveSabcResult_archiveId_periodEnd_idx"
|
||||
ON "SupplyChainProductArchiveSabcResult"("archiveId", "periodEnd");
|
||||
CREATE UNIQUE INDEX "SupplyChainProductArchiveSabcRun_periodStart_periodEnd_mode_key"
|
||||
ON "SupplyChainProductArchiveSabcRun"("periodStart", "periodEnd", "mode");
|
||||
CREATE INDEX "SupplyChainProductArchiveSabcRun_status_startedAt_idx"
|
||||
ON "SupplyChainProductArchiveSabcRun"("status", "startedAt");
|
||||
|
||||
ALTER TABLE "SupplyChainProductArchiveSabcResult"
|
||||
ADD CONSTRAINT "SupplyChainProductArchiveSabcResult_archiveId_fkey"
|
||||
FOREIGN KEY ("archiveId") REFERENCES "SupplyChainProductArchive"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -1,2 +0,0 @@
|
||||
-- Add factory stock (scmStock from plm_productDiscontinuation) to the archive main table.
|
||||
ALTER TABLE "SupplyChainProductArchive" ADD COLUMN "stockFactory" TEXT NOT NULL DEFAULT '';
|
||||
@ -1,29 +0,0 @@
|
||||
-- 全局下载中心:后台导出/下载任务
|
||||
CREATE TABLE "DownloadTask" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL DEFAULT '',
|
||||
"type" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL DEFAULT '',
|
||||
"paramsJson" TEXT NOT NULL DEFAULT '{}',
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"progress" INTEGER NOT NULL DEFAULT 0,
|
||||
"fileName" TEXT NOT NULL DEFAULT '',
|
||||
"filePath" TEXT NOT NULL DEFAULT '',
|
||||
"fileSize" INTEGER NOT NULL DEFAULT 0,
|
||||
"rowCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"error" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "DownloadTask_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "DownloadTask_userId_createdAt_idx" ON "DownloadTask"("userId", "createdAt" DESC);
|
||||
CREATE INDEX "DownloadTask_status_idx" ON "DownloadTask"("status");
|
||||
|
||||
ALTER TABLE "DownloadTask"
|
||||
ADD CONSTRAINT "DownloadTask_status_check"
|
||||
CHECK ("status" IN ('pending', 'processing', 'completed', 'failed'));
|
||||
@ -1,41 +0,0 @@
|
||||
ALTER TABLE "WorkRecordScoreRule"
|
||||
ADD COLUMN "workFrequency" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "initiativeType" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE INDEX "WorkRecordScoreRule_workFrequency_sortOrder_idx"
|
||||
ON "WorkRecordScoreRule"("workFrequency", "sortOrder");
|
||||
|
||||
CREATE INDEX "WorkRecordScoreRule_initiativeType_sortOrder_idx"
|
||||
ON "WorkRecordScoreRule"("initiativeType", "sortOrder");
|
||||
|
||||
CREATE TABLE "WorkRecordBusinessOption" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "WorkRecordBusinessOption_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "WorkRecordBusinessOption_type_value_key"
|
||||
ON "WorkRecordBusinessOption"("type", "value");
|
||||
|
||||
CREATE INDEX "WorkRecordBusinessOption_type_sortOrder_idx"
|
||||
ON "WorkRecordBusinessOption"("type", "sortOrder");
|
||||
|
||||
CREATE INDEX "WorkRecordBusinessOption_type_isActive_sortOrder_idx"
|
||||
ON "WorkRecordBusinessOption"("type", "isActive", "sortOrder");
|
||||
|
||||
INSERT INTO "WorkRecordBusinessOption" ("id", "type", "value", "sortOrder", "isActive", "createdAt", "updatedAt")
|
||||
VALUES
|
||||
('employee-hours-category-product', 'BUSINESS_CATEGORY', '产品开发', 1, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-category-design', 'BUSINESS_CATEGORY', '设计支持', 2, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-category-operation', 'BUSINESS_CATEGORY', '日常运营', 3, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-daily', 'WORK_FREQUENCY', '每日', 1, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-weekly', 'WORK_FREQUENCY', '每周', 2, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-monthly', 'WORK_FREQUENCY', '每月', 3, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-temporary', 'WORK_FREQUENCY', '临时', 4, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-initiative-active', 'INITIATIVE_TYPE', '主动', 1, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-initiative-passive', 'INITIATIVE_TYPE', '被动', 2, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
|
||||
@ -1,2 +0,0 @@
|
||||
ALTER TABLE "SupplyChainProductArchive"
|
||||
ADD COLUMN "productAttribute" TEXT NOT NULL DEFAULT '';
|
||||
@ -1,35 +0,0 @@
|
||||
CREATE TABLE "EmployeeHoursRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workDate" DATE NOT NULL,
|
||||
"nickname" TEXT NOT NULL,
|
||||
"businessContent" TEXT NOT NULL,
|
||||
"businessNote" TEXT NOT NULL DEFAULT '',
|
||||
"businessCategory" TEXT NOT NULL,
|
||||
"workFrequency" TEXT NOT NULL,
|
||||
"businessSource" TEXT NOT NULL,
|
||||
"startTime" VARCHAR(16) NOT NULL,
|
||||
"endTime" VARCHAR(16) NOT NULL,
|
||||
"minutes" INTEGER NOT NULL,
|
||||
"hours" DECIMAL(10,2) NOT NULL,
|
||||
"estimatedScore" DECIMAL(12,4) NOT NULL,
|
||||
"approvedScore" DECIMAL(12,4),
|
||||
"department" TEXT NOT NULL DEFAULT '',
|
||||
"createdById" TEXT NOT NULL,
|
||||
"updatedById" TEXT NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "EmployeeHoursRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "EmployeeHoursRecord_workDate_deletedAt_idx"
|
||||
ON "EmployeeHoursRecord"("workDate", "deletedAt");
|
||||
|
||||
CREATE INDEX "EmployeeHoursRecord_nickname_workDate_idx"
|
||||
ON "EmployeeHoursRecord"("nickname", "workDate");
|
||||
|
||||
CREATE INDEX "EmployeeHoursRecord_businessCategory_workDate_idx"
|
||||
ON "EmployeeHoursRecord"("businessCategory", "workDate");
|
||||
|
||||
CREATE INDEX "EmployeeHoursRecord_department_workDate_idx"
|
||||
ON "EmployeeHoursRecord"("department", "workDate");
|
||||
@ -1,3 +0,0 @@
|
||||
-- 店铺状态(数字码:0=正常、6=已关闭、3=已出售;来源公司报表库 rpt.fys_shopInfo,每日定时同步)
|
||||
-- NULL 表示尚未同步或在公司报表库中未匹配到对应店铺
|
||||
ALTER TABLE "StoreAccount" ADD COLUMN "shopState" INTEGER;
|
||||
@ -1,26 +0,0 @@
|
||||
-- 类目配置增加「分类」维度,并新增分类规模档位表
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig" ADD COLUMN "categoryGroup" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE INDEX "SupplyChainProductArchiveCategoryConfig_categoryGroup_idx" ON "SupplyChainProductArchiveCategoryConfig"("categoryGroup");
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SupplyChainProductArchiveCategoryGroupTier" (
|
||||
"id" TEXT NOT NULL,
|
||||
"categoryGroup" TEXT NOT NULL,
|
||||
"scaleLabel" TEXT NOT NULL,
|
||||
"performanceStart" DECIMAL(14,2) NOT NULL,
|
||||
"performanceEnd" DECIMAL(14,2),
|
||||
"dailySalesStart" DECIMAL(12,2) NOT NULL,
|
||||
"dailySalesEnd" DECIMAL(12,2),
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SupplyChainProductArchiveCategoryGroupTier_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "SupplyChainProductArchiveCategoryGroupTier_categoryGroup_scaleLabel_key" ON "SupplyChainProductArchiveCategoryGroupTier"("categoryGroup", "scaleLabel");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SupplyChainProductArchiveCategoryGroupTier_categoryGroup_idx" ON "SupplyChainProductArchiveCategoryGroupTier"("categoryGroup");
|
||||
@ -1,6 +0,0 @@
|
||||
-- 类目配置增加规模标签与业绩/日销阈值字段(终点可空 = 不设上限)
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig" ADD COLUMN "scaleLabel" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig" ADD COLUMN "performanceStart" DECIMAL(14,2);
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig" ADD COLUMN "performanceEnd" DECIMAL(14,2);
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig" ADD COLUMN "dailySalesStart" DECIMAL(12,2);
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig" ADD COLUMN "dailySalesEnd" DECIMAL(12,2);
|
||||
@ -1,19 +0,0 @@
|
||||
-- 类目规模行表:一行 = 一个二级类目 × 一档规模标签
|
||||
CREATE TABLE "SupplyChainProductArchiveCategoryScaleRow" (
|
||||
"id" TEXT NOT NULL,
|
||||
"categoryL2" TEXT NOT NULL,
|
||||
"scaleLabel" TEXT NOT NULL,
|
||||
"performanceStart" DECIMAL(14,2),
|
||||
"performanceEnd" DECIMAL(14,2),
|
||||
"dailySalesStart" DECIMAL(12,2),
|
||||
"dailySalesEnd" DECIMAL(12,2),
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SupplyChainProductArchiveCategoryScaleRow_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainProductArchiveCategoryScaleRow_categoryL2_scaleLabel_key" ON "SupplyChainProductArchiveCategoryScaleRow"("categoryL2", "scaleLabel");
|
||||
|
||||
CREATE INDEX "SupplyChainProductArchiveCategoryScaleRow_categoryL2_idx" ON "SupplyChainProductArchiveCategoryScaleRow"("categoryL2");
|
||||
@ -1,26 +0,0 @@
|
||||
CREATE TABLE "EmployeeHoursAccessGrant" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"scope" TEXT NOT NULL DEFAULT 'SELF',
|
||||
"updatedById" TEXT NOT NULL,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "EmployeeHoursAccessGrant_pkey" PRIMARY KEY ("userId"),
|
||||
CONSTRAINT "EmployeeHoursAccessGrant_scope_check" CHECK ("scope" IN ('SELF', 'DEPARTMENT', 'ALL'))
|
||||
);
|
||||
|
||||
CREATE TABLE "EmployeeHoursAuditLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"area" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"actorId" TEXT NOT NULL,
|
||||
"actorName" TEXT NOT NULL,
|
||||
"targetId" TEXT,
|
||||
"message" TEXT NOT NULL,
|
||||
"details" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "EmployeeHoursAuditLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "EmployeeHoursAuditLog_area_createdAt_idx" ON "EmployeeHoursAuditLog"("area", "createdAt");
|
||||
CREATE INDEX "EmployeeHoursAuditLog_actorId_createdAt_idx" ON "EmployeeHoursAuditLog"("actorId", "createdAt");
|
||||
CREATE INDEX "EmployeeHoursAuditLog_status_createdAt_idx" ON "EmployeeHoursAuditLog"("status", "createdAt");
|
||||
@ -1,15 +0,0 @@
|
||||
-- 合并为单表:类目规模行表自带一级类目与分类列,删除分类档位模板表
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryScaleRow" ADD COLUMN "categoryL1" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryScaleRow" ADD COLUMN "categoryGroup" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- 从类目主表回填一级类目与分类
|
||||
UPDATE "SupplyChainProductArchiveCategoryScaleRow" s
|
||||
SET "categoryL1" = c."categoryL1",
|
||||
"categoryGroup" = c."categoryGroup"
|
||||
FROM "SupplyChainProductArchiveCategoryConfig" c
|
||||
WHERE s."categoryL2" = c."categoryL2";
|
||||
|
||||
CREATE INDEX "SupplyChainProductArchiveCategoryScaleRow_categoryGroup_idx" ON "SupplyChainProductArchiveCategoryScaleRow"("categoryGroup");
|
||||
|
||||
-- 删除分类档位模板表(其数据已按类目展开至规模行表)
|
||||
DROP TABLE "SupplyChainProductArchiveCategoryGroupTier";
|
||||
@ -1,2 +0,0 @@
|
||||
ALTER TABLE "EmployeeHoursRecord"
|
||||
ADD COLUMN "attachment" JSONB;
|
||||
@ -1,57 +0,0 @@
|
||||
-- 员工工时独立业务配置(与工作记录 WorkRecordBusinessOption / WorkRecordScoreRule 解耦,空白起始)
|
||||
|
||||
CREATE TABLE "EmployeeHoursBusinessOption" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "EmployeeHoursBusinessOption_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "EmployeeHoursBusinessOption_type_value_key"
|
||||
ON "EmployeeHoursBusinessOption"("type", "value");
|
||||
|
||||
CREATE INDEX "EmployeeHoursBusinessOption_type_sortOrder_idx"
|
||||
ON "EmployeeHoursBusinessOption"("type", "sortOrder");
|
||||
|
||||
CREATE INDEX "EmployeeHoursBusinessOption_type_isActive_sortOrder_idx"
|
||||
ON "EmployeeHoursBusinessOption"("type", "isActive", "sortOrder");
|
||||
|
||||
-- 初始选项与作者环境对齐:业务来源=提前规划/临时任务,工作频率=每日/每周/每月/临时;业务分类留空由页面配置
|
||||
INSERT INTO "EmployeeHoursBusinessOption" ("id", "type", "value", "sortOrder", "isActive", "createdAt", "updatedAt")
|
||||
VALUES
|
||||
('employee-hours-frequency-daily', 'WORK_FREQUENCY', '每日', 1, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-weekly', 'WORK_FREQUENCY', '每周', 2, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-monthly', 'WORK_FREQUENCY', '每月', 3, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-frequency-temporary', 'WORK_FREQUENCY', '临时', 4, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-initiative-planned', 'INITIATIVE_TYPE', '提前规划', 1, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
('employee-hours-initiative-temporary-task', 'INITIATIVE_TYPE', '临时任务', 2, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
|
||||
|
||||
CREATE TABLE "EmployeeHoursScoreRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"primary" TEXT NOT NULL,
|
||||
"secondary" TEXT NOT NULL DEFAULT '',
|
||||
"workFrequency" TEXT NOT NULL DEFAULT '',
|
||||
"initiativeType" TEXT NOT NULL DEFAULT '',
|
||||
"unitScore" DECIMAL(12, 4),
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "EmployeeHoursScoreRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "EmployeeHoursScoreRule_primary_sortOrder_idx"
|
||||
ON "EmployeeHoursScoreRule"("primary", "sortOrder");
|
||||
|
||||
CREATE INDEX "EmployeeHoursScoreRule_workFrequency_sortOrder_idx"
|
||||
ON "EmployeeHoursScoreRule"("workFrequency", "sortOrder");
|
||||
|
||||
CREATE INDEX "EmployeeHoursScoreRule_initiativeType_sortOrder_idx"
|
||||
ON "EmployeeHoursScoreRule"("initiativeType", "sortOrder");
|
||||
|
||||
CREATE INDEX "EmployeeHoursScoreRule_isActive_sortOrder_idx"
|
||||
ON "EmployeeHoursScoreRule"("isActive", "sortOrder");
|
||||
@ -1,33 +0,0 @@
|
||||
ALTER TABLE "SupplyChainProductArchiveCategoryConfig"
|
||||
ADD COLUMN IF NOT EXISTS "categoryGroup" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS "scaleLabel" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS "performanceStart" DECIMAL(14,2),
|
||||
ADD COLUMN IF NOT EXISTS "performanceEnd" DECIMAL(14,2),
|
||||
ADD COLUMN IF NOT EXISTS "dailySalesStart" DECIMAL(12,2),
|
||||
ADD COLUMN IF NOT EXISTS "dailySalesEnd" DECIMAL(12,2);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "SupplyChainProductArchiveCategoryConfig_categoryGroup_idx"
|
||||
ON "SupplyChainProductArchiveCategoryConfig"("categoryGroup");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "SupplyChainProductArchiveCategoryScaleRow" (
|
||||
"id" TEXT NOT NULL,
|
||||
"categoryL1" TEXT NOT NULL DEFAULT '',
|
||||
"categoryL2" TEXT NOT NULL,
|
||||
"categoryGroup" TEXT NOT NULL DEFAULT '',
|
||||
"scaleLabel" TEXT NOT NULL,
|
||||
"performanceStart" DECIMAL(14,2) NOT NULL,
|
||||
"performanceEnd" DECIMAL(14,2),
|
||||
"dailySalesStart" DECIMAL(12,2) NOT NULL,
|
||||
"dailySalesEnd" DECIMAL(12,2),
|
||||
"sortOrder" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainProductArchiveCategoryScaleRow_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "SupplyChainProductArchiveCategoryScaleRow_categoryL2_scaleLabel_key"
|
||||
ON "SupplyChainProductArchiveCategoryScaleRow"("categoryL2", "scaleLabel");
|
||||
CREATE INDEX IF NOT EXISTS "SupplyChainProductArchiveCategoryScaleRow_categoryL2_idx"
|
||||
ON "SupplyChainProductArchiveCategoryScaleRow"("categoryL2");
|
||||
CREATE INDEX IF NOT EXISTS "SupplyChainProductArchiveCategoryScaleRow_categoryGroup_idx"
|
||||
ON "SupplyChainProductArchiveCategoryScaleRow"("categoryGroup");
|
||||
@ -1,53 +0,0 @@
|
||||
-- 物流到货签收时长看板的订单明细日快照(纯新增表,仅本地 Postgres 物化,不改动既有表)。
|
||||
CREATE TABLE "LogisticsArrivalSigningDurationSnapshotRun" (
|
||||
"id" TEXT NOT NULL,
|
||||
"snapshotDate" DATE NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'RUNNING',
|
||||
"windowFrom" TEXT NOT NULL DEFAULT '',
|
||||
"windowTo" TEXT NOT NULL DEFAULT '',
|
||||
"rowCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"message" TEXT NOT NULL DEFAULT '',
|
||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "LogisticsArrivalSigningDurationSnapshotRun_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "LogisticsArrivalSigningDurationSnapshotRun_snapshotDate_key" ON "LogisticsArrivalSigningDurationSnapshotRun"("snapshotDate");
|
||||
|
||||
CREATE INDEX "LogisticsArrivalSigningDurationSnapshotRun_status_date_idx" ON "LogisticsArrivalSigningDurationSnapshotRun"("status", "snapshotDate");
|
||||
|
||||
CREATE TABLE "LogisticsArrivalSigningDurationSnapshot" (
|
||||
"id" TEXT NOT NULL,
|
||||
"snapshotDate" DATE NOT NULL,
|
||||
"rowKey" TEXT NOT NULL,
|
||||
"innerOrderId" TEXT NOT NULL,
|
||||
"soId" TEXT NOT NULL DEFAULT '',
|
||||
"logisticId" TEXT NOT NULL DEFAULT '',
|
||||
"logisticCompanyName" TEXT NOT NULL DEFAULT '',
|
||||
"logisticStatusName" TEXT NOT NULL DEFAULT '',
|
||||
"logisticReturnFlag" TEXT NOT NULL DEFAULT '',
|
||||
"orderLabels" TEXT NOT NULL DEFAULT '',
|
||||
"remark" TEXT NOT NULL DEFAULT '',
|
||||
"buyerMessage" TEXT NOT NULL DEFAULT '',
|
||||
"shopName" TEXT NOT NULL DEFAULT '',
|
||||
"platformName" TEXT NOT NULL DEFAULT '',
|
||||
"warehouseName" TEXT NOT NULL DEFAULT '',
|
||||
"receiverAddress" TEXT NOT NULL DEFAULT '',
|
||||
"payTime" TEXT,
|
||||
"sendTime" TEXT,
|
||||
"fetchTime" TEXT,
|
||||
"signTime" TEXT,
|
||||
"barcode" TEXT NOT NULL DEFAULT '',
|
||||
"productName" TEXT NOT NULL DEFAULT '',
|
||||
"supplier" TEXT NOT NULL DEFAULT '',
|
||||
"purchaser" TEXT NOT NULL DEFAULT '',
|
||||
"productItems" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "LogisticsArrivalSigningDurationSnapshot_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "LogisticsArrivalSigningDurationSnapshot_date_row_key_key" ON "LogisticsArrivalSigningDurationSnapshot"("snapshotDate", "rowKey");
|
||||
|
||||
CREATE INDEX "LogisticsArrivalSigningDurationSnapshot_date_sign_time_idx" ON "LogisticsArrivalSigningDurationSnapshot"("snapshotDate", "signTime");
|
||||
@ -1,3 +0,0 @@
|
||||
-- 产品全量档案新增「产品类型」:定厂「产品生产类型」优先,JST rpt.jst_sku.vc_name 兜底。
|
||||
ALTER TABLE "SupplyChainProductArchive"
|
||||
ADD COLUMN "productionType" TEXT NOT NULL DEFAULT '';
|
||||
@ -1,5 +0,0 @@
|
||||
-- 商品 SABC 月度核算冻结:保存生成时的整行明细快照。
|
||||
-- 生成后(每月 3 日自动、或管理员手动执行核算)页面只读取该快照,
|
||||
-- 后续档案销售状态、人员字段等变化不会再改变已生成月份的数据。
|
||||
ALTER TABLE "SupplyChainProductArchiveSabcResult"
|
||||
ADD COLUMN IF NOT EXISTS "snapshot" JSONB;
|
||||
@ -1,86 +0,0 @@
|
||||
-- 停产需求管理初版:需求单、条码明细行、停产原因分类配置(纯新增表,不改动既有表)。
|
||||
CREATE TABLE "ProductionStopDemand" (
|
||||
"id" TEXT NOT NULL,
|
||||
"demandNo" TEXT NOT NULL,
|
||||
"requesterId" TEXT NOT NULL,
|
||||
"requesterName" TEXT NOT NULL,
|
||||
"department" TEXT NOT NULL DEFAULT '',
|
||||
"reasonCategory" TEXT NOT NULL,
|
||||
"reasonDetail" TEXT NOT NULL DEFAULT '',
|
||||
"status" TEXT NOT NULL DEFAULT 'SUBMITTED',
|
||||
"submittedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopDemand_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopDemand_demandNo_key" ON "ProductionStopDemand"("demandNo");
|
||||
|
||||
CREATE INDEX "ProductionStopDemand_status_submittedAt_idx" ON "ProductionStopDemand"("status", "submittedAt");
|
||||
|
||||
CREATE INDEX "ProductionStopDemand_requesterId_submittedAt_idx" ON "ProductionStopDemand"("requesterId", "submittedAt");
|
||||
|
||||
CREATE TABLE "ProductionStopDemandItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"demandId" TEXT NOT NULL,
|
||||
"barcode" TEXT NOT NULL,
|
||||
"categoryL1" TEXT NOT NULL DEFAULT '',
|
||||
"categoryL2" TEXT NOT NULL DEFAULT '',
|
||||
"spu" TEXT NOT NULL DEFAULT '',
|
||||
"sku" TEXT NOT NULL DEFAULT '',
|
||||
"productName" TEXT NOT NULL DEFAULT '',
|
||||
"productStatus" TEXT NOT NULL DEFAULT '',
|
||||
"supplierName" TEXT NOT NULL DEFAULT '',
|
||||
"purchaseOwner" TEXT NOT NULL DEFAULT '',
|
||||
"brand" TEXT NOT NULL DEFAULT '',
|
||||
"productTag" TEXT NOT NULL DEFAULT '',
|
||||
"productLabel" TEXT NOT NULL DEFAULT '',
|
||||
"barcodeCreatedAt" TIMESTAMP(3),
|
||||
"postStopSkuStatus" TEXT NOT NULL DEFAULT '',
|
||||
"sales30dWithBig" TEXT NOT NULL DEFAULT '',
|
||||
"sales30dWithBigSku" TEXT NOT NULL DEFAULT '',
|
||||
"opsDirector" TEXT NOT NULL DEFAULT '',
|
||||
"topSalesOpsDirector" TEXT NOT NULL DEFAULT '',
|
||||
"stopSuggestion" TEXT NOT NULL DEFAULT '',
|
||||
"replaceType" TEXT NOT NULL DEFAULT '',
|
||||
"replaceBarcode" TEXT NOT NULL DEFAULT '',
|
||||
"imageChange" TEXT NOT NULL DEFAULT '',
|
||||
"modificationPoint" TEXT NOT NULL DEFAULT '',
|
||||
"responseStatus" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"responderId" TEXT,
|
||||
"responderName" TEXT NOT NULL DEFAULT '',
|
||||
"responseNote" TEXT NOT NULL DEFAULT '',
|
||||
"respondedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopDemandItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopDemandItem_demandId_barcode_key" ON "ProductionStopDemandItem"("demandId", "barcode");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandItem_barcode_idx" ON "ProductionStopDemandItem"("barcode");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandItem_sku_idx" ON "ProductionStopDemandItem"("sku");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandItem_responseStatus_updatedAt_idx" ON "ProductionStopDemandItem"("responseStatus", "updatedAt");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandItem_purchaseOwner_responseStatus_idx" ON "ProductionStopDemandItem"("purchaseOwner", "responseStatus");
|
||||
|
||||
ALTER TABLE "ProductionStopDemandItem" ADD CONSTRAINT "ProductionStopDemandItem_demandId_fkey" FOREIGN KEY ("demandId") REFERENCES "ProductionStopDemand"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE "ProductionStopReasonCategory" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopReasonCategory_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopReasonCategory_name_key" ON "ProductionStopReasonCategory"("name");
|
||||
|
||||
CREATE INDEX "ProductionStopReasonCategory_isActive_sortOrder_idx" ON "ProductionStopReasonCategory"("isActive", "sortOrder");
|
||||
@ -1,2 +0,0 @@
|
||||
-- 停产需求明细删除支持写入操作日志(新增枚举值,不改动既有数据)。
|
||||
ALTER TYPE "OperationLogAction" ADD VALUE 'PRODUCTION_STOP_DEMAND_DELETE';
|
||||
@ -1,12 +0,0 @@
|
||||
-- 商品SABC打标「各部门数据分类」配置表:品牌范围、负责人归属例外、数据权限例外、目标参数。
|
||||
CREATE TABLE IF NOT EXISTS "SupplyChainProductArchiveDepartmentSetting" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" VARCHAR(64) NOT NULL,
|
||||
"value" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainProductArchiveDepartmentSetting_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "SupplyChainProductArchiveDepartmentSetting_key_key"
|
||||
ON "SupplyChainProductArchiveDepartmentSetting" ("key");
|
||||
@ -1,16 +0,0 @@
|
||||
-- 商品SABC打标「各部门数据分类」月度快照:核算生成后落库,页面/导出直接读结果,不再每次重算。
|
||||
CREATE TABLE IF NOT EXISTS "SupplyChainProductArchiveDepartmentSnapshot" (
|
||||
"id" TEXT NOT NULL,
|
||||
"periodStart" DATE NOT NULL,
|
||||
"periodEnd" DATE NOT NULL,
|
||||
"payload" JSONB NOT NULL,
|
||||
"rowTotal" INTEGER NOT NULL DEFAULT 0,
|
||||
"generatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainProductArchiveDepartmentSnapshot_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "SupplyChainProductArchiveDepartmentSnapshot_periodStart_periodEnd_key"
|
||||
ON "SupplyChainProductArchiveDepartmentSnapshot" ("periodStart", "periodEnd");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "SupplyChainProductArchiveDepartmentSnapshot_generatedAt_idx"
|
||||
ON "SupplyChainProductArchiveDepartmentSnapshot" ("generatedAt");
|
||||
@ -1,172 +0,0 @@
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2Drawing" (
|
||||
"id" TEXT NOT NULL,
|
||||
"spuCode" TEXT NOT NULL,
|
||||
"drawingCode" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"parts" JSONB,
|
||||
"steps" JSONB,
|
||||
"numbering" JSONB,
|
||||
"toolIds" JSONB,
|
||||
"uploadedModel" JSONB,
|
||||
"explodeTemplate" JSONB,
|
||||
"sourceDrawingId" TEXT,
|
||||
"sourceCopiedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2Drawing_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2NumberingConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"drawingId" TEXT NOT NULL,
|
||||
"parts" JSONB NOT NULL,
|
||||
"view" JSONB,
|
||||
"partsUpdatedAt" TIMESTAMP(3),
|
||||
"viewUpdatedAt" TIMESTAMP(3),
|
||||
"updatedById" TEXT,
|
||||
"updatedByName" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2NumberingConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2NumberingSnapshot" (
|
||||
"id" TEXT NOT NULL,
|
||||
"drawingId" TEXT NOT NULL,
|
||||
"modelUrl" TEXT NOT NULL,
|
||||
"modelIdentity" TEXT NOT NULL,
|
||||
"snapshotUrls" JSONB NOT NULL,
|
||||
"snapshotLineArtUrls" JSONB NOT NULL,
|
||||
"toolSnapshotUrls" JSONB NOT NULL,
|
||||
"toolSnapshotLineArtUrls" JSONB NOT NULL,
|
||||
"savedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2NumberingSnapshot_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2NodeMaterialConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"drawingId" TEXT NOT NULL,
|
||||
"nodeMaterialConfigs" JSONB NOT NULL,
|
||||
"updatedById" TEXT,
|
||||
"updatedByName" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2NodeMaterialConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2DrawingCapture" (
|
||||
"id" TEXT NOT NULL,
|
||||
"drawingId" TEXT NOT NULL,
|
||||
"manifest" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2DrawingCapture_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2DrawingTextAnnotation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"drawingId" TEXT NOT NULL,
|
||||
"annotationsByTargetKey" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2DrawingTextAnnotation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2Tool" (
|
||||
"id" TEXT NOT NULL,
|
||||
"toolCode" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"modelUrl" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"mountOffset" JSONB,
|
||||
"mountRotation" JSONB,
|
||||
"motionType" TEXT NOT NULL DEFAULT 'spin',
|
||||
"motionPivot" JSONB,
|
||||
"motionAxis" JSONB,
|
||||
"spinTurns" DOUBLE PRECISION NOT NULL DEFAULT 3,
|
||||
"strikeDistanceRatio" DOUBLE PRECISION NOT NULL DEFAULT 0.2,
|
||||
"articulationConfig" JSONB,
|
||||
"sourceToolId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2Tool_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallationDemoV2Share" (
|
||||
"id" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"drawingId" TEXT NOT NULL,
|
||||
"createdById" TEXT NOT NULL,
|
||||
"snapshot" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InstallationDemoV2Share_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2Drawing_spuCode_drawingCode_key" ON "InstallationDemoV2Drawing"("spuCode", "drawingCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2NumberingConfig_drawingId_key" ON "InstallationDemoV2NumberingConfig"("drawingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2NumberingSnapshot_drawingId_key" ON "InstallationDemoV2NumberingSnapshot"("drawingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2NodeMaterialConfig_drawingId_key" ON "InstallationDemoV2NodeMaterialConfig"("drawingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2DrawingCapture_drawingId_key" ON "InstallationDemoV2DrawingCapture"("drawingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2DrawingTextAnnotation_drawingId_key" ON "InstallationDemoV2DrawingTextAnnotation"("drawingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2Tool_toolCode_key" ON "InstallationDemoV2Tool"("toolCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2Tool_sourceToolId_key" ON "InstallationDemoV2Tool"("sourceToolId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2Share_token_key" ON "InstallationDemoV2Share"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallationDemoV2Share_createdById_drawingId_key" ON "InstallationDemoV2Share"("createdById", "drawingId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallationDemoV2NumberingConfig" ADD CONSTRAINT "InstallationDemoV2NumberingConfig_drawingId_fkey" FOREIGN KEY ("drawingId") REFERENCES "InstallationDemoV2Drawing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallationDemoV2NumberingSnapshot" ADD CONSTRAINT "InstallationDemoV2NumberingSnapshot_drawingId_fkey" FOREIGN KEY ("drawingId") REFERENCES "InstallationDemoV2Drawing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallationDemoV2NodeMaterialConfig" ADD CONSTRAINT "InstallationDemoV2NodeMaterialConfig_drawingId_fkey" FOREIGN KEY ("drawingId") REFERENCES "InstallationDemoV2Drawing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallationDemoV2DrawingCapture" ADD CONSTRAINT "InstallationDemoV2DrawingCapture_drawingId_fkey" FOREIGN KEY ("drawingId") REFERENCES "InstallationDemoV2Drawing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallationDemoV2DrawingTextAnnotation" ADD CONSTRAINT "InstallationDemoV2DrawingTextAnnotation_drawingId_fkey" FOREIGN KEY ("drawingId") REFERENCES "InstallationDemoV2Drawing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallationDemoV2Share" ADD CONSTRAINT "InstallationDemoV2Share_drawingId_fkey" FOREIGN KEY ("drawingId") REFERENCES "InstallationDemoV2Drawing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -1,9 +0,0 @@
|
||||
-- 物流到货签收时长看板的热路径聚合(整区间总览、环节目标、经营对象分组、超时排名)
|
||||
-- 只需要 4 个时间戳 + 4 个维度列。此前查询 SELECT 全列,Postgres 只能对 900MB 的堆表做
|
||||
-- Seq Scan(28 天区间实测 6.3 秒),加覆盖索引后可以走 Index Only Scan。
|
||||
CREATE INDEX IF NOT EXISTS "LogisticsArrivalSigningDurationSnapshot_cover_idx"
|
||||
ON "LogisticsArrivalSigningDurationSnapshot" ("snapshotDate", "signTime")
|
||||
INCLUDE ("payTime", "sendTime", "fetchTime", "warehouseName", "platformName", "shopName", "logisticCompanyName");
|
||||
|
||||
-- 让计划器立刻用上新索引。
|
||||
ANALYZE "LogisticsArrivalSigningDurationSnapshot";
|
||||
@ -1,3 +0,0 @@
|
||||
-- Add modelCode for V2 three-level catalog (SPU -> model -> drawing)
|
||||
|
||||
ALTER TABLE "InstallationDemoV2Drawing" ADD COLUMN "modelCode" TEXT;
|
||||
@ -1,87 +0,0 @@
|
||||
-- 停产需求「是否同意停产」多人确认:确认人配置、确认票、需求侧确认状态与截止时间。
|
||||
ALTER TABLE "ProductionStopDemand"
|
||||
ADD COLUMN "confirmationDeadline" TIMESTAMP(3),
|
||||
ADD COLUMN "confirmationStatus" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
ADD COLUMN "confirmationAt" TIMESTAMP(3),
|
||||
ADD COLUMN "confirmationReasons" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "confirmationPushedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "confirmationPushMessage" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- 原「采购反馈」改为需求级「是否同意停产」多人确认,移除按明细的反馈字段。
|
||||
DROP INDEX "ProductionStopDemand_status_submittedAt_idx";
|
||||
ALTER TABLE "ProductionStopDemand" DROP COLUMN "status";
|
||||
|
||||
DROP INDEX "ProductionStopDemandItem_responseStatus_updatedAt_idx";
|
||||
DROP INDEX "ProductionStopDemandItem_purchaseOwner_responseStatus_idx";
|
||||
ALTER TABLE "ProductionStopDemandItem"
|
||||
DROP COLUMN "responseStatus",
|
||||
DROP COLUMN "responderId",
|
||||
DROP COLUMN "responderName",
|
||||
DROP COLUMN "responseNote",
|
||||
DROP COLUMN "respondedAt";
|
||||
|
||||
CREATE INDEX "ProductionStopDemand_submittedAt_idx" ON "ProductionStopDemand"("submittedAt");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandItem_purchaseOwner_idx" ON "ProductionStopDemandItem"("purchaseOwner");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandItem_updatedAt_idx" ON "ProductionStopDemandItem"("updatedAt");
|
||||
|
||||
CREATE INDEX "ProductionStopDemand_confirmationStatus_confirmationDeadline_idx" ON "ProductionStopDemand"("confirmationStatus", "confirmationDeadline");
|
||||
|
||||
-- 历史需求按「当天 15:00 前提交当天 16:00 截止,否则次日 16:00 截止」补齐确认截止时间。
|
||||
-- 注意:submittedAt / confirmationDeadline 是 timestamp(无时区),Prisma 按 UTC 读写,
|
||||
-- 因此这里显式按 UTC 解释存储值,结果也写回 UTC,不依赖数据库会话时区。
|
||||
UPDATE "ProductionStopDemand"
|
||||
SET "confirmationDeadline" = (
|
||||
CASE
|
||||
WHEN (("submittedAt" AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Shanghai')::time < TIME '15:00'
|
||||
THEN date_trunc('day', ("submittedAt" AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Shanghai')
|
||||
+ INTERVAL '16 hours'
|
||||
ELSE date_trunc('day', ("submittedAt" AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Shanghai')
|
||||
+ INTERVAL '1 day 16 hours'
|
||||
END
|
||||
) AT TIME ZONE 'Asia/Shanghai' AT TIME ZONE 'UTC'
|
||||
WHERE "confirmationDeadline" IS NULL;
|
||||
|
||||
CREATE TABLE "ProductionStopConfirmationApprover" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL,
|
||||
"dingtalkUnionId" TEXT NOT NULL DEFAULT '',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopConfirmationApprover_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopConfirmationApprover_userId_key" ON "ProductionStopConfirmationApprover"("userId");
|
||||
|
||||
CREATE INDEX "ProductionStopConfirmationApprover_isActive_sortOrder_idx" ON "ProductionStopConfirmationApprover"("isActive", "sortOrder");
|
||||
|
||||
CREATE TABLE "ProductionStopDemandConfirmationVote" (
|
||||
"id" TEXT NOT NULL,
|
||||
"demandId" TEXT NOT NULL,
|
||||
"approverId" TEXT NOT NULL,
|
||||
"approverUserId" TEXT NOT NULL,
|
||||
"approverName" TEXT NOT NULL,
|
||||
"decision" TEXT NOT NULL DEFAULT 'AGREED',
|
||||
"reason" TEXT NOT NULL DEFAULT '',
|
||||
"source" TEXT NOT NULL DEFAULT 'USER',
|
||||
"respondedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopDemandConfirmationVote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopDemandConfirmationVote_demandId_approverId_key" ON "ProductionStopDemandConfirmationVote"("demandId", "approverId");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandConfirmationVote_demandId_decision_idx" ON "ProductionStopDemandConfirmationVote"("demandId", "decision");
|
||||
|
||||
CREATE INDEX "ProductionStopDemandConfirmationVote_respondedAt_idx" ON "ProductionStopDemandConfirmationVote"("respondedAt");
|
||||
|
||||
ALTER TABLE "ProductionStopDemandConfirmationVote" ADD CONSTRAINT "ProductionStopDemandConfirmationVote_demandId_fkey" FOREIGN KEY ("demandId") REFERENCES "ProductionStopDemand"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "ProductionStopDemandConfirmationVote" ADD CONSTRAINT "ProductionStopDemandConfirmationVote_approverId_fkey" FOREIGN KEY ("approverId") REFERENCES "ProductionStopConfirmationApprover"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -1,2 +0,0 @@
|
||||
-- 停产需求修改停产原因支持写入操作日志(新增枚举值,不改动既有数据)。
|
||||
ALTER TYPE "OperationLogAction" ADD VALUE 'PRODUCTION_STOP_DEMAND_UPDATE';
|
||||
@ -1,32 +0,0 @@
|
||||
-- 停产需求状态(待确认/需求驳回/已确认待走流程/已走流程待审批/流程审批通过)与流程负责人名单。
|
||||
ALTER TABLE "ProductionStopDemand"
|
||||
ADD COLUMN "demandStatus" TEXT NOT NULL DEFAULT 'PENDING_CONFIRMATION',
|
||||
ADD COLUMN "demandStatusAt" TIMESTAMP(3),
|
||||
ADD COLUMN "demandStatusNote" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE INDEX "ProductionStopDemand_demandStatus_updatedAt_idx" ON "ProductionStopDemand"("demandStatus", "updatedAt");
|
||||
|
||||
CREATE TABLE "ProductionStopFlowOwner" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopFlowOwner_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopFlowOwner_userId_key" ON "ProductionStopFlowOwner"("userId");
|
||||
|
||||
CREATE INDEX "ProductionStopFlowOwner_isActive_sortOrder_idx" ON "ProductionStopFlowOwner"("isActive", "sortOrder");
|
||||
|
||||
-- 历史需求按当前确认结果回填需求状态(确认通过→已确认待走流程;确认驳回→需求驳回)。
|
||||
UPDATE "ProductionStopDemand"
|
||||
SET "demandStatus" = CASE
|
||||
WHEN "confirmationStatus" = 'AGREED' THEN 'CONFIRMED_WAIT_FLOW'
|
||||
WHEN "confirmationStatus" = 'REJECTED' THEN 'REJECTED'
|
||||
ELSE 'PENDING_CONFIRMATION'
|
||||
END,
|
||||
"demandStatusAt" = COALESCE("confirmationAt", "createdAt");
|
||||
@ -1,3 +0,0 @@
|
||||
-- 按条码拆单后,用批次号把同一次提交的需求单串起来(用于批量确认与推送聚合)。
|
||||
ALTER TABLE "ProductionStopDemand" ADD COLUMN "batchNo" TEXT NOT NULL DEFAULT '';
|
||||
CREATE INDEX "ProductionStopDemand_batchNo_idx" ON "ProductionStopDemand"("batchNo");
|
||||
@ -1,16 +0,0 @@
|
||||
-- 盘停建议配置:人工批量维护「条码 → 盘停建议」,提单时按条码自动带出。
|
||||
CREATE TABLE "ProductionStopSuggestionConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"barcode" TEXT NOT NULL,
|
||||
"suggestion" TEXT NOT NULL DEFAULT '',
|
||||
"updatedById" TEXT NOT NULL DEFAULT '',
|
||||
"updatedByName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductionStopSuggestionConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ProductionStopSuggestionConfig_barcode_key" ON "ProductionStopSuggestionConfig"("barcode");
|
||||
|
||||
CREATE INDEX "ProductionStopSuggestionConfig_updatedAt_idx" ON "ProductionStopSuggestionConfig"("updatedAt");
|
||||
@ -1,25 +0,0 @@
|
||||
-- 简尚「替换款管理」换货规则表(商品SABC打标「同款商品编码」依赖)。
|
||||
-- 历史遗留:该表一直没进 migrations(本地用 db push 建过),正式环境用 migrate deploy 时缺失,
|
||||
-- 导致同款商品编码全部为空。这里补上建表语句,生产/本地都可重复执行。
|
||||
CREATE TABLE IF NOT EXISTS "SupplyChainProductArchiveReplacementRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sourceId" INTEGER NOT NULL,
|
||||
"sku" TEXT NOT NULL,
|
||||
"replaceSku" TEXT NOT NULL DEFAULT '',
|
||||
"replaceType" INTEGER NOT NULL DEFAULT 0,
|
||||
"isSwapping" INTEGER NOT NULL DEFAULT 0,
|
||||
"differentPoints" TEXT NOT NULL DEFAULT '',
|
||||
"creator" TEXT NOT NULL DEFAULT '',
|
||||
"sourceCreatedAt" TIMESTAMP(6),
|
||||
"syncedAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainProductArchiveReplacementRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "SupplyChainProductArchiveReplacementRule_sourceId_key"
|
||||
ON "SupplyChainProductArchiveReplacementRule" ("sourceId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "SupplyChainProductArchiveReplacementRule_sku_idx"
|
||||
ON "SupplyChainProductArchiveReplacementRule" ("sku");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "SupplyChainProductArchiveReplacementRule_replaceSku_idx"
|
||||
ON "SupplyChainProductArchiveReplacementRule" ("replaceSku");
|
||||
@ -1,17 +0,0 @@
|
||||
-- Retire the independent installation demo V2 experiment.
|
||||
-- Keep the original migrations unchanged and remove only V2-owned tables.
|
||||
-- Drop dependents first; do not cascade into unrelated objects.
|
||||
BEGIN;
|
||||
|
||||
SET LOCAL lock_timeout = '5s';
|
||||
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2NumberingConfig";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2NumberingSnapshot";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2NodeMaterialConfig";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2DrawingCapture";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2DrawingTextAnnotation";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2Share";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2Tool";
|
||||
DROP TABLE IF EXISTS "InstallationDemoV2Drawing";
|
||||
|
||||
COMMIT;
|
||||
@ -1,25 +0,0 @@
|
||||
ALTER TABLE "SupplyChainProductArchive"
|
||||
ADD COLUMN "sales30d" TEXT NOT NULL DEFAULT '0.00',
|
||||
ADD COLUMN "sales30dWithBig" TEXT NOT NULL DEFAULT '0.00';
|
||||
|
||||
CREATE TABLE "ProductArchiveMarketingSalesState" (
|
||||
"id" TEXT NOT NULL,
|
||||
"state" JSONB NOT NULL,
|
||||
"snapshot" JSONB NOT NULL,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "ProductArchiveMarketingSalesState_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ProductArchiveMarketingSalesCheck" (
|
||||
"id" TEXT NOT NULL,
|
||||
"checkedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"status" TEXT NOT NULL,
|
||||
"fingerprint" TEXT NOT NULL DEFAULT '',
|
||||
"sourceDate" TEXT NOT NULL DEFAULT '',
|
||||
"rowCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"skuCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"changedRows" INTEGER NOT NULL DEFAULT 0,
|
||||
"errorCode" TEXT NOT NULL DEFAULT '',
|
||||
CONSTRAINT "ProductArchiveMarketingSalesCheck_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "ProductArchiveMarketingSalesCheck_checkedAt_idx" ON "ProductArchiveMarketingSalesCheck"("checkedAt");
|
||||
@ -1,6 +0,0 @@
|
||||
-- 记录每批快照实际覆盖到的最大签收时间('YYYY-MM-DD HH:MM:SS')。
|
||||
-- 快照任务把 windowTo 写成"运行当天",但数据只到运行那一刻;读路径此前只比对声明窗口,
|
||||
-- 于是当天跑批后看板/导出会把尚未签收完的当天误判为已覆盖(少算当天、导出缺单)。
|
||||
-- 新增 dataThrough 后,覆盖判定取 min(windowTo - 1, dataThrough 当天),数据没到就回落实时源。
|
||||
ALTER TABLE "LogisticsArrivalSigningDurationSnapshotRun"
|
||||
ADD COLUMN IF NOT EXISTS "dataThrough" TEXT NOT NULL DEFAULT '';
|
||||
@ -1,85 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Use the same lock as permission configuration writes. Retain every old row.
|
||||
SELECT pg_advisory_xact_lock(2026081501);
|
||||
LOCK TABLE "SupplyChainPermissionOverride" IN SHARE ROW EXCLUSIVE MODE;
|
||||
|
||||
WITH mappings("source", "target") AS (
|
||||
VALUES
|
||||
('businessManagement', 'ownerConfig'),
|
||||
('businessManagement', 'limitManagement'),
|
||||
('businessManagement', 'productionStopDemands'),
|
||||
('businessManagement', 'managementBlankPage1'),
|
||||
('businessManagement', 'managementBlankPage2'),
|
||||
('newProductSampleProgress', 'finishedProductProgress')
|
||||
)
|
||||
INSERT INTO "SupplyChainPermissionOverride"
|
||||
("id", "targetKey", "subjectKey", "action", "enabled", "createdBy", "updatedBy", "createdAt", "updatedAt")
|
||||
SELECT
|
||||
'scp-' || md5(old."id" || ':' || mappings."target"),
|
||||
mappings."target", old."subjectKey", old."action", old."enabled",
|
||||
old."createdBy", old."updatedBy", old."createdAt", old."updatedAt"
|
||||
FROM "SupplyChainPermissionOverride" AS old
|
||||
JOIN mappings ON old."targetKey" = mappings."source"
|
||||
ON CONFLICT ("targetKey", "subjectKey", "action") DO NOTHING;
|
||||
|
||||
-- Logistics previously allowed exporting whenever effective view was true.
|
||||
-- Preserve that ability once, then enforce its independent download permission.
|
||||
-- Personal download=false used to suppress editing, not the view-based export.
|
||||
-- Leave inherited personal view/download unset in that case and copy edit=false.
|
||||
WITH legacy AS (
|
||||
SELECT
|
||||
"subjectKey",
|
||||
bool_or("enabled") FILTER (WHERE "action" = 'view') AS v,
|
||||
bool_or("enabled") FILTER (WHERE "action" = 'download') AS d,
|
||||
bool_or("enabled") FILTER (WHERE "action" = 'edit') AS e,
|
||||
min("id") AS "sourceId",
|
||||
max("createdBy") AS "createdBy", max("updatedBy") AS "updatedBy",
|
||||
min("createdAt") AS "createdAt", max("updatedAt") AS "updatedAt"
|
||||
FROM "SupplyChainPermissionOverride"
|
||||
WHERE "targetKey" = 'businessManagement'
|
||||
GROUP BY "subjectKey"
|
||||
), access AS (
|
||||
SELECT *,
|
||||
CASE
|
||||
WHEN "subjectKey" LIKE 'user@%' THEN
|
||||
CASE WHEN v IS NOT NULL THEN v
|
||||
WHEN d IS DISTINCT FROM FALSE AND (d IS TRUE OR e IS TRUE) THEN TRUE
|
||||
ELSE NULL END
|
||||
WHEN v IS FALSE THEN FALSE
|
||||
WHEN d IS FALSE THEN v IS TRUE
|
||||
ELSE v IS TRUE OR d IS TRUE OR e IS TRUE
|
||||
END AS "newView",
|
||||
CASE
|
||||
WHEN "subjectKey" LIKE 'user@%' THEN
|
||||
CASE WHEN v IS FALSE THEN FALSE
|
||||
WHEN v IS TRUE THEN TRUE
|
||||
WHEN d IS DISTINCT FROM FALSE AND (d IS TRUE OR e IS TRUE) THEN TRUE
|
||||
ELSE NULL END
|
||||
WHEN v IS FALSE THEN FALSE
|
||||
WHEN d IS FALSE THEN v IS TRUE
|
||||
ELSE v IS TRUE OR d IS TRUE OR e IS TRUE
|
||||
END AS "newDownload",
|
||||
CASE
|
||||
WHEN "subjectKey" LIKE 'user@%' THEN
|
||||
CASE WHEN v IS FALSE OR d IS FALSE THEN FALSE ELSE e END
|
||||
ELSE v IS DISTINCT FROM FALSE AND d IS DISTINCT FROM FALSE AND e IS TRUE
|
||||
END AS "newEdit"
|
||||
FROM legacy
|
||||
)
|
||||
INSERT INTO "SupplyChainPermissionOverride"
|
||||
("id", "targetKey", "subjectKey", "action", "enabled", "createdBy", "updatedBy", "createdAt", "updatedAt")
|
||||
SELECT
|
||||
'scp-' || md5(access."sourceId" || ':logisticsArrivalSigningDuration:' || actions."action"),
|
||||
'logisticsArrivalSigningDuration', access."subjectKey", actions."action", actions."enabled",
|
||||
access."createdBy", access."updatedBy", access."createdAt", access."updatedAt"
|
||||
FROM access
|
||||
CROSS JOIN LATERAL (VALUES
|
||||
('view', access."newView"),
|
||||
('download', access."newDownload"),
|
||||
('edit', access."newEdit")
|
||||
) AS actions("action", "enabled")
|
||||
WHERE actions."enabled" IS NOT NULL
|
||||
ON CONFLICT ("targetKey", "subjectKey", "action") DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@ -1,15 +0,0 @@
|
||||
CREATE TABLE "DiyScheme" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"parentCode" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"snapshot" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "DiyScheme_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "DiyScheme_name_not_blank" CHECK (length(btrim("name")) BETWEEN 1 AND 80),
|
||||
CONSTRAINT "DiyScheme_snapshot_object" CHECK (jsonb_typeof("snapshot") = 'object')
|
||||
);
|
||||
CREATE UNIQUE INDEX "DiyScheme_code_key" ON "DiyScheme"("code");
|
||||
CREATE INDEX "DiyScheme_createdBy_createdAt_id_idx" ON "DiyScheme"("createdBy", "createdAt", "id");
|
||||
ALTER TABLE "DiyScheme" ADD CONSTRAINT "DiyScheme_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -1,43 +0,0 @@
|
||||
CREATE TABLE "DiyCatalog" (
|
||||
"spu" TEXT PRIMARY KEY, "name" TEXT NOT NULL, "version" INTEGER NOT NULL DEFAULT 1,
|
||||
"sourceHash" TEXT NOT NULL, "importedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE "DiyAssetFile" (
|
||||
"id" TEXT PRIMARY KEY, "spu" TEXT NOT NULL REFERENCES "DiyCatalog"("spu") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
"storageKey" TEXT NOT NULL UNIQUE, "url" TEXT NOT NULL, "sourcePath" TEXT NOT NULL,
|
||||
"sha256" TEXT NOT NULL, "bytes" INTEGER NOT NULL CHECK ("bytes" > 0),
|
||||
"contentType" TEXT NOT NULL, "kind" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE "DiyCabinetStyle" (
|
||||
"code" TEXT PRIMARY KEY, "spu" TEXT NOT NULL REFERENCES "DiyCatalog"("spu") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
"name" TEXT NOT NULL, "displaySize" TEXT NOT NULL,
|
||||
"width" DOUBLE PRECISION NOT NULL CHECK ("width" > 0),
|
||||
"height" DOUBLE PRECISION NOT NULL CHECK ("height" > 0),
|
||||
"depth" DOUBLE PRECISION NOT NULL CHECK ("depth" > 0),
|
||||
"cellCount" INTEGER NOT NULL CHECK ("cellCount" > 0), "panelCount" INTEGER NOT NULL CHECK ("panelCount" > 0),
|
||||
"panels" JSONB NOT NULL, "cells" JSONB NOT NULL, "sortOrder" INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE "DiyCabinetVariant" (
|
||||
"itemCode" TEXT PRIMARY KEY, "styleCode" TEXT NOT NULL REFERENCES "DiyCabinetStyle"("code") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
"name" TEXT NOT NULL, "barcodes" TEXT[] NOT NULL,
|
||||
"factoryModel" TEXT NOT NULL, "factoryCode" TEXT NOT NULL, "orderQty" INTEGER NOT NULL, "sortOrder" INTEGER NOT NULL,
|
||||
"thumbnailFileId" TEXT NOT NULL REFERENCES "DiyAssetFile"("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
CREATE TABLE "DiyDoorPattern" (
|
||||
"itemCode" TEXT PRIMARY KEY, "spu" TEXT NOT NULL REFERENCES "DiyCatalog"("spu") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
"name" TEXT NOT NULL, "barcodes" TEXT[] NOT NULL,
|
||||
"factoryModel" TEXT NOT NULL, "factoryCode" TEXT NOT NULL, "orderQty" INTEGER NOT NULL, "sortOrder" INTEGER NOT NULL,
|
||||
"imageFileId" TEXT NOT NULL REFERENCES "DiyAssetFile"("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
CREATE TABLE "DiyMaterial" (
|
||||
"itemCode" TEXT PRIMARY KEY, "spu" TEXT NOT NULL REFERENCES "DiyCatalog"("spu") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
"name" TEXT NOT NULL, "kind" TEXT NOT NULL CHECK ("kind" IN ('main', 'shelf')), "colorHex" TEXT NOT NULL,
|
||||
"barcodes" TEXT[] NOT NULL, "factoryModel" TEXT NOT NULL, "factoryCode" TEXT NOT NULL, "sortOrder" INTEGER NOT NULL,
|
||||
"textureFileId" TEXT REFERENCES "DiyAssetFile"("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
CREATE INDEX "DiyAssetFile_spu_idx" ON "DiyAssetFile"("spu");
|
||||
CREATE INDEX "DiyCabinetStyle_spu_sortOrder_idx" ON "DiyCabinetStyle"("spu", "sortOrder");
|
||||
CREATE UNIQUE INDEX "DiyCabinetVariant_styleCode_name_key" ON "DiyCabinetVariant"("styleCode", "name");
|
||||
CREATE INDEX "DiyDoorPattern_spu_sortOrder_idx" ON "DiyDoorPattern"("spu", "sortOrder");
|
||||
CREATE UNIQUE INDEX "DiyMaterial_spu_name_key" ON "DiyMaterial"("spu", "name");
|
||||
@ -1,18 +0,0 @@
|
||||
ALTER TABLE "DiyCabinetStyle"
|
||||
ADD COLUMN "modelFileId" TEXT,
|
||||
ADD COLUMN "mainNodeNames" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
ADD COLUMN "shelfNodeNames" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
ADD COLUMN "modelRotationY" DOUBLE PRECISION NOT NULL DEFAULT 0;
|
||||
ALTER TABLE "DiyCabinetStyle" ADD CONSTRAINT "DiyCabinetStyle_modelFileId_fkey"
|
||||
FOREIGN KEY ("modelFileId") REFERENCES "DiyAssetFile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "DiyCabinetVariant"
|
||||
ADD COLUMN "mainMaterialCode" TEXT,
|
||||
ADD COLUMN "shelfMaterialCode" TEXT;
|
||||
ALTER TABLE "DiyCabinetVariant" ADD CONSTRAINT "DiyCabinetVariant_mainMaterialCode_fkey"
|
||||
FOREIGN KEY ("mainMaterialCode") REFERENCES "DiyMaterial"("itemCode") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "DiyCabinetVariant" ADD CONSTRAINT "DiyCabinetVariant_shelfMaterialCode_fkey"
|
||||
FOREIGN KEY ("shelfMaterialCode") REFERENCES "DiyMaterial"("itemCode") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "DiyDoorPattern"
|
||||
ADD COLUMN "width" DOUBLE PRECISION,
|
||||
ADD COLUMN "height" DOUBLE PRECISION,
|
||||
ADD COLUMN "thickness" DOUBLE PRECISION;
|
||||
@ -1,85 +0,0 @@
|
||||
-- 限价管理 · 破价订单审核
|
||||
|
||||
CREATE TABLE "SupplyChainBreachOrder" (
|
||||
"id" TEXT NOT NULL,
|
||||
"platform" TEXT NOT NULL DEFAULT '',
|
||||
"shopName" TEXT NOT NULL DEFAULT '',
|
||||
"soId" TEXT NOT NULL DEFAULT '',
|
||||
"oId" TEXT NOT NULL DEFAULT '',
|
||||
"payDate" TIMESTAMP(3),
|
||||
"itemCode" TEXT NOT NULL DEFAULT '',
|
||||
"shopItemCode" TEXT NOT NULL DEFAULT '',
|
||||
"shopStyleCode" TEXT NOT NULL DEFAULT '',
|
||||
"linkGroupKey" TEXT NOT NULL DEFAULT '',
|
||||
"qty" INTEGER NOT NULL DEFAULT 1,
|
||||
"buyerPaidAmount" DECIMAL(18,4) NOT NULL DEFAULT 0,
|
||||
"platformBearAmount" DECIMAL(18,4) NOT NULL DEFAULT 0,
|
||||
"sellerIncomeAmount" DECIMAL(18,4) NOT NULL DEFAULT 0,
|
||||
"limitUnitPrice" DECIMAL(18,4),
|
||||
"limitTotal" DECIMAL(18,4),
|
||||
"gapAmount" DECIMAL(18,4),
|
||||
"ownerName" TEXT NOT NULL DEFAULT '',
|
||||
"profitCenter" TEXT NOT NULL DEFAULT '',
|
||||
"orderStatus" TEXT NOT NULL DEFAULT '',
|
||||
"refundStatus" TEXT NOT NULL DEFAULT '',
|
||||
"listedAt" TIMESTAMP(3),
|
||||
"flowEndDate" TIMESTAMP(3),
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING_APPEAL',
|
||||
"appealType" TEXT NOT NULL DEFAULT '',
|
||||
"appealScope" TEXT NOT NULL DEFAULT 'ORDER',
|
||||
"processNo" TEXT NOT NULL DEFAULT '',
|
||||
"appealRound" INTEGER NOT NULL DEFAULT 1,
|
||||
"previousAppeal" JSON,
|
||||
"appealDesc" TEXT NOT NULL DEFAULT '',
|
||||
"appealImages" JSON NOT NULL DEFAULT '[]',
|
||||
"appliedFromLink" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autoSuggested" TEXT NOT NULL DEFAULT '',
|
||||
"autoSuggestedNote" TEXT NOT NULL DEFAULT '',
|
||||
"reviewedBy" TEXT NOT NULL DEFAULT '',
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"reviewDecision" TEXT NOT NULL DEFAULT '',
|
||||
"reviewReason" TEXT NOT NULL DEFAULT '',
|
||||
"reviewNote" TEXT NOT NULL DEFAULT '',
|
||||
"fineAmount" DECIMAL(10,2) NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainBreachOrder_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainBreachOrder_oId_itemCode_key" ON "SupplyChainBreachOrder"("oId", "itemCode");
|
||||
CREATE INDEX "SupplyChainBreachOrder_status_payDate_idx" ON "SupplyChainBreachOrder"("status", "payDate" DESC);
|
||||
CREATE INDEX "SupplyChainBreachOrder_linkGroupKey_idx" ON "SupplyChainBreachOrder"("linkGroupKey");
|
||||
CREATE INDEX "SupplyChainBreachOrder_ownerName_status_idx" ON "SupplyChainBreachOrder"("ownerName", "status");
|
||||
CREATE INDEX "SupplyChainBreachOrder_shopName_itemCode_idx" ON "SupplyChainBreachOrder"("shopName", "itemCode");
|
||||
|
||||
CREATE TABLE "SupplyChainBreachOrderLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orderId" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"operatorId" TEXT NOT NULL DEFAULT '',
|
||||
"operatorName" TEXT NOT NULL DEFAULT '',
|
||||
"detail" JSON NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainBreachOrderLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "SupplyChainBreachOrderLog_orderId_createdAt_idx" ON "SupplyChainBreachOrderLog"("orderId", "createdAt");
|
||||
|
||||
CREATE TABLE "SupplyChainLinkLimitPrice" (
|
||||
"id" TEXT NOT NULL,
|
||||
"shopName" TEXT NOT NULL,
|
||||
"itemCode" TEXT NOT NULL,
|
||||
"spu" TEXT NOT NULL DEFAULT '',
|
||||
"limitUnitPrice" DECIMAL(18,4) NOT NULL,
|
||||
"listedAt" TIMESTAMP(3),
|
||||
"flowEndDate" TIMESTAMP(3),
|
||||
"source" TEXT NOT NULL DEFAULT 'MANUAL',
|
||||
"ownerName" TEXT NOT NULL DEFAULT '',
|
||||
"updatedById" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainLinkLimitPrice_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainLinkLimitPrice_shopName_itemCode_key" ON "SupplyChainLinkLimitPrice"("shopName", "itemCode");
|
||||
CREATE INDEX "SupplyChainLinkLimitPrice_itemCode_idx" ON "SupplyChainLinkLimitPrice"("itemCode");
|
||||
@ -1,10 +0,0 @@
|
||||
-- Uploaded models have a file reference rather than generated panel geometry.
|
||||
-- A cabinet without door openings is a valid model-only cabinet.
|
||||
ALTER TABLE "DiyCabinetStyle"
|
||||
DROP CONSTRAINT "DiyCabinetStyle_cellCount_check",
|
||||
ADD CONSTRAINT "DiyCabinetStyle_cellCount_check"
|
||||
CHECK ("cellCount" >= 0 AND jsonb_typeof(cells) = 'array' AND jsonb_array_length(cells) = "cellCount"),
|
||||
DROP CONSTRAINT "DiyCabinetStyle_panelCount_check",
|
||||
ADD CONSTRAINT "DiyCabinetStyle_panelCount_check"
|
||||
CHECK ("panelCount" >= 0 AND jsonb_typeof(panels) = 'array' AND jsonb_array_length(panels) = "panelCount"
|
||||
AND ("panelCount" > 0 OR "modelFileId" IS NOT NULL));
|
||||
@ -1,9 +0,0 @@
|
||||
CREATE TABLE "SupplyChainBreachRuleConfig" (
|
||||
"id" TEXT NOT NULL DEFAULT 'default',
|
||||
"data" JSON NOT NULL DEFAULT '{}',
|
||||
"updatedById" TEXT NOT NULL DEFAULT '',
|
||||
"updatedByName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainBreachRuleConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@ -1,17 +0,0 @@
|
||||
-- 加工单人工维护值:条码 → 最终加工量 / 加工单备注,支持单元格编辑与批量修改。
|
||||
CREATE TABLE "ModularProcessingManualValue" (
|
||||
"id" TEXT NOT NULL,
|
||||
"barcode" TEXT NOT NULL,
|
||||
"finalProcessingQuantity" INTEGER,
|
||||
"processingNote" TEXT NOT NULL DEFAULT '',
|
||||
"updatedById" TEXT,
|
||||
"updatedByName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ModularProcessingManualValue_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ModularProcessingManualValue_barcode_key" ON "ModularProcessingManualValue"("barcode");
|
||||
|
||||
CREATE INDEX "ModularProcessingManualValue_updatedAt_idx" ON "ModularProcessingManualValue"("updatedAt");
|
||||
@ -1,2 +0,0 @@
|
||||
-- 人工维护值增加「叫货仓」:导入模板与单元格编辑都要能覆盖产品档案里的仓库。
|
||||
ALTER TABLE "ModularProcessingManualValue" ADD COLUMN "orderWarehouse" TEXT;
|
||||
@ -1,2 +0,0 @@
|
||||
-- 「图片」列:提单时按条码从产品全量档案库快照图片地址,列表在条码前展示缩略图。
|
||||
ALTER TABLE "ProductionStopDemandItem" ADD COLUMN "image" TEXT NOT NULL DEFAULT '';
|
||||
@ -1,10 +0,0 @@
|
||||
ALTER TABLE "DiyMaterial"
|
||||
ADD COLUMN "deletedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "deletedBy" TEXT;
|
||||
|
||||
ALTER TABLE "DiyDoorPattern"
|
||||
ADD COLUMN "deletedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "deletedBy" TEXT;
|
||||
|
||||
CREATE INDEX "DiyMaterial_deletedAt_idx" ON "DiyMaterial"("deletedAt");
|
||||
CREATE INDEX "DiyDoorPattern_deletedAt_idx" ON "DiyDoorPattern"("deletedAt");
|
||||
@ -1,25 +0,0 @@
|
||||
CREATE TABLE "NativeImageGenerationJob" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"providerConfigId" TEXT NOT NULL,
|
||||
"providerFingerprint" TEXT NOT NULL,
|
||||
"requestHash" TEXT NOT NULL,
|
||||
"request" JSONB NOT NULL,
|
||||
"completion" JSONB NOT NULL,
|
||||
"progress" JSONB NOT NULL DEFAULT '[]',
|
||||
"status" TEXT NOT NULL DEFAULT 'QUEUED',
|
||||
"retryCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextRunAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"leaseOwner" TEXT,
|
||||
"leaseUntil" TIMESTAMP(3),
|
||||
"errorMessage" TEXT,
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "NativeImageGenerationJob_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "NativeImageGenerationJob_status_valid" CHECK ("status" IN ('QUEUED', 'RUNNING', 'FINALIZING', 'SUCCEEDED', 'FAILED', 'BLOCKED')),
|
||||
CONSTRAINT "NativeImageGenerationJob_retry_valid" CHECK ("retryCount" >= 0),
|
||||
CONSTRAINT "NativeImageGenerationJob_identity_valid" CHECK (length(btrim("id")) > 0 AND length(btrim("createdBy")) > 0)
|
||||
);
|
||||
CREATE INDEX "NativeImageGenerationJob_status_nextRunAt_leaseUntil_idx" ON "NativeImageGenerationJob"("status", "nextRunAt", "leaseUntil");
|
||||
CREATE INDEX "NativeImageGenerationJob_createdBy_createdAt_idx" ON "NativeImageGenerationJob"("createdBy", "createdAt");
|
||||
@ -1,37 +0,0 @@
|
||||
-- 限价流程同步:货号负责人限价(来源:限价流程的期望日常限价,按货号维度)
|
||||
CREATE TABLE "SupplyChainStyleLimitPrice" (
|
||||
"id" TEXT NOT NULL,
|
||||
"styleCode" TEXT NOT NULL,
|
||||
"ownerLimitPrice" DECIMAL(18,4),
|
||||
"processInstanceId" TEXT NOT NULL DEFAULT '',
|
||||
"processNo" TEXT NOT NULL DEFAULT '',
|
||||
"applyType" TEXT NOT NULL DEFAULT '',
|
||||
"initiator" TEXT NOT NULL DEFAULT '',
|
||||
"approvedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainStyleLimitPrice_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainStyleLimitPrice_styleCode_key" ON "SupplyChainStyleLimitPrice"("styleCode");
|
||||
|
||||
-- 限价流程同步:价格授权(同价申请 / 活动限价;供破价判定豁免与超期判定)
|
||||
CREATE TABLE "SupplyChainLimitFlowAuth" (
|
||||
"id" TEXT NOT NULL,
|
||||
"processInstanceId" TEXT NOT NULL,
|
||||
"processNo" TEXT NOT NULL DEFAULT '',
|
||||
"applyType" TEXT NOT NULL DEFAULT '',
|
||||
"styleCode" TEXT NOT NULL DEFAULT '',
|
||||
"spu" TEXT NOT NULL DEFAULT '',
|
||||
"approvedAt" TIMESTAMP(3),
|
||||
"startDate" TIMESTAMP(3),
|
||||
"endDate" TIMESTAMP(3),
|
||||
"adjustedLimitPrice" DECIMAL(18,4),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainLimitFlowAuth_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SupplyChainLimitFlowAuth_processInstanceId_styleCode_applyType_key" ON "SupplyChainLimitFlowAuth"("processInstanceId", "styleCode", "applyType");
|
||||
CREATE INDEX "SupplyChainLimitFlowAuth_styleCode_idx" ON "SupplyChainLimitFlowAuth"("styleCode");
|
||||
CREATE INDEX "SupplyChainLimitFlowAuth_applyType_idx" ON "SupplyChainLimitFlowAuth"("applyType");
|
||||
@ -1,19 +0,0 @@
|
||||
-- 产品全量档案字段权限(黑名单):主体对字段的屏蔽条目,只存被屏蔽的字段。
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProductArchiveFieldPermissionBlock" (
|
||||
"id" TEXT NOT NULL,
|
||||
"subjectKey" TEXT NOT NULL,
|
||||
"fieldKey" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"updatedBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductArchiveFieldPermissionBlock_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ProductArchiveFieldPermissionBlock_subjectKey_fieldKey_key" ON "ProductArchiveFieldPermissionBlock"("subjectKey", "fieldKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ProductArchiveFieldPermissionBlock_subjectKey_idx" ON "ProductArchiveFieldPermissionBlock"("subjectKey");
|
||||
@ -1,20 +0,0 @@
|
||||
-- Preserve explicit empty personal overrides separately from following a position.
|
||||
CREATE TABLE "ProductArchiveFieldPermissionSubject" (
|
||||
"subjectKey" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"updatedBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "ProductArchiveFieldPermissionSubject_pkey" PRIMARY KEY ("subjectKey")
|
||||
);
|
||||
|
||||
-- Keep every existing blocked field and its subject.
|
||||
INSERT INTO "ProductArchiveFieldPermissionSubject" ("subjectKey", "createdAt", "updatedAt")
|
||||
SELECT "subjectKey", MIN("createdAt"), MAX("updatedAt")
|
||||
FROM "ProductArchiveFieldPermissionBlock"
|
||||
GROUP BY "subjectKey";
|
||||
|
||||
ALTER TABLE "ProductArchiveFieldPermissionBlock"
|
||||
ADD CONSTRAINT "ProductArchiveFieldPermissionBlock_subjectKey_fkey"
|
||||
FOREIGN KEY ("subjectKey") REFERENCES "ProductArchiveFieldPermissionSubject"("subjectKey")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -1,13 +0,0 @@
|
||||
-- 仓库属性人工覆盖:数据源 remark2 异常时在本系统修正展示值,不影响聚水潭原始数据。
|
||||
CREATE TABLE "SupplyChainProductArchiveWarehouseAttributeOverride" (
|
||||
"warehouseName" TEXT NOT NULL,
|
||||
"attribute" TEXT NOT NULL DEFAULT '',
|
||||
"updatedById" TEXT,
|
||||
"updatedByName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SupplyChainProductArchiveWarehouseAttributeOverride_pkey" PRIMARY KEY ("warehouseName")
|
||||
);
|
||||
|
||||
CREATE INDEX "SupplyChainProductArchiveWarehouseAttributeO_updatedAt_idx" ON "SupplyChainProductArchiveWarehouseAttributeOverride"("updatedAt");
|
||||
@ -1,13 +0,0 @@
|
||||
CREATE TABLE "IntakeProductPhotoUploadGrant" (
|
||||
"id" TEXT NOT NULL,
|
||||
"productId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"grantedBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "IntakeProductPhotoUploadGrant_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "IntakeProductPhotoUploadGrant_productId_fkey" FOREIGN KEY ("productId") REFERENCES "IntakeNonIntegratedProduct"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "IntakeProductPhotoUploadGrant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "IntakeProductPhotoUploadGrant_grantedBy_fkey" FOREIGN KEY ("grantedBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX "IntakeProductPhotoUploadGrant_productId_userId_key" ON "IntakeProductPhotoUploadGrant"("productId", "userId");
|
||||
CREATE INDEX "IntakeProductPhotoUploadGrant_userId_idx" ON "IntakeProductPhotoUploadGrant"("userId");
|
||||
@ -1,107 +0,0 @@
|
||||
-- 停产下架:每日通知快照(编码/链接)与乐捐明细
|
||||
CREATE TABLE "SupplyChainDiscontinuationNotice" (
|
||||
"id" TEXT NOT NULL,
|
||||
"barcode" TEXT NOT NULL,
|
||||
"statDate" DATE NOT NULL,
|
||||
"statTime" TIMESTAMP(3) NOT NULL,
|
||||
"productStatus" TEXT NOT NULL DEFAULT '',
|
||||
"stockJianshang" INTEGER NOT NULL DEFAULT 0,
|
||||
"stockFactory" INTEGER NOT NULL DEFAULT 0,
|
||||
"handlingNote" TEXT NOT NULL DEFAULT '',
|
||||
"replacementBarcode" TEXT NOT NULL DEFAULT '',
|
||||
"changeImage" BOOLEAN NOT NULL DEFAULT false,
|
||||
"difference" TEXT NOT NULL DEFAULT '',
|
||||
"priceDiff" DECIMAL(18,2),
|
||||
"channelStock" INTEGER NOT NULL DEFAULT 0,
|
||||
"specialWarehouseNote" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainDiscontinuationNotice_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SupplyChainDiscontinuationNotice_barcode_statDate_key"
|
||||
ON "SupplyChainDiscontinuationNotice"("barcode", "statDate");
|
||||
CREATE INDEX "SupplyChainDiscontinuationNotice_statDate_idx"
|
||||
ON "SupplyChainDiscontinuationNotice"("statDate");
|
||||
CREATE INDEX "SupplyChainDiscontinuationNotice_barcode_idx"
|
||||
ON "SupplyChainDiscontinuationNotice"("barcode");
|
||||
|
||||
CREATE TABLE "SupplyChainDiscontinuationNoticeLink" (
|
||||
"id" TEXT NOT NULL,
|
||||
"noticeId" TEXT NOT NULL,
|
||||
"itemId" TEXT NOT NULL DEFAULT '',
|
||||
"shopName" TEXT NOT NULL DEFAULT '',
|
||||
"platform" TEXT NOT NULL DEFAULT '',
|
||||
"shopSkuCode" TEXT NOT NULL DEFAULT '',
|
||||
"onlineItemCode" TEXT NOT NULL DEFAULT '',
|
||||
"onlineStock" INTEGER NOT NULL DEFAULT 0,
|
||||
"onlineStatus" TEXT NOT NULL DEFAULT '',
|
||||
"ownerName" TEXT NOT NULL DEFAULT '',
|
||||
"deptHead" TEXT NOT NULL DEFAULT '',
|
||||
"profitCenter" TEXT NOT NULL DEFAULT '',
|
||||
"statTime" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainDiscontinuationNoticeLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SupplyChainDiscontinuationNoticeLink_noticeId_itemId_key"
|
||||
ON "SupplyChainDiscontinuationNoticeLink"("noticeId", "itemId");
|
||||
CREATE INDEX "SupplyChainDiscontinuationNoticeLink_itemId_idx"
|
||||
ON "SupplyChainDiscontinuationNoticeLink"("itemId");
|
||||
CREATE INDEX "SupplyChainDiscontinuationNoticeLink_ownerName_idx"
|
||||
ON "SupplyChainDiscontinuationNoticeLink"("ownerName");
|
||||
ALTER TABLE "SupplyChainDiscontinuationNoticeLink"
|
||||
ADD CONSTRAINT "SupplyChainDiscontinuationNoticeLink_noticeId_fkey"
|
||||
FOREIGN KEY ("noticeId") REFERENCES "SupplyChainDiscontinuationNotice"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE "SupplyChainDiscontinuationPenalty" (
|
||||
"id" TEXT NOT NULL,
|
||||
"monthKey" TEXT NOT NULL,
|
||||
"oId" TEXT NOT NULL,
|
||||
"soId" TEXT NOT NULL DEFAULT '',
|
||||
"barcode" TEXT NOT NULL DEFAULT '',
|
||||
"itemId" TEXT NOT NULL DEFAULT '',
|
||||
"shopName" TEXT NOT NULL DEFAULT '',
|
||||
"platform" TEXT NOT NULL DEFAULT '',
|
||||
"ownerName" TEXT NOT NULL DEFAULT '',
|
||||
"payAt" TIMESTAMP(3) NOT NULL,
|
||||
"noticeAt" TIMESTAMP(3) NOT NULL,
|
||||
"statTime" TIMESTAMP(3) NOT NULL,
|
||||
"orderStatus" TEXT NOT NULL DEFAULT '',
|
||||
"penaltyAmount" DECIMAL(10,2) NOT NULL DEFAULT 10,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING_APPEAL',
|
||||
"appealType" TEXT NOT NULL DEFAULT '',
|
||||
"appealDesc" TEXT NOT NULL DEFAULT '',
|
||||
"appealImages" JSONB NOT NULL DEFAULT '[]',
|
||||
"reviewedBy" TEXT NOT NULL DEFAULT '',
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"reviewNote" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SupplyChainDiscontinuationPenalty_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SupplyChainDiscontinuationPenalty_oId_itemId_key"
|
||||
ON "SupplyChainDiscontinuationPenalty"("oId", "itemId");
|
||||
CREATE INDEX "SupplyChainDiscontinuationPenalty_monthKey_ownerName_idx"
|
||||
ON "SupplyChainDiscontinuationPenalty"("monthKey", "ownerName");
|
||||
CREATE INDEX "SupplyChainDiscontinuationPenalty_status_idx"
|
||||
ON "SupplyChainDiscontinuationPenalty"("status");
|
||||
|
||||
-- 停产下架 · 每日库存预警快照(页面只读取本地落库结果)
|
||||
CREATE TABLE "SupplyChainDiscontinuationInventoryAlertSnapshot" (
|
||||
"id" TEXT NOT NULL,
|
||||
"statDate" DATE NOT NULL,
|
||||
"statTime" TIMESTAMP(3) NOT NULL,
|
||||
"barcode" TEXT NOT NULL,
|
||||
"spu" TEXT NOT NULL DEFAULT '',
|
||||
"sku" TEXT NOT NULL DEFAULT '',
|
||||
"productName" TEXT NOT NULL DEFAULT '',
|
||||
"productStatus" TEXT NOT NULL DEFAULT '',
|
||||
"stockJianshang" INTEGER NOT NULL DEFAULT 0,
|
||||
"stockFactory" INTEGER NOT NULL DEFAULT 0,
|
||||
"warehouseName" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SupplyChainDiscontinuationInventoryAlertSnapshot_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SupplyChainDiscontinuationInventoryAlertSnapshot_barcode_statDate_key"
|
||||
ON "SupplyChainDiscontinuationInventoryAlertSnapshot"("barcode", "statDate");
|
||||
CREATE INDEX "SupplyChainDiscontinuationInventoryAlertSnapshot_statDate_idx"
|
||||
ON "SupplyChainDiscontinuationInventoryAlertSnapshot"("statDate");
|
||||
@ -1,2 +0,0 @@
|
||||
-- 产品全量档案新增「安装方式」列:取值来源产品开发(产品定厂 SKU installMethod,整装/拆装)。
|
||||
ALTER TABLE "SupplyChainProductArchive" ADD COLUMN "installMethod" TEXT NOT NULL DEFAULT '';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user