Commit 0b01156f authored by wenwen.tang's avatar wenwen.tang 😕

docs(memory): 加「合同原件」章节;精简 MEMORY.md(17.6KB→13.6KB 并保持口径完整)

parent bcbfeeba
# 2026-10-15 · 房租车位费台账「合同原件」列
## 需求(巴特菲)
> @项目合约/ 房租车位费数据表需要支持上传合同留档并可查阅合同,增加合同原件的列。
## 交付
**新列 AL「合同原件」**(`type=files`,排在 AJ 之后、最右),一格可挂多份
(租赁合同 + 补充协议 + 车位合同)。单元格存紧凑 JSON `[{"id","name"}]`,文件本体独立存放。
**新增 `rentos/contracts.py`**(对照 `attachments.py`,但白名单与上限不同)
- `data/contracts/{id}.{ext}` + `index.json`;id = uuid4 hex;`clean_id` 挡路径穿越 + `_safe_name` 去目录
- 白名单 pdf/doc/docx/xls/xlsx/png/jpg/gif/webp/bmp,单份 **≤ 20MB**(合同扫描件比图片大)
- `save / find / path_of / list_all / remove / parse / dump / describe`
- `parse` 容错:JSON 数组 / 纯 id 串 / 脏项 / 去重;`dump` 空列表 → `""`
- `describe` 给前端补 url / size / at / exists(文件被删也留位置提示)
- `_decode` 兜底规则:data URL 带的类型不认识时**回退按扩展名**猜(否则 octet-stream 会被拒)
**`rentos/config.py`**:加 `CONTRACT_DIR = data/contracts`,进 `ensure_dirs()`
**`rentos/tables.py`**
- `TYPES` 加 `files`;`_c("AL","合同原件","","files",168)` 排在 `AJ` 之后
- `coerce(ctype='files')` → `_files_value()`:list → 紧凑 JSON;`""`/`"[]"` → `None`
- 新增 `files_text()`:导出 xlsx 时该列输出**文件名文本**(多份换行 + `wrap_text`)
- 列定义以代码为准 → **加列不需要数据迁移**(实测线上/本地都无需 migrate)
**`rentos/server.py`**
- `GET /api/contracts`(列表)· `GET /api/contracts/{id}`(内联预览 PDF/图片,`?dl=1` 下载,支持 `?token=`)
· `POST /api/contracts`(上传)· `DELETE /api/contracts/{id}`
- `_CTYPES` 补 doc/docx/xls/xlsx/png/jpg/jpeg;**权限**:合同写接口不匹配任何 `CAP_ELEC` 规则 →
落到 `_write_cap` 兜底 `"*"` = **仅管理员**;访客只读能查阅、不能增删
**前端 `web/app.js` / `admin.html` / `style.css`**
- `filesOf / fileUrl / rowLabelOf / filesValueOf / ensureFileIndex / setFiles / openFilesDlg /
renderFilesDlg / pickContract / removeContract`(`FDLG` 当前行、`FINDEX` 索引缓存,进页重置)
- `td.files` 渲染按钮:有合同 → `📎 N 份`;编辑态空 → `+ 合同`;只读空 → `—`
- 浮层:文件名 / 大小 / 上传时间 + 查阅 / 下载 / 移除,底部「上传合同」;
**增删走 `stageCell(rowId,'AL','files',…)` 暂存**,与台账「保存 N 处改动」同一口径;
只读态浮层无上传/移除,文案改为「需要增删请先勾选编辑模式」
- `startEdit` 对 `files` 列直接 return;`delRow`/`startEdit` 的行标签统一走 `rowLabelOf()`
- `fmtVal` 加 `files` 分支(历史/确认框里显示文件名列表)
**批量导入 `tools/import_contracts.py`**(默认干跑)
- 解析文件名**开头**的单元号串:`1101、1115` / `124.155号` / `132&227车位` / `501-502室` / `601A&702` / `169`
- 每个号按台账实际存在的 `n室` / `n车位` 定夺(两者都在时看「车位/号」线索)
- 干跑结果:**29 个文件 → 35 个单元,零未匹配、零歧义**(详见下方清单要点)
- `--apply` 写入(追加;`--replace` 覆盖)
**`.gitignore`**:加 `data/contracts/`(运行时数据)与 `项目合约/`(93MB + 商业条款,不入库)
## 验证
- 单测 **328 项全绿**:新增 `test_contracts.py` **20 项**(解码/存取/穿越/列值容错/coerce/files_text/
台账写读+导出 xlsx/目录隔离);`test_rules` 3 处列数断言 31→32(含「合同列排最后」)
- API 冒烟 **`tools/smoke_verify_contracts.py` 24 项全过**(经 runner 沙箱):
上传→查阅 200+application/pdf+字节一致→`?dl=1` 是 attachment→写 AL→导出 xlsx 是文件名→
访客上传/删 403 但可查阅→非法类型 400→穿越 404→清空 AL→删文件后 404
- UI 回归 **`tools/ui_verify12.py` 30 项全过**(自带沙箱,真浏览器):
表头「合同原件」在最后一列 / 只读无上传入口 / 编辑态 `+ 合同` → 浮层 → 真实文件选择器上传 →
浮层出现文件与三个操作 → 查阅链接带 token 且返回原字节 → 关浮层格子变 `📎 1 份` 且 pending →
保存后待保存条消失 + AL 落盘 → 只读浮层只有查阅/下载 → 移除 → AL 清空但**服务器文件仍在** → 无 console 错误
- **修掉一个真 bug**:`renderGrid` 的 `files` 分支原先读台账原值 `raw`,
上传(暂存未保存)后格子仍显示「+ 合同」→ 改为优先读 `TB_PEND` 暂存值
## 代码提交
- 待提交:`rentos/contracts.py`(新) `rentos/config.py` `rentos/tables.py` `rentos/server.py`
`web/app.js` `web/admin.html` `web/style.css` `.gitignore`
`tools/test_contracts.py`(新) `tools/smoke_verify_contracts.py`(新) `tools/ui_verify12.py`(新)
`tools/import_contracts.py`(新) `tools/test_rules.py` `tools/ui_verify_runner.py`
`tools/ui_verify7/8/9/10/11.py` `tools/check_scheduler.py`
## 待办
- **29 份合同要不要真导入**(本地 / 线上)——工具已就绪,干跑零误判,等巴特菲发话
- 9 月电费批次错计(10 行 ¥167,947.41 / 少计 601B ¥299.67 / 出租方错记精锐)仍未处置
- `ui_verify11` 里「保存修改」按钮的断言需同步改写(10-14 删了该按钮)
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment