Compare commits
No commits in common. "main" and "codex/wuyongjie-bom-estimate-agent-phase0" have entirely different histories.
main
...
codex/wuyo
@ -5,10 +5,6 @@ NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB_NAME?schema=public
|
||||
|
||||
# 员工工时花名只读人员配置来源(可选;开发环境配置后会合并正式人员配置)
|
||||
SUPPLY_CHAIN_PRODUCT_ARCHIVE_READ_DATABASE_URL=postgresql://READ_USER:READ_PASSWORD@READ_HOST:5432/READ_DB
|
||||
SUPPLY_CHAIN_PRODUCT_ARCHIVE_READ_DATABASE_SSL_REJECT_UNAUTHORIZED=true
|
||||
|
||||
# 产品全量档案的聚水潭 SQL Server 只读来源
|
||||
JST_REPORT_DB_HOST=
|
||||
JST_REPORT_DB_PORT=1433
|
||||
@ -78,15 +74,11 @@ JST_PLANNING_SQLSERVER_TRUST_SERVER_CERTIFICATE=true
|
||||
JWT_SECRET=replace-with-random-secret
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# 钉钉登录(OAuth 应用;只用于登录授权和用户信息获取)
|
||||
DINGTALK_LOGIN_CLIENT_ID=
|
||||
DINGTALK_LOGIN_CLIENT_SECRET=
|
||||
# 钉钉登录
|
||||
DINGTALK_CLIENT_ID=
|
||||
DINGTALK_CLIENT_SECRET=
|
||||
DINGTALK_CALLBACK_ORIGIN=
|
||||
|
||||
# 钉钉开放平台 API(通讯录、待办、审批等服务端调用)
|
||||
DINGTALK_API_CLIENT_ID=
|
||||
DINGTALK_API_CLIENT_SECRET=
|
||||
|
||||
# 钉钉待办:OA 阶段超时预警,以及预计出货日前 15 天的品牌策划主管提醒。
|
||||
DINGTALK_TODO_ENABLED=false
|
||||
DINGTALK_TODO_DETAIL_BASE_URL=
|
||||
@ -176,9 +168,6 @@ ALIBABA_1688_IMAGE_SEARCH_TAGS=4306497
|
||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_ENABLED=false
|
||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_BASE_URL=https://example.com
|
||||
MATERIAL_LIBRARY_WHITE_BACKGROUND_API_KEY=replace-with-material-library-api-key
|
||||
# 检测报告查询(不填则复用上面素材平台的 BaseUrl / API Key;都没有时「是否有检测报告」显示为空)
|
||||
MATERIAL_LIBRARY_CERTIFICATE_REPORT_API_BASE_URL=
|
||||
MATERIAL_LIBRARY_CERTIFICATE_REPORT_API_KEY=
|
||||
|
||||
# 产品全量档案以图搜图(模型文件放在仓库外,不提交到 Git)
|
||||
IMAGE_SEARCH_MODEL_PATH=
|
||||
|
||||
5
furniture-3d-system/.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/node_modules.pnpm-mixed
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
@ -16,7 +15,6 @@
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/.next-*/
|
||||
/.next-oa-preview*/
|
||||
/.next-oa-webpack/
|
||||
/out/
|
||||
@ -76,6 +74,3 @@ local-packages/npm-cache/cache/_logs/
|
||||
|
||||
# 脚本监控 Agent 本机凭据
|
||||
agents/script-monitor/config.json
|
||||
|
||||
# 下载中心本地运行时产物
|
||||
.download-center/
|
||||
|
||||
@ -21,13 +21,11 @@ archive foreign key is introduced.
|
||||
| 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 |
|
||||
| Unit cost | DF barcode/day single-unit observations first; otherwise `goods_cost / goods_count`; latest available daily cost in selected period |
|
||||
|
||||
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.
|
||||
Cost ties use single-unit frequency, total frequency, then descending unit cost.
|
||||
|
||||
## Deployment Prerequisites
|
||||
|
||||
@ -54,8 +52,8 @@ included in this change.
|
||||
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
|
||||
The migration only adds `skuOperator` (empty by default), `unitCost` (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.
|
||||
|
||||
|
||||
@ -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`。
|
||||
- 源库连接信息只允许放在生产本地 `/opt/furniture-3d-system-prod/shared/employee-directory-sync.env`,不能写入 Git 或共享文档。
|
||||
- 生产应用内部接口密钥继续从 `/opt/furniture-3d-system-prod/shared/.env` 的 `HR_SYNC_SECRET` 读取。
|
||||
- 人员信息表的钉钉组织同步使用 `furniture-3d-system/scripts/install-dingtalk-directory-sync-timer.sh`,生产 systemd 名称为 `furniture-dingtalk-directory-sync.service` / `furniture-dingtalk-directory-sync.timer`。
|
||||
- 钉钉同步每天 `03:00` 执行一次,读取生产共享 `.env` 中的 `DINGTALK_API_CLIENT_ID` 和 `DINGTALK_API_CLIENT_SECRET`,并自动更新人员信息;正式页面仍保持只读。钉钉网页登录使用独立的 `DINGTALK_LOGIN_CLIENT_ID`、`DINGTALK_LOGIN_CLIENT_SECRET`,两组凭据不能混用。
|
||||
- 人员信息的每日更新口径:钉钉新人自动新增一行;在职人员按钉钉更新部门/负责人;人工覆盖(manualOverride)的行保持不变;**离职人员不做删除,只在页面把「在职状态」变为离职**(方案 B:先比对上一轮在册且本轮抓取范围内消失的人,再逐个调 `user/get` 确认 60121 用户不存在才标记;抓取人数明显缩水时整轮跳过并写入告警)。
|
||||
- 首次部署后需在正式服务器执行该安装脚本,并手动执行一次 `npm run sync:dingtalk-directory:apply` 完成首次填充;后续由 systemd 每日执行。
|
||||
|
||||
## BOM 成本参考镜像
|
||||
|
||||
@ -54,19 +50,6 @@
|
||||
- 准确度回测:`npm run backtest:bom-cost-reference -- --sample-size=30`,只读最新成功镜像,不访问上游;样本限制 20 至 50 个,任一可比较 SKU 超过固定 10% 误差门槛即失败。
|
||||
- 迁移、真实同步、安装/启用定时器和生产回测都必须另行获得生产操作授权;部署代码本身不代表这些动作已经执行。
|
||||
|
||||
## 商品 SABC 月度核算
|
||||
|
||||
- 核算命令:`npm run run:product-sabc -- --year=2026 --month=8 --mode=APPLY --confirm=run-product-sabc`;试算(只算不写库)用 `--mode=PREVIEW`,末尾加 `--skip-if-exists` 可跳过已生成月份。
|
||||
- 8 月人工表对账命令:`npm run compare:product-sabc-xlsx`,默认读取本机下载的人工工作簿;生产执行前应先完成产品全量档案日销售数据同步。
|
||||
- 生产定时器安装脚本:`furniture-3d-system/scripts/install-product-sabc-timer.sh`。
|
||||
- 生产 systemd 名称:`furniture-product-sabc-monthly.service` / `furniture-product-sabc-monthly.timer`。
|
||||
- 默认调度:每月 3 日中国标准时间 `03:00`,附带最多 10 分钟随机延迟;服务生成上一个自然月的数据,已生成过则跳过(不覆盖管理员的重算结果)。
|
||||
- 生成即冻结:每次生成会把整行明细写入 `SupplyChainProductArchiveSabcResult.snapshot`,页面、筛选下拉、导出和经营总览都只读取该快照。生成之后档案的销售状态、人员字段等变化都不会再改变已生成月份的数据。
|
||||
- 重新生成(页面「执行核算」)仅限管理员(`ADMIN` / `SUPER_ADMIN`);其他账号只能查看冻结结果。本地一键刷新 `node scripts/local-sync-archive.mjs` 在每月 3 日前会跳过 SABC 步骤,需要立刻重算时用 `--force-sabc`。
|
||||
- SABC 的销售收款总金额、真实业绩、发货件数、商品毛利额和售后金额统一读取 `SupplyChainProductArchiveSalesPerformanceDaily`,与产品全量档案页面保持同一月度口径。
|
||||
- 首次安装生成权限为 `600` 的 `/opt/furniture-3d-system-prod/shared/product-sabc-timer.env`,默认 `PRODUCT_SABC_TIMER_MODE=APPLY`;如需先观察可临时改为 `PREVIEW`(只试算不写库、不冻结)。
|
||||
- SABC 自动任务只读取现有 `DATABASE_URL`;上游日销售同步任务继续使用 `JST_REPORT_DB_*`,凭据不写入定时器配置。安装、迁移、首次 APPLY 和生产核算都需要单独授权。
|
||||
|
||||
## 操作日志保留策略
|
||||
|
||||
- 清理脚本:`npm run db:cleanup-operation-logs` 默认只做 dry-run;只有显式传入 `--apply --confirm=cleanup-operation-logs` 才会删除数据。
|
||||
@ -77,15 +60,6 @@
|
||||
- 安装脚本会在 `/opt/furniture-3d-system-prod/shared/operation-log-retention.env` 保存权限为 `600` 的数据库身份门禁和非敏感参数;身份未填写或保留天数不是 30 天时拒绝启用。
|
||||
- 首次启用前必须先在生产执行 dry-run,核对候选数量、数据库身份和 systemd 落点;生产安装、首次 `--apply` 和后续代码部署均需另行获得授权。
|
||||
|
||||
## 停产需求确认超时收口
|
||||
|
||||
- 收口脚本:`furniture-3d-system/scripts/finalize-production-stop-confirmations.ts`,把超过确认截止时间仍未反馈的停产需求按「默认同意停产」落库,并给未反馈的确认人记 `TIMEOUT` 票。
|
||||
- 生产安装入口:`furniture-3d-system/scripts/install-production-stop-confirmation-finalize-timer.sh`。
|
||||
- 生产 systemd 名称:`furniture-production-stop-confirmation-finalize.service` / `furniture-production-stop-confirmation-finalize.timer`,默认每 10 分钟检查一次(北京时间),附带最多 1 分钟随机延迟;可用 `PRODUCTION_STOP_FINALIZE_CALENDAR` 覆盖调度。
|
||||
- 收口逻辑幂等:只处理「已过截止时间且仍待确认」的需求,重复执行不会重复收口;失败由 systemd 在 5 分钟后自动重试。
|
||||
- 运行日志:`journalctl -u furniture-production-stop-confirmation-finalize -n 100 --no-pager`,每次运行输出本次收口的需求数量。
|
||||
- 首次安装与启用:在项目根执行 `bash scripts/install-production-stop-confirmation-finalize-timer.sh`(需 root,脚本写入 `/etc/systemd/system/` 并 `enable --now`);安装/启用定时器属于生产操作,需要另行获得授权。
|
||||
|
||||
## 明确排除
|
||||
|
||||
- `192.168.58.20` 不是当前 `furniture-3d-system` 的生产部署目标。
|
||||
|
||||
@ -1,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:` 维度直接关联。
|
||||
@ -78,13 +78,9 @@
|
||||
# 是否启用 OA 看板钉钉待办
|
||||
DINGTALK_TODO_ENABLED=false
|
||||
|
||||
# 钉钉开放平台 API 应用配置(待办、审批等服务端调用)
|
||||
DINGTALK_API_CLIENT_ID=
|
||||
DINGTALK_API_CLIENT_SECRET=
|
||||
|
||||
# 钉钉网页登录使用独立 OAuth 应用配置,不与 API 应用混用
|
||||
DINGTALK_LOGIN_CLIENT_ID=
|
||||
DINGTALK_LOGIN_CLIENT_SECRET=
|
||||
# 钉钉应用基础配置
|
||||
DINGTALK_CLIENT_ID=
|
||||
DINGTALK_CLIENT_SECRET=
|
||||
DINGTALK_CALLBACK_ORIGIN=
|
||||
|
||||
# 待办跳转回系统的基础地址
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 供应链产品全量档案字段来源对照表
|
||||
|
||||
更新时间:2026-10-16
|
||||
更新时间:2026-08-10
|
||||
|
||||
## 1. 合并与追踪规则
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
| `sales7d` | 7日日销 | | | `rpt.pm_stockMonitor.sevenAvgSale` | 按条码匹配最新记录 |
|
||||
| `sales15d` | 15日日销 | | | `rpt.pm_stockMonitor.fifteenAvgSale` | 按条码匹配最新记录 |
|
||||
| `purchasePrice` | 采购价 | `rpt.jst_sku.purchase_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空 |
|
||||
| `costPrice` | 成本价(聚水潭) | `rpt.jst_sku.cost_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空。DF 条码自 2026-10-16 起同样取本来源(原 `rpt.dws_sn_sales_full_info_di.goods_cost` 为金额口径,已停用) |
|
||||
| `costPrice` | 成本价(聚水潭) | `rpt.jst_sku.cost_price` | | | 按档案条码精确匹配 `jst_sku.sku_id`;匹配不到时留空 |
|
||||
| `costPriceSecondary` | 成本价(新品) | | `external.product_catalog_wide_v1.cost_price` | | 按档案条码精确匹配 `barcode_business_key`,只读取 `catalog_source = INTAKE_READY` 的新品行;匹配不到或来源为空时留空 |
|
||||
|
||||
## 3. JST_SKU 第一批字段覆盖率
|
||||
|
||||
@ -1,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'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
`frame-src 'self' ${lodopScriptSources.join(' ')}`,
|
||||
"form-action 'self'",
|
||||
"img-src 'self' data: blob: http: https:",
|
||||
"font-src 'self' data:",
|
||||
@ -240,7 +239,7 @@ const nextConfig = {
|
||||
},
|
||||
|
||||
// 服务器外部包配置(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: {},
|
||||
|
||||
54
furniture-3d-system/package-lock.json
generated
@ -26,7 +26,6 @@
|
||||
"bcryptjs": "^3.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dockerode": "^4.0.9",
|
||||
"exceljs": "^4.4.0",
|
||||
@ -43,7 +42,7 @@
|
||||
"next": "^16.1.6",
|
||||
"obj2gltf": "^3.2.0",
|
||||
"pg": "^8.17.2",
|
||||
"pinyin-pro": "^3.29.5",
|
||||
"pinyin-pro": "^3.28.1",
|
||||
"postcss": "^8.5.6",
|
||||
"print-js": "^1.6.0",
|
||||
"puppeteer": "^24.40.0",
|
||||
@ -53,12 +52,10 @@
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"server-only": "^0.0.1",
|
||||
"sharp": "^0.34.5",
|
||||
"ssh2": "^1.17.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"three": "^0.182.0",
|
||||
"uuid": "^11.1.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.2"
|
||||
@ -69,7 +66,6 @@
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dockerode": "^4.0.1",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/jest": "^30.0.0",
|
||||
@ -6149,13 +6145,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/crypto-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
|
||||
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/docker-modem": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/docker-modem/-/docker-modem-3.0.6.tgz",
|
||||
@ -8884,13 +8873,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
@ -9355,20 +9337,6 @@
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dockerode/node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
|
||||
@ -15537,9 +15505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pinyin-pro": {
|
||||
"version": "3.29.5",
|
||||
"resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.5.tgz",
|
||||
"integrity": "sha512-Fk5YoRmtdHqJWqAuzYVG/NzvzeCbaG+ZEwbY6Nbps3QAIrvQQEtcm49frcyCM8i2b5najnzPDtYqrHRYTTiuwQ==",
|
||||
"version": "3.28.1",
|
||||
"resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.28.1.tgz",
|
||||
"integrity": "sha512-oqz8ulwRgtUXRi0vbqEfGNly19zpyCxYrjhkk5TibGcgSW6eNwS5woajCXRwqURi8Ehc2yOFTiB4uNoZ+NJOnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
@ -16953,12 +16921,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/server-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
|
||||
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
@ -18870,16 +18832,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
|
||||
"integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
|
||||
@ -3,9 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"worker:ai-image": "node scripts/run-ai-image-worker.mjs",
|
||||
"predev": "npm run prisma:check-client-sync",
|
||||
"dev": "next dev --webpack",
|
||||
"dev": "next dev",
|
||||
"build": "prisma generate && next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint",
|
||||
@ -20,18 +19,7 @@
|
||||
"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",
|
||||
"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",
|
||||
"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:bom-cost-reference": "tsx scripts/sync-bom-cost-reference.ts",
|
||||
"backtest:bom-cost-reference": "tsx scripts/backtest-bom-cost-reference.ts",
|
||||
@ -75,7 +63,6 @@
|
||||
"materials:sync-standard": "tsx scripts/sync-standard-finishes.ts",
|
||||
"materials:sync-standard:apply": "tsx scripts/sync-standard-finishes.ts --apply",
|
||||
"sync:jst-sku-stock": "tsx scripts/sync-jst-sku-stock-products.ts",
|
||||
"sync:product-inventory-snapshot": "tsx scripts/sync-product-inventory-snapshot.ts",
|
||||
"image-search:model:prepare": "tsx scripts/prepare-product-archive-image-search-model.ts",
|
||||
"image-search:index": "tsx scripts/index-product-archive-images.ts",
|
||||
"repair:barcode-print:historical": "tsx scripts/repair-barcode-print-source-rows.ts",
|
||||
@ -83,17 +70,12 @@
|
||||
"sync:oa-board:apply": "tsx scripts/sync-oa-board.ts --apply",
|
||||
"sync:oa-brand-planning": "tsx scripts/sync-oa-brand-planning-board.ts",
|
||||
"sync:oa-brand-planning:apply": "tsx scripts/sync-oa-brand-planning-board.ts --apply",
|
||||
"sync:oa-video-flow": "tsx scripts/sync-oa-video-flow-board.ts",
|
||||
"sync:oa-video-flow:apply": "tsx scripts/sync-oa-video-flow-board.ts --apply",
|
||||
"sync:oa-art-flow-raw": "tsx scripts/sync-oa-art-flow-raw.ts",
|
||||
"sync:oa-art-flow-raw:apply": "tsx scripts/sync-oa-art-flow-raw.ts --apply",
|
||||
"cache:oa-board:local": "tsx scripts/cache-oa-board-local-preview.ts",
|
||||
"import:oa-board-activity-mappings": "tsx scripts/import-oa-board-activity-mappings.ts",
|
||||
"import:oa-board-node-rules": "tsx scripts/import-oa-board-node-rules.ts",
|
||||
"report:oa-board-activities": "tsx scripts/report-oa-board-activities.ts",
|
||||
"sync:employee-directory": "tsx scripts/sync-employee-directory.ts",
|
||||
"sync:employee-directory:apply": "tsx scripts/sync-employee-directory.ts --apply",
|
||||
"sync:dingtalk-directory:apply": "tsx scripts/sync-dingtalk-directory.ts --apply",
|
||||
"sync:external-jsreport": "tsx scripts/sync-external-readonly-to-jsreport.ts",
|
||||
"sync:external-jsreport:apply": "tsx scripts/sync-external-readonly-to-jsreport.ts --apply",
|
||||
"db:verify-master-data:truth": "node scripts/verify-master-data-truth-sources.js",
|
||||
@ -146,7 +128,6 @@
|
||||
"bcryptjs": "^3.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dockerode": "^4.0.9",
|
||||
"exceljs": "^4.4.0",
|
||||
@ -163,7 +144,7 @@
|
||||
"next": "^16.1.6",
|
||||
"obj2gltf": "^3.2.0",
|
||||
"pg": "^8.17.2",
|
||||
"pinyin-pro": "^3.29.5",
|
||||
"pinyin-pro": "^3.28.1",
|
||||
"postcss": "^8.5.6",
|
||||
"print-js": "^1.6.0",
|
||||
"puppeteer": "^24.40.0",
|
||||
@ -173,12 +154,10 @@
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"server-only": "^0.0.1",
|
||||
"sharp": "^0.34.5",
|
||||
"ssh2": "^1.17.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"three": "^0.182.0",
|
||||
"uuid": "^11.1.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.2"
|
||||
@ -212,7 +191,6 @@
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dockerode": "^4.0.1",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/jest": "^30.0.0",
|
||||
@ -235,20 +213,5 @@
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@prisma/engines",
|
||||
"cpu-features",
|
||||
"esbuild",
|
||||
"onnxruntime-node",
|
||||
"prisma",
|
||||
"protobufjs",
|
||||
"puppeteer",
|
||||
"sharp",
|
||||
"ssh2",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,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 |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" fill="#0064fa" stroke="#0064fa" stroke-width="4" stroke-linejoin="round"/><path fill-rule="evenodd" clip-rule="evenodd" d="M24 11C25.3807 11 26.5 12.1193 26.5 13.5C26.5 14.8807 25.3807 16 24 16C22.6193 16 21.5 14.8807 21.5 13.5C21.5 12.1193 22.6193 11 24 11Z" fill="#FFF"/><path d="M24.5 34V20H23.5H22.5" stroke="#FFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 34H28" stroke="#FFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
Before Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB |