diff --git a/LICENSE b/LICENSE index 4a3554d..d925be3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,14 @@ -MIT License +# ShiErFeng 专有版权声明 -Copyright (c) 2024 insistence +Copyright (c) 2024-2026 Shierfeng International Biotechnology Co., Ltd. +All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +除 [THIRD-PARTY-NOTICES.md](./THIRD-PARTY-NOTICES.md) 或具体文件另有说明的 +第三方代码外,本仓库中的源代码、业务逻辑、界面设计、文档及其他原创内容 +均为施尔丰国际生物科技有限公司的专有内容。 -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +未经版权持有人书面许可,不得复制、修改、分发、再许可或将上述原创内容 +用于其他项目或商业用途。 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +本文件不改变第三方代码所适用的原始许可证。RuoYi 来源代码的版权声明和 +MIT License 请参阅 [THIRD-PARTY-NOTICES.md](./THIRD-PARTY-NOTICES.md)。 diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md new file mode 100644 index 0000000..591a167 --- /dev/null +++ b/THIRD-PARTY-NOTICES.md @@ -0,0 +1,32 @@ +# 第三方软件声明 + +本文件用于说明本项目中涉及的第三方来源代码及其适用许可证。本项目自身的业务代码、界面设计、文档和品牌内容仍以仓库根目录 [LICENSE](./LICENSE) 中的专有版权声明为准。 + +## RuoYi-Vue3-FastAPI + +本项目 Web 前端部分参考并包含来源于 [RuoYi-Vue3-FastAPI](https://github.com/insistence/RuoYi-Vue3-FastAPI) 的代码及其衍生部分。 + +来源项目保留以下版权声明和 MIT 许可证文本: + +```text +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/docs/pallet-workflow-deployment.md b/docs/pallet-workflow-deployment.md index 08153f6..2aff8a0 100644 --- a/docs/pallet-workflow-deployment.md +++ b/docs/pallet-workflow-deployment.md @@ -1,5 +1,65 @@ # 托盘流转业务部署说明 +## 2026-10-08 会议流程补齐(本机开发库已执行) + +本轮源码增加物料日期、原发货标签、收货核对、隧道报表,以及“允许跳过先到托盘但必须填写原因”。用户明确批准开发库迁移和该 FIFO 规则后,已在本机开发库执行下列三份增量补丁并恢复9099后端。其他环境未执行,不能沿用本记录当作已迁移证明。 + +### 本机开发库执行与恢复验证记录 + +- 目标:`.env.dev` 对应 `127.0.0.1:3306/shierfeng-fastapi`,MySQL 8.0.36;43张业务及系统表均为 InnoDB。 +- 已核实并停止本项目 `app.py --env=dev` 服务;迁移前无其他活跃目标库查询、无启用的 MySQL Event。 +- 完整备份:`/private/tmp/shierfeng-dev-migration-20261008-6ce_sy6c/before.sql`,262663字节,文件权限600、父目录700。SHA-256:`c70393e09574f24820e252c9cfb084e9e15d86b185c8a7aa981a8088cd21a15b`。备份包括全部表、触发器、存储过程和事件;口令不进入命令行,临时口令文件已删除。 +- 备份已实际恢复到隔离库 `shierfeng_rehearsal_20261008_6ce_sy6c`;43张表逐行摘要及数量与源库一致。三份补丁先在隔离库试跑通过,然后核对源库未变化、脚本摘要一致,再执行开发库迁移。 +- 开发库执行顺序:`20261008_material_dates_mysql.sql` → `20261008_shipment_receipt_mysql.sql` → `20261008_usage_fifo_mysql.sql`,均成功。没有重跑初始化或旧隧道脚本。 +- 迁移后按迁移前列集比对43张表的全部原数据摘要,结果一致;仅新增收货菜单1119。原库存、包数、编号、批号及历史事件未变;旧确认发货单收货状态为 `LEGACY`,其他为 `PENDING`;新增日期、收货人/时间和 FIFO 历史列均为空。新 CHECK 约束已核对;未自动授予任何普通角色。 +- 后端已按原 Python 环境与 `--env=dev` 恢复,MySQL、Redis连接正常,`/docs` HTTP200;未登录访问收货接口返回业务401。没有修改 `.env.dev`。 +- 实际恢复的隔离 MySQL 库还运行了完整业务服务验收:两托100包、原标签/日期、在途禁止入隧、无现场权限拒绝、确认收货、跳过原因校验、同隧道跨批使用、20包使用/80包原托退回、原标签快照不变、收货幂等不恢复已消耗数量,以及三条独立连接同时重放同键只扣减一次。数据仅新建于隔离库;另有一组新建夹具使用正式scope验证两托并发,排除测试批次总锁掩盖冲突,未向实际开发库灌入测试业务。 +- 真实 MySQL 验收同时发现并修复入隧事务结束后访问过期 ORM 属性导致的 `MissingGreenlet`;ID及返回模型现在在事务结束前取值。验收使用与应用一致的提交后属性过期行为。 + +备份与验收产物位于系统临时目录,可能被系统清理;需长期留存时应转存到受控备份位置,不提交仓库。隔离演练库暂保留用于复核,不接业务服务。恢复到业务库将覆盖迁移后变化,必须另行确认停写及恢复目标;失败时不得直接重跑增量补丁。 + +### 开发库只读预检记录(2026-10-08) + +使用 `.env.dev` 指向的 `127.0.0.1:3306/shierfeng-fastapi`,在只读事务中查询版本、结构元数据和收货菜单冲突,随后回滚并关闭连接;未执行备份、DDL、业务数据写入、角色授权或服务重启。 + +- 实际版本:MySQL 8.0.36;相关业务表均为 InnoDB。 +- `biz_test_batch`、`biz_tunnel`、库存隧道/溯源列和事件隧道/溯源快照列已存在。 +- `biz_filling_record.production_date/expiry_date` 尚不存在。 +- 发货单5个收货字段以及 `biz_pallet_inventory.site_arrived_at` 尚不存在。 +- 父菜单510为预期的 `business:shipment:list` 页面;菜单1119、`business:shipment:receive` 权限及1119的孤立角色授权均不存在。 +- 后端9099端口仍有服务监听。执行迁移前仍须获准停写、完成备份与恢复方案核对;上述检查不代表已进入维护窗口,也不替代脚本内完整前置约束检查。 + +上述为执行前的只读预检历史记录;实际执行结果见上节。不要直接重跑已有补丁或初始化脚本。其他环境必须重新核对现状。 + +### 迁移顺序与风险 + +1. 只读核对目标库、MySQL版本、既有补丁和约束;不要仅凭文件存在判断已迁移。旧库需先具备 `20260922_test_data_batch_mysql.sql` 及 `20261005_tunnel_material_flow_mysql.sql` 的结构,不可重复执行已落库补丁。 +2. 确认维护窗口、停写,完整备份结构、业务数据、菜单及角色关联,并验证恢复方案。 +3. 执行 `sql/patches/20261008_material_dates_mysql.sql`:新增生产日期、有效期及日期关系约束;历史记录保持空值,不按装填时间补造日期。 +4. 执行 `sql/patches/20261008_shipment_receipt_mysql.sql`:新增收货状态、人员、时间和幂等字段,库存实际收货时间,以及 `IN_TRANSIT` 状态和 `SHIPMENT_RECEIVED` 事件。旧已确认发货单标记 `LEGACY`;原库存位置、状态、包数和历史事件不变。 +5. 执行 `sql/patches/20261008_usage_fifo_mysql.sql`,为使用记录和托盘事件新增可空原因及推荐快照列;旧记录保持空值。三份脚本均应在代码启用前完成。 +6. 新权限 `business:shipment:receive` 不自动授予普通角色。先核对菜单 ID 1119 未被其他菜单占用;按职责授权发货页面入口、收货核对按钮及目标现场数据范围。接收方不需要来源仓库权限,发货方原双范围校验不放宽。 +7. 逐项核对表结构、约束、历史保留结果后再发布三端代码。旧数据库直接运行新后端会因缺少列而报错,不能仅重启服务解决。MySQL DDL 隐式提交,出错必须停止;不可依赖 `ROLLBACK` 恢复整个迁移。 + +新建空库可使用已同步的 `sql/shierfeng-fastapi.sql`;禁止将包含重建语句的初始化脚本用于现有业务库升级。 + +### 新流程与历史兼容 + +- 新发货:仓库有料 → 出库在途 → 接收方逐托核对、整单确认 → 现场未使用 → 扫码入隧 → 实际使用 → 原托盘余料退回。未收货不可入隧;未全部核对不能确认,不支持部分收货。 +- 收货不新增托盘号、生产批号或装填记录,不增加/扣减发货快照数量;重复确认采用独立幂等键,不把后续已消耗的余量恢复。 +- 标签从原发货装填快照读取品种、生产批号、日期、数量;可补打,历史日期显示“未登记”。印刷托盘号可作为扫码入口,写操作统一使用返回的规范二维码标识。 +- 混批与隧道溯源保留在后台及报表;普通操作页面不以内部溯源号代替生产批号。 +- 先进先出采用已批准的“允许跳过但必须记录原因”:入隧和实际使用均在后端锁定原业务记录后用当前读复核同现场、同品种的可用候选;其他隧道或旧溯源周期物料不推荐。存在严格更早的实际到货候选时,必须填写不超过500字的原因,原因和推荐托盘/批号/数量/到货时间快照随原事件及使用记录提交。相同到货时间不强排ID;当前历史到货时间未知时不伪造先后,保留人工核对提示。 +- FIFO 原因参与幂等摘要;无原因时保持旧摘要兼容。已成功请求重试直接返回原结果,不因候选变化再次扣减。仅服务端明确返回 `code=500, data=FIFO_SKIP_REASON_REQUIRED` 的已回滚请求可解除本机对应凭据以便补原因;网络结果未知和其他失败继续沿用原payload/key。 +- 候选当前读使用 `FOR UPDATE NOWAIT`,锁冲突不会跳过早到候选或持有现场/隧道锁等待候选锁。仅 MySQL 3572/1213/1205 触发最多3次整事务原键重试,每次先完成回滚;断连等提交结果未知错误不会自动重放。持续繁忙时仍需客户端按原键恢复核对。 +- APP 的 HTTP 非2xx或不完整业务结果不视为成功,入隧、使用及恢复重试均在确认结果后才解除原凭据。PC 成功回调仅清理与该请求完全一致的持久化记录,避免旧页面响应删除新操作;恢复时已确认的 FIFO 必填要求,不会因推荐刷新失败或延迟而丢失。 + +### 运行态验收门禁 + +迁移获批且完成后,用获准测试批次验证:两托各100包 → 标签与收货核对 → 同隧道跨批使用 → 20包使用/80包原托退回 → 原批号不变;同时验证重复点击、断网结果未知后重试、无现场权限、收货前使用、漏扫与错扫均被正确处理。核对四类隧道报表和两端日期展示。 + +本机隔离 MySQL 已验证完整流程、同键并发,以及正式scope两托交错入隧/使用的锁冲突回滚重试。普通角色菜单配置、打印机出纸和东集 CRUISE Gep/D737P_V1.0.5 实机 HID/回车行为仍需现场验收;构建成功不能替代设备验证,未接入设备原生广播 SDK。 + ## 适用范围与当前状态 本文适用于 MySQL 增量补丁: @@ -76,7 +136,7 @@ MySQL DDL 会隐式提交,因此必须将补丁的 DDL、DML 分节执行, 本地环境已使用独立查询参数验证管理员 `/getRouters`:包含 3 个新业务目录和 17 个业务页面,不包含 AI 管理或“施尔丰仓库”。该结果不等于已完成 Redis 清理,也不代表普通角色已完成授权验收。 -## 本轮未验证项 +## 2026-07-27 当时未验证项(历史记录) 以下事项需要在后续真实环境中验证,本轮没有完成: diff --git a/docs/superpowers/plans/2026-10-08-meeting-flow-completion.md b/docs/superpowers/plans/2026-10-08-meeting-flow-completion.md new file mode 100644 index 0000000..810d1d5 --- /dev/null +++ b/docs/superpowers/plans/2026-10-08-meeting-flow-completion.md @@ -0,0 +1,136 @@ +# 会议物料流转缺口补齐 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 完整补齐会议纪要中现有三个业务模块的缺口,并对齐 PC、APP/PDA 与后端。 + +**Architecture:** 复用现有 controller/service/dao/vo/do 分层、库存状态机、作用域与事件流水。报表和 APP 接口纠偏先独立完成;日期、收货与 FIFO 在共享模型和迁移中顺序集成。 + +**Tech Stack:** FastAPI、SQLAlchemy、Pydantic、MySQL、Vue 3、Element Plus、uni-app、Python unittest、Node 20。 + +**Spec:** docs/superpowers/specs/2026-10-08-meeting-flow-completion-design.md + +## Global Constraints + +- 托盘号沿用发货方录入值;生产批号、托盘号和内部溯源号互不替代。 +- 正式数据与测试批次隔离;权限、仓库/现场数据范围、事务锁、幂等和事件流水继续在后端执行。 +- 不安装依赖、不修改运行环境、不修改无关文件、不提交既有暂存内容。 +- 仅准备初始化 SQL 和增量迁移;连接真实数据库执行结构变更、补数或业务写入前另行获得明确确认。 + +## Task 1: 隧道报表与混批明细 + +**Files:** backend module_business/dao/report_dao.py、entity/vo/report_vo.py、service/report_service.py;frontend src/views/report;相关测试。 + +**Interfaces:** 复用现有 tunnel_id、trace_no、batch_no_snapshot;增加 camelCase 隧道筛选和名称字段;保持所有既有报表路径。 + +- [x] 用数据库查询生成或隔离内存数据测试证明:line_id=NULL 的使用记录仍出现,跨测试批次关联不泄漏。 +- [x] 先运行新增测试确认失败,再修改产线可选关联、隧道与溯源筛选/输出、退回快照。 +- [x] PC 报表改为可读隧道编号/名称,提供同隧道/同溯源关系的多批次明细,不混加使用量与退回量。 +- [x] 运行 `python -B -m unittest tests.module_business.test_report_service -q` 及新增专项测试;Node 20 运行 PC 契约测试。 + +## Task 2: APP/PDA 隧道操作 + +**Files:** app src/api/business/usage.js、src/pages/business/usage/index.vue、tests 下专项测试。 + +**Interfaces:** GET /business/usage/tunnel-options?siteId;POST /business/usage/enter-tunnel;POST /business/usage {qrToken,tunnelId,lineId?,usedPackages,idempotencyKey}。 + +- [x] 用真实页面脚本/API封装的行为测试覆盖未入隧不能使用、选隧道、失败重试键不变、切换输入旧响应无效、产线可空。 +- [x] 运行新增测试确认旧实现失败。 +- [x] 对齐后端接口,入隧独立提交;不在前端推算最终库存;内部溯源码不占普通操作主界面。 +- [x] Node 20 运行 APP 契约测试、H5 与 APP 资源构建。 + +## Task 3: 批次日期与完整标签 + +**Files:** operation_do.py、operation_vo.py、filling_service.py、各扫描响应、PC/APP filling 页面、PC shipment 标签、SQL与测试。 + +**Interfaces:** productionDate、expiryDate(YYYY-MM-DD);历史读取可空,新增录入必须完整且有效期不早于生产日期。 + +- [x] 写日期缺失、倒置、合法日期与幂等内容变化的失败测试。 +- [x] 录入并保留原生产批次,日期参与请求摘要;扫描与发货标签读同一装填记录。 +- [x] 实现发货标签,不以操作日期推导历史生产日期。 +- [x] 运行 filling、shipment、模型与PC/APP相关测试。 + +## Task 4: 接收核对闭环 + +**Files:** shipment controller/service/dao/model、state machine、PC/APP shipment 页面、权限 SQL、事件流水与相关测试。 + +**Interfaces:** 接收确认附带原单据与核对托盘集合、独立幂等键;服务端复核目标现场权限和全部物料身份。 + +- [x] 写发货未接收不可入隧、越权接收、漏扫/错扫、重复确认、同键不同内容的失败测试。 +- [x] 增加接收动作与时间/操作人/状态留痕;接收不新增托盘、批号或重复扣减包数。 +- [x] 同步PC/PDA状态显示、按钮权限及扫码核对交互。 +- [x] 准备历史数据兼容迁移和回滚/备份说明,执行前停在确认门。 +- [x] 运行 shipment、usage、return、状态机、权限、测试批次图与清理回归测试。 + +## Task 5: 印刷托盘号解析 + +**Files:** inventory DAO、业务扫描服务/响应、PC/APP扫码消费端与测试。 + +**Interfaces:** 扫描入口接受二维码固定标识或印刷托盘号,返回 canonical qrToken;写命令保持二维码身份一致。 + +- [x] 写二维码输入、托盘号输入、两者歧义、跨范围/跨批次的失败测试。 +- [x] 服务端解析并执行原业务权限检查;客户端提交规范化标识。 +- [x] 运行五类扫码服务与PC/APP组件行为测试。 + +## Task 6: 先到先用与界面简化 + +**Files:** usage service/dao/vo、PC/APP usage、相关测试。 + +**Interfaces:** 排序依据实际接收时间;同现场同品种。用户已批准“允许跳过但必须记录原因”。入隧与使用写命令新增可选 `fifoSkipReason`(去首尾空白,最多500字);服务端在原锁与幂等流程内复核,仅存在严格更早的已知到货候选时要求原因。同时间不按ID强迫先后;历史到货未知时保留人工核对提示。原因及推荐快照随原事件/使用记录同事务保存。 + +- [x] 完成只读推荐边界测试:实际到货顺序、部分使用、同现场同品种、在途/停用/零库存排除、测试隔离、旧溯源周期排除和历史时间未知。 +- [x] 实现推荐提示,保留权限与幂等;界面显示推荐托盘,不暴露长内部码。 +- [x] 用户批准允许跳过但必须记录原因;完成两端输入/恢复、后端入隧及使用复核、原因与推荐快照同事务留痕和增量迁移。 +- [x] 运行 usage、return 与PC/APP行为测试;退回清空本轮到货时间,新收货记录实际时间。 + +## Task 7: 全量覆盖审计与迁移交付 + +- [x] 逐条对照设计,独立复查数据闭环、权限、测试隔离、重试与历史兼容性。 +- [x] 运行业务单元测试、PC与APP契约测试、构建;记录真实命令及结果。 +- [x] 获得“批准开发库迁移,FIFO按推荐规则执行”授权后,完成停写、43表完整备份、隔离恢复演练、三份补丁试跑、开发库迁移与历史数据摘要核对、后端恢复。 +- [x] 更新部署文档,明确实机扫码和HTTP联调尚未覆盖的部分。 + +## 首轮执行记录(迁移授权前,历史) + +- 已确认工作分支为 codex/test-data-batch-cleanup;当前未提交隧道改动是本次基础,不回滚或复制丢失。 +- 基线:后端 report_service + tunnel_flow_contract 共36项通过。 +- 默认 shell Node 是16;现有 Node20 位于 /Users/jiaolongyan/.nvm/versions/node/v20.20.2/bin,后续显式使用,不安装或修改全局环境。 +- 用户已要求直接实施评估中缺口;唯一待业务选择项为FIFO执行强度。代码之外的数据库执行仍需独立确认。 +- 726项后端业务回归通过;新增收货读取/权限/幂等24项包含SQLite与ASGI验证,不等于真实MySQL并发验证。 +- 复查补上退回确认事件的原隧道字段;回归先复现缺失,再验证48项退回服务测试通过。 +- 两端未知请求凭据落盘、离页恢复与可选产线失败降级均已有真实页面行为测试。PC静态使用断言改为执行行为套件,避免旧源码匹配要求与安全重试冲突。 +- 原许可证、登录页面、缓存/日志和既有隧道改动均保留;本轮未提交、暂存、推送、执行迁移、授角色或重启服务。 + +## 首轮集成验证记录(2026-10-08,迁移授权前) + +- 后端:`LOG_FILE_ENABLED=false PYTHONDONTWRITEBYTECODE=1 /Users/jiaolongyan/miniconda3/envs/ShiErFeng/bin/python -B -m unittest discover -s tests/module_business -q`,727项通过。 +- PC、APP分别在项目目录,使用现有Node20执行 `npm run test:business`,分别208项、75项通过;PC套件含两端收货组件行为测试。 +- PC `npm run build:prod -- --outDir /private/tmp/shierfeng-meeting-pc.kB6NUN` 退出0。 +- APP `UNI_OUTPUT_DIR=/private/tmp/shierfeng-meeting-app.oow8jU npm run build:app` 退出0。此为APP资源构建,不是签名APK;未覆盖当前 `dist/dev/app`。 +- 主线程修改涉及的收货/FIFO分层文件、库存DAO、报表DAO、装填/入库/发货/使用/退回服务及物料模型定向Ruff检查通过;限定任务路径 `git diff --check` 通过。 +- 审查发现的旧溯源FIFO候选已修复:已入隧候选必须匹配当前隧道和已验证trace,未知数量统计复用同一过滤;先观察失败,再运行7项FIFO测试通过,并经只读复核关闭问题。 +- 未执行真实MySQL迁移、行锁竞争验收、生产HTTP联调、角色授予、打印机和PDA实机测试。迁移和FIFO执行强度仍待用户明确选择,不能将这些状态写为已完成。 +- 最终前端独立复审无Critical/Important;补验两端×账号/批次切换×恢复/人工解除的8种交错场景通过。人工解除仅清理核对后的本机待确认记录,不发业务写请求,不宣称原操作成功。 + +## 数据库只读预检补充(2026-10-08) + +- 继续轮完成了新证据核对,不是重复报告状态:实际开发目标为 `127.0.0.1:3306/shierfeng-fastapi`,MySQL 8.0.36,已有测试批次和隧道结构。 +- 两个日期列、发货单5个收货列、库存实际收货时间列尚未落库;收货菜单1119和同名权限均未占用,也无1119孤立角色授权。 +- 查询采用只读事务,仅结构元数据和菜单冲突检查,未做备份、DDL、业务写入、授权或重启。后端9099仍有监听,不能在未获准停写时执行补丁。 +- 余下目标仍依赖用户确认两份增量迁移的维护窗口,以及FIFO跳过策略;未将自动目标续跑当作数据库写入授权。 + +## 迁移授权后实施与最终验证(2026-10-08) + +本节是用户明确“批准开发库迁移,FIFO按推荐规则执行”后的当前状态;上文待授权、未迁移等描述保留为历史,不代表仍需重复审批。 + +- 已对本机 `127.0.0.1:3306/shierfeng-fastapi` 完成停写、43表完整备份、隔离库恢复及逐行摘要比对、三份补丁试跑、开发库迁移、原数据摘要核对和9099服务恢复。依次执行日期、收货、FIFO补丁;没有重跑初始化、清空业务表或自动授予普通角色。备份路径及SHA-256见 `docs/pallet-workflow-deployment.md`。 +- FIFO已实现为“允许跳过严格更早到货的可用托盘,但必须填写最多500字原因”。入隧、使用两个写入口强制校验;原因及推荐快照同事务保存,参与幂等摘要;同时间和历史未知时间不伪造顺序。PC、APP均接入。 +- 真实MySQL发现的提交/回滚后ORM过期属性读取已修复;返回值和ID在事务结束前取值。FIFO候选改为 `FOR UPDATE NOWAIT`,只对3572/1213/1205最多三次整事务原键重试,避免反向锁等待且不跳过早到候选。 +- 前端审查发现的3项问题已补回归并修复:APP无业务code的HTTP5xx及不完整结果误清凭据、PC跨页面旧成功删除新待确认记录、PC恢复FIFO拒绝后推荐失败丢失已知必填要求。 +- 主线程在最终代码运行后端 `LOG_FILE_ENABLED=false PYTHONDONTWRITEBYTECODE=1 /Users/jiaolongyan/miniconda3/envs/ShiErFeng/bin/python -B -m unittest discover -s tests/module_business -q`:741项,0失败,其中2项需显式隔离库而跳过。出现一条asyncio慢任务警告,不影响测试结果。 +- 随后在已恢复的本机隔离库,设置 `SHIERFENG_MYSQL_FLOW_TEST_DATABASE=shierfeng_rehearsal_20261008_6ce_sy6c`,使用同一Python、`LOG_FILE_ENABLED=false DB_ECHO=false PYTHONDONTWRITEBYTECODE=1` 执行 `-m unittest discover -s tests/module_business -p test_material_flow_mysql.py -v`:2项均通过。覆盖完整物料流程、四类报表、三连接同键不重复扣减,以及正式scope两托交错入隧/使用的真实锁冲突回滚重试。未写实际开发库业务数据。 +- 两端分别使用 `PATH=/Users/jiaolongyan/.nvm/versions/node/v20.20.2/bin:$PATH npm run test:business`:PC 243/243、APP 144/144通过,无跳过。 +- PC `npm run build:prod -- --outDir /private/tmp/shierfeng-fifo-final-pc.kMm0to` 退出0;APP `UNI_OUTPUT_DIR=/private/tmp/shierfeng-fifo-final-app.BepwcI npm run build:app` 退出0,均使用上述Node20。构建到独立临时目录,未覆盖用户当前 `dist/dev/app`;APP产物为资源包,不是签名APK。 +- 最终FIFO生产文件定向Ruff通过,限定任务路径 `git diff --check` 通过。`GET /docs` 返回HTTP200;未登录收货列表返回业务401,验证运行态及访问保护正常。 +- 独立复审已关闭后端候选锁顺序及部署清单2项、前端F1/F2/F3三项问题,限定范围内无剩余阻断项;复审另行实跑后端11项及3组补充检查、PC 12项、APP 42项定向回归。没有用审查报告替代上述主线程最终全套验证。 +- 普通角色配置、真实登录账号的HTTP写入联调、打印机及东集PDA实机仍未验收;未新增依赖、未改生产环境、未暂存/提交/推送,原有许可证、登录页、日志、缓存等改动均保留。 diff --git a/docs/superpowers/specs/2026-10-08-meeting-flow-completion-design.md b/docs/superpowers/specs/2026-10-08-meeting-flow-completion-design.md new file mode 100644 index 0000000..c33cc32 --- /dev/null +++ b/docs/superpowers/specs/2026-10-08-meeting-flow-completion-design.md @@ -0,0 +1,44 @@ +# 会议物料流转缺口补齐设计 + +## 目标与依据 + +依据 10 月 4 日会议纪要及用户要求“直接修改所缺的相关功能”,在现有基础资料维护、业务管理、报表查询三个一级模块内补齐功能,不重建业务系统。 + +## 不变量 + +- 托盘号沿用发货方录入值;生产批号、托盘号和内部溯源号互不替代。 +- 新装填固定 100 包;使用按实际包数扣减;余料退回不换托盘、不换原物料批次。 +- 正式数据与测试批次隔离;权限、仓库/现场数据范围、事务锁、幂等和事件流水继续在后端执行。 +- 不安装依赖、不修改运行环境、不修改无关文件、不提交既有暂存内容。 +- 仅准备初始化 SQL 和增量迁移;连接真实数据库执行结构变更、补数或业务写入前另行获得明确确认。 +- 在当前 codex/test-data-batch-cleanup 工作区增量修改,保留已有未提交隧道功能,不另建遗漏这些修改的工作树。 + +## 功能设计 + +### 1. 报表与混批追溯 + +消耗报表允许产线为空,以隧道为主要查询维度。库存、消耗、退回、托盘追溯返回隧道编号/名称与内部溯源关系;按同一溯源关系展示多托盘、多批次的使用与退回明细,避免将不同业务阶段的数量重复相加。普通操作页显示业务名称,内部码仅在查询/详情中使用。 + +### 2. APP/PDA + +对齐现有后端 tunnel-options、enter-tunnel 和 usage 接口。先扫码、选择隧道、确认入隧,再提交实际使用量;产线可选。入隧与使用独立幂等键,失败重试保持原键,改动业务输入后更新键。继续支持现有扫描头与手工输入,不新增原生 SDK。 + +### 3. 批次信息与标签 + +装填记录增加 production_date(生产日期)和 expiry_date(有效期至),日期不得倒置。历史记录允许为空并显示“未登记”,不得用装填时间伪造生产日期。PC 与 APP 新录入一致;扫描、单据详情和相关报表读同一记录。发货标签包含原托盘号、客户/现场、品种、批号、生产日期、有效期、包数以及固定二维码。 + +### 4. 发货与接收核对 + +在现有发货模块内增加接收方核对确认,不新建一级菜单。发货确认与接收确认分别留痕,接收按原发货单的托盘和物料快照处理,不能新编托盘/批号;未核对接收的物料不能投入使用。接收权限必须受目标现场范围控制。历史已发货业务通过明确迁移策略保持可用,不重扣库存。 + +### 5. 无二维码录入 + +允许在已有扫码入口输入印刷托盘号,由服务端在当前测试批次与业务数据范围内解析。实际提交使用规范二维码标识,避免同一托盘用两种输入生成不同业务身份。若托盘号与其他二维码标识冲突,明确拒绝歧义,不能随意取第一条。 + +### 6. 先到先用 + +同现场、同品种、可用库存按真实接收时间排序推荐;用户已批准“允许跳过但必须记录原因”。入隧和实际使用均由后端复核,存在严格更早候选时要求最多500字的原因,随事件/使用记录保存推荐快照并参与幂等摘要。到货时间相同或当前历史时间未知时不以托盘ID强排先后,未知时间提示人工核对。保留跨托盘、跨批次混用及余料原托退回。 + +## 验收 + +先写回归测试再实现,覆盖无产线报表、数据隔离、混批追溯、日期边界、二维码/托盘号歧义、收货权限/状态/幂等与 FIFO。PC 和 APP 运行现有契约测试及构建。最终单独列出已验证代码、待执行迁移和待实机验收,不把它们混为完成。 diff --git a/shierfeng-fastapi-app/package.json b/shierfeng-fastapi-app/package.json index c30cf7b..a85634a 100644 --- a/shierfeng-fastapi-app/package.json +++ b/shierfeng-fastapi-app/package.json @@ -3,7 +3,7 @@ "version": "1.9.0", "packageManager": "pnpm@10.28.1", "scripts": { - "test:business": "node --test tests/business-contract.test.mjs", + "test:business": "node --test tests/*.test.mjs", "dev": "uni -p mp-weixin", "build": "npm run build:mp-weixin", "dev:app": "uni -p app", diff --git a/shierfeng-fastapi-app/src/api/business/filling.js b/shierfeng-fastapi-app/src/api/business/filling.js index 22782b0..7d0fd20 100644 --- a/shierfeng-fastapi-app/src/api/business/filling.js +++ b/shierfeng-fastapi-app/src/api/business/filling.js @@ -17,7 +17,7 @@ export function getFillingStrainOptions() { export function scanFillingPallet(qrToken) { return request({ - url: "/business/filling/scan/" + qrToken, + url: "/business/filling/scan/" + encodeURIComponent(qrToken), method: "get", }); } diff --git a/shierfeng-fastapi-app/src/api/business/inbound.js b/shierfeng-fastapi-app/src/api/business/inbound.js index 264d13b..c607753 100644 --- a/shierfeng-fastapi-app/src/api/business/inbound.js +++ b/shierfeng-fastapi-app/src/api/business/inbound.js @@ -17,7 +17,7 @@ export function getInboundWarehouseOptions() { export function scanInboundPallet(qrToken, query) { return request({ - url: "/business/inbound/scan/" + qrToken, + url: "/business/inbound/scan/" + encodeURIComponent(qrToken), method: "get", params: query, }); diff --git a/shierfeng-fastapi-app/src/api/business/return.js b/shierfeng-fastapi-app/src/api/business/return.js index e1f29a3..65a9dac 100644 --- a/shierfeng-fastapi-app/src/api/business/return.js +++ b/shierfeng-fastapi-app/src/api/business/return.js @@ -17,7 +17,7 @@ export function getReturnWarehouseOptions() { export function scanReturnPallet(qrToken, query) { return request({ - url: "/business/return/scan/" + qrToken, + url: "/business/return/scan/" + encodeURIComponent(qrToken), method: "get", params: query, }); diff --git a/shierfeng-fastapi-app/src/api/business/shipment.js b/shierfeng-fastapi-app/src/api/business/shipment.js index c3c9753..0c5a35e 100644 --- a/shierfeng-fastapi-app/src/api/business/shipment.js +++ b/shierfeng-fastapi-app/src/api/business/shipment.js @@ -24,7 +24,7 @@ export function getShipmentSiteOptions() { export function scanShipmentPallet(qrToken, query) { return request({ - url: "/business/shipment/scan/" + qrToken, + url: "/business/shipment/scan/" + encodeURIComponent(qrToken), method: "get", params: query, }); diff --git a/shierfeng-fastapi-app/src/api/business/shipmentReceipt.js b/shierfeng-fastapi-app/src/api/business/shipmentReceipt.js new file mode 100644 index 0000000..23028db --- /dev/null +++ b/shierfeng-fastapi-app/src/api/business/shipmentReceipt.js @@ -0,0 +1,22 @@ +import request from '@/utils/request' + +// 收货仅依赖目标现场范围,使用独立端点,不能复用出库查询。 +export function listShipmentReceipts(query) { + return request({ url: '/business/shipment-receipt/list', method: 'get', params: query }) +} + +export function getShipmentReceipt(shipmentId) { + return request({ url: '/business/shipment-receipt/' + shipmentId, method: 'get' }) +} + +export function scanShipmentReceipt(shipmentId, identifier) { + return request({ + url: '/business/shipment-receipt/' + shipmentId + '/scan/' + encodeURIComponent(identifier), + method: 'get' + }) +} + +export function confirmShipmentReceipt(shipmentId, data) { + return request({ url: '/business/shipment-receipt/' + shipmentId + '/confirm', method: 'put', data }) +} + diff --git a/shierfeng-fastapi-app/src/api/business/usage.js b/shierfeng-fastapi-app/src/api/business/usage.js index 07a25e6..b865c03 100644 --- a/shierfeng-fastapi-app/src/api/business/usage.js +++ b/shierfeng-fastapi-app/src/api/business/usage.js @@ -16,13 +16,37 @@ export function getUsageLineOptions(siteId) { }); } +export function getUsageTunnelOptions(siteId) { + return request({ + url: "/business/usage/tunnel-options", + method: "get", + params: { siteId }, + }); +} + +export function enterUsageTunnel(data) { + return request({ + url: "/business/usage/enter-tunnel", + method: "post", + data, + }); +} + export function scanUsagePallet(qrToken) { return request({ - url: "/business/usage/scan/" + qrToken, + url: "/business/usage/scan/" + encodeURIComponent(qrToken), method: "get", }); } +export function getUsageFifoRecommendation(qrToken, tunnelId) { + return request({ + url: "/business/usage-fifo/recommendation", + method: "get", + params: { qrToken, ...(tunnelId != null ? { tunnelId } : {}) }, + }); +} + export function getUsage(usageId) { return request({ url: "/business/usage/" + usageId, diff --git a/shierfeng-fastapi-app/src/components/business/PalletScanInput.vue b/shierfeng-fastapi-app/src/components/business/PalletScanInput.vue index 031957a..6cb153b 100644 --- a/shierfeng-fastapi-app/src/components/business/PalletScanInput.vue +++ b/shierfeng-fastapi-app/src/components/business/PalletScanInput.vue @@ -9,7 +9,7 @@ maxlength="128" :focus="focusRequested" :disabled="disabled || loading" - placeholder="请扫描或输入托盘二维码后按回车" + placeholder="扫描二维码或输入托盘号后按回车" @confirm="handleSubmit" @input="clearError" /> diff --git a/shierfeng-fastapi-app/src/components/business/ShipmentReceiptPanel.vue b/shierfeng-fastapi-app/src/components/business/ShipmentReceiptPanel.vue new file mode 100644 index 0000000..1229979 --- /dev/null +++ b/shierfeng-fastapi-app/src/components/business/ShipmentReceiptPanel.vue @@ -0,0 +1,302 @@ + + + + diff --git a/shierfeng-fastapi-app/src/config/businessMenu.js b/shierfeng-fastapi-app/src/config/businessMenu.js index 983fdda..55445b2 100644 --- a/shierfeng-fastapi-app/src/config/businessMenu.js +++ b/shierfeng-fastapi-app/src/config/businessMenu.js @@ -22,6 +22,7 @@ export const businessMenuEntries = [ icon: "i-mdi-truck-delivery", iconClass: "bg-orange-50 text-orange-600", permission: "business:shipment:list", + permissions: ["business:shipment:list", "business:shipment:receive"], }, { path: "/pages/business/usage/index", diff --git a/shierfeng-fastapi-app/src/pages/business/filling/index.vue b/shierfeng-fastapi-app/src/pages/business/filling/index.vue index 78adc18..39afea5 100644 --- a/shierfeng-fastapi-app/src/pages/business/filling/index.vue +++ b/shierfeng-fastapi-app/src/pages/business/filling/index.vue @@ -80,6 +80,18 @@ @input="markPayloadChanged" /> + + 生产日期 + + {{ form.productionDate || '选择来源生产日期' }} + + + + 有效期至 + + {{ form.expiryDate || '选择有效期截止日期' }} + + 包装规格 托盘容量 {{ scannedPallet.capacityPackages }} 包,最终容量由服务端校验 @@ -177,8 +188,10 @@ const form = reactive({ warehouseId: null, strainId: null, batchNo: "", + productionDate: "", + expiryDate: "", packageSpec: "", - initialPackages: "", + initialPackages: 100, }); let listGeneration = 0; let scanGeneration = 0; @@ -251,8 +264,10 @@ function resetOperation() { form.warehouseId = null; form.strainId = null; form.batchNo = ""; + form.productionDate = ""; + form.expiryDate = ""; form.packageSpec = ""; - form.initialPackages = ""; + form.initialPackages = 100; scanError.value = ""; formError.value = ""; scanLoading.value = false; @@ -280,7 +295,7 @@ function handleFillingScan(qrToken) { flowState: pallet.flowState, capacityPackages: pallet.capacityPackages, }; - form.qrToken = qrToken; + form.qrToken = pallet.qrToken || qrToken; form.warehouseId = pallet.warehouseId; loadStrainOptions(); }) @@ -313,6 +328,12 @@ function markPayloadChanged() { if (!submitLoading.value) idempotencyKey.value = ""; } +function handleMaterialDateChange(field, event) { + if (operationPending.value) return; + form[field] = event.detail.value; + markPayloadChanged(); +} + function ensureIdempotencyKey() { if (!idempotencyKey.value) idempotencyKey.value = createIdempotencyKey(); return idempotencyKey.value; @@ -329,8 +350,12 @@ function submitFilling() { formError.value = "请完整填写菌种、批次和包装规格"; return; } - if (!Number.isInteger(initialPackages) || initialPackages <= 0) { - formError.value = "初始包数必须为正整数"; + if (!form.productionDate || !form.expiryDate || form.expiryDate < form.productionDate) { + formError.value = "请填写生产日期和有效期,且有效期不能早于生产日期"; + return; + } + if (initialPackages !== 100) { + formError.value = "新装填固定为 100 包"; return; } submitLoading.value = true; @@ -340,6 +365,8 @@ function submitFilling() { warehouseId: form.warehouseId, strainId: form.strainId, batchNo: form.batchNo.trim(), + productionDate: form.productionDate, + expiryDate: form.expiryDate, packageSpec: form.packageSpec.trim(), initialPackages, idempotencyKey: ensureIdempotencyKey(), diff --git a/shierfeng-fastapi-app/src/pages/business/shipment/index.vue b/shierfeng-fastapi-app/src/pages/business/shipment/index.vue index b1d21b6..b3f7242 100644 --- a/shierfeng-fastapi-app/src/pages/business/shipment/index.vue +++ b/shierfeng-fastapi-app/src/pages/business/shipment/index.vue @@ -4,13 +4,13 @@ - 发货出库 + {{ activeTab === 'receipt' ? '收货核对' : '发货出库' }} - 选择来源仓库和目标现场,扫码创建整托发货单 + {{ activeTab === 'receipt' ? '扫描原托盘标签,逐托核对目标现场收货' : '选择来源仓库和目标现场,扫码创建整托发货单' }} + -