Commit c6dc2406 authored by wenwen.tang's avatar wenwen.tang 😕

docs(memory): 记录行粒度核对/账期汇总/楼层总表,以及回归脚本污染真实 data 的事故与闸门

parent 57cf814d
......@@ -485,3 +485,129 @@ env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY git push origin ma
否则 `is_visible` 恒为 False。
3. 单测里 `DELETE /api/batches/NO-SUCH` 才把上面那个 None 崩溃暴露出来 ——
给「应当放行」的用例补了 `assertLess(st, 500)`,防止接口悄悄 500 还判成通过。
---
## 第十一轮:批次重编 + 逐室核对 + 电费全手动 + 3F/6F/7F 总表(10-13 16:0x~17:0x)
### 需求(巴特菲)
1. 电费批次支持「重新编辑」→ 回录入页改完覆盖原批次
2. 批次台账加「核对」→ 逐室 ✔,**确认核对后才能发送**,详情显示核对状态
3. 电费**全部手动发送**(设置里去掉电费自动发送)
4. 新增 3F / 6F / 7F **总表**,与本期电费一并录入(明细类目/上期/本期/用量/电价/系数),
**不发账单、只支持导出**
5. 追加:设置页把**电费配置整行删掉**、**出租方 · 汇款公司**卡片删掉(已迁公司管理),"不要冗余展示"
### 后端
- **新模块 `rentos/masters.py`**:`FLOORS=(3F,6F,7F)`、`ITEMS=公共12.25/套内1.00/空调总12.25`、
`spec()` / `blank_rows()` / `build(submitted, price)` / `totals()`
- 归一:`floor_of` 3/3楼/3层→3F;`label_of` 公摊→公共、空调总表/空调→空调总
- 计费复用 `elec_parser.compute_row`(用量=本期−上期、金额=用量×电价×系数,只填上期→pending)
- ⚠️ **总表不进 `group_by_company` → 不出单**;批次里存 `batch["masters"]`,
`totals` 新增 `master_rows/master_pending/master_amount`(**不并入 document_amount**)
- `service.ingest_rows(..., masters=None)` / `ingest_parts(..., masters=None)` / `reingest(..., masters=None)`
(reingest 传 None = 沿用原值,传数组 = 覆盖,传 [] = 清空)
- `export_batch_xlsx`:有总表时多一个 **「总表」Sheet**(在 明细 与 汇总 之间,带合计行),
汇总里加 `总表行数/总表金额`;没总表仍是 3 个 Sheet
- `store.list_batches` 加 `master_rows / master_amount`
- `server`:`GET /api/masters`(规格+空表+默认电价);`POST /api/batches`、`PUT /api/batches/{id}`
透传 `body.masters`
### 前端
- `admin.html`:录入卡内新增**总表区块**(`#mGrid/#mBody/#mSum/#btn-m-clear`),
楼层 rowspan 合并、类目只读、用量/金额算出来只显示
- `app.js`:`MSPEC/MASTERS`、`loadMasterSpec()`(boot 时拉规格)、`ensureMasters()`、
`clearMasters()`、`calcMaster()`(把 `calcRow` 的算法抽成共用的 `calcLine()`)、
`renderMasterRows()/bindMasterInputs()/updateMasterSum()/masterRowsForSubmit()/fillMastersFromBatch()`
- 重编/核对/提交/取消 都会同步总表;提交后清空总表
- 批次详情加「查看楼层总表」折叠块(`masterTableHtml`);批次台账加「总表×N」标签
- **设置页(本轮追加)**:`renderPlans` 只渲染 `auto !== false` 的账单 → **电费行彻底不出现**;
概览页排期表、侧栏「定时发送 x/y 张账单」同样按 auto 过滤;
保存计划/确认框/文案里电费相关的分支全部删掉(不再提「缴费期限」,改为「下一账单日」);
删掉设置页「出租方 · 汇款公司」卡片(`公司管理` 页的保留)
- ⚠️ **踩坑**:`new Map(arr.map(r => r.key))` 这种写法会抛
`Iterator value xxx is not an entry object`,把整个提交回调打进 catch → 提示变报错、列表不刷新。
必须 `map(r => [key, r])`。
### 验证
- 单测 **293 项全绿**(rules 45 · directory 33 · rent 53 · round5 54 · accounts 60 ·
**round6 29** · **masters 19**)
- `test_round6.py` 修了假后端:造行必须带**公司**(否则 `group_by_company` 丢弃 → 0 单据),
fake_render 的入参形状是 `{units:[{unit,rows,amount}]}` 而不是 `d["rows"]`
- `test_rules` 里 4 个用例按新口径改写(电费 `enabled` 强制 False、`due_plans` 永不含 elec、
排期用例改用房租车位费、总开关关闭时电费是 `manual` 而非 `off`)
- **新增 `tools/ui_verify11.py`(52/0,自带沙箱)**:总表 9 行/合并单元格/单价×系数、
一并录入、总表不出单、导出 4 Sheet、重新编辑(批次号不变+重编×1+总表保留+核对重置)、
未核对发送 409、逐室核对→确认→发送解禁、取消勾选自动撤销、设置页无电费行/无汇款公司
- `ui_verify_runner.py` 跑 **ui_verify2/3/4/10 全通过**;为口径改动改了 ui_verify2(计划表只剩
房租车位费)与 ui_verify4(设置页不再有汇款公司指引)
### 待办
- 本轮改动**未提交、未部署**(线上仍是旧口径:电费会按排期自动发)
- 9 月电费批次错计(10 行 ¥167,947.41 / 少计 601B ¥299.67 / 出租方错记精锐)仍未处置
---
## 第十二轮:核对改「行」粒度 + 部分确认 + 台账按账期汇总(10-13 17:5x~18:3x)
### 需求(巴特菲原话,四点)
1. 电费批次台账明细**按账期(月)分组**,与「按账期月份留档统计」口径一致(10 月一堆批次汇总到一起)
2. 列表里**不展示「重新编辑」「核对」**,统一在**批次详情**里进行
3. 核对时**可选只展示哪些列**,方便查看核对
4. **部分核对**、只核照明或空调都可以;**部分核对也支持点「确认核对」**(原来必须全部勾完)
### 实现要点
- **核对粒度「房间」→「行」**:行键 = `单元|类目`(`302室|照明`)
- `blank_verify()` → `{units:[], rows:[], done:False, at:"", by:"", scope:[]}`
- `_row_keys(batch)`(去重保序、跳空单元、meter 空→「照明」)/ `_checked_rows(batch,v)`(旧 units 按整室展开)
- `verify_summary()` 重写:`{total,verified,missing,missing_units,units,rows,done,complete,ok,partial,at,by}`;
**`ok = done`**(不再要求全勾完),新增 `partial`
- `set_verify()`:done=True 时把勾选快照存 `scope`;之后进度保存若勾选集合少于 scope → **自动撤销确认**
(防改完数据还挂「已核对」);归一后 `v["units"]=[]` 只留 rows
- `set_period_verify()` 去掉「必须全勾」校验
- `store.list_batches` 摘要字段:`verify_total/verify_units` → `verify_rows / verify_rows_done / verify_ok(=done) / verify_partial`
- `period_summary`:行并集;一行算已核对 ⇔ **含它的每张批次都勾了它**(严格口径,已实测复核)
- 前端 `app.js`:
- `verifyRows()/rowKey()/rowChecked()` 取代 `verifyRooms()/unitChecked()`,`EVERIFY` 存行键
- **列显示菜单**:`VCOLS` 12 项 + 每格 `data-col` + `localStorage['rentos_vcols']`,「核对」列不可隐藏
- 三态提示(全部完成 / 部分可确认 / 至少勾一行);`canConfirm = done>0` 才禁用确认按钮
- `confirmVerify` 支持部分确认(标题「确认核对(部分核对)」+ 列出未核对行);未勾任何行拒绝确认
- **台账按账期汇总**:`periodRowHtml(p, colSpan)` 汇总头(账期 / N 批次 / 单元 / 单据 / ¥X | 核对 a/b 行
+ 状态标签 + 「展开批次」);每账期一个 `<tbody class="pgrp collapsed" data-pgrp>` 默认折叠 + `togglePgrp()`
- 批次行去掉 `data-reingest`/`data-verify-period`(收敛到详情),加 `class="prow"`,发送状态列附 `verifyTag`
- `partialVerifyWarn()`:发送前对「部分核对」弹二次危险确认框
- 发送闸门 409 文案改「行」口径;`admin.html` 电费录入卡说明同步更新
### 踩坑(本轮 4 处)
1. **归一化坑(真 bug)**:确认时把派生出的 `units`(整室展开)写回 `verify` → 行级取消勾选被旧
`units` 展开抵消、撤销失效 → 改为 `v["units"]=[]`,只保留 `rows`
2. 回归脚本 `js(fn, rk)` 把字符串当数组参数传,解构出首字符 → 改成不传参的 JS
3. **hash 导航不刷新页面**,`tbody.pgrp` 折叠状态延续 → 回归脚本只点仍 `.collapsed` 的分组
4. `test_rules` 写死「免租 3 段 / 只有 302室」过期(801/802 新增「每年8-9月免租」)→ 改数据驱动对拍
### ⚠️ 数据污染事件与根治(重要)
- **发现**:真实 `data/batches/` 多出 **17 个测试批次**(16:12~17:47)——全部 `302室/303室 · 仅照明 ·
账期 2026年10月`,明细雷同(100→180 ¥96 / 100→140 ¥48);分两拨:16:12~17:04 旧代码 12 个 +
17:44~17:47 新代码 5 个(后 5 个还被点了「确认核对」by="u",带 rows/scope 新结构)
- **根因**:`ui_verify2.py` 等「需连服务」脚本**未走 runner 沙箱**,直连了当时跑在 8899 的真实服务。
受控实验:单经 runner 跑 `ui_verify.py` 增量 0,未复现 → 确认泄漏来自直连
- **处置**:17 个移入 `data/batches/_trash/20261013/`(store 只 listdir 顶层 `*.json`,子目录隐身、
可恢复,附 README);**保留 4 个真实批次**(10-09 三批 + 13:48 的 9 月补单 `134828`,
301/302/708 三户、¥26,163.54、3 份单据)
- **根治**:`ui_verify` 1~6 号脚本加**硬闸门** —— 未设 `RENTOS_BASE` 且未设 `RENTOS_ALLOW_REAL=1`
直接退出并提示沙箱用法;实测直跑 `ui_verify2.py` 被拦(exit 1)。`ui_verify7/10/11` 自带沙箱不受影响
### 验证(全绿)
- 单测 **303 项**:round6 39 · directory 33 · rules 45 · rent 53 · round5 54 · accounts 60 · masters 19
- UI 回归:`ui_verify` 1组/0 · `2` 73/0 · `3` 42/0 · `4` 41/0 · `5` 43/0 · `6` 29/0 · `7` 69/0 ·
`10` 69/0 · **`11` 85/0**(本轮重写:录 2 行让「部分核对」成立、账期分组与默认折叠断言、
列筛选 + localStorage、只勾一行→部分确认→partial=True→详情 Banner→发送闸门放行→发送前二次提醒→
重进保留勾选→全取消后确认撤销)
- `node --check web/app.js` 通过;控制台无 error/pageerror
- **live 冒烟**(重启后 curl):`/api/periods?kind=elec` → 9月 2 批次合并「4/58 行」、8月「10/60 行」;
电费 3 批 + 房租 1 批;`period_summary` 严格口径手算复核一致(606A 两行只在 120146 且已勾 → 计入)
### 收尾
- 预览重启:`start.bat safe` 内层 `start` 开新控制台被环境拦(SIGTERM 无输出)→ 改用**后台任务**直起
`python -m rentos.server --port 8899 --no-scheduler`(PID 57876,`GET /` 200,端口 127.0.0.1:8899)
- **未提交、未部署**:连同第十一轮 masters 总表等存量一起,待巴特菲发话
# 2026-10-14 工作日志(租金通知系统)
## 第十三轮:列显示菜单收不回来(用户报「当前缩不回去,会遮挡页面」)
巴特菲要求直接改、不回测。
### 根因
`web/style.css` 的 `.colpick-menu` 写了 `display: flex`,**类选择器盖掉 UA 的
`[hidden] { display: none }`**(同为 0,1,0 但后加载的应用样式胜出),
所以 `menu.hidden = true` 完全不起作用 —— 菜单一旦生成就永远挂在页面上。
次生问题:`bindVCols()` 每次 `renderEModeBar()` 都被调用(勾选一行就重渲染一次),
里面的 `document.addEventListener('click', …)` 会**无限累积**,且旧闭包持有已被
`innerHTML` 移除的 `menu` 节点,点击时反而会把当前菜单误关。
### 改动
- `web/style.css`
- 加 `.colpick-menu[hidden] { display: none; }`(**核心修复**:凡带 `display` 声明的
组件类,都要显式配 `[hidden]` 规则)
- `left:0` → `right:0`;加 `max-height:60vh; overflow:auto`
- 标题行加 `×` 关闭按钮样式;加 `.colpick-menu.up`(向上弹)
- `web/app.js`
- 新增模块级 `VCOLS_OPEN`(菜单开关状态)—— 模式条重渲染不再丢状态
- 新增 `VCOLS_DOC_BOUND`:document 级「点外部收起」+ Esc 监听**只绑一次**,
且每次点击动态查 `#vcolsMenu`(不再捕获旧节点)
- `setVColsOpen(on)` 统一开关;按钮文案「列显示」↔「收起列显示」;空间不足自动 `up`
- `cancelElecMode()` 与 `enterVerify()` 里调 `setVColsOpen(false)` 复位
### 验证
`node --check web/app.js` 通过;静态资源已由 8899 服务直接生效(前端改动免重启,强刷即可)。
**按用户要求未跑单测 / UI 回归。**
### 教训
- HTML `hidden` 属性**不是** `!important`,任何组件类上的 `display: flex/grid/block`
都会让它失效 —— 用 `hidden` 控制显隐时必须补 `[hidden]` 选择器。
- 会 `innerHTML` 重渲染的绑定函数里,**不要挂 document 级监听**;要么加一次性标志,
要么事件委托到稳定容器。
## 待办
- 本轮改动**未提交、未部署**(线上仍是旧口径)
- 9 月电费批次错计(10 行 ¥167,947.41 / 少计 601B ¥299.67 / 出租方错记精锐)仍待处置
\ No newline at end of file
......@@ -13,7 +13,8 @@
- AJ「下一账单日」**只用于定时排期**(推送日 = AJ − 提前天数);出单口径看 AC;出单后 AC 与 AJ 一起推进
- **AD~AI 房产/房产税六列已删**(`RETIRED_TAX_COLS`,守护 `test_retired_cols_not_used_by_billing`)
- 默认锁定只读;改单元格/增行/删行必须 `confirm=true`(否则 428);备份 `data/tables/.backup/`
- **电费批次** `data/batches/{id}.json`:只留原始数据(details/totals/documents/发送状态),按账期月份留档
- **电费批次** `data/batches/{id}.json`:只留原始数据(details/totals/documents/发送状态),按账期月份留档;
测试残留移入 `data/batches/_trash/`(store 只 listdir 顶层 `*.json`,子目录隐身、可恢复)
## 免租(`rentos/rentfree.py`,2026-10-13 重写为两种类型)
- `首N月免租`:窗口 `[I, I+L月)`,含起始不含结束;0.5 月 = 15 天
......@@ -37,6 +38,23 @@
- 两张账单(`plans.py`):`elec` 触发日 = 缴费期限 − 提前天数;`rent_parking` 触发日 = 台账 **AJ − 提前天数**,
按行判定(`rent_state()`),到点自动出单+发送(`service.auto_issue_rent()`)
## 电费核对(2026-10-13 改「行」粒度)
- 行键 = `单元|类目`(如 `302室|照明`);`verify = {units:[], rows:[], done, at, by, scope}`
(旧 `units` 按整室自动展开,向后兼容;归一后只留 `rows`,否则取消某行会被 units 抵消)
- `verify_summary()["ok"] = done`(**已点确认即可,允许部分核对**);`partial` = done 且有未勾行
- **自动撤销**:确认时把勾选快照存 `scope`;之后进度保存若勾选少于 scope → 撤销确认
- 发送闸门:未确认 → 409;部分核对发送前前端 `partialVerifyWarn()` 二次提醒
- 账期级:`period_summary` 行并集,一行算已核对 ⇔ **含它的每张批次都勾了它**;
`POST /api/periods/verify` 一次写全账期
- 前端:核对模式按行勾选 + 列显示菜单(`localStorage['rentos_vcols']`,「核对」列不可隐藏);
**重新编辑/核对入口只在批次详情**;台账列表按账期汇总、默认折叠(`tbody.pgrp.collapsed`)
- 列显示菜单(`setVColsOpen`):开关状态存模块级 `VCOLS_OPEN`(模式条重渲染不丢),
收起方式 = 再点按钮 / `×` / 点外部 / Esc;空间不足自动 `.up` 向上弹
- ⚠️ **HTML `hidden` 属性不是 `!important`**:组件类上只要写了 `display:flex/grid`,
`hidden=true` 就**完全失效** → 凡用 `hidden` 控制显隐,必须补 `[hidden]{display:none}`
- ⚠️ **会 `innerHTML` 重渲染的绑定函数里不要挂 document 级监听**:每渲染一次堆一个,
旧闭包持有已移除的节点 → 用一次性模块标志(`VCOLS_DOC_BOUND`)或事件委托
## 房租车位费出单
链路:`rent_parser.py` → `rent_render.py`(→ `notice_pdf`)→ `service.ingest_rent` → 批次
- 计费:期间起 = **AC**,止 = 起 + N 个自然月 − 1 天;`房屋租赁费 = P × N`、`物业费 = O × N`;
......@@ -88,6 +106,8 @@
## 启动 / 部署(192.168.68.121 · CentOS 7)
- `start.bat [safe] [端口]` / `stop.bat [端口]`;`notify.enabled=True` 时双击 `start.bat` **会开定时发送**
→ **会话内启动一律 `start.bat safe`**
- ⚠️ 会话里跑 `start.bat safe` 时,其内层 `start` 开新控制台会被环境拦(SIGTERM、无输出)→
改用后台任务直起等价命令:`python -m rentos.server --port 8899 --no-scheduler`(`run_in_background`)
- 云端 `http://192.168.68.121:8899/`(admin / 1989@rent05),容器 `rentos`,root / 12345678
- **主用 Git 同步**(⚠️ **必须用 venv python** `…/envs/default/Scripts/python.exe`,3.13.12 无 paramiko):
`RENTOS_SSH_PASSWORD=12345678 …python tools/deploy_git.py sync`(setup/pull/build/run/status/logs)
......@@ -106,16 +126,19 @@
`* text=auto eol=lf`、`*.bat -text`、**Dockerfile 必须 LF**
## 验证脚本(tools/)
- 单测 **245 项**:`test_rules` 45 · `test_directory` 33 · `test_rent` 53 · `test_round5` 54 · `test_accounts` 60
- 单测 **303 项**:`test_rules` 45 · `test_directory` 33 · `test_rent` 53 · `test_round5` 54 ·
`test_accounts` 60 · `test_round6` 39 · `test_masters` 19
(`test_scheduler.py` 需活跃 8899 服务,无服务时失败属预期)
- UI 回归:**自带沙箱** `ui_verify7/8/9/10.py`;**需连服务** `ui_verify`·`2`·`3`·`4`·`5`·`6.py`
- UI 回归:**自带沙箱** `ui_verify7/8/9/10/11.py`;**需连服务** `ui_verify`·`2`·`3`·`4`·`5`·`6.py`
→ 依次覆盖 台账+电费录入 / 设置+锁定+批量暂存 / 带入+照明+图片 / 公司管理 / 房租出单全链 /
收款开票+导出 / 访客(纯只读 + guest 仅电费)+收款同步 / 账期区间 / 删房产列+免租两类型+批量编辑 /
**访客能力三方对照(guest 69 项)**
访客能力三方对照 / **行粒度核对+部分确认+账期汇总+列筛选(ui_verify11 85 项)**
- ⚠️ `ui_verify7` 的只读样本是脚本自建的 `ro_verify`(无能力)—— guest 已改成「电费通知单操作员」,
不能再当纯只读样本用
- **`tools/ui_verify_runner.py <脚本…>`**:每个脚本起**全新沙箱**(复制 `data/` + 改写 config 常量 +
另起端口 + 注入 `RENTOS_BASE`),真实 `data/` 零改动 —— 跑本次的 3/4/5/6 必用
另起端口 + 注入 `RENTOS_BASE`),真实 `data/` 零改动 —— 跑 1~6 必用
- ⚠️ **1~6 号脚本已加硬闸门**:未设 `RENTOS_BASE` 且未设 `RENTOS_ALLOW_REAL=1` 直接退出
(2026-10-13 直连 8899 曾误写 17 个测试批次进真实 `data/batches/`)
- 端到端 `e2e_round5.py`(PDF 出单 → 收款开票 → 导出 xlsx → 排期 → 删除并回滚)
- 迁移:`migrate_rentfree_type.py`(删 AD~AI + 加 AK + 回填) · `import_park_notes.py`(车位备注→AA) · `migrate_next_bill.py`(AJ=AC)
- **断言不要写死行数/列数/日期** —— 读接口现取;表格**按列 id / 表头文字定位**,不按第 N 列
......
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