# erp-inbound
**Repository Path**: yhditto/erp-inbound
## Basic Information
- **Project Name**: erp-inbound
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: AGPL-3.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-08-10
- **Last Updated**: 2026-10-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 仓库管理系统(加工)
ERP 来料加工仓库管理系统,覆盖采购来料、生产加工、库存管理、质检、销售发货全链路,支持单据冲销、权限管控与移动端适配。
## 技术栈
| 层级 | 技术 |
|------|------|
| 后端 | FastAPI 0.115 + SQLAlchemy 2.0 + PyJWT |
| 前端 | React 18 + TypeScript 5.5 + Vite 5 + React Router 6 |
| 移动端 | uni-app + Vue 3 + TypeScript(微信小程序 / Android·iOS App / H5) |
| 数据库 | MySQL 8.0 |
| 部署 | Docker / 宝塔面板 + Nginx |
## 功能模块
### 采购来料
- 采购订单(创建/审批/撤销)
- 收货单(创建/审核过账/红冲)
### 生产加工
- 生产工单(创建/下发/领料/完工入库/撤销)
- BOM 物料清单管理
- 生产领料与退料
- 生产入库
### 库存管理
- 库存查询(按物料/仓库/状态)
- 库存盘点
- 出入库记录
- 入库汇总、库存汇总
- 冲销记录(工单冲销 + 采购冲销合并展示)
### 质量管理
- IQC 来料检验(待检→可用/限用/隔离/报废)
- 检验方案配置
- 检验明细查询
### 销售发货
- 销售订单管理
- 发货单(创建/确认)
- 发货汇总
### 系统管理
- 用户与角色管理(RBAC)
- 菜单与权限树分配
- 仓库数据权限(按仓库过滤);部门仅作组织架构归属(单据记录归属部门,不按部门隔离)
- 字典管理(状态/类型统一维护)
- 审计日志(操作追溯)
- 通知配置与库存预警
### 数据看板
- KPI 汇总卡片( clickable 导航)
- 库存监控面板
- 预警通知
### 移动端(小程序 / App)
- 首页看板:KPI 汇总(点击跳转对应单据明细)+ 库存预警账本(按物料过滤跳库存)
- 作业页:按业务流程顺序编排三大分组(物料管理 / 生产管理 / 库存作业),常用功能直达
- 库存控制台:深蓝 HUD 风格头部 + 药丸分段导航,四视图切换(库存明细 / 出库汇总 / 入库汇总 / 冲销记录),点击库存卡片进入该物料库存流水明细
- 库存盘点:新建盘点单 → 录入实盘数 → 差异调整
- 全链路单据操作:采购订单(含流转记录)、收货确认、IQC 检验、BOM、生产工单(下达/领料/完工入库)、生产领料退料、成品入库、采购退货
- 统一主题弹窗(ThemeModal)与底部选项器(OptionSheet),无原生样式;状态/类型全部走字典
## 项目结构
```
erp-inbound/
├── backend/ # 后端
│ ├── app/
│ │ ├── api/ # 路由层(auth/documents/production/masterdata...)
│ │ ├── core/ # 配置/数据库/安全/状态常量/幂等
│ │ ├── models/ # ORM 模型
│ │ ├── repositories/ # 数据访问层
│ │ ├── services/ # 业务逻辑层
│ │ ├── schemas/ # DTO
│ │ └── main.py # 入口
│ ├── requirements.txt
│ └── .env.example
├── frontend/ # 前端
│ ├── src/
│ │ ├── api/ # API 客户端
│ │ ├── components/ # 通用组件(侧边栏/标签栏/分页/通知...)
│ │ ├── hooks/ # 自定义 Hooks
│ │ ├── pages/ # 页面组件
│ │ ├── stores/ # 状态管理
│ │ ├── utils/ # 工具函数
│ │ ├── App.tsx # 应用根组件
│ │ └── styles.css # 全局样式(含移动端响应式)
│ ├── index.html
│ └── vite.config.ts
├── mobile/ # 移动端(uni-app + Vue3 + TS,一套代码产出微信小程序 / App / H5)
│ ├── src/
│ │ ├── pages/ # 页面(index 看板 / work 作业 / stock 库存 / workorder 工单 / receipt 收货 / inspection 检验 / count 盘点 / purchase 采购 / bom / issue 领料 / prodreceipt 成品入库 / return 退货 / item 物料 / mine / login / change-password)
│ │ ├── components/ # 主题组件(ThemeModal 弹窗 / OptionSheet 选项器 / StockDetail 库存明细 / StockFlow 流水 / SearchPicker / StockPickSheet / ReversalList)
│ │ ├── utils/ # modal-bus 弹窗总线 / stock-bus 库存跳转筛选 / flow 流程引导 / nav / format
│ │ ├── config.ts # 后端地址(local/remote 双环境开关)
│ │ └── pages.json # 页面路由与 tabBar
│ ├── switch-env.ps1 # 环境切换脚本(local ↔ remote,自动重建产物并验证连通)
│ └── package.json # build:mp-weixin / build:app / build:h5 等脚本
├── db/ # 数据库迁移脚本(01~60)
├── deploy/ # 部署配置(Docker/Nginx/宝塔)
├── docs/ # 设计文档
├── start.bat / start.ps1 # 一键启动(自动探测局域网 IP,手机真机可直接访问)
└── stop.ps1 # 一键停止
```
## 快速开始
### 环境要求
- Python 3.12+
- Node.js 18+
- MySQL 8.0+
### 1. 数据库初始化
```bash
mysql -u root -p < db/01_schema_core.sql
mysql -u root -p < db/02_schema_inbound.sql
mysql -u root -p < db/03_schema_inventory.sql
mysql -u root -p < db/04_seed.sql
# 按编号顺序执行后续迁移脚本至 60(17/18/19/20 存在同号多个脚本,按文件名字典序执行);跳过 09_reset_seed_chinese.sql 与 cleanup_business_data.sql(破坏性,仅手工重置时用)
mysql -u root -p erp_inbound < db/07_migrate_status_to_chinese.sql
# ...
mysql -u root -p erp_inbound < db/58_remove_asn.sql
mysql -u root -p erp_inbound < db/59_remove_supplier.sql
```
> Docker 部署(deploy/docker-compose.yml)首次启动只自行初始化 01~06 基线,07~60 迁移用 `bash deploy/docker-migrate.sh` 补跑;
> admin 首次登录口令用 `deploy/set-admin-password.ps1`(本地)或 `deploy/set-admin-password.sh`(服务器)生成(must_change_password=1,首次登录强制改密)。
### 2. 后端启动
```bash
cd backend
copy .env.example .env # 编辑数据库连接信息
pip install -r requirements.txt
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
打开 http://127.0.0.1:8000/docs 查看 API 文档。
### 3. 前端启动
```bash
cd frontend
npm install
npm run dev
```
打开 http://127.0.0.1:5173 访问系统。
### 4. 前端构建(生产环境)
```bash
cd frontend
npm run build
# 构建产物输出至 frontend/dist/,由 Nginx 托管静态文件并反代 /api 到后端(参考 deploy/nginx.conf)
```
### 5. 一键启动(Windows)
```bash
start.bat # 同时启动后端和前端,自动探测并绑定局域网 IP
```
后端与前端均绑定内网 IP(而非 127.0.0.1),手机与电脑同一 WiFi 即可真机访问后端 API 与前端页面。
### 6. 移动端(小程序 / App)
移动端为独立 uni-app 工程,复用后端同一套 API,无需改动后端。
```bash
cd mobile
npm install
npm run dev:mp-weixin # 微信小程序开发(监视编译,产物 dist/dev/mp-weixin)
npm run build:mp-weixin # 小程序发布构建(产物 dist/build/mp-weixin)
npm run build:app # App 资源构建(产物 dist/build/app)
```
- 用微信开发者工具导入 `mobile/dist/dev/mp-weixin` 或 `dist/build/mp-weixin` 目录运行;本地联调需在开发者工具"详情 → 本地设置"勾选**不校验合法域名**
- **后端环境切换**:`mobile/src/config.ts` 中 `CURRENT_ENV` 支持 `local`(局域网后端)/ `remote`(线上服务器);或直接用脚本切换:
```powershell
.\switch-env.ps1 # 查看当前环境
.\switch-env.ps1 remote # 切线上并重建 build 产物、验证连通
.\switch-env.ps1 local # 切本地(dev 产物由监视编译自动生效)
```
- **真机调试**:手机与电脑同一 WiFi,`local` 环境指向电脑局域网 IP;真机调试面板需勾选「不校验合法域名」
- **体验版**:暂无域名时(http + IP),手机打开体验版 → 右上角「···」→ 开启**开发调试**即可跳过域名校验;正式发布需 HTTPS + 已备案域名并配置 request 合法域名
- **App 打包**:HBuilderX 导入 `mobile/` 目录 → 发行 → 原生 App-云打包(Android 用公共测试证书即可出 APK;App 无域名校验,直接连 `remote` 地址);App 图标在 HBuilderX 的 manifest.json 可视化界面配置
- 物料/单据的状态与类型均走后端字典,与 PC 端共用同一套状态机
## 部署
### 腾讯云日常部署(当前生产环境)
```powershell
.\deploy\deploy-to-tencent.ps1 # 构建+上传+迁移+重启(默认 43.142.135.15)
.\deploy\deploy-to-tencent.ps1 -SkipBuild # 前端不变时跳过构建
```
服务器是腾讯云 43.142.135.15(布局与流程见 `deploy/README-Tencent.md`);`bt-deploy.sh` 等旧布局脚本已废弃。
### Docker 部署
```bash
cd deploy
docker-compose up -d
```
### 宝塔面板部署
参考 `deploy/README-BT.md`,使用 `deploy/bt-deploy.sh` 一键部署。
### Nginx 配置
参考 `deploy/nginx.conf`,前端静态文件 + 后端 API 反向代理。
## 默认账户
| 用户名 | 密码 | 角色 |
|--------|------|------|
| admin | (部署时由运维生成并安全交付的临时口令,首次登录后强制修改) | 管理员 |
> 安全要求:请勿将任何可用口令写入代码仓库或文档。生产部署后应立即轮换初始口令。
## 移动端适配
系统支持移动端访问,768px 以下自动切换为:
- 侧边栏抽屉模式(点击遮罩关闭)
- 表单/筛选栏纵向堆叠
- 弹窗全屏化
- 网格布局单列展示
## 更新日志
### 2026-10-12
**线上修复:库存汇总账报「未知报表类型」(后端)**
- 现象:打开库存汇总账返回 `{"code":0,"message":"报表类型不存在","data":{"error":"未知报表类型: inventory_summary"}}`
- 根因:`7c310d2` 把报表分发重构进 `app/services/document_query.py` 时引入回归——分发表命中后返回的是报表实体数据,接口层却改成 `if not data` 判空;四个报表返回的都是列表(空表 `[]` 是假值),于是「库存为空」或「普通用户未分配仓库 → scope=[] 过滤后无行」被误报成未知类型(重构前是 `if not fn` 判函数是否存在,不会误报)
- 修复:`document_query` 新增模块级注册表 `REPORT_TYPES`(与分发表同源,防二者漂移),`GET /api/v1/reports/{report_type}` 改为按注册表判未知类型;合法报表的空结果照常返回,前端渲染空表,未知类型的错误包络与文案保持不变
- 同批修复第二处回归(同一重构引入):`document_query` 顶层漏了 `from app.repositories.report import ReportRepository`,而 `quality_pass_rate` / `pending_qc_aging` / `inventory_turnover` 直接引用该类名 → 运行时 NameError → 报表中心另三个报表 500
- 同批修复历史缺陷:`pending_qc_aging` 用 `date.today()` 减 `Stock.last_in_at`(DateTime 列)必然 TypeError,待检库存非空时「待检库存时效」报表 500;改为 `rd.date()` 与同文件 `inventory_turnover` 一致
- 影响面:无库表迁移、无前端改动(前端 `extrasApi.report` 语义不变,仅需重启后端);部署后报表中心四个报表与库存汇总账页面恢复正常
- 验证:新增 `backend/tests/test_report_dispatch.py`(9 项全绿:空结果/有数据/未知类型包络/scope 下传/注册表一致性,末尾一例走真实仓库层 SQL 覆盖四个报表类型);三个缺陷在修复前分别以「报表类型不存在」/ NameError / TypeError 复现,修复后全部消失;`ruff check --select F821` 全后端无未定义名
### 2026-10-12
**库存明细:「已退」行点「明细」弹窗空白(后端 + Web + 移动端)**
- 现象:库存明细页把状态筛成「已退」后,点任意一行的「明细」,弹窗恒为「暂无流水记录」
- 根因:「已退」在库存侧不是真实状态,而是红冲/采购退货出库流水的聚合视图(`StockRepository.query_returned_stock` 按物料+仓库+库位聚合,硬编码 `stock_status='已退'`、`id` 取负数);流水表 `inv_transaction.stock_status` 记的是被冲销/退回时的**原状态**(可用/残品),按字面过滤恒为 0 行。移动端此前靠「已退不带状态过滤」绕开,代价是显示该物料全部流水(含入库)
- 修复:`TransactionRepository.list_with_details` 把 `stock_status=已退` 翻译成等价条件 `direction=-1 AND trans_type IN (红冲, 采购退货)`,与已退视图同源;移动端去掉特判,两端一致
- 明细精确到库位:`/api/v1/inventory/transactions` 与仓库层新增 `location_id` 过滤,PC 弹窗(`StockTxnModal` 增加库位展示)与移动端流水页跳转都带上库位——点哪条库存行就只看哪条(此前同物料同仓库其它库位的退回量会被一并算进来,如点 100 的行合计显示 130)
- 同批修复:数据看板「按库存状态」卡片跳转此前传英文字典码(`RETURNED`),与字典中文值不匹配,五张卡片点进去的库存明细均为空;改为传中文值
- 影响面:无库表迁移;后端重启 + 前端/移动端重新构建生效
- 验证:本地库修复前 `stock_status=已退` 查流水 0 行、修复后 12 行且全为「出库 + 红冲/采购退货」;未过滤 94 行、`stock_status=可用` 21 行不变;`backend/tests/regression_source_no.py` 全 PASS
### 2026-10-10
**审计日志新增登录记录(后端 + Web + 移动端)**
- 留痕范围:登录成功记「登录」、登录失败记「登录失败」(密码错误 / 未知用户名 / 停用账号三条分支都留痕)、登出记「退出登录」;`entity_type=sys_user`、`entity_id`=用户ID,与既有业务审计(中间件自动记录写操作)落在同一张 `sys_audit_log`、同一个审计日志页,无库表迁移、无新字典
- 实现位置:留痕写在服务层(`app/services/auth_svc.py` 的 `_audit_login`,独立提交 + 失败仅告警),因为失败分支会抛 `DomainError` 触发事务回滚,不单独提交则失败记录会一起消失;`/api/v1/auth` 路径仍由审计中间件排除,避免与登出端点重复记一条
- 新增 `POST /api/v1/auth/logout` 端点(需令牌,匿名/失效令牌 401 且不留痕):只写审计、不撤销令牌——令牌各自有有效期、客户端已清本地凭证,与改密「撤销全部令牌(全设备下线)」语义分开,一次登出不影响其它设备
- 真实客户端 IP:新增 `app/core/request_ip.py`,取 `X-Forwarded-For` **末段**(Nginx 以 `$proxy_add_x_forwarded_for` 追加的那一段,客户端伪造不了;取首段等于让被审计对象自己填 IP),再回退 `X-Real-IP`、直连对端;非法/超长值一律跳过(保护 `VARCHAR(45)`)。审计中间件与登录链路同步改用该函数,修复生产环境审计 IP 记成反代地址 127.0.0.1 的问题
- 前端/移动端:登出先 fire-and-forget 上报留痕(在清除本地令牌前发起,失败静默不阻塞登出);移动端 401 失效路径传 `report=false`(令牌已失效,再报只会再收一个 401),并新增 `noLoading` 静默请求选项避免退出瞬间闪 loading
- 验证:新增 `backend/tests/e2e_auth_audit_test.py`(30 项断言全绿:IP 解析 11 例 + HTTP 链路留痕/文案/权限 19 例,跑完精确清理探针数据);`tsc -b` 与移动端 `vue-tsc --noEmit` 零错误
### 2026-10-08
**全仓库审计修复批量(后端 + 前端 + 移动端 + DB + 部署)**
- 后端权限:用户列表收回明文密码展示,查看密码改为独立受控端点(`db/60` 补配套权限)+ AES-GCM 加密存储;`PASSWORD_ENC_KEY` 生产环境强校验;删除/移动端点补仓库 scope,盘点行接口补状态与仓库 scope 校验;报表/看板/审计日志补权限门与仓库范围过滤
- 后端正确性:幂等占位改原子操作防并发双建;工单/采购红冲加锁后复核单据状态;`ItemCost` 原子更新;盘点过账校验库存变动结果,未生效即阻断过账;`approve_po` 审批+过账收敛为单事务
- 前端:新增路由权限守卫,登出时清理页签 localStorage,写操作按钮 hasPerm 权限门补齐
- 移动端:强制改密闭环;401 用 refresh_token 静默续期;OptionSheet 并发弹窗修复
- DB:基线回填 `uk_stock_dim` 唯一键与 `sys_user` 四列默认值,`receipt_line` 过期列清理
- 部署:新增 `deploy/docker-migrate.sh`(补跑 07~60 迁移)与 admin 初始口令工具 `deploy/set-admin-password.ps1/.sh`(置 must_change_password=1,首次登录强制改密);修复 `tencent-update.sh` 吞迁移脚本问题;README/部署文档全面对齐
### 2026-10-07
**取消部门数据隔离(后端)**
- 拆除部门维度过滤与越权校验:删除 `get_dept_scope` 依赖与 `dept_filter` 过滤助手,`check_data_scope`/`check_model_scope`/`check_edit_scope` 去掉部门分支,7 个路由文件 43 处注入点同步移除——所有登录用户可见全部部门的单据,详情/审批/过账/冲销/编辑不再按部门 403
- 保留:单据创建仍落库 dept_id(归属部门),列表「部门」列继续展示;部门管理(组织架构)、用户归属部门、仓库维度数据权限(`get_warehouse_scope`)不变
- 无数据库迁移,前端/移动端零改动;现有 e2e/回归测试无部门隔离断言,不受影响
**部门管理上级部门选择修复(前端)**
- 新增/编辑部门的「上级部门」从手填改为下拉选择(树拍平候选,全角空格缩进层级);编辑时排除自身及子孙节点,防止把部门挂到自己子树下成环;编辑回填剥离 `children`,防止整棵子树被写进表单
- 构建产物同步 `backend/app/static`(哈希未变),源文件头 BOM 清理
**安全与稳定性批量修复(后端 + 前端 + 移动端 + 部署)**
- 后端并发安全:13 个过账/审批/红冲入口加 `SELECT ... FOR UPDATE` 行锁(收货审核、PO审批、退货/领料/盘点过账、销售审批、发货过账、工单下达、成品入库过账、检验判定、收货/PO/工单红冲),并发双击不再可能双读草稿双过账
- 后端安全细节:登录「用户不存在」与「密码错误」统一 400 同文案防用户名枚举,停用判断移到密码验证之后;新增 422 参数校验异常处理器(标准包络可读文案,前端不再显示 [object Object])
- 后端一致性:「卡单自愈」从 GET 列表路径移除、改为应用启动时执行一次(lifespan 钩子);删除会把待检 FQC 单据强转已入库的 receipt_repair 修复2(防悬空检验单,卡单显式暴露给人工)
- 后端通知:`create_notification` 改独立短会话旁路写入(不再触碰调用方会话,彻底拆除 rollback 埋雷);缺货/预警 60 分钟去重加 MySQL 命名锁串行化「查重→写入」防并发双写;通知列表库存状态批量预取消 N+1
- 前端:过账类请求全部携带确定性 `Idempotency-Key`(按动作+单据ID),审核/审批/入库按钮加 busy 防连点(收货/采购/成品入库/BOM/退货);补齐 12 个页面写操作按钮的 hasPerm 权限门(BOM、通知配置页此前全页无门);StockPage 请求序号防竞态;新建弹窗金额与库存汇总整数化累加规避浮点尾差;行编辑器改稳定 key(删中间行不再焦点/IME 错位);OptionSelect searchable 打开时高亮定位到当前选中项;ImportModal 重选同名文件可正常触发;主题防抖上报在登出时撤销(防跨账号写入主题);index.html 增加 CSP(内联脚本按内容哈希白名单,构建产物生效、dev 自动剥离)
- 移动端:线上地址从已停用的 121.40.118.86 改为 `https://jschain.cn`(P0 级,此前 remote 模式全断);8 处 `uni.$once` 选择回传前先 `$off` 清残留(取消返回/连点导致的重复 push 与跨页污染);modal-bus 并发弹窗先结清前一个 Promise(不再永久悬挂)
- 部署:现役 vhost 副本与 nginx 两份配置补 `client_max_body_size 20m`(xlsx 导入超 1M 曾必 413)、/assets location 内重复声明安全头(nginx add_header 继承语义);docker-compose 只挂 01~06 基础 schema(不再整目录连 09_reset/清理脚本一起首启执行)、后端端口只绑回环、移除 mysql 容器里无关的 REDIS_PASSWORD、Dockerfile.backend 加非 root USER;erp-backend.service 标注废弃指路现役;现役 `erp-inbound.service` 从服务器取回纳入版本管理;tools/db-reset.sh 表清单对齐 db/55~59 后 schema(inb_asn*/inv_lot/inv_putaway* 已删)
- 验证:后端导入与回归测试全过(含 26 个库存维度余额==流水重放一致性)、无BOM链路 e2e 13 项全过、`tsc`/`vue-tsc` 零错误、生产构建(含 CSP)浏览器冒烟通过(看板/BOM 页正常渲染取数)
### 2026-10-06
**修复:销售订单新建后回填筛选定位不到新单(前端)**
- 问题:新建销售订单后把新订单号回填进「发货单号」筛选框(新单此刻还没有发货单号),且不清空其他残留筛选值——一旦「订单号」等框里留有旧值(如成品入库单号 PGR…),多条件 AND 组合后列表必然为空,看起来"回填数据匹配不上销售单"
- 修复:`SalesOrderPage.save()` 创建成功后先 `reset()` 清空全部筛选,再把新订单号回填进语义正确的「订单号」筛选框,列表立即唯一定位到新单;同时移除「发货单号」筛选上为旧回填方案加的 `match:["order_no","ship_no"]` 跨字段匹配 hack,恢复该框只按发货单号匹配的语义
- 顺修 `FilterBar.useFilter` 隐患:URL 参数初始化 effect 的依赖从每次渲染都变的 configs 数组改为键名签名(字符串按值比较),避免页面带 hash 参数进入时每次渲染都用 URL 参数整体覆盖 vals、清掉用户输入或程序回填的其他筛选值(`/issue?work_order_no=`、`/prod-receipt?order_no=` 等引导跳转页面同样受益);`frontend/dist` 已重新构建同步
### 2026-10-06
**移除送货通知(ASN)功能(后端 + 前端 + 移动端 + DB)**
- 后端:删除 ASN 服务/ORM/仓库与全部 `/inventory/asn*` 路由,收货单与 ASN 的关联引用移除
- 前端:删除「送货通知」页面与路由、ASN API
- 移动端:删除 asn-create/asn-list 页面与路由注册、API
- DB:db/58 幂等迁移——inb_receipt/inb_receipt_line 去 asn_id/asn_line_id 列与外键、DROP inb_asn/inb_asn_line、清理 /asn 菜单(含部门/角色授权)、inbound.asn* 权限、asn_delivery 通知配置与历史通知、ASN 流水号序列
### 2026-10-06
**移除供应商档案与供货目录(后端 + 前端 + DB)**
- 后端:删除供应商服务/ORM/仓库与相关路由;采购订单/收货/退货对供应商的引用移除
- 前端:删除「供应商管理」「供应商供货目录」页面与路由、供应商 API
- DB:db/59 幂等迁移——pur_order/inb_receipt/inb_return 去 supplier_id 列与外键、DROP mst_supplier_item/mst_supplier、清理 /suppliers 与 /supplier-items 菜单(含部门/角色授权)及 master.supplier* 权限
### 2026-10-06
**批次(lot)功能整体移除:库存维度改为物料+仓库+库位+状态(后端 + 前端 + 移动端 + DB)**
- 库存模型简化:删除 `inv_lot` 批次表与全部 `lot_id` 维度——`inv_stock`/`inv_transaction` 唯一键改为 (item, warehouse, location, stock_status),`uk_stock_dim` 同步重建;所有单据(收货/退货/领料/盘点/上架历史/发货/成品入库/检验)不再记录批次,行成本统一取该库存维度的移动加权成本
- 后端:`stock_in/stock_out/status_transfer` 去 lot 参数(出库按库位显式选择,不再支持"按批次出库");删除 `repositories/lot.py`、`/inventory/lots` 两接口、FIFO 分库存按 `last_in_at` 排序;收货审核不再生成批次(`lots_created` 字段移除);收货单/ASN 行的 `manufacture_date`/`expiry_date`(纯为建批次服务的采集字段)连同前端录入控件一并移除;检验来源常量 `LOT_*` 更名 `INSP_*`;批号序列 LOT、红冲批次关联校验改为物料级
- 前端:删除「批次维护」「批次库存」两页与路由;库存明细/进出库记录/汇总账/发货/退货/领料/盘点/检验/收货等页面的批次列、批次筛选、批次选择全部移除(发货/领料改为按库位+状态出库);`StockRow`/`TransactionRow` 类型去 lot 字段,看板 `lot_count` 更名 `stock_count`;`lot-meta` 样式类更名 `doc-meta`
- 移动端:库存明细/流水/领料/退货/收货详情等页面的批次展示与录入移除,API 类型同步
- DB:db/57 幂等迁移——inv_stock 按 (item,warehouse,location,status) 去重加权合并、删除全部 lot 外键/索引/列、DROP inv_lot、清理批次菜单/权限/`lot_source_type` 字典/LOT 序列;01/02/03/04/05/06/07/09/12/13/18/32/52 及 _mysql57 建表与种子同步(27 号"只删批次外键"的迁移已无对象,随之删除);已在本地库执行验证(幂等,26 个库存维度账账一致)
- 测试:regression/e2e 三件套按无批次 API 重写(含中文状态兼容断言),本地库回归全部通过
### 2026-10-06
**移除上架功能:成品入库免检/FQC 判定后库存直接到位(后端 + 前端 + 移动端 + DB)**
- 流程简化:删除"成品入库→待上架→上架确认→已入库"中间环节——免检行入库确认时直接落正常区库位(可用)并计入移动加权成本,单据直接转已入库并触发工单自动完工;FQC 判定完成后正品直接从待检区转移到正常区(成本即时更新),入库单转已入库并触发自动完工,判定后原地自动弹出该成品入库单的库存明细
- 后端:删除上架服务/上架单 ORM/仓库/全部 `/inventory/putaways` 路由;删除"待上架"状态、"上架单"来源类型、"上架"交易类型常量;卡单自愈(检验全部判定→已入库)适配新流程;工单链路步骤去掉上架阶段;删除 `prod_receipt_post` 的"去上架管理"引导
- 前端:删除上架管理页面与路由、上架 API、工单详情"关联上架单"、流程面板上架单区块;入库/检验页面文案与状态选项同步
- 移动端:删除上架三个页面与路由注册、API、工作台"上架管理"入口、状态筛选与文案
- DB:db/56 幂等迁移——存量"待上架"入库单的待检区可用库存搬入本仓库正常区库位(补状态转移出入流水,uk_stock_dim 冲突时加权合并)、单据转已入库、满足条件的待入库工单自动完工;DROP 上架单表、删除上架菜单/按钮/权限/角色关联/"待上架"字典项/PUT 序列(历史上架流水与 S_PUTAWAY/T_PUTAWAY 字典项保留展示);03/04/09/13/15/20 及 _mysql57 种子同步;已在本地库执行验证(幂等重跑 0 错误)
- 测试:e2e_no_wo_receipt/e2e_no_bom 改为过账即已入库+正常区库存断言(e2e_no_bom 过账后直接自动完工),e2e_production 断言 FQC 判定后正品进正常区+工单自动完工并补清理逻辑保证可重复执行,regression_source_no 移除上架单用例;四个回归全部通过
- 补记:db/56 迁移文件曾遗失(本地库已应用但文件未留存),本次按原语义重建——待检区"可用"库存逐行搬入本仓正常区(同维度加权合并、携带预留量、补"上架"出入两条流水并共享组号、移动加权成本先减后收与旧"上架确认"代码路径一致)、待上架/已判完检验中的入库单转已入库、待入库工单自动完工、DROP 上架单表并清理菜单/权限/字典/序列;已用 9/21 备份库(含上架/批次旧态)注入五种场景完成 55→59 全链验证与 57 后幂等复跑
**成品入库支持不关联工单直接入库(后端 + 前端 + 移动端)**
- 数据模型:`prd_receipt.work_order_id` 放开为可空(db/55 幂等迁移 + db/05 建表脚本同步;外键 `fk_pr_wo` 对 NULL 不生效故保留,绑单数据完整性不受影响),ORM 列改 `int | None`
- 后端:建单/编辑接口 `work_order_id` 改可选——绑了工单维持原有全部校验(材料阶段 check_wo_material_stage、明细与工单产品一致、在途单防重、累计入库量不超计划量);不绑则跳过工单侧校验,明细物料自选、无容量限制。入库确认对无工单入库单跳过工单同步(sync_actual_qty/状态推进仅在有工单时执行);成品入库列表/详情 JOIN 改 outerjoin,无工单入库单不再从列表与详情中消失
- 前端:成品入库表单生产工单改可选——可检索 `OptionSelect`(含"不关联工单(直接入库)"空选项,可随时切回),选工单仍自动带出产出物料与仓库,不选则手动添加产出物料行(限成品/半成品);必填校验从"请选择生产工单"改为"请选择入库仓库";无可用工单提示从阻断性错误改为指引文案;列表/详情关联工单空值显示"-"
- 移动端:pr-create 生产工单改可选,新增入库仓库 SearchPicker(无工单时必选,选工单自动回填并允许修改),提交 `work_order_id` 可空
- 看板/工单链路不受影响:库存转换明细、工单流程步骤、工单红冲均以工单为中心查询,无工单入库单天然不参与
### 2026-10-05
**消息通知触发时机修正(后端 + 前端)**
- 漏触发补齐:盘点调整(盘亏)、收货单红冲、采购订单批量红冲、工单红冲(成品冲回)同样减少库存(可能扣到 0),此前不触发缺货检查——领料/发货/退货出库会报缺货,盘亏与红冲却静默;现统一在各事务提交后触发(红冲批量场景由 `execute_po_reversal` 在整体 commit 后聚合触发,`reverse_receipt` commit=False 时经返回值 `item_ids` 上交物料集合)
- 实现「库存预警」(stock_warning,低于安全库存):该类型此前只存在于通知配置与前端图标,后端从未创建——检查逻辑取了 `safety_stock` 却只用作跳过门槛、从不比较;现在总在库量为 0 发缺货通知、0<在库量≤安全库存发库存预警(各自 60 分钟去重);收货审核后总量仍低于安全线也会预警,原挂在该处的检查由"永不命中"变为有效
- 删除两处永不命中的死触发点:检验判定与状态转移(上架/库位移动)不改变总在库量,而旧检查只在"总在库量为 0"时发通知,执行后必不命中;`stock_out` 中"预警不放出库内以防状态转移中间态误报"的设计注释保留
- 失效抑制同步分级:缺货通知恢复在库即隐藏(原行为不变);库存预警改为回到安全线以上才隐藏(安全库存被调低/清零时旧预警自动消失)
- 检验结果通知落地(inspection_result):判定存在次品/残品时广播全员(全部合格不打扰),此前该类型同样只有配置无触发点
- 通知写入统一改为旁路 best-effort:`create_notification`/`check_stock_for_item` 内部吞异常记日志(`create_notification` 失败回滚本会话返回 0),通知写失败不再让已提交的业务事务返回 500 诱发重复建单——此前 7 处触发点仅状态转移一处包了 try/except
- 前端 `useNotifications` 首次加载只建立未读基线不弹 Toast:此前 `prevCount` 初始为 0,每次进页面都会把历史未读当"新消息"弹出右下角弹窗
**BOM 物料类型过滤(前端)**
- 新建/编辑 BOM 弹窗(共用同一表单)下拉按物料类型收敛:产出物料只列成品(IT_FG,原为成品+半成品),占位提示同步改为"请选择成品";子物料明细排除成品类型,只列原材料/半成品/辅料(原先全量列出)
- 过滤沿用 `useDictValues("item_type")` 按字典编码解析,未硬编码中文;仅收敛前端显示范围,后端 `create_bom`/`update_bom` 仍只校验物料存在,API 直调不拦类型
**下拉选择器支持关键字检索(前端)**
- `OptionSelect` 新增 `searchable` 属性:弹层顶部出现检索框,按 label/值不区分大小写子串过滤(无匹配显示"无匹配项"),打开即聚焦可直接输入,检索词变化后高亮回首项,关闭自动清空;中文输入法组词期间(isComposing)的 Enter/方向键不触发选择与导航,Home/End 在检索框内保留光标移动语义
- 键盘导航升级为在过滤后列表上工作:↑↓ 首次从检索框按下定位到首项,Enter 选中高亮项,Esc 关闭;列表改为独立滚动容器(`.osel-list`,max-height 234px),检索框随弹层固定不被滚走,非 searchable 场景 DOM 仅多一层包裹、观感不变
- BOM 新建/编辑弹窗接入(产出物料、子物料明细两处,各约 200 条物料):原生 select 无法检索,替换为可检索 `OptionSelect`,空选项以 `{value:"",label:"请选择成品"/"选择子物料"}` 保留在列表首位(与 FilterBar 空值约定一致),换选/清空语义不变;其余约 60 处原生 select 暂未替换,需要时给对应下拉传 searchable 并换用本组件即可
- 修复弹窗内弹层被裁剪(实测反馈"有遮挡"):`.osel-pop` 原为 absolute 定位,被 `.modal-body { overflow:auto }` 滚动盒裁剪,BOM 弹窗内弹层伸不出弹窗——与 DateTimePicker 同一问题,采用同款方案(160bf39):弹层改 `position:fixed` 按触发器矩形实时定位,右侧放不下右对齐触发器、下方放不下向上展开,滚动/缩放实时跟随;触发器最小宽度改由触发器矩形内联提供(原 CSS `min-width:100%` 在 fixed 下按视口计算会爆宽,已移除),全部使用处(FilterBar/仓库页/时间选择器时分/BOM)一并生效
### 2026-09-30
**全部查询列表统一增加时间范围筛选(前端)**
- `FilterBar.useFilter` 新增第三参 `timeField`:筛选栏配置 `{ key:"date_from" }`/`{ key:"date_to" }` 两个 datetime 项即对该字段做区间过滤(行数据已在内存,客户端完成,不新增后端接口);行缺时间值时设了任一边界即排除;时间值支持 URL hash 预填,解析兼容 ISO 微秒/空格分隔/date-only(统一按本地时区,规避 `new Date("YYYY-MM-DD")` 按 UTC 解析的 8 小时偏差)
- 修复出入库记录/入库汇总/出库汇总三页既有 bug:此前设了开始/结束时间后,客户端过滤把 `item["date_from"]` 当行字段做 includes 匹配,全部行被误滤空;现由 timeField 统一处理,与服务端 `created_at >= date_from / <= date_to` 口径一致
- 17 个列表页接入(字段):采购订单(order_date)、收货单(receipt_date)、采购退货(return_date)、销售订单(order_date)、发货单(ship_date)、成品入库(receipt_date)、生产领料(issue_date)、生产工单(release_date)、上架单(confirmed_at)、盘点单(count_date)、库存查询(last_txn_at)、冲销记录两页(reversed_at)、批次库存/批次维护(receipt_date)、审计日志(created_at,加载上限 100→1000 条)
- `StockTxnModal` 库存流水弹窗新增开始/结束时间筛选与「清除时间」,累计入库/出库、流水笔数随所选范围统计
- 时间控件 UI 主题化打磨(`styles.css`):`datetime-local` 宽度提升至 172px 不再被 130px 挤压截断;日历图标改为 mask 以主题令牌渲染;空值时段显示弱化占位色(组件按值注入 `data-empty`)——本条随后被下方「自绘选择器」整体取代(原生弹层无法定制)
**原生时间控件替换为自绘日期时间选择器(前端)**
- 原生 `datetime-local` 的日历弹层由浏览器自绘、CSS 无法定制(深色主题下灰底弹层与主题脱节,截图确认过丑);新增 `components/DateTimePicker.tsx` 全量主题令牌自绘:月历 42 格(周一起始、前后月补位、今天描边)+ 时/分下拉(默认保留已选时分,空值落 00:00)+「今天/清除」快捷;点击外部/Esc 关闭;触发框与表单输入同观感(空值弱化占位、悬停图标变强调色)
- 值格式保持 `YYYY-MM-DDTHH:MM`(与 datetime-local 一致),替换全部 11 处原生输入:FilterBar 时间筛选(一处覆盖 20 个列表页)、StockTxnModal 两个筛选框,以及收货时间/订单时间/预计到货/发货时间/领料时间/批次收货·生产·过期时间 8 处表单字段
- `styles.css` 移除原生输入打磨块、新增 `.dtp-*` 全套样式(弹层 z-index 120 高于弹窗 100;`.filter-bar button` 等高特异性规则以 `.dtp` 前缀规避;`.field .dtp` 撑满表单列宽)
- 修复触发框被撑成大卡片:修饰类名 `empty` 与全局空态样式(styles.css `.empty { padding:48px 24px; text-align:center }`,表格空态用)同名冲突,类特异性同为 (0,1,0) 且后者靠后生效,把 trigger 撑到 98px 高并居中文字;改名 `is-empty` 解除冲突,实测恢复 172×32 与相邻控件一致(dev server 实测 getComputedStyle 验证);`.dtp-pop` 弹层无同名冲突(position:absolute / 288px / z-index 120)
- 主数据(物料/用户/角色/字典)、报表各汇总页与来料检验页(仅判定时间、列表无时间列)无业务时间语义,不加时间筛选;移动端未涉及
**自绘下拉选择器替换原生 select 弹层(前端)**
- 原生 select 的弹出列表同样由浏览器自绘、CSS 无法定制(深色主题下灰底弹层、选项文字对比度差,截图确认);新增 `components/OptionSelect.tsx` 全量主题令牌自绘:触发框与表单输入同观感(空值弱化占位、禁用态 55% 透明)、弹层选项悬停/键盘高亮 `accent-soft`、当前项 accent 实底白字 + 勾选图标、箭头随开合旋转;键盘 ↑↓/Home/End/Enter/Esc 导航,高亮跟随滚动,点击外部关闭;值语义与原生一致(onChange 回传字符串,需要空选项时把 `{value:"",label:"全部"}` 放进 options)
- 本轮接入:`FilterBar` 的 select 分支(一处覆盖 30 个列表页的筛选下拉)、`WarehousePage` 全部 8 处(仓库主选择/仓库类型/库区类型/所属库区/存储模式/批量建库位三项,disabled 联动保留)、`DateTimePicker` 时/分(弹层内不再嵌原生弹层);其余约 60 处原生 select 暂保留,新增全局兜底 `select option { background: var(--surface-elevated) }` + `option:checked` 主题色,深色主题下选项恢复可读
- 容器适配:筛选栏 32px + min-width 130px、Field 内撑满、wh-select 内 26px 无边框透明(仓库页上下文样式)、时间弹层内 30px、弹层 z-index 120(wh-select 场景 130,覆盖其上方 chips)
**操作提示条自动消失(前端)**
- 现象:页面顶部的成功/错误提示条(`Msg` 组件纯文本形态)永久停留,直到下一条消息覆盖或刷新页面(截图确认)
- 修复:`Msg` 组件内置定时——成功条 3.5s 自动淡出(`msgOut` 动画 0.2s)、错误条 8s(错误文案需要阅读时间)、带「前往下一步」的确认遮罩框保持等用户操作不自动消失;纯条补一个 × 手动关闭按钮(继承提示条文字色);定时器 deps 仅 `[msg, skip]`(对象身份),页面轮询重渲染不会重置计时
**部署修复:解压前清理远端 dist,杜绝历史构建堆积(deploy)**
- 现象:服务器 `/opt/erp-inbound/frontend/dist` 与 nginx 根目录 `index-*.js` 累积到 7 个历史 hash——tar 解压只覆盖不清理,堆积随每次部署线性增长(rsync `--delete` 忠实镜像了已堆积的源,形同虚设)
- 修复:`deploy-to-tencent.ps1` 解压命令前置 `rm -rf frontend/dist`,远端 dist 与本次构建完全一致,rsync `--delete` 镜像后 nginx 根目录同步归零;已重跑部署核验(index 文件仅剩最新 1 个)
### 2026-09-30
**部署脚本更名对齐腾讯云(纯改名,不改行为)**
- `deploy/deploy-to-aliyun.ps1` → `deploy/deploy-to-tencent.ps1`、`deploy/aliyun-update.sh` → `deploy/tencent-update.sh`、`deploy/README-Aliyun.md` → `deploy/README-Tencent.md`(git mv 保留历史;43.142.135.15 实为腾讯云,旧名沿用自上一台阿里云服务器)
- 同步更新内部调用(更新脚本路径)、banner、`setup-ssh.ps1` 提示语、`bt-deploy.sh`/`bt-update.sh`/`quick-deploy.sh` 废弃指路文案与部署文档;`mirrors.aliyun.com` pip/Docker 镜像源为真实下载地址,保持不变
### 2026-09-29
**主题切换跟随账号持久化:服务器端存储 + 登录同步(SQL + 后端 + 前端)**
- 新增 `db/53_add_user_theme.sql`:`sys_user.ui_theme VARCHAR(16) DEFAULT 'classic'`(幂等脚本,历史空/非法值统一回落 classic)
- 后端新增 `GET/PUT /api/v1/auth/theme`:读取/保存当前登录用户的界面主题(`UI_THEMES` 白名单校验,非法值 422);ORM `User.ui_theme` 列同步补充
- 前端 `theme.ts`:切换主题时本地 localStorage 即时持久化不变,同时防抖 400ms + AbortController 竞态剔除静默上报服务器(未登录/失败静默);登录/恢复会话成功后 `adoptThemeFromServer()` 拉取服务器端主题对齐本机(服务器值优先,拉取期间用户又切换则以本机为准);`useTheme` 改为 `useSyncExternalStore` 订阅模块级主题源,服务器采纳/多标签页 `storage` 事件改主题时 React 侧(顶栏切换器当前态)自动同步
**部署脚本修复:补 UTF-8 BOM(否则 PowerShell 5.1 误解析,脚本无法运行)**
- `deploy-to-aliyun.ps1` 是仓库内少数**未带 BOM** 的 .ps1(`push.ps1` / `start.ps1` / `tools/jschain-tunnel.ps1` 均带 BOM)且用 LF 换行;PowerShell 5.1 读取无 BOM 的 UTF-8 时按 GBK 解码,注释行末尾全角括号的尾字节吃掉换行,使其后的 `if (Test-Path ...\OpenSSH\ssh.exe)` **整行被并入注释**——`$sshBin` 恒为空,脚本以「ssh.exe not found」中止(`scp` 检测行位置靠后,未受影响,故现象是"偏偏 ssh 找不到")
- 修复:为该文件补 UTF-8 BOM(内容零改动,仅 +3 字节),与仓库其余 .ps1 保持一致
**PC 端四主题系统(前端)**
- 新增 `src/theme.ts`:四套主题 classic(靛蓝经典,默认)/ porcelain(青瓷,全浅色)/ obsidian(黑曜,深色高对比)/ midnight(子夜蓝,深色圆润);`data-theme` 挂在 `` 上、其余三套以 CSS 变量覆盖实现,选中项存 localStorage(`erp_ui_theme`),并同步 `` 供移动端地址栏取色
- 新增 `components/ThemeSwitcher.tsx`:顶栏下拉切换(三色板预览 + 中英文名 + 一句话说明),点击外部自动关闭
- `index.html` 首屏防闪烁:样式表加载前先按 localStorage 恢复 `data-theme`,避免深色主题闪白
- `styles.css` 新增主题层(`:root[data-theme="..."]` 覆盖,约 970 行),侧栏/卡片/表格/标签/hover 全量适配;新增 `public/theme-matrix.html` 主题色板对照页(随构建产出,部署后位于 /theme-matrix.html)
**列表页表格增强:列宽拖拽 + 表头互换(前端)**
- 新增 `components/DataTable.tsx`:用 `` 包住页面现有 `` 即可获得 1) 列宽拖拽——拖动开始时冻结整行各列当前宽度,拖动中只有被拖列变化(表格总宽随之增减,超出容器由外层横向滚动),松手交还 `width:100%` 不重排 2) 表头整列拖拽互换位置(含首列)
- 布局按 tableId 存 localStorage(`dtbl2:`)跨会话记忆;列号统一按「页面原始列下标」,互换/调宽/记忆互不干扰;tbody 缺尾列的行按原始列号落位并补空 td、跨列行(合计/空态)与异常行原样跳过、`tableLayout !== auto` 的特殊行布局优雅降级不增强
- 已接入 46 个页面与明细弹窗
**字典颜色补全(SQL + 前端标签配色)**
- 新增 `db/52_dict_colors.sql`:为 trans_type / doc_status / count_type / inspection_result / return_type / source_type / lot_source_type / item_type / location_type 九类字典项补写颜色语义码(`sys_dict_item.color`),前端以 `.tag.` 渲染;以「仅补空白(`color IS NULL OR ''`)」为原则批量处理,另对已撤销单据、上架、反冲等项显式指定,避免覆盖既有配色
**仓库库位页 v5 重构(前端)**
- `WarehousePage` + `warehouse.css` 改版:改为「两行命令条 + 全宽工作表」,去掉侧栏、双分页与死区;样式改为复用系统 CSS 变量、随四主题自适应;仓库/库位的导入、导出与库区批量管理入口保留
**部署脚本加固:锁定 System32 OpenSSH + 上传校验**
- 当天同步时 scp 在 PowerShell 下报成功但服务器上无文件(从 Git Bash 启动时 PATH 里 Git 的 MSYS scp 遮蔽 System32,路径转换偶发改写参数);`deploy-to-aliyun.ps1` 改为优先锁定 `System32\OpenSSH` 的 ssh/scp,并在上传后核对远端文件大小,静默失败转为显式报错
**首页看板纳入权限分配(SQL + 前端)**
- 此前 /dashboard 菜单 permission 为空(公共菜单人人可见),权限树中无权限点、勾选框无效;新增 `db/51_add_dashboard_permission.sql`:权限点 `dashboard`(模块级 M)+ 菜单绑定 + 存量角色全量补授(升级前后可见性不变,回收走界面取消勾选)
- 前端 App.tsx:固定「首页」页签随权限补挂/摘除;无首页权限访问 /dashboard(登录跳转、直输 URL)自动落到第一个可见菜单,完全无菜单时提示联系管理员;关闭页签兜底目标同步调整
**部署脚本修复:ssh/scp 加 -q**
- 新版 Windows OpenSSH 客户端每次连接向 stderr 打印 post-quantum 警告,在脚本 `$ErrorActionPreference=Stop` 下经 `2>&1` 被当作错误中断解压步骤;`deploy-to-aliyun.ps1` 三处 ssh/scp 调用加 `-q` 修复
**主数据表格导入导出(后端 + 前端)**
- 后端新增 `services/dataio.py` 通用导入导出工具(xlsx/csv 解析、Excel 生成、逐行导入执行器),依赖新增 openpyxl、python-multipart
- 新增 7 个端点:物料/库位各含 导入(POST /import,复用单条创建校验与查重,逐行报错跳过)+导出(GET /export,支持与页面一致的筛选参数)+模板下载(GET /import-template,含填写说明页),另有仓库导出;库位导出附库区 sheet;导入操作自动记入审计日志("导入物料"等)
- 前端 `client.ts` 新增 `upload`/`downloadFile` 传输函数;通用 `ImportModal` 组件(字段说明+下载模板+选文件+逐行错误结果)
- 物料页筛选区加「导入」「导出」(导出跟随当前筛选),仓库页侧栏加仓库「导出」、右侧头部加库位「导出/导入库位」;权限沿用各资源 create 权限
- 测试 `tests/e2e_import_export_test.py`:xlsx/csv(UTF-8 BOM+GBK) 导入、重复与错误行逐行报错、导出筛选、模板与审计断言,可重复执行(自动清理 IMPTEST 前缀数据)
**无BOM工单隐藏领料入口(前端 + 移动端)**
- Web `WorkOrderPage`:操作列「领料」按钮增加 `has_bom !== false` 条件,无BOM工单(已下达/待入库)不再显示领料入口,与其免领料直接入库的流程一致
- Mobile `wo-detail`:「生产领料」按钮同样按 `has_bom` 隐藏;「完工入库」放开「已下达+无BOM」,与 Web `ProdReceiptPage` 的 `canReceipt` 规则对齐
- Mobile `pr-create`:可入库工单筛选纳入「无BOM已下达」;`issue-create`:领料工单筛选排除无BOM工单
### 2026-09-28
**部署脚本适配新生产服务器(43.142.135.15)**
- `deploy-to-aliyun.ps1`:服务器参数更新为新布局(`/opt/erp-inbound` + `erp-inbound.service`:8001,IP 设为默认值);打包范围收敛为服务器实际运行的 backend/db/frontend/deploy 四项(98.7MB→2.1MB);强制使用 Windows 自带 bsdtar,修复从 Git Bash 调起时 GNU tar 把 `C:\` 当远程主机导致的打包失败
- `aliyun-update.sh`:按新布局重写(`.venv`/8001/`erp-inbound` 服务),新增"归属修正(root 解压→erp 运行)"与"前端 dist rsync 同步到 nginx 根目录 `/www/wwwroot/erp_inbound`"两步,迁移/备库/健康检查逻辑保留
- `setup-ssh.ps1`:IP 更新为 43.142.135.15
- 旧服务器一次性安装脚本 bt-deploy.sh / bt-update.sh / quick-deploy.sh 加废弃保护(执行即退出并指引新脚本),防止在新服务器误跑破坏布局
- `deploy/README-Aliyun.md`:更新为当前生产服务器架构图与同步流程,旧布局说明存档
### 2026-09-28
**表格空态提示居中(前端)**
- 修复各列表页"暂无××"提示靠左的问题:61 处 `` 的居中样式此前被 `th, td { text-align: left }` 直接选择器覆盖(继承不敌直接命中),新增 `.empty td { text-align: center }` 一处修复全部页面
### 2026-09-28
**免检成品入库恢复"待上架→上架选库位"流程(后端)**
- 免检成品入库单过账后不再直达「已入库」:库存暂存待检区库位(状态直接「可用」),入库单转「待上架」,由上架单选定正常库位确认后才转「已入库」并触发工单自动完工(`production_receipt.py` 免检分支,恢复 39bb610 之前的设计)
- 免检与需检在上架环节统一:需检行经 FQC 判定后与免检行同为「待检区+可用」,同一张上架单移出;上架确认时统一更新移动加权成本(免检直达期间缺失的成本更新随之恢复)
- 上架页/成品入库页/表单提示文案本就按"待上架"描述,行为与文案重新对齐;`putaway.py` 模块注释"(FQC检验后或免检)从待检区移至正常区"重新成立
- 无 BOM 链路同步调整:下达 → 跳过领料直接成品入库 → 过账后待上架 → 上架后自动完工,工单流程步骤中重新出现「上架」阶段
- `e2e_no_bom_test.py` 增加上架两段式断言(过账后待上架/待入库,上架后已入库/已完工),需检流程回归 `e2e_production_test.py` 不受影响
### 2026-09-28
**网站底部展示 ICP 备案号(前端 / 线上)**
- 新增 `frontend/src/components/SiteFooter.tsx`:展示「陕ICP备2026027276号」并链接工信部政务服务平台(`https://beian.miit.gov.cn/`,新窗口打开 + `rel="noopener noreferrer"`)
- 接入两处底部:未登录访客看到的登录页,以及登录后主框架(首页及各内页),两处由同一组件渲染
- 登录页外层新增 `auth-page-wrap`(竖向 flex + 登录区 `flex: 1`):页脚走正常文档流,避免绝对定位在矮窗口下压住登录卡片
- 新增 `.site-footer` 样式(12px 次要色 + 上分隔线 + hover 变品牌色),打印时隐藏
- 线上 jschain.cn 已发布该构建;备案通过后同步恢复 vhost 中被临时停用的 HSTS(`max-age=31536000`)
### 2026-09-23
**批量删除结果提示修正(前端)**
- 修复"删除失败却弹绿色成功框":此前所有批量删除回调都硬编码 `ok: true`,只要接口没抛异常就按成功样式提示。现统一走 `frontend/src/utils/batchMsg.ts` 的 `batchDeleteMsg()`,只要有一条失败就返回 `ok: false`(红色提示框),并附上去重后的失败原因,例如"成功0条,失败1条。失败原因:物料已被以下业务引用,不可删除:库存余额"
- 覆盖物料、仓库/库区/库位、字典、部门、菜单、通知配置、采购/收货/退货/领料/盘点/上架/工单/成品入库/销售订单/发货等 18 个页面 19 处批量删除入口
- 提示框(`.msg`)最宽 720px 并允许换行,较长的失败原因不再把提示撑出屏幕
**收货/来料检验/上架完成后自动弹出对应单据明细**
- 免检物料:收货审核即入库,原地自动弹出「入库明细 - 收货单号」,不再需要点按钮或跳转
- 需检验物料:审核时只引导「去来料检验」,待来料检验判定完成后自动弹出该收货单的「入库明细」(判定接口不再返回"去库存明细"跳转引导)
- 上架确认:原地自动弹出「库存明细 - 上架单号」;`flow.py` 移除已无引用的 `putaway_confirm` 引导
- 统一走 `StockTxnModal`(新增 `title` 入参),后端以 `auto_detail.source_no` 下发"弹哪张单",前端据此本地弹窗
- 修复「按来源单号查库存流水」只认采购收货单号:上架单/成品入库单/领料单/发货单/盘点单/检验单/退货单此前一律落到 `1=0`,导致上架确认后「去库存明细」恒为空。新增 `source_resolver.resolve_source_ref` 复用 `_MAP` 覆盖全部单据类型,采购收货单原有的"按批次+检验单展开"行为不变
- 修复库存页首屏 URL 筛选不生效:此前只透传 `stock_status`/`receipt_no`,`item_code` 等筛选框显示已填但列表仍是全量;现按筛选配置统一透传
- 测试:新增 `backend/tests/regression_source_no.py`(8 类单据号反查 + 流水条数校验)
**标签标题改为后端菜单表下发(前端)**
- 新增 `GET /auth/page-titles`(`auth_svc.get_page_titles` + `MenuRepository.list_path_titles`),返回"路径 → 页面名称"映射;不按角色过滤,标签仅用于展示,路由可用性仍由各接口权限决定
- 前端移除 `TabBar` 内硬编码的 `PATH_TITLES` 与 `buildTitleMap`;`useAuth` 独立容错获取标题(取不到只影响标签文字,不影响登录态);菜单表未覆盖的路由直接显示路径,便于发现漏配
### 2026-09-22
**生产工单支持无 BOM 直接入库**
- 下达工单不再强制要求已审批 BOM:有 BOM 走原领料链路(须领齐至「待入库」),无 BOM 下达后允许跳过领料直接成品入库;下达接口返回 `has_bom`,无 BOM 时下一步引导指向「去成品入库」
- 成品入库前的工单材料阶段校验统一为 `check_wo_material_stage`(建单/编辑/入库确认三处同口径):有已审批 BOM 的工单仍必须材料领齐,无 BOM 工单(已下达/领料中/待入库)直接放行;入库确认后状态推进放宽到「已下达/领料中」,无 BOM 工单直入后同样自动完工
- 工单列表/详情返回 `has_bom`;无 BOM 工单的业务链路不显示「领料」阶段
- 前端:成品入库可选无 BOM 的「已下达」工单;工单页下达提示与快捷入库按钮按 `has_bom` 区分
- 拆分:卡单自愈 `repair_stuck_receipts` 移至 `receipt_repair.py`(原文件超 150 行上限);前端工单/入库单表单与详情弹窗拆分为独立组件(`WorkOrderFormModal` / `WorkOrderDetailModal` / `ProdReceiptFormModal`)
- 测试:新增 `backend/tests/e2e_no_bom_test.py`(有 BOM 未领料直入被拒 / 无 BOM 直入自动完工 / 红冲平衡);`e2e_production_test.py` 改为动态选取无 BOM 物料并修复状态中文化后的英文状态残留;冒烟脚本支持 `SMOKE_TOKEN` / `SMOKE_USER`+`SMOKE_PASS` 认证
### 2026-09-21
**安全(对应 code-review/review-2026-09-21.md)**
- 权限补齐:26 个仅靠登录态即可调用的写接口全部挂上 `require_permission`(单据编辑 8 个、字典项/库位等主数据 9 个、BOM 创建/审批、部门菜单分配等);新增迁移 `db/50_add_bom_permissions.sql` 补 BOM 权限码
- 数据范围校验:审批/过账/下达/冲销/确认/到货/判定等 14 个写接口补 `check_model_scope` 仓库+部门归属校验(admin 跳过);单据编辑接口同轮接入
- 令牌链路:`/auth/refresh` 加入免鉴权白名单(修复 access token 过期后无法刷新);refresh token 携带版本号,改密后旧刷新令牌一并失效;`must_change_password` 增加服务端闸门(未改密仅放行认证接口)
- CORS 中间件注册顺序修正(此前 401 响应不带跨域头)
**正确性**
- 修复盘点单删除接口 ImportError(模型从错误模块导入,调用必 500)
- 收货/领料单草稿编辑:回填 `need_inspection`(免检物料不再被改成需检)、行成本取批次成本(不再清零)
- 红冲改为按收货量逐行分配冲减额度(此前同批次有盘点盈余时会超冲并错误回退成本)
- 库存首入改为 `INSERT ... ON DUPLICATE KEY UPDATE` 原子 upsert(并发同维度不再撞唯一键 500);流水结存数改为 SQL 回读(不再失真)
- 库存预警通知改用独立会话写入(不再在检验判定/上架确认事务中途 commit 打穿原子性)
**迁移与部署**
- `db/07/09/17` 修复 MySQL 8 断链:`DROP CONSTRAINT IF EXISTS`(MariaDB 方言)改为 information_schema 守卫存储过程;07 引用基线不存在的旧列已改用新列;09 移除仅适用于老库的结构迁移段;17 结构迁移按列存在性守卫执行
- 新增 `db/49_fix_return_status_check.sql`:`ck_ret_status` 允许「已退货」(此前 MySQL 8 下退货过账会被 CHECK 拒绝)
- `bt-deploy.sh` 改增量迁移(`_schema_migrations` 去重),显式跳过 09 重置/cleanup 脚本,失败即停;`aliyun-update.sh` 增加首跑基线检测;口令统一走 `MYSQL_PWD` 环境变量
**前端 / 移动端**
- 修复 HashRouter 下 5 处 `window.location.search` 取参失效(改 `utils/query.ts hashQuery()`),工单页→领料/成品入库/上架的自动预填跳转恢复;`replaceState` 不再抹掉 hash
- 物料列表传 `size: 500`(此前默认 50 条静默截断);请求 headers 显式合并(不再顶掉 Authorization);401 同步清权限缓存
- 移动端请求封装:15s 超时、计数器式 loading、401 单例去重并清 Pinia 内存态
- `vite.config.ts` 去除硬编码 IP(`host: true` + `VITE_API_TARGET` 环境变量)
- 手动端到端测试脚本纳入版本管理(`backend/tests/`,含说明)
**生产领料与本地运行脚本**
- 新建生产领料:工单产品存在已审批 BOM 即显示「BOM版本」选择列表(此前仅多版本时显示,单版本被隐藏,无法确认与切换);BOM 版本号显示修正(库存值已含 "V" 前缀时不再重复显示成 "VV1");未选版本 / BOM 未审批 / 未配置 BOM 三种状态分别给出准确提示
- `IssuePage.tsx` 拆分出 `IssueCreateModal.tsx`(新建/编辑领料弹窗与提交逻辑独立为组件,单文件行数回归 150 行上限内,行为不变)
- `start.ps1` 加固:端口被占用先尝试终止占用进程,杀不掉(如系统服务占用 8000)自动回退备用端口(后端 8001/8002/8010,前端 5175/5176),并通过 `VITE_API_TARGET` 把后端实际端口传给前端代理;后端改绑 `0.0.0.0`(内网 IP 变化不再导致绑定失败);占用进程按进程树终止(`taskkill /T /F`);pip/npm 失败不再中断启动;前后端 TCP 就绪检查通过后才打开浏览器;恢复 UTF-8 BOM(修复 PowerShell 5.1 下中文乱码)
- `stop.ps1` 加固:覆盖全部回退端口、进程树终止;杀不掉的系统服务给出明确提示而非中断
### 2026-08-27
**后端(业务规则与自动流转)**
- 工单状态自动推进:领料过账后自动判定领齐推进工单(按净需求=单耗×计划量,不含损耗率);成品入库后自动判定完工
- 工单入库防重防超:同一工单只允许一张在途成品入库单;累计入库不得超工单计划量;上架待检量查询接口(`GET /inventory/putaways/pending-qty/{pr_id}`)
- 完工数量修复:`db.flush()` 显式落库(autoflush 关闭场景下完工后实际数量不再显示 0)
- 领料 BOM 配额校验(新增 `services/bom_quota.py`):累计领料量(本次+已过账历史+多草稿叠加)不超 单耗×计划量×(1+损耗率),创建/编辑/过账三处共用;同一工单只允许一张在途领料草稿
- 退货单在途草稿唯一性(防多张草稿叠加绕过"单张≤收货量"校验造成超退)
- IQC 待检库存匹配:多批次时取 `qty_on_hand` 最大批次
- 库存查询支持 `item_name` / `lot_no` 精确过滤(供移动端库存流水页使用)
- 盘点明细行增删 API:`POST /inventory/counts/{cid}/lines`、`DELETE /inventory/counts/lines/{id}`(新建盘点单闭环)
**PC 前端**
- 新组件 `StockPicker`:通用库存选择器(搜索 + 批次芯片 + 现存数展示 + 等宽字体),接入领料/成品入库/上架页
- 盘点页重构:明细行增删拆分为 `CountDetailModal` / `CountLineAddPanel` 组件,支持新建盘点单
- 上架页显示待检量
- `vite.config.ts`:dev server 与代理绑定内网 IP(手机可访问 PC 前端)
**基础设施**
- `start.ps1` 修复绑定地址(原写死 127.0.0.1,现自动探测内网 IP),后端/PC 前端均可被手机局域网访问
- `mobile/src/config.ts` 双环境(local / remote=线上服务器)+ `switch-env.ps1` 一键切换(自动重建产物并验证连通);小程序体验版经「开发调试」模式连线上 IP
- App 打包支持:`package.json` 新增 `build:app` / `dev:app`,App 资源编译验证通过(HBuilderX 云打包出 APK;App 无域名校验)
- 新增 `db/cleanup_business_data.sql`:业务数据清理脚本(保留物料/BOM/权限等基础数据)
**小程序**
- 物料类型默认「原材料」(新建不再卡必选);库存页首次打开空数据修复;库存状态过滤按中文状态值匹配后端;采购订单详情补流转记录卡片
- 首页数据明细联动:KPI 卡片点击跳转对应单据列表(修复工单 KPI 误跳盘点),预警行点击按物料编码过滤跳库存 tab
### 2026-08-26
- **移动端工程(新增 `mobile/`,70 文件约 7300 行)**:uni-app + Vue3 + TS,覆盖登录、首页看板、作业页、库存控制台、采购/收货/检验/上架/盘点/工单/领料/成品入库/退货全链路
- **库存控制台重设计**:深蓝 HUD 风格头部 + 药丸分段导航(库存明细 / 出库汇总 / 入库汇总 / 冲销记录四视图)、悬浮吸顶筛选卡、状态胶囊、卡片状态彩条;新增库存流水明细页(按物料+仓库+批次+状态,对齐 PC 端)
- **库存盘点闭环**:盘点从只读升级为可新建盘点单(录入实盘数 + 差异调整)
- **作业页流程化编排**:菜单按业务流程顺序分组(物料管理 6 项 → 生产管理 5 项 → 库存作业 2 项)
- **上架自动跳库存**:上架确认成功后自动跳转库存 tab 并按物料精确过滤(stock-bus 模块级筛选传递)
- **体验统一**:全端替换原生弹窗/选择器为 ThemeModal / OptionSheet(modal-bus 总线),IQC 详情扁平结构修复
## 许可证
私有项目,保留所有权利。