Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d8419a124 |
@ -5,10 +5,6 @@ NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|||||||
# 数据库
|
# 数据库
|
||||||
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB_NAME?schema=public
|
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 只读来源
|
# 产品全量档案的聚水潭 SQL Server 只读来源
|
||||||
JST_REPORT_DB_HOST=
|
JST_REPORT_DB_HOST=
|
||||||
JST_REPORT_DB_PORT=1433
|
JST_REPORT_DB_PORT=1433
|
||||||
@ -78,15 +74,11 @@ JST_PLANNING_SQLSERVER_TRUST_SERVER_CERTIFICATE=true
|
|||||||
JWT_SECRET=replace-with-random-secret
|
JWT_SECRET=replace-with-random-secret
|
||||||
JWT_EXPIRES_IN=7d
|
JWT_EXPIRES_IN=7d
|
||||||
|
|
||||||
# 钉钉登录(OAuth 应用;只用于登录授权和用户信息获取)
|
# 钉钉登录
|
||||||
DINGTALK_LOGIN_CLIENT_ID=
|
DINGTALK_CLIENT_ID=
|
||||||
DINGTALK_LOGIN_CLIENT_SECRET=
|
DINGTALK_CLIENT_SECRET=
|
||||||
DINGTALK_CALLBACK_ORIGIN=
|
DINGTALK_CALLBACK_ORIGIN=
|
||||||
|
|
||||||
# 钉钉开放平台 API(通讯录、待办、审批等服务端调用)
|
|
||||||
DINGTALK_API_CLIENT_ID=
|
|
||||||
DINGTALK_API_CLIENT_SECRET=
|
|
||||||
|
|
||||||
# 钉钉待办:OA 阶段超时预警,以及预计出货日前 15 天的品牌策划主管提醒。
|
# 钉钉待办:OA 阶段超时预警,以及预计出货日前 15 天的品牌策划主管提醒。
|
||||||
DINGTALK_TODO_ENABLED=false
|
DINGTALK_TODO_ENABLED=false
|
||||||
DINGTALK_TODO_DETAIL_BASE_URL=
|
DINGTALK_TODO_DETAIL_BASE_URL=
|
||||||
@ -176,9 +168,6 @@ ALIBABA_1688_IMAGE_SEARCH_TAGS=4306497
|
|||||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_ENABLED=false
|
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_ENABLED=false
|
||||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_BASE_URL=https://example.com
|
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_BASE_URL=https://example.com
|
||||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_KEY=replace-with-material-library-api-key
|
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)
|
# 产品全量档案以图搜图(模型文件放在仓库外,不提交到 Git)
|
||||||
IMAGE_SEARCH_MODEL_PATH=
|
IMAGE_SEARCH_MODEL_PATH=
|
||||||
|
|||||||
5
furniture-3d-system/.gitignore
vendored
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/node_modules.pnpm-mixed
|
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
.yarn/*
|
||||||
@ -16,7 +15,6 @@
|
|||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
/.next-*/
|
|
||||||
/.next-oa-preview*/
|
/.next-oa-preview*/
|
||||||
/.next-oa-webpack/
|
/.next-oa-webpack/
|
||||||
/out/
|
/out/
|
||||||
@ -76,6 +74,3 @@ local-packages/npm-cache/cache/_logs/
|
|||||||
|
|
||||||
# 脚本监控 Agent 本机凭据
|
# 脚本监控 Agent 本机凭据
|
||||||
agents/script-monitor/config.json
|
agents/script-monitor/config.json
|
||||||
|
|
||||||
# 下载中心本地运行时产物
|
|
||||||
.download-center/
|
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
# 财务系统店铺密码直接显示契约
|
# 财务系统店铺密码按需读取契约
|
||||||
|
|
||||||
状态:生产接口已上线;2026-09-09 按当前明文存储规则修正 `passwordConfigured`。
|
状态:代码和本地测试交付;生产迁移、配置、发布及真实接口验收须单独批准。
|
||||||
|
|
||||||
## 存储事实与授权边界
|
## 存储事实与授权边界
|
||||||
|
|
||||||
- 密码真相源是 `public."StoreAccount"."passwordCiphertext"`。字段名是历史遗留;当前规则将新值以明文保存,读取链路对历史 `v1:` AES-256-GCM 值保留兼容。
|
- 密码真相源是 `public."StoreAccount"."passwordCiphertext"`,使用现有 `src/lib/store-account-crypto.ts` 的 AES-256-GCM(随机 12 字节 IV、认证标签、`v1:` 封装)。不是明文或不可逆 Hash。
|
||||||
- 存量 `v1:` 值已由 `scripts/migrate-store-account-plaintext-20260908.js` 迁移。`STORE_ACCOUNT_ENCRYPTION_KEY` 仅用于可能残留或恢复出的历史 `v1:` 值,不参与当前明文值读取。
|
- 生产密钥来自 `STORE_ACCOUNT_ENCRYPTION_KEY`,不从数据库读取,不复用财务服务认证凭据。已有银行账号加密也使用此密钥,轮换时必须同时处理。
|
||||||
- `passwordConfigured` 只表示存储值是否非空,不校验字符集或密码强度;因此包含 `+`、`/`、`=` 等字符的密码也会正确返回 true。它不返回密码本身。
|
- 2026-09-08 对 `furniture3d_prod` 的只读核验:`accounts_v1` COUNT=958、DISTINCT id=958;957 条非空密文均可在生产主机内存中解密,空值 1、解密失败 0。过程只输出聚合,不导出密码或密文。
|
||||||
|
- 2026-09-09 外部只读账号复核:`passwordConfigured` 聚合为 **false=958**,与上述 957/1/0 基线不符。已定位为 `20260908120000` 的判定正则只接受 URL-safe base64,标准 base64 的 `+`、`/`、`=` 填充会让正则整体失配(Node 的 base64url 解码器对填充宽容,因此"可解密"与"正则失配"并不矛盾)。修正见 `20260909120000_fix_store_account_password_configured_regex`,纯视图 DDL、不读写任何数据,生产执行仍需 DBA 审批。
|
||||||
- 普通 `store_account_reader` 可读视图、不能读取基表或密码列。此次迁移不增加其基表权限。
|
- 普通 `store_account_reader` 可读视图、不能读取基表或密码列。此次迁移不增加其基表权限。
|
||||||
- 本接口只信任专用服务身份 `finance-store-accounts`。财务系统必须在自身后端校验用户登录态、查看/复制权限及店铺范围,然后从可信会话中取 `requesterId`。不得把客户端传入的任意人员 ID 当成操作者。服务凭据仅保存在财务后端,禁止交给浏览器。
|
- 本接口只信任专用服务身份 `finance-store-accounts`。财务系统必须在自身后端校验用户登录态、查看/复制权限及店铺范围,然后从可信会话中取 `requesterId`。不得把客户端传入的任意人员 ID 当成操作者。服务凭据仅保存在财务后端,禁止交给浏览器。
|
||||||
- 不提供密码列表或批量导出。财务页面可在已通过自身页面权限校验后,自动读取当前可见页最多 50 个账号;CSV 和共享镜像仍只读取 `passwordConfigured`。复制操作继续走单账号接口。上游审计记录的用户 ID 是专用财务服务的身份断言,未授权请求中的 ID 不可信,`serviceId=null`。
|
- 不提供列表、批量密码导出或自动预取。列表只读取 `passwordConfigured`;每次明确的查看/复制操作单独调用接口。上游审计记录的用户 ID 是专用财务服务的身份断言,未授权请求中的 ID 不可信,`serviceId=null`。
|
||||||
|
|
||||||
## HTTP 契约
|
## HTTP 契约
|
||||||
|
|
||||||
@ -17,8 +18,6 @@
|
|||||||
|
|
||||||
`POST https://cp.jiansun.vip/internal/store-accounts/{id}/password/reveal`
|
`POST https://cp.jiansun.vip/internal/store-accounts/{id}/password/reveal`
|
||||||
|
|
||||||
`POST https://cp.jiansun.vip/internal/store-accounts/visible-page/password/reveal`
|
|
||||||
|
|
||||||
`id` 为 `accounts_v1.id`,仅接受单个 1–128 位字母、数字、下划线或连字符。无查询参数;禁止在 URL、请求体、请求头中传待读取密码。
|
`id` 为 `accounts_v1.id`,仅接受单个 1–128 位字母、数字、下划线或连字符。无查询参数;禁止在 URL、请求体、请求头中传待读取密码。
|
||||||
|
|
||||||
请求头:
|
请求头:
|
||||||
@ -36,30 +35,12 @@ Content-Type: application/json
|
|||||||
|
|
||||||
`purpose` 仅允许 `view` 或 `copy`。`requesterId` 是财务会话中的稳定人员标识,1–128 位 `[A-Za-z0-9_.:@-]`。不接受批量 ID、查询参数、普通登录 Cookie/JWT、普通数据库凭据或其他内部任务密钥。
|
`purpose` 仅允许 `view` 或 `copy`。`requesterId` 是财务会话中的稳定人员标识,1–128 位 `[A-Za-z0-9_.:@-]`。不接受批量 ID、查询参数、普通登录 Cookie/JWT、普通数据库凭据或其他内部任务密钥。
|
||||||
|
|
||||||
当前页批量请求严格只接受以下字段,正文最大 8192 字节;`accountIds` 为 1 至 50 个互不重复且符合单账号 ID 规则的值:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "requesterId": "finance-user-123", "purpose": "view", "accountIds": ["account-1", "account-2"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
成功 `200`:
|
成功 `200`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "requestId": "<服务端生成的 UUID>", "password": "<仅本次响应体中的密码>" }
|
{ "requestId": "<服务端生成的 UUID>", "password": "<仅本次响应体中的密码>" }
|
||||||
```
|
```
|
||||||
|
|
||||||
当前页批量成功的 HTTP 状态为 `200`,每个账号分别返回 `200/404/429/503`。每项都有独立 UUID;只有状态 `200` 的项包含密码,并按请求顺序完整返回:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"requestId": "<批次 UUID>",
|
|
||||||
"items": [
|
|
||||||
{ "accountId": "account-1", "requestId": "<项目 UUID>", "status": 200, "password": "<密码>" },
|
|
||||||
{ "accountId": "account-2", "requestId": "<项目 UUID>", "status": 404 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
所有状态均返回 `Cache-Control: no-store`、`Pragma: no-cache`、`X-Content-Type-Options: nosniff`。`X-Request-Id` 与响应体、审计的追踪 ID 相同。不会在响应头中放密码。
|
所有状态均返回 `Cache-Control: no-store`、`Pragma: no-cache`、`X-Content-Type-Options: nosniff`。`X-Request-Id` 与响应体、审计的追踪 ID 相同。不会在响应头中放密码。
|
||||||
|
|
||||||
错误体:
|
错误体:
|
||||||
@ -79,7 +60,7 @@ Content-Type: application/json
|
|||||||
|
|
||||||
## 限流和超时
|
## 限流和超时
|
||||||
|
|
||||||
- 单账号数据库滚动窗口:服务全部账号 30 次/分钟、300 次/24 小时;同一人员 10 次/分钟;同一账号 5 次/分钟。公网 TLS 网关保留 1 KiB 敏感请求正文上限,财务端把 50 行当前页拆成两个最多 25 个账号的批次;批次标记因此按服务 20 次/分钟、1000 次/24 小时、同一人员 10 次/分钟限流,等价于原每页额度。批次中的每个账号仍受同一账号 5 次/分钟限制。查看和复制共享账号额度,轮换令牌不重置服务身份或额度。
|
- 数据库滚动窗口:服务全部账号 30 次/分钟、300 次/24 小时;同一人员 10 次/分钟;同一账号 5 次/分钟。查看和复制共享额度,轮换令牌不重置服务身份或额度。
|
||||||
- 已认证的成功、404、参数拒绝和解密失败均占额度;429 不延长窗口。额度检查与审计在同一数据库事务中用事务级锁串行化,跨进程/重启共享状态。
|
- 已认证的成功、404、参数拒绝和解密失败均占额度;429 不延长窗口。额度检查与审计在同一数据库事务中用事务级锁串行化,跨进程/重启共享状态。
|
||||||
- 429 不携带剩余数量或精确窗口,避免推测其他用户行为。客户端不得自动重试密码读取;提示用户稍后再试,持续超限联系管理员。
|
- 429 不携带剩余数量或精确窗口,避免推测其他用户行为。客户端不得自动重试密码读取;提示用户稍后再试,持续超限联系管理员。
|
||||||
- 财务客户端设置总超时 10 秒、禁用自动重定向和重试。后端池上限 4、连接获取 1 秒、SQL 执行 1.5 秒/客户端查询 2 秒、锁等待 1 秒,路由运行预算 10 秒。网关必须配置请求体 1 秒和总请求截止,防止慢请求占用。
|
- 财务客户端设置总超时 10 秒、禁用自动重定向和重试。后端池上限 4、连接获取 1 秒、SQL 执行 1.5 秒/客户端查询 2 秒、锁等待 1 秒,路由运行预算 10 秒。网关必须配置请求体 1 秒和总请求截止,防止慢请求占用。
|
||||||
@ -89,13 +70,12 @@ Content-Type: application/json
|
|||||||
|
|
||||||
| 名称 | 所在系统与要求 |
|
| 名称 | 所在系统与要求 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| STORE_ACCOUNT_ENCRYPTION_KEY | 仅用于兼容历史 `v1:` 密文,至少 32 字符;生产不回退到 JWT_SECRET |
|
| STORE_ACCOUNT_ENCRYPTION_KEY | 上游已有独立加密密钥,至少 32 字符;生产不回退到 JWT_SECRET |
|
||||||
| STORE_ACCOUNT_FINANCE_TOKEN | 上游专用服务令牌,密码学随机至少 32 字节,base64url 编码,43–128 字符;缺失时拒绝全部调用 |
|
| STORE_ACCOUNT_FINANCE_TOKEN | 上游专用服务令牌,密码学随机至少 32 字节,base64url 编码,43–128 字符;缺失时拒绝全部调用 |
|
||||||
| STORE_ACCOUNT_FINANCE_TOKEN_PREVIOUS | 上游可选旧令牌,仅用于短时轮换;主令牌缺失时旧令牌也不生效 |
|
| STORE_ACCOUNT_FINANCE_TOKEN_PREVIOUS | 上游可选旧令牌,仅用于短时轮换;主令牌缺失时旧令牌也不生效 |
|
||||||
| STORE_ACCOUNT_REVEAL_EDGE_TOKEN | 上游与可信 TLS 网关之间的独立随机凭据,base64url 43–128 字符;与财务令牌完全分离,不提供给财务系统;缺失时拒绝全部调用 |
|
| STORE_ACCOUNT_REVEAL_EDGE_TOKEN | 上游与可信 TLS 网关之间的独立随机凭据,base64url 43–128 字符;与财务令牌完全分离,不提供给财务系统;缺失时拒绝全部调用 |
|
||||||
| DATABASE_URL | 上游现有数据库连接;仅专用不输出查询/异常的连接池读取密文、写审计 |
|
| DATABASE_URL | 上游现有数据库连接;仅专用不输出查询/异常的连接池读取密文、写审计 |
|
||||||
| STORE_ACCOUNT_REVEAL_URL | 财务后端配置完整 HTTPS 地址模板 |
|
| STORE_ACCOUNT_REVEAL_URL | 财务后端配置完整 HTTPS 地址模板 |
|
||||||
| STORE_ACCOUNT_REVEAL_VISIBLE_PAGE_URL | 财务后端配置当前页批量 HTTPS 地址;必须使用精确 `visible-page` 路径 |
|
|
||||||
| STORE_ACCOUNT_REVEAL_TOKEN | 财务后端保存与上游专用令牌对应的值 |
|
| STORE_ACCOUNT_REVEAL_TOKEN | 财务后端保存与上游专用令牌对应的值 |
|
||||||
| STORE_REVEAL_TEST_ADMIN_URL | 仅本地集成测试;必须指向回环地址、有建测试库/角色权限的 PostgreSQL |
|
| STORE_REVEAL_TEST_ADMIN_URL | 仅本地集成测试;必须指向回环地址、有建测试库/角色权限的 PostgreSQL |
|
||||||
| STORE_REVEAL_TEST_POSTGRES_CONTAINER | 仅本地日志验收,指定本地 PostgreSQL 容器以只读扫描测试期间日志 |
|
| STORE_REVEAL_TEST_POSTGRES_CONTAINER | 仅本地日志验收,指定本地 PostgreSQL 容器以只读扫描测试期间日志 |
|
||||||
@ -106,25 +86,26 @@ Content-Type: application/json
|
|||||||
2. 应用不以 URL 或未经认证的转发头证明 TLS。必须由受控网关在实际 HTTPS 入口覆盖 `X-Forwarded-Proto: https` 及 `X-Store-Reveal-Edge-Token`(值来自独立 `STORE_ACCOUNT_REVEAL_EDGE_TOKEN`),剥除客户端原有同名头;HTTP 入口直接拒绝,不能注入上述凭据。应用对网关凭据做定时安全比较,缺失/错误或协议不是 https 时拒绝。凭据仅上游和网关持有,不提供给财务系统;配置文件 0600,不通过 `nginx -T` 等诊断输出展示。仍需封闭应用直连并逐跳验证 TLS,独立凭据不代替网络加密。
|
2. 应用不以 URL 或未经认证的转发头证明 TLS。必须由受控网关在实际 HTTPS 入口覆盖 `X-Forwarded-Proto: https` 及 `X-Store-Reveal-Edge-Token`(值来自独立 `STORE_ACCOUNT_REVEAL_EDGE_TOKEN`),剥除客户端原有同名头;HTTP 入口直接拒绝,不能注入上述凭据。应用对网关凭据做定时安全比较,缺失/错误或协议不是 https 时拒绝。凭据仅上游和网关持有,不提供给财务系统;配置文件 0600,不通过 `nginx -T` 等诊断输出展示。仍需封闭应用直连并逐跳验证 TLS,独立凭据不代替网络加密。
|
||||||
3. 对精确路径 `/internal/store-accounts/*/password/reveal` 禁止网关缓存、响应落盘缓冲、CDN缓存、请求/响应体及 Authorization 采集;包括 APM、WAF、异常上报、链路追踪和调试抓包。网关日志只允许时间、方法、路径(不带参数)、状态、耗时、追踪 ID。不要采集响应对象。
|
3. 对精确路径 `/internal/store-accounts/*/password/reveal` 禁止网关缓存、响应落盘缓冲、CDN缓存、请求/响应体及 Authorization 采集;包括 APM、WAF、异常上报、链路追踪和调试抓包。网关日志只允许时间、方法、路径(不带参数)、状态、耗时、追踪 ID。不要采集响应对象。
|
||||||
4. 网关自己的 403/404/429/503 也使用脱敏 JSON、`Cache-Control: no-store` 和追踪 ID;不得返回上游诊断页。禁止将该路由接入通用 `withApi`/request logger;目前路由直接返回白名单响应,不记录异常对象。
|
4. 网关自己的 403/404/429/503 也使用脱敏 JSON、`Cache-Control: no-store` 和追踪 ID;不得返回上游诊断页。禁止将该路由接入通用 `withApi`/request logger;目前路由直接返回白名单响应,不记录异常对象。
|
||||||
5. 数据库查询结果包含当前明文密码;严禁记录 SQL 结果、绑定参数或异常对象,不要启用该连接池的参数/结果追踪。密码写入及历史格式迁移期间同样关闭 PostgreSQL/pgAudit 的绑定参数和错误参数记录。
|
5. 数据库不执行明文解密,SQL 参数不包含明文密码。现有密码写入或密钥轮换时,关闭 PostgreSQL/pgAudit 的绑定参数及错误参数记录,防止记录密文。不要启用该连接池的 SQL 结果或参数追踪。
|
||||||
6. 财务后端同样禁用正文采集、缓存及异常对象打印。浏览器在进入已授权页面后只读取当前可见页,避免 localStorage/sessionStorage/Service Worker 持久化;页面隐藏或离开时清除页面密码,不把密码放入通知、埋点或消息队列。
|
6. 财务后端同样禁用正文采集、缓存及异常对象打印。浏览器仅在用户点击后获取,避免 localStorage/sessionStorage/Service Worker 持久化;显示或复制完清除页面内存状态,不把密码放入通知、埋点或消息队列。
|
||||||
|
|
||||||
## 视图与审计迁移
|
## 视图与审计迁移
|
||||||
|
|
||||||
初始迁移:`prisma/migrations/20260908120000_store_account_password_reveal/migration.sql`。明文状态修复:`prisma/migrations/20260909100000_fix_store_account_password_configured_plaintext/migration.sql`。
|
迁移:`prisma/migrations/20260908120000_store_account_password_reveal/migration.sql`。
|
||||||
|
|
||||||
- 新增 `StoreAccountPasswordAccessAudit`,固定字段为请求追踪 ID、人员标识、服务身份、账号 ID、用途、HTTP状态、数据库时间;不关联用户/店铺外键,以保留未授权、不存在和删除账号的失败审计。状态 200 表示成功,其他状态表示失败。
|
- 新增 `StoreAccountPasswordAccessAudit`,固定字段为请求追踪 ID、人员标识、服务身份、账号 ID、用途、HTTP状态、数据库时间;不关联用户/店铺外键,以保留未授权、不存在和删除账号的失败审计。状态 200 表示成功,其他状态表示失败。
|
||||||
- 只在原视图存在时,以原视图查询作为子查询,在末尾追加 `passwordConfigured boolean`,保留原字段、筛选、每行内容、ID、所有者与 ACL;`EXISTS` 不会产生重复行。未配置外部视图的环境不擅自创建外部读入口。
|
- 只在原视图存在时,以原视图查询作为子查询,在末尾追加 `passwordConfigured boolean`,保留原字段、筛选、每行内容、ID、所有者与 ACL;`EXISTS` 不会产生重复行。未配置外部视图的环境不擅自创建外部读入口。
|
||||||
- `passwordConfigured` 的当前定义为 `passwordCiphertext <> ''`。修复迁移仅替换这个布尔表达式,并在事务内核对原字段逐行内容、视图所有者和 ACL 未变;不读取或输出密码。
|
- `passwordConfigured` 表示存在格式完整的、非空 AES-GCM 密码封装。SQL 不接触密钥,所以不在每次列表查询时验证 GCM 标签/密钥健康;生产预检必须做主机内存中的解密聚合验证。密钥丢失或密文损坏属于 503 运维故障,不能把它伪装成未设置密码。
|
||||||
- 明文迁移已由独立脚本完成。本状态修复不改任何密码或银行账号数据,也不增加普通只读账号权限。审计数据至少保留 365 天。
|
- 判定正则必须同时接受 base64url 与带 `=` 填充的标准 base64。只认 URL-safe 字符集会把全部存量密文误判成"未配置"(`20260909120000` 修正的就是这一点);同时仍保留 `v1:` 前缀、三段结构与 IV 长度约束,空值、空串与明文垃圾值依旧判定为 false。
|
||||||
|
- 无历史明文迁移,无现有密码或银行账号数据变更,无额外只读权限。审计数据增量写入,上线后按安全审计要求保留至少 365 天;此 PR 不创建自动删除任务。
|
||||||
|
|
||||||
迁移前由 DBA 只读确认目标库、视图定义/所有者/ACL、字段顺序、行数与唯一性、密码聚合、普通读取账号和其成员角色权限。若生产仍以本次基线验收,COUNT 与 DISTINCT id 必须均为 958;若业务新增/删除则暂停并重新确认基线,不改数据凑数。
|
迁移前由 DBA 只读确认目标库、视图定义/所有者/ACL、字段顺序、行数与唯一性、密码聚合、普通读取账号和其成员角色权限。若生产仍以本次基线验收,COUNT 与 DISTINCT id 必须均为 958;若业务新增/删除则暂停并重新确认基线,不改数据凑数。
|
||||||
|
|
||||||
备份与执行顺序(生产必须在明确批准后执行):
|
备份与执行顺序(生产必须在明确批准后执行):
|
||||||
|
|
||||||
1. 备份当前 `main`/部署 SHA、现有视图 DDL/ACL/所有者及 `_prisma_migrations` 状态。生成目标库 `pg_dump -Fc` 备份并验证可读取;备份进入生产主机权限 0700 目录,文件 0600,不进入 Git 或共享日志。备份可能含密文,按敏感材料管理。
|
1. 备份当前 `main`/部署 SHA、现有视图 DDL/ACL/所有者及 `_prisma_migrations` 状态。生成目标库 `pg_dump -Fc` 备份并验证可读取;备份进入生产主机权限 0700 目录,文件 0600,不进入 Git 或共享日志。备份可能含密文,按敏感材料管理。
|
||||||
2. 本次不修改任何密码、银行卡号或服务令牌;备份不得进入 Git 或普通日志。
|
2. 本次不换加密密钥;核实独立密钥在受限凭据存储中有可恢复副本,不在命令行、文档中显示。记录备份校验和、时间及恢复演练证据。
|
||||||
3. 执行明文状态修复迁移,视图短时获得 DDL 锁(最多等 5 秒,执行预算 30 秒);不回放未审查的其他迁移。
|
3. 保持新财务令牌未配置,执行批准的该迁移,视图短时获得 DDL 锁(最多等 5 秒,执行预算 30 秒),新增审计表及索引;不回放未审查的其他迁移。
|
||||||
4. 在同一只读快照比较迁移前后原字段行集、顺序/类型、行数/唯一性和权限;运行 `scripts/sql/check-store-account-password-reveal.sql`,确认最后字段 boolean,报告 true/false/null 聚合。预计当前数据为 957/1/0,但必须执行后实测。
|
4. 在同一只读快照比较迁移前后原字段行集、顺序/类型、行数/唯一性和权限;运行 `scripts/sql/check-store-account-password-reveal.sql`,确认最后字段 boolean,报告 true/false/null 聚合。预计当前数据为 957/1/0,但必须执行后实测。
|
||||||
5. 按项目主线发布流程部署,完成逐跳 TLS、网关日志/限流/缓存检查后再配令牌。生产虚构账号的创建、调用与删除属于额外生产写入,需单独批准;不要调用真实账号作为测试。
|
5. 按项目主线发布流程部署,完成逐跳 TLS、网关日志/限流/缓存检查后再配令牌。生产虚构账号的创建、调用与删除属于额外生产写入,需单独批准;不要调用真实账号作为测试。
|
||||||
|
|
||||||
@ -136,18 +117,17 @@ Content-Type: application/json
|
|||||||
|
|
||||||
网关凭据:暂停此接口,在网关及上游同时更新 `STORE_ACCOUNT_REVEAL_EDGE_TOKEN` 后验收并恢复;不支持旧值并行。不得将网关凭据与服务令牌设成相同值。
|
网关凭据:暂停此接口,在网关及上游同时更新 `STORE_ACCOUNT_REVEAL_EDGE_TOKEN` 后验收并恢复;不支持旧值并行。不得将网关凭据与服务令牌设成相同值。
|
||||||
|
|
||||||
历史兼容密钥:当前明文规则不需要为新值轮换数据密钥。若确认数据库中不存在 `v1:` 值并且备份保留策略允许,可另行评估退役历史解密密钥;退役前必须检查备份恢复场景,不能直接删除。
|
数据加密密钥:现有 `v1:` 不含 key ID,不能直接替换环境变量。必须另行批准维护窗口,暂停所有店铺密码及银行账号的写入/读取,备份对应表和旧密钥;用受控迁移进程在内存中旧钥解密、新钥重新 AES-GCM 加密,并在同一数据库事务中更新两类密文字段(SQL 永不接收明文)。验证全量解密聚合、行数及虚构账号后,协调所有应用实例切换新环境密钥,再恢复流量;失败时同时恢复密文备份和旧钥。关闭 SQL 参数/异常参数、APM及备份内容输出;本 PR 不执行此轮换,也不引入第二套密钥或把新旧密钥存入数据库。需要无停机轮换时,应另行实现带 key ID 的 Envelope Encryption/KMS 版本迁移。
|
||||||
|
|
||||||
## 本次验证记录
|
## 本次验证记录
|
||||||
|
|
||||||
- 当前页批量扩展的服务、HTTP 路由和代理边界共 3 个测试套件、41 项测试通过;`npm run type-check` 通过。测试覆盖 1–50 个账号、重复账号拒绝、部分 404/429/503、批次与每账号审计,以及失败时不返回明文。
|
|
||||||
- 单元/HTTP/代理路由测试:34 项通过,含伪造 HTTPS URL/转发头和错误网关凭据拒绝。
|
- 单元/HTTP/代理路由测试:34 项通过,含伪造 HTTPS URL/转发头和错误网关凭据拒绝。
|
||||||
- 追加现有代理路由的 7 项回归,与 10 项集成测试合计 6 套件、51 项全部通过。
|
- 追加现有代理路由的 7 项回归,与 10 项集成测试合计 6 套件、51 项全部通过。
|
||||||
- 隔离本地 PostgreSQL 集成测试:10 项通过,覆盖完整加解密与 HTTP handler、已提交审计、403/404/429/no-store、并发/跨实例限流、分钟/24小时配额、审计失败拒绝泄露、原字段及筛选保留、958/958 与 boolean 957/1/0、普通只读角色拒绝基表/密码/审计读取。
|
- 隔离本地 PostgreSQL 集成测试:10 项通过,覆盖完整加解密与 HTTP handler、已提交审计、403/404/429/no-store、并发/跨实例限流、分钟/24小时配额、审计失败拒绝泄露、原字段及筛选保留、958/958 与 boolean 957/1/0、普通只读角色拒绝基表/密码/审计读取。
|
||||||
- 开启本地测试连接的 PostgreSQL statement 日志,只在内存扫描 `docker logs`;应用日志、审计行、SQL请求与真实数据库日志均未检出随机测试密码。测试无需打印测试密码,断言只输出布尔结果。
|
- 开启本地测试连接的 PostgreSQL statement 日志,只在内存扫描 `docker logs`;应用日志、审计行、SQL请求与真实数据库日志均未检出随机测试密码。测试无需打印测试密码,断言只输出布尔结果。
|
||||||
- Type-check 通过;范围内 ESLint 通过。全仓库主数据边界巡检有 6 项已有违规,均位于本次未修改模块;不得据此声称全仓库通过。
|
- Type-check 通过;范围内 ESLint 通过。全仓库主数据边界巡检有 6 项已有违规,均位于本次未修改模块;不得据此声称全仓库通过。
|
||||||
- 2026-09-09 生产只读复核确认:958 行、958 个唯一 ID、957 条非空密码、1 条空密码;旧正则导致状态误报为全 false。本迁移用于恢复预期聚合 true/false/null = 957/1/0。
|
- 生产已完成只读基线核查;`passwordConfigured` 已随 `20260908120000` 落库,但当前 958 行全部判定为 false,修正迁移 `20260909120000` 已交付、待 DBA 审批执行。修正后应复跑 `scripts/sql/check-store-account-password-reveal.sql`,预期回到 957/1/0;若仍不符,说明生产密文存在第三种形态,需暂停并重新确认。生产接口尚未发布,因此没有生产新接口的验收结论。
|
||||||
|
|
||||||
运行:`npx jest --runInBand --runTestsByPath src/server/services/store-account-password-reveal.service.test.ts src/proxy-store-account-reveal.test.ts "src/app/internal/store-accounts/[id]/password/reveal/route.test.ts" tests/integration/store-account-password-reveal.test.ts`。未配置本地测试库变量时,集成套件明确 skip,不能算真实数据库验收通过。
|
运行:`npx jest --runInBand --runTestsByPath src/server/services/store-account-password-reveal.service.test.ts src/proxy-store-account-reveal.test.ts "src/app/internal/store-accounts/[id]/password/reveal/route.test.ts" tests/integration/store-account-password-reveal.test.ts`。未配置本地测试库变量时,集成套件明确 skip,不能算真实数据库验收通过。
|
||||||
|
|
||||||
收口:**passed with warnings**。这是自审,不是独立复审。Route → Service → Repository 边界保持;未修改 UI;状态字段与当前明文存储规则对齐,专用认证、审计和限流边界不变。
|
收口:**passed with warnings**。这是自审,不是独立复审。Route → Service → Repository 边界保持;未修改 UI;复用密码真相源、加密和专用服务认证,未通过明文视图绕开根因。剩余门禁为生产批准、备份/TLS拓扑/网关配置及发布后真实验收。
|
||||||
|
|||||||
@ -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,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 或不可读取地址不能靠索引任务生成图片,需要按条码定位源图片问题。
|
|
||||||
@ -34,10 +34,6 @@
|
|||||||
- 定时频率:每天 `03:00` 和 `15:00`。
|
- 定时频率:每天 `03:00` 和 `15:00`。
|
||||||
- 源库连接信息只允许放在生产本地 `/opt/furniture-3d-system-prod/shared/employee-directory-sync.env`,不能写入 Git 或共享文档。
|
- 源库连接信息只允许放在生产本地 `/opt/furniture-3d-system-prod/shared/employee-directory-sync.env`,不能写入 Git 或共享文档。
|
||||||
- 生产应用内部接口密钥继续从 `/opt/furniture-3d-system-prod/shared/.env` 的 `HR_SYNC_SECRET` 读取。
|
- 生产应用内部接口密钥继续从 `/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 成本参考镜像
|
## BOM 成本参考镜像
|
||||||
|
|
||||||
@ -54,19 +50,6 @@
|
|||||||
- 准确度回测:`npm run backtest:bom-cost-reference -- --sample-size=30`,只读最新成功镜像,不访问上游;样本限制 20 至 50 个,任一可比较 SKU 超过固定 10% 误差门槛即失败。
|
- 准确度回测:`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` 才会删除数据。
|
- 清理脚本:`npm run db:cleanup-operation-logs` 默认只做 dry-run;只有显式传入 `--apply --confirm=cleanup-operation-logs` 才会删除数据。
|
||||||
@ -77,15 +60,6 @@
|
|||||||
- 安装脚本会在 `/opt/furniture-3d-system-prod/shared/operation-log-retention.env` 保存权限为 `600` 的数据库身份门禁和非敏感参数;身份未填写或保留天数不是 30 天时拒绝启用。
|
- 安装脚本会在 `/opt/furniture-3d-system-prod/shared/operation-log-retention.env` 保存权限为 `600` 的数据库身份门禁和非敏感参数;身份未填写或保留天数不是 30 天时拒绝启用。
|
||||||
- 首次启用前必须先在生产执行 dry-run,核对候选数量、数据库身份和 systemd 落点;生产安装、首次 `--apply` 和后续代码部署均需另行获得授权。
|
- 首次启用前必须先在生产执行 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` 的生产部署目标。
|
- `192.168.58.20` 不是当前 `furniture-3d-system` 的生产部署目标。
|
||||||
|
|||||||
@ -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 看板钉钉待办
|
# 是否启用 OA 看板钉钉待办
|
||||||
DINGTALK_TODO_ENABLED=false
|
DINGTALK_TODO_ENABLED=false
|
||||||
|
|
||||||
# 钉钉开放平台 API 应用配置(待办、审批等服务端调用)
|
# 钉钉应用基础配置
|
||||||
DINGTALK_API_CLIENT_ID=
|
DINGTALK_CLIENT_ID=
|
||||||
DINGTALK_API_CLIENT_SECRET=
|
DINGTALK_CLIENT_SECRET=
|
||||||
|
|
||||||
# 钉钉网页登录使用独立 OAuth 应用配置,不与 API 应用混用
|
|
||||||
DINGTALK_LOGIN_CLIENT_ID=
|
|
||||||
DINGTALK_LOGIN_CLIENT_SECRET=
|
|
||||||
DINGTALK_CALLBACK_ORIGIN=
|
DINGTALK_CALLBACK_ORIGIN=
|
||||||
|
|
||||||
# 待办跳转回系统的基础地址
|
# 待办跳转回系统的基础地址
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# 供应链产品全量档案字段来源对照表
|
# 供应链产品全量档案字段来源对照表
|
||||||
|
|
||||||
更新时间:2026-10-16
|
更新时间:2026-08-10
|
||||||
|
|
||||||
## 1. 合并与追踪规则
|
## 1. 合并与追踪规则
|
||||||
|
|
||||||
@ -70,7 +70,7 @@
|
|||||||
| `sales7d` | 7日日销 | | | `rpt.pm_stockMonitor.sevenAvgSale` | 按条码匹配最新记录 |
|
| `sales7d` | 7日日销 | | | `rpt.pm_stockMonitor.sevenAvgSale` | 按条码匹配最新记录 |
|
||||||
| `sales15d` | 15日日销 | | | `rpt.pm_stockMonitor.fifteenAvgSale` | 按条码匹配最新记录 |
|
| `sales15d` | 15日日销 | | | `rpt.pm_stockMonitor.fifteenAvgSale` | 按条码匹配最新记录 |
|
||||||
| `purchasePrice` | 采购价 | `rpt.jst_sku.purchase_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空 |
|
| `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` 的新品行;匹配不到或来源为空时留空 |
|
| `costPriceSecondary` | 成本价(新品) | | `external.product_catalog_wide_v1.cost_price` | | 按档案条码精确匹配 `barcode_business_key`,只读取 `catalog_source = INTAKE_READY` 的新品行;匹配不到或来源为空时留空 |
|
||||||
|
|
||||||
## 3. JST_SKU 第一批字段覆盖率
|
## 3. JST_SKU 第一批字段覆盖率
|
||||||
|
|||||||
@ -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
@ -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,124 +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` 不需要灌数:接口读取时用代码内置默认值,首次在「规则配置」保存时落库。
|
|
||||||
|
|
||||||
## 五、待办
|
|
||||||
|
|
||||||
- [ ] 限价流程数据接入 + 筛选逻辑(生效时间版本、货号维度)
|
|
||||||
- [ ] 非负责人限价 10-07 规则、负责人触发规则(前两个月同价 / 两个月后按销量)落到判定引擎
|
|
||||||
- [ ] 按天增量任务(cron / worker)与失败告警
|
|
||||||
- [ ] 凭证查重(hash / 相似度)、关键字段一致性校验
|
|
||||||
- [ ] 举证期限、乐捐财务对接流程
|
|
||||||
- [ ] 首单礼金 / 新品礼金出资方确认后补录出资方字典
|
|
||||||
@ -74,7 +74,6 @@ const contentSecurityPolicy = [
|
|||||||
"base-uri 'self'",
|
"base-uri 'self'",
|
||||||
"object-src 'none'",
|
"object-src 'none'",
|
||||||
"frame-ancestors 'none'",
|
"frame-ancestors 'none'",
|
||||||
`frame-src 'self' ${lodopScriptSources.join(' ')}`,
|
|
||||||
"form-action 'self'",
|
"form-action 'self'",
|
||||||
"img-src 'self' data: blob: http: https:",
|
"img-src 'self' data: blob: http: https:",
|
||||||
"font-src 'self' data:",
|
"font-src 'self' data:",
|
||||||
@ -240,7 +239,7 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 服务器外部包配置(Next.js 16新配置)
|
// 服务器外部包配置(Next.js 16新配置)
|
||||||
serverExternalPackages: ['sharp', 'three', 'gsap', '@huggingface/transformers', 'onnxruntime-node', 'mssql'],
|
serverExternalPackages: ['sharp', 'three', 'gsap', '@huggingface/transformers', 'onnxruntime-node'],
|
||||||
|
|
||||||
// Turbopack配置(Next.js 16默认启用)
|
// Turbopack配置(Next.js 16默认启用)
|
||||||
turbopack: {},
|
turbopack: {},
|
||||||
|
|||||||
54
furniture-3d-system/package-lock.json
generated
@ -26,7 +26,6 @@
|
|||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dockerode": "^4.0.9",
|
"dockerode": "^4.0.9",
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
@ -43,7 +42,7 @@
|
|||||||
"next": "^16.1.6",
|
"next": "^16.1.6",
|
||||||
"obj2gltf": "^3.2.0",
|
"obj2gltf": "^3.2.0",
|
||||||
"pg": "^8.17.2",
|
"pg": "^8.17.2",
|
||||||
"pinyin-pro": "^3.29.5",
|
"pinyin-pro": "^3.28.1",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"puppeteer": "^24.40.0",
|
"puppeteer": "^24.40.0",
|
||||||
@ -53,12 +52,10 @@
|
|||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"server-only": "^0.0.1",
|
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"ssh2": "^1.17.0",
|
"ssh2": "^1.17.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"three": "^0.182.0",
|
"three": "^0.182.0",
|
||||||
"uuid": "^11.1.1",
|
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.2"
|
"zustand": "^5.0.2"
|
||||||
@ -69,7 +66,6 @@
|
|||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/crypto-js": "^4.2.2",
|
|
||||||
"@types/dockerode": "^4.0.1",
|
"@types/dockerode": "^4.0.1",
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^4.28.10",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
@ -6149,13 +6145,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/docker-modem": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/docker-modem/-/docker-modem-3.0.6.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/docker-modem/-/docker-modem-3.0.6.tgz",
|
||||||
@ -8884,13 +8873,6 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/css-line-break": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||||
@ -9355,20 +9337,6 @@
|
|||||||
"node": ">= 8.0"
|
"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": {
|
"node_modules/doctrine": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
|
||||||
@ -15537,9 +15505,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pinyin-pro": {
|
"node_modules/pinyin-pro": {
|
||||||
"version": "3.29.5",
|
"version": "3.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.5.tgz",
|
"resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.28.1.tgz",
|
||||||
"integrity": "sha512-Fk5YoRmtdHqJWqAuzYVG/NzvzeCbaG+ZEwbY6Nbps3QAIrvQQEtcm49frcyCM8i2b5najnzPDtYqrHRYTTiuwQ==",
|
"integrity": "sha512-oqz8ulwRgtUXRi0vbqEfGNly19zpyCxYrjhkk5TibGcgSW6eNwS5woajCXRwqURi8Ehc2yOFTiB4uNoZ+NJOnA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
@ -16953,12 +16921,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/set-blocking": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
@ -18870,16 +18832,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "11.1.1",
|
"version": "10.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
|
||||||
"integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
|
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/broofa",
|
"https://github.com/sponsors/broofa",
|
||||||
"https://github.com/sponsors/ctavan"
|
"https://github.com/sponsors/ctavan"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist/esm/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
|
|||||||
@ -3,9 +3,8 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"worker:ai-image": "node scripts/run-ai-image-worker.mjs",
|
|
||||||
"predev": "npm run prisma:check-client-sync",
|
"predev": "npm run prisma:check-client-sync",
|
||||||
"dev": "next dev --webpack",
|
"dev": "next dev",
|
||||||
"build": "prisma generate && next build",
|
"build": "prisma generate && next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
@ -18,26 +17,11 @@
|
|||||||
"prisma:push": "node scripts/prisma-db-push-local.js",
|
"prisma:push": "node scripts/prisma-db-push-local.js",
|
||||||
"prisma:studio": "prisma studio",
|
"prisma:studio": "prisma studio",
|
||||||
"sync:supply-chain-product-archive": "tsx scripts/sync-supply-chain-product-archive.ts",
|
"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",
|
|
||||||
"sync:supply-chain-product-archive-enrichment": "tsx scripts/sync-supply-chain-product-archive-enrichment.ts",
|
"sync:supply-chain-product-archive-enrichment": "tsx scripts/sync-supply-chain-product-archive-enrichment.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",
|
|
||||||
"backfill:product-archive-opshead:apply": "tsx scripts/backfill-product-archive-opshead-from-jst-sku-info-manage.ts --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",
|
"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",
|
"sync:jst-sku-product-archive": "tsx scripts/sync-jst-sku-product-archive.ts",
|
||||||
"sync:bom-cost-reference": "tsx scripts/sync-bom-cost-reference.ts",
|
"sync:bom-cost-reference": "tsx scripts/sync-bom-cost-reference.ts",
|
||||||
"backtest:bom-cost-reference": "tsx scripts/backtest-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:yunshan-created-at": "tsx scripts/import-yunshan-product-created-at.ts",
|
||||||
"import:product-archive-drawing-media": "tsx scripts/import-product-archive-drawing-media.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",
|
"db:verify-master-data": "node scripts/verify-master-data-foundation.js",
|
||||||
@ -75,7 +59,6 @@
|
|||||||
"materials:sync-standard": "tsx scripts/sync-standard-finishes.ts",
|
"materials:sync-standard": "tsx scripts/sync-standard-finishes.ts",
|
||||||
"materials:sync-standard:apply": "tsx scripts/sync-standard-finishes.ts --apply",
|
"materials:sync-standard:apply": "tsx scripts/sync-standard-finishes.ts --apply",
|
||||||
"sync:jst-sku-stock": "tsx scripts/sync-jst-sku-stock-products.ts",
|
"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:model:prepare": "tsx scripts/prepare-product-archive-image-search-model.ts",
|
||||||
"image-search:index": "tsx scripts/index-product-archive-images.ts",
|
"image-search:index": "tsx scripts/index-product-archive-images.ts",
|
||||||
"repair:barcode-print:historical": "tsx scripts/repair-barcode-print-source-rows.ts",
|
"repair:barcode-print:historical": "tsx scripts/repair-barcode-print-source-rows.ts",
|
||||||
@ -83,17 +66,12 @@
|
|||||||
"sync:oa-board:apply": "tsx scripts/sync-oa-board.ts --apply",
|
"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": "tsx scripts/sync-oa-brand-planning-board.ts",
|
||||||
"sync:oa-brand-planning:apply": "tsx scripts/sync-oa-brand-planning-board.ts --apply",
|
"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",
|
|
||||||
"cache:oa-board:local": "tsx scripts/cache-oa-board-local-preview.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-activity-mappings": "tsx scripts/import-oa-board-activity-mappings.ts",
|
||||||
"import:oa-board-node-rules": "tsx scripts/import-oa-board-node-rules.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",
|
"report:oa-board-activities": "tsx scripts/report-oa-board-activities.ts",
|
||||||
"sync:employee-directory": "tsx scripts/sync-employee-directory.ts",
|
"sync:employee-directory": "tsx scripts/sync-employee-directory.ts",
|
||||||
"sync:employee-directory:apply": "tsx scripts/sync-employee-directory.ts --apply",
|
"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": "tsx scripts/sync-external-readonly-to-jsreport.ts",
|
||||||
"sync:external-jsreport:apply": "tsx scripts/sync-external-readonly-to-jsreport.ts --apply",
|
"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",
|
"db:verify-master-data:truth": "node scripts/verify-master-data-truth-sources.js",
|
||||||
@ -146,7 +124,6 @@
|
|||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dockerode": "^4.0.9",
|
"dockerode": "^4.0.9",
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
@ -163,7 +140,7 @@
|
|||||||
"next": "^16.1.6",
|
"next": "^16.1.6",
|
||||||
"obj2gltf": "^3.2.0",
|
"obj2gltf": "^3.2.0",
|
||||||
"pg": "^8.17.2",
|
"pg": "^8.17.2",
|
||||||
"pinyin-pro": "^3.29.5",
|
"pinyin-pro": "^3.28.1",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"puppeteer": "^24.40.0",
|
"puppeteer": "^24.40.0",
|
||||||
@ -173,12 +150,10 @@
|
|||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"server-only": "^0.0.1",
|
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"ssh2": "^1.17.0",
|
"ssh2": "^1.17.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"three": "^0.182.0",
|
"three": "^0.182.0",
|
||||||
"uuid": "^11.1.1",
|
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.2"
|
"zustand": "^5.0.2"
|
||||||
@ -212,7 +187,6 @@
|
|||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/crypto-js": "^4.2.2",
|
|
||||||
"@types/dockerode": "^4.0.1",
|
"@types/dockerode": "^4.0.1",
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^4.28.10",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
@ -235,20 +209,5 @@
|
|||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@parcel/watcher",
|
|
||||||
"@prisma/engines",
|
|
||||||
"cpu-features",
|
|
||||||
"esbuild",
|
|
||||||
"onnxruntime-node",
|
|
||||||
"prisma",
|
|
||||||
"protobufjs",
|
|
||||||
"puppeteer",
|
|
||||||
"sharp",
|
|
||||||
"ssh2",
|
|
||||||
"unrs-resolver"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,92 +0,0 @@
|
|||||||
BEGIN;
|
|
||||||
SET LOCAL lock_timeout = '5s';
|
|
||||||
SET LOCAL statement_timeout = '30s';
|
|
||||||
|
|
||||||
DO $preflight$
|
|
||||||
DECLARE
|
|
||||||
actual_columns TEXT[];
|
|
||||||
expected_columns CONSTANT TEXT[] := ARRAY[
|
|
||||||
'id', 'storeName', 'operationOwner', 'operationDepartmentHead',
|
|
||||||
'operationDirector', 'profitCenter', 'subject', 'accountType', 'account',
|
|
||||||
'boundPhone', 'verificationContact', 'jstOrderService', 'accountGroupName',
|
|
||||||
'createdAt', 'updatedAt', 'passwordConfigured'
|
|
||||||
];
|
|
||||||
BEGIN
|
|
||||||
IF to_regclass('external_store_accounts.accounts_v1') IS NULL THEN
|
|
||||||
RETURN;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
SELECT array_agg(column_name ORDER BY ordinal_position)
|
|
||||||
INTO actual_columns
|
|
||||||
FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'external_store_accounts'
|
|
||||||
AND table_name = 'accounts_v1';
|
|
||||||
|
|
||||||
IF actual_columns IS DISTINCT FROM expected_columns THEN
|
|
||||||
RAISE EXCEPTION 'External account contract needs preflight review';
|
|
||||||
END IF;
|
|
||||||
END $preflight$;
|
|
||||||
|
|
||||||
DO $migration$
|
|
||||||
DECLARE
|
|
||||||
original_owner OID;
|
|
||||||
original_acl ACLITEM[];
|
|
||||||
BEGIN
|
|
||||||
IF to_regclass('external_store_accounts.accounts_v1') IS NULL THEN
|
|
||||||
RETURN;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
SELECT relowner, relacl
|
|
||||||
INTO original_owner, original_acl
|
|
||||||
FROM pg_class
|
|
||||||
WHERE oid = 'external_store_accounts.accounts_v1'::regclass;
|
|
||||||
|
|
||||||
CREATE TEMP TABLE store_account_view_rows_before ON COMMIT DROP AS
|
|
||||||
SELECT to_jsonb(account_row) - 'passwordConfigured' AS row_value
|
|
||||||
FROM external_store_accounts.accounts_v1 account_row;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW external_store_accounts.accounts_v1 AS
|
|
||||||
SELECT
|
|
||||||
source.id,
|
|
||||||
source."storeName",
|
|
||||||
source."operationOwner",
|
|
||||||
source."operationDepartmentHead",
|
|
||||||
source."operationDirector",
|
|
||||||
source."profitCenter",
|
|
||||||
source.subject,
|
|
||||||
source."accountType",
|
|
||||||
source.account,
|
|
||||||
source."boundPhone",
|
|
||||||
source."verificationContact",
|
|
||||||
source."jstOrderService",
|
|
||||||
source."accountGroupName",
|
|
||||||
source."createdAt",
|
|
||||||
source."updatedAt",
|
|
||||||
(source."passwordCiphertext" <> '')::boolean AS "passwordConfigured"
|
|
||||||
FROM public."StoreAccount" source;
|
|
||||||
|
|
||||||
IF EXISTS (
|
|
||||||
(SELECT row_value FROM store_account_view_rows_before
|
|
||||||
EXCEPT ALL
|
|
||||||
SELECT to_jsonb(account_row) - 'passwordConfigured'
|
|
||||||
FROM external_store_accounts.accounts_v1 account_row)
|
|
||||||
UNION ALL
|
|
||||||
(SELECT to_jsonb(account_row) - 'passwordConfigured'
|
|
||||||
FROM external_store_accounts.accounts_v1 account_row
|
|
||||||
EXCEPT ALL
|
|
||||||
SELECT row_value FROM store_account_view_rows_before)
|
|
||||||
) THEN
|
|
||||||
RAISE EXCEPTION 'External account rows changed while fixing password status';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM pg_class
|
|
||||||
WHERE oid = 'external_store_accounts.accounts_v1'::regclass
|
|
||||||
AND (relowner <> original_owner OR relacl IS DISTINCT FROM original_acl)
|
|
||||||
) THEN
|
|
||||||
RAISE EXCEPTION 'External account owner or ACL changed';
|
|
||||||
END IF;
|
|
||||||
END $migration$;
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
-- 修正 external_store_accounts.accounts_v1 的 passwordConfigured 误判为 false。
|
||||||
|
--
|
||||||
|
-- 背景:
|
||||||
|
-- 20260908120000 把 passwordConfigured 追加为
|
||||||
|
-- EXISTS(public."StoreAccount" 中 id 相同且 passwordCiphertext ~ '^v1:[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{2,}$')
|
||||||
|
-- 该视图的 FROM 子查询本身就取自 public."StoreAccount",EXISTS 再用 credential.id = original.id
|
||||||
|
-- 自连接同一行,因此 id 必然命中;唯一可能失败的判定就是密文正则。
|
||||||
|
-- 生产实测 958 行全部 false,而 DBA 基线核验为 957 条非空且可解密密文,说明密文真实格式不匹配该正则。
|
||||||
|
--
|
||||||
|
-- 根因:
|
||||||
|
-- 正则只接受 URL-safe base64 字符集(A-Za-z0-9_-)且长度固定。若密文以标准 base64 保存,
|
||||||
|
-- 会出现 +、/、以及 = 填充(16 字节 tag 的填充形态为 22 字符 + '=='),正则整体失配。
|
||||||
|
-- 本迁移放宽字符集并允许尾部填充,不改变 v1: 前缀、三段结构与 IV 长度约束。
|
||||||
|
--
|
||||||
|
-- 数据影响:**无**。本迁移只重写视图定义(DDL),不读写、不改写、不删除任何业务数据,
|
||||||
|
-- 不触碰 passwordCiphertext,不重建索引,不修改 owner/ACL(CREATE OR REPLACE VIEW 保留既有授权)。
|
||||||
|
-- 列集合、列顺序、列类型完全不变,仅替换布尔表达式中的正则字面量。
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
SET LOCAL lock_timeout = '5s';
|
||||||
|
SET LOCAL statement_timeout = '30s';
|
||||||
|
|
||||||
|
DO $fix$
|
||||||
|
DECLARE
|
||||||
|
d TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- 未接入该外部视图的环境直接跳过,不擅自创建外部读入口。
|
||||||
|
IF to_regclass('external_store_accounts.accounts_v1') IS NULL THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 只有已经追加过 passwordConfigured 的库才需要修正;否则保持原状。
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'external_store_accounts'
|
||||||
|
AND table_name = 'accounts_v1'
|
||||||
|
AND column_name = 'passwordConfigured'
|
||||||
|
) THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
d := rtrim(pg_get_viewdef('external_store_accounts.accounts_v1'::regclass, true), E';\n\r ');
|
||||||
|
|
||||||
|
-- 幂等:已修正过则直接跳过。
|
||||||
|
IF position('[A-Za-z0-9+/_=-]{22}={0,2}' IN d) > 0 THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 只替换字符集片段,避开 pg_get_viewdef 对反斜杠的转义差异。
|
||||||
|
-- IV 段 {16} 不动:12 字节 IV 在两种编码下都是 16 字符且永不填充。
|
||||||
|
IF position('[A-Za-z0-9_-]{22}' IN d) = 0
|
||||||
|
OR position('[A-Za-z0-9_-]{2,}' IN d) = 0 THEN
|
||||||
|
RAISE EXCEPTION '未找到待修正的旧正则片段,视图可能已被其它迁移改写,需人工复核';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
d := replace(d, '[A-Za-z0-9_-]{22}', '[A-Za-z0-9+/_=-]{22}={0,2}');
|
||||||
|
d := replace(d, '[A-Za-z0-9_-]{2,}', '[A-Za-z0-9+/_=-]{2,}={0,2}');
|
||||||
|
|
||||||
|
-- 列集合与类型不变,CREATE OR REPLACE VIEW 合法;既有 ACL 与 owner 保持。
|
||||||
|
EXECUTE format(
|
||||||
|
'CREATE OR REPLACE VIEW external_store_accounts.accounts_v1 AS %s',
|
||||||
|
d
|
||||||
|
);
|
||||||
|
END $fix$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@ -1,7 +0,0 @@
|
|||||||
-- Persist the Jianshang (JST public available) stock on the archive main table
|
|
||||||
-- so list filters, page rows and exports all read one source of truth.
|
|
||||||
ALTER TABLE "SupplyChainProductArchive"
|
|
||||||
ADD COLUMN "stockJianshang" TEXT NOT NULL DEFAULT '';
|
|
||||||
|
|
||||||
CREATE INDEX "SupplyChainProductArchive_stockJianshang_idx"
|
|
||||||
ON "SupplyChainProductArchive"("stockJianshang");
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
-- Product archive period-aware sales facts and sync audit.
|
|
||||||
CREATE TABLE "SupplyChainProductArchiveSalesPerformanceDaily" (
|
|
||||||
"barcode" VARCHAR(100) NOT NULL,
|
|
||||||
"statDate" DATE NOT NULL,
|
|
||||||
"shipAmount" DECIMAL(24,4) NOT NULL,
|
|
||||||
"returnAmount" DECIMAL(24,4) NOT NULL,
|
|
||||||
"realPerformance" DECIMAL(24,4) NOT NULL,
|
|
||||||
"shipQty" BIGINT NOT NULL,
|
|
||||||
"returnQty" BIGINT NOT NULL,
|
|
||||||
"clearSalesCost" DECIMAL(24,4) NOT NULL,
|
|
||||||
"sendFreight" DECIMAL(24,4) NOT NULL,
|
|
||||||
"platformCommission" DECIMAL(24,4) NOT NULL,
|
|
||||||
"xyCost" DECIMAL(24,4) NOT NULL,
|
|
||||||
"afterSaleAmount" DECIMAL(24,4) NOT NULL,
|
|
||||||
"pushFeeTotal" DECIMAL(24,4) NOT NULL,
|
|
||||||
"marketingFeeTotal" DECIMAL(24,4) NOT NULL,
|
|
||||||
"advertisementFeeTotal" DECIMAL(24,4) NOT NULL,
|
|
||||||
"sharedWarehousingSum" DECIMAL(24,4) NOT NULL,
|
|
||||||
"otherFee" DECIMAL(24,4) NOT NULL,
|
|
||||||
"operatingFee" DECIMAL(24,4) NOT NULL,
|
|
||||||
"sourceRowCount" INTEGER NOT NULL,
|
|
||||||
"syncedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT "SupplyChainProductArchiveSalesPerformanceDaily_pkey" PRIMARY KEY ("barcode", "statDate")
|
|
||||||
);
|
|
||||||
CREATE INDEX "SupplyChainProductArchiveSalesPerformanceDaily_statDate_idx" ON "SupplyChainProductArchiveSalesPerformanceDaily"("statDate");
|
|
||||||
CREATE INDEX "SupplyChainProductArchiveSalesPerformanceDaily_syncedAt_idx" ON "SupplyChainProductArchiveSalesPerformanceDaily"("syncedAt");
|
|
||||||
|
|
||||||
CREATE TABLE "SupplyChainProductArchiveSalesSyncRun" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"completedAt" TIMESTAMP(3),
|
|
||||||
"status" VARCHAR(24) NOT NULL,
|
|
||||||
"rangeStart" DATE NOT NULL,
|
|
||||||
"rangeEnd" DATE NOT NULL,
|
|
||||||
"sourceLatestDate" DATE,
|
|
||||||
"rowsRead" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"rowsWritten" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"unmatchedBarcodes" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"errorMessage" TEXT,
|
|
||||||
CONSTRAINT "SupplyChainProductArchiveSalesSyncRun_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
CREATE INDEX "SupplyChainProductArchiveSalesSyncRun_startedAt_idx" ON "SupplyChainProductArchiveSalesSyncRun"("startedAt");
|
|
||||||
CREATE INDEX "SupplyChainProductArchiveSalesSyncRun_status_startedAt_idx" ON "SupplyChainProductArchiveSalesSyncRun"("status", "startedAt");
|
|
||||||
@ -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,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,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,2 +0,0 @@
|
|||||||
-- 产品全量档案新增「安装方式」列:取值来源产品开发(产品定厂 SKU installMethod,整装/拆装)。
|
|
||||||
ALTER TABLE "SupplyChainProductArchive" ADD COLUMN "installMethod" TEXT NOT NULL DEFAULT '';
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
-- 商品库存快照:条码 × 实际仓库粒度,由 15 分钟定时任务全量替换写入;失败保留上一份快照。
|
|
||||||
CREATE TABLE "ProductInventorySnapshot" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"barcode" TEXT NOT NULL,
|
|
||||||
"wmsCoId" INTEGER NOT NULL,
|
|
||||||
"productName" TEXT NOT NULL DEFAULT '',
|
|
||||||
"category" TEXT NOT NULL DEFAULT '',
|
|
||||||
"sku" TEXT NOT NULL DEFAULT '',
|
|
||||||
"productionStatus" TEXT NOT NULL DEFAULT '',
|
|
||||||
"supplier" TEXT NOT NULL DEFAULT '',
|
|
||||||
"purchaser" TEXT NOT NULL DEFAULT '',
|
|
||||||
"stockQty" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"orderLockQty" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"availableQty" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"cloudAvailableQty" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"warehouseName" TEXT NOT NULL DEFAULT '',
|
|
||||||
"warehouseAttribute" TEXT NOT NULL DEFAULT '',
|
|
||||||
"warehouseOwnership" TEXT NOT NULL DEFAULT '',
|
|
||||||
"warehouseHead" TEXT NOT NULL DEFAULT '',
|
|
||||||
"syncedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "ProductInventorySnapshot_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "ProductInventorySnapshot_barcode_wmsCoId_key" ON "ProductInventorySnapshot"("barcode", "wmsCoId");
|
|
||||||
|
|
||||||
CREATE INDEX "ProductInventorySnapshot_warehouseName_idx" ON "ProductInventorySnapshot"("warehouseName");
|
|
||||||
|
|
||||||
CREATE INDEX "ProductInventorySnapshot_syncedAt_idx" ON "ProductInventorySnapshot"("syncedAt");
|
|
||||||
|
|
||||||
-- 同步运行状态:每次定时任务一行,记录 RUNNING / COMPLETED / FAILED。
|
|
||||||
CREATE TABLE "ProductInventorySnapshotRun" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"completedAt" TIMESTAMP(3),
|
|
||||||
"status" VARCHAR(24) NOT NULL,
|
|
||||||
"rowsRead" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"rowsWritten" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"syncedAt" TIMESTAMP(3),
|
|
||||||
"errorMessage" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "ProductInventorySnapshotRun_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "ProductInventorySnapshotRun_startedAt_idx" ON "ProductInventorySnapshotRun"("startedAt");
|
|
||||||
|
|
||||||
CREATE INDEX "ProductInventorySnapshotRun_status_startedAt_idx" ON "ProductInventorySnapshotRun"("status", "startedAt");
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
-- 通知中心待办同步状态(停产确认 / 待走流程两类)。
|
|
||||||
CREATE TABLE "ProductionStopNotificationSync" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"kind" TEXT NOT NULL,
|
|
||||||
"recipientKey" TEXT NOT NULL,
|
|
||||||
"recipientName" TEXT NOT NULL DEFAULT '',
|
|
||||||
"pendingCount" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"snapshot" TEXT NOT NULL DEFAULT '',
|
|
||||||
"lastSentAt" TIMESTAMP(3),
|
|
||||||
"completedAt" TIMESTAMP(3),
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
CONSTRAINT "ProductionStopNotificationSync_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "ProductionStopNotificationSync_kind_recipientKey_key"
|
|
||||||
ON "ProductionStopNotificationSync"("kind", "recipientKey");
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
-- 含大单近30日日销的数值快照列:用于 >、>=、<、<= 数值筛选与数值排序。
|
|
||||||
ALTER TABLE "ProductionStopDemandItem" ADD COLUMN "sales30dWithBigValue" DECIMAL(18,4);
|
|
||||||
ALTER TABLE "ProductionStopDemandItem" ADD COLUMN "sales30dWithBigSkuValue" DECIMAL(18,4);
|
|
||||||
|
|
||||||
UPDATE "ProductionStopDemandItem"
|
|
||||||
SET "sales30dWithBigValue" = "sales30dWithBig"::numeric
|
|
||||||
WHERE "sales30dWithBig" ~ '^-?[0-9]+(\.[0-9]+)?$';
|
|
||||||
|
|
||||||
UPDATE "ProductionStopDemandItem"
|
|
||||||
SET "sales30dWithBigSkuValue" = "sales30dWithBigSku"::numeric
|
|
||||||
WHERE "sales30dWithBigSku" ~ '^-?[0-9]+(\.[0-9]+)?$';
|
|
||||||
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |