# 模块开发进度

> 每次会话结束时更新本文件，下次会话开场先读此文件。
> 状态：✅ 完成 | 🚧 进行中 | ❌ 未开始 | ⚠️ 有问题待处理

## 系统用户

| 模块 | Model | Action | View | 状态 | 备注 |
|------|-------|--------|------|------|------|
| depa（部门） | ✅ | ✅ | ✅ | ✅ 完成 | |
| users（用户） | ✅ | ✅ | ✅ | ✅ 完成 | |
| member（会员） | ✅ | ✅ | ✅ | ✅ 完成 | |

## 权限

| 模块 | Model | Action | View | 状态 | 备注 |
|------|-------|--------|------|------|------|
| site（站点） | ✅ | ✅ | ✅ | ✅ 完成 | |
| role（角色） | ✅ | ✅ | ✅ | ✅ 完成 | |
| permission（权限） | ✅ | ✅ | ✅ | ✅ 完成 | |
| rolePermission（角色与权限） | ✅ | ✅ | ✅ | ✅ 完成 | |
| roleAdmin（角色与系统用户） | ✅ | ✅ | ✅ | ✅ 完成 | |

## 系统

| 模块 | Model | Action | View | 状态 | 备注 |
|------|-------|--------|------|------|------|
| attrType（属性类型） | ✅ | ✅ | ✅ | ✅ 完成 | |
| attrValue（属性值） | ✅ | ✅ | ✅ | ✅ 完成 | |
| gridLayout（表格布局） | ✅ | ✅ | — | ✅ 完成 | 无独立视图 |
| langTool（语言包工具） | — | ✅ | ✅ | ✅ 完成 | 无 Model |

## 数据清单

| 模块 | Model | Action | View | 状态 | 备注 |
|------|-------|--------|------|------|------|
| washSheet（数据单） | ✅ | ✅ | ✅ | ⚠️ 待建表 | 表 `wash_sheet` 未创建，DDL 见 `sql/wash_sheet.sql`，需人工审核后执行；列表点标题打开单张单的明细标签 |
| washSheet/detail（单张单的明细） | — | ✅ | ✅ | ⚠️ 待建表 | 路由 `/washSheet/detail/k/sheetId/{单号}`，数据接口复用 washSheetItem（带 `sheetId` 限定范围）；带「导入数据单」粘贴导入 |
| washSheetItem（数据单明细） | ✅ | ✅ | ✅ | ⚠️ 待建表 | 表 `wash_sheet_item` 未创建，DDL 见 `sql/wash_sheet_item.sql`；明细增删改会同步刷新数据单的更新时间 |

导入规则（`washSheetItem/import`）：一行一条，`厂商:号码` 为格式2，其余按格式1 当作 OE 号；
厂商必须在「号码厂商」里已登记（`OE` 首次导入时自动补一条），未登记的整行跳过并回报；
按「厂商+号码」在本单内去重，已存在的不重复新增。

数据清洗（`washSheetItem/clean`，逻辑在 `model/Wash.php`）：
两个明细页都有「清洗数据」按钮（多选 + 勾选框常驻），弹窗选清洗方式（仅处理未清洗部分 / 重新清洗），
前端按选中行**逐条**调后端并走进度条——因为 `tcd/checkNumberData` 传多个号码时结果是合并返回的，拆不回单条。
网络类错误（status 0 / 5xx）隔 1 秒重试一次，业务错误不重试；关掉进度弹窗即停止后续行。
链路：TCD = `tcd/cleanData`；EPC = `tcd/checkNumberData` 取 etk_id/pro_id/grp2_id → `etk/cleanData`。
结果只存摘要（`wash_sheet_item_tcd` / `wash_sheet_item_epc`），因为 `tcd/cleanData` 单条响应约 60KB，整包落库会拖垮 grid。

结果列展示（`washClean.js` 的 `washCleanColumns()`）：
- **TCD 列**取 `checkNumberData` 的 `tcd.ga_chk` —— 分类名称 + `[N个]` 号码数，点名称展开该分类下的「厂商: 号码」，
  每行一个、单选（同一行内跨分类只能选一个）。名称中英文都存（`name_cn` / `name_en`），前端按 `APP.LOCALE` 选。
  接口每个分类只回前 10 条明细，`count` 大于明细数时列尾会提示。
- **EPC 列**：零件组/零件名 + OE 号（单选）+ 替换号列表。`renum` 由接口按新→旧排序，**`renum[0]` 打「新」标记**；
  `isnew` 是另一回事（=1 表示被查号码本身就是最新号，实测查 5Q0199868J 得 1、查其旧号 5Q0199868F 得 -1），一并存着备用。
- 号码链接：TCD 每个号码用各自的 `nums[].pro_url`；EPC 的 OE 号用 `epc.pro_url`（取自 checkNumberData 的 etk 产品，etk/cleanData 不带链接）。
  只有号码是链接、厂商不是，新标签打开，href 只接受 http/https。替换号接口没给链接，保持纯文字。
  **2026-09-14 之前清洗过的数据没有 pro_url，需要用「重新清洗」刷一遍才出链接。**
- 两个单选目前**只是界面操作**，不落库不提交，刷新/翻页会丢，等后续功能接上再持久化。

`wash_sheet_item_format` 是号码的格式化值（`formatNum()` 生成：全角转半角 → 只留字母数字 → 转大写），
后台自动维护、界面不显示：单条新增/修改走 `WashSheetItem::applyNumberFormat()`，批量导入在组行时直接算好；
前端提交的该字段一律丢弃（`stripReadOnly()`）。本次没改号码时不动这个字段，避免把已有值刷空。

> 注意：`model/Wash.php` / `admin/action/wash.php` 是对接 ypAPI 的洗车数据客户端，跟本组模块无关，不要混用。

## 零件关联属性

| 模块 | Model | Action | View | 状态 | 备注 |
|------|-------|--------|------|------|------|
| numberVendor（号码厂商） | ✅ | ✅ | ✅ | ⚠️ 待建表 | 表 `attr_factory` 未创建，DDL 见 `sql/attr_factory.sql`，需人工审核后执行 |

## 框架层待处理

| 问题 | 文件 | 状态 |
|------|------|------|
| ~~PHP 版本兼容性（构造函数提升/联合类型）~~ | `mvc/v2/Model.php` | ✅ 作废 |

> 上面这条基于"生产 PHP < 8.0"的错误前提，已作废。生产环境确认为 PHP 8+，
> 项目按 PHP 8.1 编写，口径见根契约的「PHP 版本约束」。

## 最近会话记录

- 2026-08-31：修复 Model.php PHP 版本兼容问题；规划会话分层与契约体系
- 2026-09-07：新增「零件关联属性」菜单组（图标 fa-puzzle-piece）与 numberVendor（号码厂商）模块：Model/Action/View/菜单/语言包已完成，表 `attr_factory`（零件关联属性统一用 attr_ 前缀）建表 SQL 待人工执行
- 2026-09-07：项目分析（只读）；确认生产为 PHP 8+，改正三份契约的 PHP 版本约束（根契约 / 框架契约 / AGENTS.md），下限定为 8.1
- 2026-09-07：新增「数据单」菜单组（图标 fa-file-text-o）与 washSheet（数据单）/ washSheetItem（数据单明细）两个模块：Model/Action/View/菜单/语言包已完成；DB.md 补充 `wash_` 前缀与两表的主键、FK 命名；表 `wash_sheet` / `wash_sheet_item` 建表 SQL 待人工执行
- 2026-09-07：数据单列表点标题打开单张单的明细标签（`washSheet/detail`）+ 粘贴导入号码；`dxGrid.js` 增加 `params`（固定请求参数，主从页面限定范围用）和 `toolbar.custom`（业务自定义按钮）两个通用能力；`adminLayout.js` 的标签标题改用 `.text()` 写入（标题来自业务数据，不能拼 HTML）
- 2026-09-07：框架层 `mvcFunction.php` 增加号码去格式公共函数 `formatNum($string, $keep = "")`（全角转半角 → 只保留字母数字和 `$keep` 里的额外字符 → 转大写）及其依赖的 `getSemiangle()`（全角转半角，走 mbstring）；暂无业务调用点，供后续模块使用
- 2026-09-08：修固定列行高错位——清洗结果展开号码后，勾选框/操作列那张表不跟着变高。`dxGrid.js` 增加 `dxSyncRowHeights()`（先比高度，错位才调 DevExtreme 的 `updateDimensions()`），并在 `createDxGrid()` 里按点击自动补一次；单元格模板不用管这件事。没用 ResizeObserver/rAF，因为后台标签页 iframe 是 `display:none`，不渲染就不派发回调。新增 `contracts/local.md`（本机地址/PHP 路径，每人一份不提交），根契约加「第三方代码不可改」一节
- 2026-09-07：数据清洗上线。`model/Wash.php` 重写为 ypAPI 清洗客户端（cleanTcd / cleanEpc / checkNumber + 摘要提取），`washSheetItem/clean` 单条清洗端点，`washClean.js` 共用清洗弹窗与进度条，明细两页新增 TCD / EPC 结果列并开多选。**跨层改动**：`mvc/v2/lib/HttpClient.php` 增加 `postForm()`（表单编码 POST）——ypAPI 的 `tcd/*` POST 接口只读 `$_POST`，实测 GET 返回空、JSON body 报「token不存在」，业务层无法绕开。`langTool` 的 SCAN_DIRS 补上 `admin/action`（原先 action 里的 lg() 文案扫不到，会被当成废弃 key 删掉）
- 2026-09-07：`wash_sheet_item` 增加 `wash_sheet_item_format` 字段（号码格式化值，`formatNum()` 生成，不在界面显示），新增/修改/批量导入三条写入路径都同步维护，`formatNum()` 有了第一个业务调用点
