Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
R
rentos
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
accounting
rentos
Commits
066f0d99
Commit
066f0d99
authored
Oct 15, 2026
by
wenwen.tang
😕
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
docs(memory): 用户交互流程图交付;MEMORY.md 二次精简(16 章节口径未丢)
parent
2dfe5556
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
118 additions
and
121 deletions
+118
-121
2026-10-15.md
.workbuddy/memory/2026-10-15.md
+10
-0
MEMORY.md
.workbuddy/memory/MEMORY.md
+108
-121
No files found.
.workbuddy/memory/2026-10-15.md
View file @
066f0d99
...
...
@@ -154,6 +154,16 @@
-
`output/合同-台账核对报告.md`
(可入库)
-
`output/合同-台账核对明细.csv`
(35 行 × 17 列,output/
*
.csv 被忽略)
## 用户交互流程图(16:10)
-
需求:生成用户交互流程图,展示业务流程
-
产出
`output/rentos-用户交互流程图.html`
:
**自包含无外链**
,含 4 张 SVG ——
① 全局交互地图(登录 → 概览 → 三条业务线)② 电费链路(录入→生成→逐行核对→确认存盘→发送→流水)
③ 房租车位费链路(台账/合同 → 账期预览 → 出单 → 收款发送)④ 自动排期(AJ − 提前天数 → 自动出单发送 → AC/AJ 推进)
外加角色权限对照表 + 贯穿性交互约定表;带
`@media print`
可直接打印
-
图按
**真实**
`PAGES`
5 项 +
`admin.html`
按钮清单绘制(不凭印象);会话内另用内联图逐张展示
-
⚠️ 配色用固定浅色底 + 深字(不依赖主题变量),保证明暗主题下都清晰
-
顺带:MEMORY.md 二次精简(18.9KB → 17.7KB / 11143 字符),16 个章节口径未丢
## 待办
-
上面 17 个单元的修正要不要动手(涉及台账写操作,需
`confirm=true`
)
-
4 张本机残留批次是否移入
`_trash/`
...
...
.workbuddy/memory/MEMORY.md
View file @
066f0d99
#
租金通知系统 · 项目
长期记忆
#
RentOS 租金通知系统 ·
长期记忆
## 目标 & 命名
三份 Excel → 固定模板 + 数据填充 →
**PDF 收费通知书**
,自动发企微 + Web 维护。
-
文件名 =
`{单元列表}-{费用类型}-收费通知书.pdf`
;单元按
`B.unit_sort_key`
排序、
`、`
连接;
**不含公司名**
-
费用类型词序固定:房租 → 物业费 → 车位费
三份 Excel → 固定模板填充 →
**PDF 收费通知书**
(reportlab 程序化绘制),发企微 + Web 维护。
文件名
`{单元列表}-{费用类型}-收费通知书.pdf`
:单元按
`B.unit_sort_key`
排序、
`、`
连接、
**不含公司名**
;
费用类型词序固定 房租 → 物业费 → 车位费。
## 数据模型
-
**唯一台账**
`rent_parking`
(85 行 ×
**32 列**
)
-
`BILL_COLS`
:owner=A unit=C tenant=F begin=I end=J rentfree=L
**free_type=AK**
cycle=N fee=O rent=P
last_paid=AB bill_start=AC next_bill=AJ;另
**AL「合同原件」**
(type=
`files`
,排最右)
-
**AK「免租类型」**
(显示在 L 之后):
`首N月免租`
/
`每年8-9月免租`
,空=无免租
-
**唯一台账**
`rent_parking`
(85 行 ×
**32 列**
)。关键列:
owner=A unit=C tenant=F begin=I end=J rentfree=L
**free_type=AK**
cycle=N fee=O rent=P;
last_paid=AB bill_start=AC next_bill=AJ;
**AL「合同原件」**
(type=
`files`
,最右)
-
AJ
**只用于定时排期**
(推送日 = AJ − 提前天数);出单口径看 AC;出单后 AC/AJ 一起推进
-
**AD~AI 房产/房产税六列已删**
(
`RETIRED_TAX_COLS`
,有守护测试);列定义
**以代码为准**
(
`load_table`
用 spec 覆盖存档列)→
**加列不需要数据迁移**
-
默认锁定只读;改单元格/增行/删行必须
`confirm=true`
(否则 428);备份
`data/tables/.backup/`
-
**批次**
`data/batches/{id}.json`
(= 点一次「生成通知书」的产物,id=
`YYYYMMDD-HHMMSS`
);
测试残留移入
`data/batches/_trash/YYYYMMDD/`
(store 只 listdir 顶层
`*.json`
,子目录隐身可恢复)
-
**账期口径**
:
`period_label`
只是批次上的
**文本字段**
,
`list_periods()`
按它归组 = 页面「账期汇总」
-
来源:① 录入页「所属期间」(默认今天年月;带入上期时 +1 月)②
`elec_parser._extract_period`
③ 后端兜底用
**通知日期**
年月
-
⚠️
**自由文本、无归一化**
:
`strip()`
后精确比较 →「2026-11」与「2026年11月」会分成两组
-
同账期多张批次正常(分批录 / 按公司分批 / 补录)→ 台账按账期汇总并默认折叠
-
⚠️ 列定义
**以代码为准**
(
`load_table`
用 spec 覆盖存档列)→
**加列不需要数据迁移**
-
默认只读;改格/增行/删行须
`confirm=true`
(否则 428);备份
`data/tables/.backup/`
-
**批次**
`data/batches/{id}.json`
(点一次「生成通知书」的产物,id=
`YYYYMMDD-HHMMSS`
);
测试残留移
`data/batches/_trash/YYYYMMDD/`
(store 只 listdir 顶层
`*.json`
,子目录隐身可恢复)
-
**账期**
:
`period_label`
只是批次上的
**文本字段**
,
`list_periods()`
按它归组 = 页面「账期汇总」。
来源 ① 录入页「所属期间」(默认今天年月,带入上期 +1 月)②
`elec_parser._extract_period`
③ 后端兜底用通知日期年月。⚠️
**自由文本无归一化**
:
`strip()`
精确比较 →「2026-11」≠「2026年11月」
-
同账期多张批次正常(分批/按公司/补录)→ 台账按账期汇总并默认折叠
-
8F/11F 付款周期统一
**3 个月**
、
**不收物业费**
(O 列留空)
## 免租(`rentos/rentfree.py`)
-
`首N月免租`
:
窗口
`[I, I+L月)`
,含起始不含结束;0.5 月 = 15 天
-
`每年8-9月免租`
:合约期
`[I, J]`
内每
个自然
年
`[max(该年08-01,I), min(该年10-01, J+1天))`
(跨年裁剪)
-
`首N月免租`
:
`[I, I+L月)`
,含起始不含结束;0.5 月 = 15 天
-
`每年8-9月免租`
:合约期
`[I, J]`
内每年
`[max(该年08-01,I), min(该年10-01, J+1天))`
(跨年裁剪)
-
`hit()`
按时点;
`overlaps()`
按账单期间交集(「每年8-9月」必需)
-
⚠️
`windows()`
返回键是
**`begin`/`end`**
(非 from/to)
,另有
`type`
/
`months`
/
`year`
/
`key`
-
⚠️
`windows()`
返回键是
**`begin`/`end`**
(非 from/to)
;AK「免租类型」空 = 无免租
## 名册(`data/config.json`,唯一入口 `rentos/directory.py`)
-
`companies`
租客(名称即唯一键,
`kind`
=company|person);
`owners`
出租方(
**键=简称**
)
-
当前 4 家:精锐 37 / 科博 24 / 瑞格嘉德 16 / 国隆 8
-
`companies`
租客(名称即唯一键,
`kind`
=company|person);
`owners`
出租方(
键=简称
)
——
当前 4 家:精锐 37 / 科博 24 / 瑞格嘉德 16 / 国隆 8
-
`None`
/缺失 = 未维护过 → 回落
`DEFAULT_OWNERS`
;
**写过(哪怕 `{}`)= 权威**
-
改名
**联动台账**
(F/A 列);删除有引用 → 409,
`force=true`
才真删
-
归属三级优先:
**行内 `owner` > 台账 A 列 > 名单兜底(仅当名单恰好一家)**
-
⚠️ 写入必须
`store.set_config()`
(顶层整体替换);
`patch_config`
浅合并
**删不掉字典键**
## 业务口径
-
电费:
`用量 = 本期 − 上期`
;
`空调 = 用量 × 电价 × 12.25`
、
**`照明` = 用量 × 电价**
-
只填上期不填本期 →
`pending`
不出单;
**车位不抄电表**
(
`P.is_park`
,候选/带入/入库三处都要挡)
-
8F/11F
**付款周期统一 3 个月**
、
**不收物业费**
(O 列留空)
-
两张账单(
`plans.py`
):
`elec`
触发日 = 缴费期限 − 提前天数;
`rent_parking`
=
**AJ − 提前天数**
,
-
电费:
`用量 = 本期 − 上期`
;
`空调 = 用量 × 电价 × 12.25`
、
`照明 = 用量 × 电价`
;
只填上期不填本期 →
`pending`
不出单;
**车位不抄电表**
(
`P.is_park`
,候选/带入/入库三处都要挡)
-
`plans.py`
两张账单:
`elec`
触发日 = 缴费期限 − 提前天数;
`rent_parking`
=
**AJ − 提前天数**
,
按行判定(
`rent_state()`
),到点自动出单+发送(
`service.auto_issue_rent()`
)
## 电费核对(行粒度)
-
行键 =
`单元|类目`
(如
`302室|照明`
);
`verify = {units:[], rows:[]
, done, at, by, scope}`
-
`
verify_summary()["ok"]
= done`
(
**确认即可,允许部分核对**
);
`partial`
= done 且有未勾行
-
行键 =
`单元|类目`
;
`verify = {units, rows
, done, at, by, scope}`
-
`
ok
= done`
(
**确认即可,允许部分核对**
);
`partial`
= done 且有未勾行
-
**自动撤销**
:确认时把勾选快照存
`scope`
;之后勾选少于 scope → 撤销确认
-
发送闸门:未确认 → 409;部分核对发送前
前端
`partialVerifyWarn()`
二次提醒
-
发送闸门:未确认 → 409;部分核对发送前
`partialVerifyWarn()`
二次提醒
-
账期级:一行算已核对 ⇔
**含它的每张批次都勾了它**
;
`POST /api/periods/verify`
一次写全账期
-
核对表
**可直接编辑**
,
**改数据不影响核对状态**
;
`confirmVerify()`
两步:
①
`saveVerifyData()`
按
`_bid`
分组逐张
`PUT /api/batches/{id}`
(有改动才发)
②
`POST /api/periods/verify {rows, done:true}`
→ 要不要重核
**由用户勾选决定**
,服务端不猜
-
`confirmVerify()`
两步:①
`saveVerifyData()`
按
`_bid`
分组逐张
`PUT /api/batches/{id}`
(有改动才发)
②
`POST /api/periods/verify {rows, done:true}`
—— 要不要重核
**由用户勾选决定**
,服务端不猜
-
已删
`keep_verify`
那套;
`reingest`
=
**一律重置核对**
-
脏基线:
`ELEC_CMP_FIELDS`
+
`ESNAP`
/
`takeVerifySnapshot()`
/
`dirtyRows()`
/
`editSummary()`
-
⚠️ 基线键用稳定
`_rid`
,
**不能用数组下标**
;⚠️ 改动提示只改文字、绝不重渲染模式条
(否则输入框失焦)
-
⚠️ 脏基线(
`ELEC_CMP_FIELDS`
/
`ESNAP`
/
`takeVerifySnapshot`
/
`dirtyRows`
/
`editSummary`
)用稳定
`_rid`
,
**不能用数组下标**
;⚠️ 改动提示只改文字、
**绝不重渲染模式条**
(否则输入框失焦)
-
⚠️
`PUT`
是
**全量覆盖**
:每组必须带该批次
**全部行**
;退出核对有未保存改动要
**明确警告会丢**
-
前端:核对模式按行勾选 + 列显示菜单(
`localStorage['rentos_vcols']`
);
**重新编辑/核对入口只在批次详情**
-
列显示菜单:开关状态存模块级
`VCOLS_OPEN`
;收起 = 再点按钮 /
`×`
/ 点外部 / Esc;空间不足自动
`.up`
-
⚠️
**HTML `hidden` 不是 `!important`**
:类里写了
`display:flex/grid`
就完全失效 → 必须补
`[hidden]{display:none}`
-
核对表
**可直接编辑**
,
**改数据不影响核对状态**
;
**重新编辑/核对入口只在批次详情**
-
⚠️
**HTML `hidden` 不是 `!important`**
:类里写了
`display:flex/grid`
就失效 → 必须补
`[hidden]{display:none}`
-
⚠️
**会 `innerHTML` 重渲染的绑定函数里别挂 document 级监听**
(用一次性模块标志或事件委托)
-
列显示菜单:
`localStorage['rentos_vcols']`
,开关存模块级
`VCOLS_OPEN`
;
收起 = 再点按钮 /
`×`
/ 点外部 / Esc;空间不足自动
`.up`
## 电费录入「临时保存」草稿
-
「临时保存」把明细整份落盘
`data/drafts/elec__{账号}.json`
(
**按账号隔离**
);下次进页
**自动恢复**
;
生成通知书 / 清空 / 手动丢弃时删除。
-
接口
`GET|PUT|DELETE /api/drafts/elec`
(
`_write_cap`
记
`CAP_ELEC`
,访客可用)
-
前端
`saveElecDraft/clearElecDraft/restoreElecDraft/elecBootFill`
;
`applyNav`
改调
`elecBootFill()`
-
明细整份落盘
`data/drafts/elec__{账号}.json`
(
**按账号隔离**
);下次进页
**自动恢复**
;
生成通知书 / 清空 / 手动丢弃时删除;接口
`GET|PUT|DELETE /api/drafts/elec`
(
`CAP_ELEC`
,访客可用)
-
前端
`saveElecDraft/clearElecDraft/restoreElecDraft/elecBootFill`
;
`applyNav`
调
`elecBootFill()`
——
**有草稿就不「带入上期」**
## 合同原件(台账 AL 列
,2026-10-15
)
一格可挂多份(租赁合同 + 补充协议 + 车位合同)。
-
单元格值 = 紧凑 JSON
`[{"id","name"}]`
;本体在
`data/contracts/{id}.{ext}`
+
`index.json`
-
`rentos/contracts.py`
:save/find/path_of/list_all/remove/parse/dump/describe;
白名单
pdf/doc/docx/xls/xlsx/png/jpg/gif/webp/bmp,单份
**≤ 40MB**
;id=uuid4 hex,
`clean_id`
挡路径穿越
## 合同原件(台账 AL 列)
一格可挂多份(租赁合同 + 补充协议 + 车位合同)。
单元格值 = 紧凑 JSON
`[{"id","name"}]`
;
本体
`data/contracts/{id}.{ext}`
+
`index.json`
。
-
`rentos/contracts.py`
:save/find/path_of/list_all/remove/parse/dump/describe;
白名单
pdf/doc/docx/xls/xlsx/png/jpg/gif/webp/bmp,单份
**≤ 40MB**
;id=uuid4 hex,
`clean_id`
挡路径穿越
-
⚠️ 40MB 不是随手定的:扫描件能到 21.8MB,而 40MB base64 后 53MB 仍 < server
`MAX_BODY=64MB`
-
接口
:
`POST|GET /api/contracts`
·
`GET /api/contracts/{id}`
(内联预览;
`?dl=1`
;支持
`?token=`
)·
`DELETE
/api/contracts/{id}
`
——
**增删仅管理员**
(访客可查阅、不能增删)
-
前端
:
`td.files`
按钮(
`📎 N 份`
/ 编辑态
`+ 合同`
)→
`openFilesDlg()`
;
增删走
`stageCell(rowId,'AL','files',…)`
,
**与「保存 N 处改动」同一口径**
-
接口
`POST|GET /api/contracts`
·
`GET /api/contracts/{id}`
(内联预览;
`?dl=1`
;支持
`?token=`
)·
`DELETE`
——
**增删仅管理员**
(访客可查阅、不能增删)
-
前端
`td.files`
按钮(
`📎 N 份`
/ 编辑态
`+ 合同`
)→
`openFilesDlg()`
;增删走
`stageCell(rowId,'AL','files',…)`
(
**与「保存 N 处改动」同一口径**
)
-
⚠️
**从某行移除只解关联、不删文件**
(多行可共享同一份)
-
⚠️
`renderGrid`
渲染
`files`
列
**必须读 `TB_PEND` 暂存值**
,否则上传后格子仍显示旧按钮
-
导出 xlsx 该列输出
**文件名文本**
(
`tables.files_text`
),不是 JSON
-
导入
:
`tools/import_contracts.py`
(默认干跑,
`--apply`
写入,
`--replace`
覆盖);
解析文件名
**开头**
的单元号串(
`1101、1115`
/
`124.155号`
/
`132&227车位`
/
`501-502室`
/
`601A&702`
)
-
⚠️
**同一份文件只落盘一次**
(按文件名缓存 meta),多单元挂同一个
id —— 否则 29 份膨胀成 35 份
-
**线上已导入**
:29 份 →
**35 个单元**
,落盘
**29 份 / 92.3MB**
、无失效关联;
6 组共用:
307/603 · 601A/702 · 132/227车位 · 124/155车位 · 1101/1115 · 501/502
-
导入
`tools/import_contracts.py`
(默认干跑,
`--apply`
写入,
`--replace`
覆盖):解析文件名
**开头**
的单元号串(
`1101、1115`
/
`124.155号`
/
`132&227车位`
/
`501-502室`
/
`601A&702`
);
⚠️
**同一份文件只落盘一次**
(按文件名缓存 meta),多单元挂同一
id —— 否则 29 份膨胀成 35 份
-
**线上已导入**
:29 份 →
**35 个单元**
,落盘
**29 份 / 92.3MB**
、无失效关联;
6 组共用:
307/603 · 601A/702 · 132/227车位 · 124/155车位 · 1101/1115 · 501/502
-
线上跑法:文件放
`/opt/rentos/uploads/项目合约`
(容器内
`/app/uploads/...`
)→
`docker exec rentos python /app/tools/import_contracts.py --dir /app/uploads/项目合约 --apply`
-
源副本留服务器
`/opt/rentos/contracts-src/项目合约`
(93MB),
`uploads/`
已清空
## 合同 × 台账核对(2026-10-15 下半场)
-
**`tools/sync_pull.py`**
—— 数据
**回拉**
通道(
`deploy_git.py`
只管推代码)。
`--dry`
列差异 → 备份
`data/_snap/pull-<ts>/`
→ SFTP 递归下载;默认跳
`attachments`
、
**永不回拉 `sessions.json`**
;⚠️
**只增/改,不删本地独有文件**
→ 本地残留需自己清。
-
**`tools/check_contracts_vs_table.py`**
—— 文件名级机械比对(复用
`import_contracts`
的单元解析)
-
⚠️
**合同文件名不可信**
!已知 4 处与正文不符:119车位(租客/期限都写错)、
1102(漏「安装」)、307&603、601A&702(多主体)。
**判断一律以正文为准**
-
读扫描件:
`pypdfium2`
渲染成图人工判读。房屋合同的甲乙方在
**第 2 页**
,
「第三条 期限与交付」在
**第 3~4 页**
(因模板而异,别写死页号)
-
读
`.doc`
:
`raw.decode("utf-16-le","ignore")`
+ 中文串抽取即可读到正文
-
**核对结论(29 份 → 35 单元,18 对 / 17 有出入)**
:
-
影响出单:903室月租台账 36300/合同 16500;904室月租空/合同 19800;
501室期空、502室标「(空置)」(合同 501+502 一体租给明境 2025-06-16~2028-06-15、月租合计 35648);
126/128车位合同期整体早一年(应 2026-03-19~/2026-03-01~);154车位台账空置(合同 徐相天);
601B室租客名带
**「(空置)」后缀**
会被出单逻辑跳过;315室期错(合同 2026-11-01~2028-10-31,JR202609315);
305室物业费台账 1512 / 合同 1312
-
待确认:132/227车位合同承租方=
**上海伯凌建筑设计**
而台账=上海爱种草(且已按爱种草出单);
`docker exec rentos python /app/tools/import_contracts.py --dir /app/uploads/项目合约 --apply`
;
源副本留
`/opt/rentos/contracts-src/项目合约`
(93MB),
`uploads/`
已清空
## 合同 × 台账核对
-
**`tools/sync_pull.py`**
= 数据
**回拉**
通道(
`deploy_git.py`
只管推代码):
`--dry`
列差异 →
备份
`data/_snap/pull-<ts>/`
→ SFTP 递归下载;默认跳
`attachments`
、
**永不回拉 `sessions.json`**
;
⚠️
**只增/改,不删本地独有文件**
→ 本地残留需自己清
-
**`tools/check_contracts_vs_table.py`**
= 文件名级机械比对(复用
`import_contracts`
的单元解析)
-
⚠️
**合同文件名不可信**
!已知 4 处与正文不符:119车位(租客/期限都写错)、1102(漏「安装」)、
307&603、601A&702(多主体)。
**判断一律以正文为准**
-
读扫描件:
`pypdfium2`
渲染成图人工判读;房屋合同甲乙方多在
**第 2 页**
、「第三条 期限与交付」在
**第 3~4 页**
(因模板而异,别写死页号)。读
`.doc`
:
`raw.decode("utf-16-le","ignore")`
+ 中文串抽取
-
**结论(29 份 → 35 单元,18 对 / 17 有出入)**
:明细见
`output/合同-台账核对报告.md`
(入库)。
影响出单 9 条:903室月租 36300/合同 16500;904室月租空/合同 19800;501室期空、502室标「(空置)」
(合同 501+502 一体租给明境 2025-06-16~2028-06-15、合计 35648);126/128车位合同期整体早一年
(应 2026-03-19~/2026-03-01~);154车位台账空置(合同 徐相天);601B室租客名带
**「(空置)」后缀**
会被出单逻辑跳过;315室期错(应 2026-11-01~2028-10-31);305室物业费 1512/合同 1312
-
待确认:132/227车位合同承租方=
**上海伯凌建筑设计**
而台账=爱种草(已按爱种草出单);
119/124/155/191/227 车位合同甲方均为
**精锐**
,台账出租方是 瑞格嘉德/科博;
903/904 面积都填 271.17(均值,应 247.24/295.10);306/310「租赁期(月)」12/19 应为 24
-
**当前不出单的 9 个单元**
(未出现在任何房租批次):401 · 501 · 502 · 903 · 904 · 126车位 ·
128车位 · 154车位 · 601B室,月租合计约
**¥98,262/月**
-
产出:
`output/合同-台账核对报告.md`
(入库)+
`output/合同-台账核对明细.csv`
(output/
*
.csv 被忽略)
-
**当前不出单的 9 个单元**
:401 · 501 · 502 · 903 · 904 · 126车位 · 128车位 · 154车位 · 601B室,
月租合计约
**¥98,262/月**
## 房租车位费出单
链路
:
`rent_parser.py`
→
`rent_render.py`
(→
`notice_pdf`
)→
`service.ingest_rent`
→ 批次
链路
`rent_parser.py`
→
`rent_render.py`
(→
`notice_pdf`
)→
`service.ingest_rent`
→ 批次
-
计费:期间起 =
**AC**
,止 = 起 + N 个自然月 − 1 天;
`房屋租赁费 = P × N`
、
`物业费 = O × N`
;
单元号含「车位」→ 车位租赁费;N 缺值回落
`DEFAULT_MONTHS = 3`
-
**不可出单三条件**
:空置(F 空或含「空置」)/ 每月租金 ≤ 0 / AC 空
(各带
`reasons`
)
-
**按免租数据跳过**
:
`rent_preview`
对每行与每份单据标
`rentfree`
-
房间费与车位费
**合并一张**
(键
`(出租方, 租客, 期间)`
);批次按
`kind`
隔离
-
**不可出单三条件**
:空置(F 空或含「空置」)/ 每月租金 ≤ 0 / AC 空
-
`rent_preview`
对每行与每份单据标
`rentfree`
供跳过;房间费与车位费
**合并一张**
(键
`(出租方, 租客, 期间)`
);批次按
`kind`
隔离
-
推进/回滚走
`tables.update_cells`
;
`rollback_rent_advance`
**只回滚当前值仍等于推进后的行**
-
**账期区间筛选**
:
`classify_period(from, to)`
命中 = 账期起始日落在区间内(旧
`?on=`
是
`to`
别名)
-
接口
:
`GET /api/batches?kind=`
·
`GET /api/rent/preview?from=&to=&all=1`
·
`POST /api/rent/batches`
·
-
接口
`GET /api/batches?kind=`
·
`GET /api/rent/preview?from=&to=&all=1`
·
`POST /api/rent/batches`
·
`DELETE /api/batches/{id}`
`{rollback:true}`
·
`POST /api/batches/{id}/receipt`
(
**只房租批次**
)·
`GET /api/batches/{id}/export`
-
⚠️
`GET /api/batches/{id}`
的
`receipt`
在
**响应顶层**
`GET /api/batches/{id}/export`
⚠️
`GET /api/batches/{id}`
的
`receipt`
在
**响应顶层**
## 台账编辑(批量暂存)
-
改格
**不逐格弹确认**
:
`stageCell()`
暂存 + 黄底高亮(
`TB_PEND`
),顶条显示「N 处改动待保存」
-
「保存全部」→ 一次
`confirmBox`
→ 单次
`PUT /api/tables/{key}/cells`
(
`{changes:[{row_id,col,value}], confirm:true, who}`
)→
`pushHist({t:'batch'})`
可
**整批撤销**
-
`navTo()`
/
`beforeunload`
拦截未保存改动
-
⚠️
`renderGrid()`
末尾必须同时调
`paintPending()`
**和**
`renderPendBar()`
-
确认框是
`.modal-mask.show`
(
**不是 `.open`**
)
-
⚠️
`renderGrid()`
末尾必须同时调
`paintPending()`
**和**
`renderPendBar()`
;
确认框是
`.modal-mask.show`
(
**不是 `.open`**
)
##
系统骨架
/ 渲染
-
启动
`python -m rentos.server --port 8899`
;
`--no-scheduler`
只开 Web;admin / 1989@rent05
;
访客
**guest / guest@2026**
(
`role=viewer`
+
`perms=['elec']`
)
##
骨架 / 权限
/ 渲染
-
启动
`python -m rentos.server --port 8899`
(
`--no-scheduler`
只开 Web)
;
admin / 1989@rent05;
访客
**guest / guest@2026**
(
`role=viewer`
+
`perms=['elec']`
)
-
**权限模型**
:
`role`
(admin|viewer)+
`perms`
能力清单(目前只有
`elec`
)
-
管理员隐含
`{'*'}`
;
`viewer`
按
`perms`
**逐接口**
放行,
`perms=[]`
即纯只读
-
`auth.caps()/can()`
;
`server._write_cap(method,path)`
解析所需能力,
`_guard_write()`
判定
-
`auth.caps()/can()`
;
`server._write_cap(method,path)`
+
`_guard_write()`
判定
-
**批次类接口按 `kind` 复核**
:房租车位费的删/重出/发送/收款
**仍只有管理员**
-
前端:
`body.cap-elec`
才显示
`[data-cap="elec"]`
;
`data-ro`
= 仅管理员;
**主账号不可降级**
-
导航 5 项:概览 / 电费通知单 / 房租车位费 / 公司管理 / 设置;hash 路由在
`app.js`
的
`PAGES`
+
`applyNav()`
;
**新增页面必须同时登记 PAGES 与 `#nav`**
-
**唯一渲染层**
`rentos/notice_pdf.py`
:reportlab + platypus
程序化绘制
(
**不用 win32com / LibreOffice**
);
-
**唯一渲染层**
`rentos/notice_pdf.py`
:reportlab + platypus(
**不用 win32com / LibreOffice**
);
字体
`simhei.ttf`
子集嵌入;A4、
`MARGIN = 1.9cm`
## 环境
-
Python
`…/python/versions/3.13.12/python.exe`
(openpyxl / reportlab / pillow;
**无 pypdfium2**
)
-
venv
`…/envs/default/Scripts/python.exe`
:
**Playwright + reportlab + pypdfium2 + PyPDF2**
→
**单测 / UI 回归 / 合同判读都用它**
-
venv
`…/envs/default/Scripts/python.exe`
:
**Playwright + reportlab + pypdfium2 + PyPDF2**
→
**单测 / UI 回归 / 合同判读都用它**
(3.13.12 无 pypdfium2、无 paramiko)
-
Bash 先
`export PATH=…/PortableGit/versions/1.2.0/usr/bin:…/bin:$PATH`
-
`.bat`
必须
**cp936 + CRLF**
;分支用
`goto`
标签;echo 不放半角括号/
`!`
-
**改完后端代码必须 kill 所有在跑实例再重启**
(旧进程会按旧逻辑写同一
`data/`
)
-
⚠️ 本工具链会把命令里的
`\n`
字面化 → 写多行脚本用 heredoc +
`textwrap.dedent`
,别用
`"\n".join`
-
⚠️ 别用
`io.TextIOWrapper(sys.stdout.buffer,…)`
二次包 stdout(旧包装器 GC 时关掉 buffer →
「I/O operation on closed file」);用
`sys.stdout.reconfigure(encoding="utf-8")`
-
⚠️ 本工具链会把命令里的
`\n`
字面化 → 多行脚本用 heredoc +
`textwrap.dedent`
-
⚠️ 别用
`io.TextIOWrapper(sys.stdout.buffer,…)`
二次包 stdout;用
`sys.stdout.reconfigure`
## 启动 / 部署(192.168.68.121 · CentOS 7)
-
`start.bat [safe] [端口]`
/
`stop.bat`
;
`notify.enabled=True`
时双击
`start.bat`
**会开定时发送**
→
**会话内一律 `start.bat safe`**
;会话里它内层
`start`
会被拦 → 直接后台起
`python -m rentos.server --port 8899 --no-scheduler`
## 部署(192.168.68.121 · CentOS 7)
-
云端
`http://192.168.68.121:8899/`
,容器
`rentos`
,root / 12345678
-
⚠️
**线上 admin 密码已不是 1989@rent05**
(10-14 之后被改过,
`auth.verify`
实测 False)→
部署自检已改成登录失败时提示并跳过 HTTP 抽查,不再抛栈
-
**主用 Git 同步**
(⚠️
**必须用 venv python**
,3.13.12 无 paramiko):
`RENTOS_SSH_PASSWORD=12345678 …python tools/deploy_git.py sync`
-
push 绕代理:
`env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY git push`
-
⚠️
**线上 admin 密码已不是 1989@rent05**
(
`auth.verify`
实测 False)→ 部署自检改为
登录失败时提示并跳过 HTTP 抽查,不再抛栈
-
**主用 Git 同步**
(⚠️
**必须用 venv python**
):
`RENTOS_SSH_PASSWORD=12345678 …python
tools/deploy_git.py sync`
;push 绕代理
`env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY git push`
-
代码落
`/opt/rentos/repo`
,与数据同级独立 →
`git reset --hard`
**不碰线上数据**
-
⚠️
**sync 只更代码、不动数据**
→ 改列模型/加列的迁移
**部署后要在容器里手动再跑**
(
`docker exec rentos python /app/tools/xxx.py`
,服务器
**无 Python 3**
)
-
构建
必须
在
**仓库根**
:
`docker build -f deploy/Dockerfile -t rentos:latest .`
,零联网;
-
构建在
**仓库根**
:
`docker build -f deploy/Dockerfile -t rentos:latest .`
,零联网;
**切勿 `docker build … | tail`**
(吞掉失败会拿旧镜像重启)
-
`docker -v`
的 SELinux 选项是
**冒号 `:z`**
(写成
`,z`
的症状:页面 200 但台账 0 行)
-
云端已能直发企微:
`/usr/local/bin/wecom-cli`
+ 凭据
`/opt/rentos/wecom-config/`
(700)
-
⚠️
**线上是唯一生产方,本机只作预览**
;
**本机与云端不要同时开定时器**
-
远端
`https://git.thiztech.com/accounting/rentos.git`
(Gitea),分支
`main`
;
`.gitignore`
排除
`data/sessions.json`
/
`batches`
/
`drafts`
/
`contracts`
/
`attachments`
/
`_snap`
/
`_trash`
/
`output/*`
(
**`!output/*.md` 保留验收报告**
)/
`项目合约/`
;
`.gitattributes`
:
`* text=auto eol=lf`
、
-
`start.bat [safe] [端口]`
/
`stop.bat`
;
`notify.enabled=True`
时双击
`start.bat`
**会开定时发送**
→ 会话内一律
`start.bat safe`
-
远端
`https://git.thiztech.com/accounting/rentos.git`
(Gitea)分支
`main`
;
`.gitignore`
排除
`data/sessions.json`
/
`batches`
/
`drafts`
/
`contracts`
/
`attachments`
/
`_snap`
/
`_trash`
/
`output/*`
(
**`!output/*.md` 保留验收报告**
)/
`项目合约/`
;
`.gitattributes`
:
`* text=auto eol=lf`
、
`*.bat -text`
、
**Dockerfile 必须 LF**
## 验证脚本(tools/)
...
...
@@ -187,19 +174,18 @@
`test_accounts`
60 ·
`test_round6`
44 ·
`test_masters`
19 ·
`test_contracts`
21
(
`test_scheduler.py`
需活跃 8899 服务,无服务时失败属预期)
-
UI 回归:
**自带沙箱**
`ui_verify7~12.py`
;
**需连服务**
`ui_verify`
·
`2`
~
`6.py`
→ 台账+电费录入 / 设置+锁定+批量暂存 / 带入+照明+图片 / 公司管理 / 房租出单全链
/
收款开票+导出 / 访客能力 / 账期区间 / 批量编辑 / 行粒度核对+账期汇总 /
**合同原件(ui_verify12)**
(台账+电费录入 / 设置+批量暂存 / 带入+图片 / 公司管理 / 房租出单全链 / 收款开票+导出
/
访客能力 / 账期区间 / 批量编辑 / 行粒度核对 /
**合同原件**
)
-
API 冒烟(沙箱):
`smoke_verify_edit.py`
·
`smoke_verify_contracts.py`
(合同 24 项)
-
⚠️
`ui_verify7`
的只读样本是脚本自建
`ro_verify`
(guest 已是「电费操作员」,不能当纯只读样本)
-
**`tools/ui_verify_runner.py <脚本…>`**
:每个脚本起
**全新沙箱**
(复制 data/ + 改写 config 常量 +
另起端口 + 注入
`RENTOS_BASE`
);⚠️
**1~6 号有硬闸门**
(未设
`RENTOS_BASE`
/
`RENTOS_ALLOW_REAL=1`
直接退出)
-
⚠️
**沙箱常量清单要同步补**
(
`BATCH_DIR`
/
`OUTPUT_DIR`
/
`DRAFT_DIR`
/
`CONTRACT_DIR`
)——
共 7 处:
`ui_verify_runner`
+
`ui_verify7/8/9/10/11`
+
`check_scheduler`
;漏了
就
会写进
**真实**
`data/`
共 7 处:
`ui_verify_runner`
+
`ui_verify7/8/9/10/11`
+
`check_scheduler`
;漏了会写进
**真实**
`data/`
(2026-10-13 直连 8899 曾误写 17 个测试批次)
-
迁移:
`migrate_rentfree_type.py`
·
`import_park_notes.py`
·
`migrate_next_bill.py`
-
端到端
`e2e_round5.py`
(PDF 出单 → 收款开票 → 导出 xlsx → 排期 → 删除回滚)
-
**断言不要写死行数/列数/日期**
,
**按列 id / 表头文字定位**
,不按第 N 列
(新增列使 DOM 位置后移,
`ui_verify3`
的
`tds[14]`
曾误读「付款周期」)
-
其他:迁移
`migrate_rentfree_type/import_park_notes/migrate_next_bill.py`
;端到端
`e2e_round5.py`
-
**断言不要写死行数/列数/日期**
,
**按列 id / 表头文字定位**
(新增列使 DOM 位置后移,
`ui_verify3`
的
`tds[14]`
曾误读「付款周期」)
## 工作规矩(巴特菲)
-
**UI 回归不要每次小改都跑**
,等他发话;小改只跑单元测试
...
...
@@ -211,6 +197,7 @@
## 待巴特菲拍板
多办公地是否带公司名、车位是否算「办公地」、同名单据加期间、电费 0 用量是否出单、
4F/5F 与爱种草渠道口径、通知日期/缴费期限口径、是否做收款核销;
**合同核对发现的 17 处台账出入要不要动手修**
;本机 4 张残留测试批次是否移
`_trash/`
。
**合同核对发现的 17 处台账出入要不要动手修**
;本机 4 张残留测试批次是否移
`_trash/`
(
`20261014-134503`
、
`20261014-134504`
、
`20261015-134717`
、
`20261015-135238`
)。
**未处置**
:9 月电费批次多计 10 行 ¥167,947.41 / 少计 601B ¥299.67 / 出租方错记「精锐」;
`ui_verify11`
里「保存修改」按钮的断言待改写。
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment