diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index f10c16f..1e87033 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -197,6 +197,10 @@ }, "active": "849c5ff8936a2b67", "lastOpenFiles": [ + "prop-acc/scenarios/meter/replace-broken-meter.md", + "prop-acc/scenarios/meter/register-single-meter.md", + "prop-acc/scenarios/meter/init-new-community-batch.md", + "prop-acc/scenarios/meter", "prop-acc/maps/meter-knowledge-map.md", "prop-acc/concepts/meter/decommission-and-locking.md", "prop-acc/concepts/meter/reading-source-and-photo-proof.md", @@ -221,17 +225,13 @@ "prop-acc/scenarios/prepaid/deposit-via-miniapp-pending.md", "prop-acc/scenarios/prepaid/deposit-additional-topup.md", "prop-acc/scenarios/prepaid/deposit-first-time.md", - "prop-acc/concepts/prepaid/account-state-machine.md", "prop-acc/scenarios/prepaid", - "prop-acc/index.md", - "prop-acc/scenarios/deposit/deposit-first-time-renovation.md", "prop-acc/concepts/prepaid", "prop-acc/scenarios/deposit", "prop-acc/concepts/deposit", "prop-acc/scenarios/adhoc", "prop-acc/concepts/adhoc", "resident-portal/scenarios", - "resident-portal/reference", - "resident-portal/procedures" + "resident-portal/reference" ] } \ No newline at end of file diff --git a/prop-acc/scenarios/meter/init-new-community-batch.md b/prop-acc/scenarios/meter/init-new-community-batch.md new file mode 100644 index 0000000..ea82cac --- /dev/null +++ b/prop-acc/scenarios/meter/init-new-community-batch.md @@ -0,0 +1,185 @@ +--- +title: prop-acc · meter · 场景 - 新社区批量建表 + 初始读数 Excel 导入 +aliases: + - 批量建表 + - 新社区计量表初始化 + - init-new-community-batch + - 场景-新社区批量建表 +tags: + - 场景 + - prop-acc + - 计量表 + - 表管理 + - 批量导入 +audience: + - 业务人员 + - 抄表员 +status: 已发布 +sub_feature: meter +last_review: 2026-05-26 +code_version: 2026-05-22 +--- + +# 场景:新社区批量建表 + 初始读数 Excel 导入 + +物业**新接管社区**(或老社区从 0 接入本系统),需要**批量建表 + 录初始读数**。通过 `MeterInitializationImporter` + `ImportActionWithExcel` 一次性导入。 + +## 典型情境 + +> [!example] 真实情境 +> 平台新签了"嘉禾花园"社区,本月底接管。该社区有 300 户业主 + 公共部位 + 商铺,合计约 1,200 张表(每户水电气 3 张 + 公共部位 + 商铺各类)。 +> +> 物业财务王主管不可能手工建 1,200 张表,**走批量导入**: +> +> 1. 抄表员李师傅 + 物业前任团队 出 Excel 表(包含每张表的房号、表号、初始读数) +> 2. 王主管在系统下载"建表初始化模板" +> 3. 把数据填入模板 → 上传 → 系统批量建表 + +## 业务人员视角 + +### 第 1 步:下载初始化模板 + +后台 → 计量表 → 列表 → 顶部 **"下载初始化模板"** 按钮(`ExportMeterInitializationTemplateAction`)。 + +下载到的 Excel 包含: + +| 列 | 说明 | 示例 | +|---|---|---| +| 房号 / 资产编号 | 关联 asset(必填,系统按房号查 asset_id) | 12-3-501 | +| 费用类型 | 水费 / 电费 / 燃气费(必填) | 电费 | +| 表编号 | 物理表牌号(必填) | E-501 | +| 倍率 | multiplier(可选,默认 1)| 1 | +| 初始读数 | initial_reading(必填,首次接管时表上的读数)| 0 / 8523 / etc. | +| 安装日期 | installed_at(可选,默认导入日)| 2026-05-26 | +| 备注 | (可选) | "新装" | + +模板列名清晰,业务人员 / 抄表员看得懂。 + +> [!warning] 模板列含义"双义"问题(已知 issue) +> 当前 `MeterInitializationImporter` 用**一个 Importer 处理两种 Excel layout**(住宅单元 vs 商铺/公共)。某些列 label 是"双义"形式,导入选项里选错 `asset_type` 不报错,只是数据写到错误字段(silent corruption)。issue.md Q5 已记录,待拆成两个独立 Importer。**当前预防**:务必仔细选 asset_type。 + +### 第 2 步:填写数据 + +物业 / 抄表员把 1,200 张表的信息填入模板。 + +关键字段对齐: + +- 房号:对应系统里 asset 表已存在的编号(若不存在,先到 community 模块建 asset) +- 费用类型:对应系统配置的 FeeType(水/电/燃气,各社区独立配置) +- 表编号:物业自编(常见 `<费用类型简写>-<房号>` 模式) +- 初始读数:**接管当天**的物理表读数(关键!首次接管之前的用量物业不管) + +### 第 3 步:上传 + 导入 + +后台 → 计量表 → 列表 → 顶部 **"导入初始化"** 按钮(`ImportActionWithExcel` + `MeterInitializationImporter`)→ 选 asset_type(住宅 / 商铺)→ 上传 Excel → 提交。 + +系统: + +1. **解析 Excel**(走 `BaseImporter` + `ImportActionWithExcel`,支持 .xlsx / .xls / .csv) +2. **每行校验**(房号 asset 存在?费用类型存在?表编号是否在该社区重复?) +3. **批量建 Meter**(每行一张 Meter 记录) +4. **可选:同时建第一条 MeterReading**(若模板含"初始读数",建初始 reading 来锁定 `previous_reading` 起点) +5. **报告**:成功 N 张,失败 M 张 + 每条失败的原因 + +> [!info] BaseImporter + chunk rollback +> 走 `App\Filament\Importers\BaseImporter`(host 基类,详见 saas-baseline 规范)+ `TransactionalImportCsv` job。一批 100 行任意一行失败 → 该批全回滚。 +> +> 这避免"部分建好部分没建"的脏中间态。失败的批可下载"失败行" Excel,修复后再导入。 + +### 第 4 步:核对 + +导入后: + +- 后台 → 计量表 → 按社区过滤 → 看是否 1,200 张表都在 +- 抽样核对:打开几张表看初始读数对不对 + +### 第 5 步:启动月度抄表 + +接管下一个月 → 抄表员去现场抄读数(走 [[read-batch-via-excel-import]] 或 [[read-single-meter-manual]])→ 系统按 `current - initial × multiplier` 算用量 → 生成第一份账单(走 [[bill-generation-pipeline]])。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 王主管 + participant Filament + participant ImportActionWithExcel + participant MeterInitializationImporter + participant 数据库 + + Note over 王主管: 已填好 1200 行 Excel + + 王主管->>Filament: ListMeters → 导入初始化 → 选 asset_type + 上传 + Filament->>ImportActionWithExcel: parse .xlsx + ImportActionWithExcel->>MeterInitializationImporter: 按 chunk 处理(100 行/批) + + loop 每批 + MeterInitializationImporter->>数据库: 开启事务 + loop 每行 + MeterInitializationImporter->>数据库: 校验 asset / fee_type / code 唯一 + alt 校验通过 + MeterInitializationImporter->>数据库: 建 Meter + 可选 initial MeterReading + else 校验失败 + MeterInitializationImporter->>MeterInitializationImporter: 收集失败行 + end + end + alt 全成功 + MeterInitializationImporter->>数据库: 提交 + else 任一失败 + MeterInitializationImporter->>数据库: 回滚整批 + end + end + + MeterInitializationImporter-->>Filament: 报告 "成功 1198,失败 2" + Filament-->>王主管: 通知 + 下载失败行 Excel +``` + +## 业户视角 + +业户**不感知**这一步。新接管社区会发个公告"本月起本物业系统升级,各位业户的水电气计量将从 X 月 X 日起按本系统记账"。具体业户看到的: + +- 接管前最后一份账单(由前任物业 / 自建系统出) +- 接管后第一份账单(由本系统出,用量从接管那天起算) + +中间**绝不能有"重复账单"或"漏账"** —— 接管时的 `initial_reading` 必须准确反映物理表当时读数。 + +## 常见问题 + +> [!question] 为什么需要"初始读数"? +> 系统计算用量公式是 `(current - previous) × multiplier`。新表的"上一次读数"在系统里没有,所以接管时存的 `initial_reading` 就是"`previous_reading` 的起点"。后续每月抄表 → 当前 - 上次 = 用量。 +> +> 如果不填初始读数 → 第一次抄表算用量会爆炸(`current - 0 = 所有历史用量`),业户被收一笔巨账,投诉。 + +> [!question] 导入失败的常见原因? +> - **房号(asset)不存在**:在 community 模块的 asset 还没建好。先建 asset 再导入表 +> - **费用类型不存在**:RatePlan 没配置。先到 FeeType 配置 +> - **表编号在该社区重复**(社区内 code 应唯一,虽然 issue.md Q5 提到目前是 nullable + 非 unique 的"待治理"状态) +> - **倍率格式错**(非数字 / 负数) +> - **初始读数格式错**(非数字 / 负数) + +> [!question] 失败的行可以单独处理吗? +> 可以。导入完成后系统提供"下载失败行"Excel(走 host 的 `TransactionalImportCsv` 机制),业务人员修复后单独导入失败行。已成功的不影响。 + +> [!question] 同一社区多次导入会重复建表吗? +> 视 Importer 实现。若有 unique 校验(asset_id + fee_type_id 不可重复)→ 重复行会失败,需手工合并。若无校验 → 重复建,**灾难**。 + +> [!question] 老社区已经有一年的抄表历史,接管时怎么办? +> 简化做法:**只导入接管那天的状态**(initial_reading = 接管那天的物理读数),历史数据**不进系统**(在 Excel 备查)。 +> +> 复杂做法:**导入历史 reading 数据**,让本系统有完整历史。需要业务方决定(用户对账复杂度 vs 系统数据完整度的权衡)。 + +> [!question] 商铺表 / 公共部位表怎么导入? +> 同样走 `MeterInitializationImporter`,但**选不同 asset_type**(public / shop)。导入时系统按 asset_type 决定列含义。详见 issue.md Q5"双义列名"问题。 + +## 异常分支 + +- 单张新表(后续装机 / 个别加表)→ [[register-single-meter]] +- 老表换新表 → [[replace-broken-meter]] +- 抄表(初始化后日常)→ [[read-batch-via-excel-import]] / [[read-single-meter-manual]] + +## 相关文档 + +- [[meter-vs-meter-reading]] +- [[register-single-meter]] +- [[read-batch-via-excel-import]] +- [[bill-generation-pipeline]] diff --git a/prop-acc/scenarios/meter/register-single-meter.md b/prop-acc/scenarios/meter/register-single-meter.md new file mode 100644 index 0000000..3739a24 --- /dev/null +++ b/prop-acc/scenarios/meter/register-single-meter.md @@ -0,0 +1,153 @@ +--- +title: prop-acc · meter · 场景 - 单独新增一张表 +aliases: + - 新增计量表 + - 单录建表 + - register-single-meter + - 场景-新增计量表 +tags: + - 场景 + - prop-acc + - 计量表 + - 表管理 +audience: + - 业务人员 +status: 已发布 +sub_feature: meter +last_review: 2026-05-26 +code_version: 2026-05-22 +--- + +# 场景:单独新增一张表 + +社区**已经初始化完成**(走过 [[init-new-community-batch]] 或老社区已有数据),后续个别新装一张表(新业户入住装表 / 旧业户加装电表分户 / 商铺新进驻装表),走**后台单录**而非批量。 + +## 典型情境 + +> [!example] 真实情境 +> 嘉禾花园 12-3-501 业户陈先生最近**装修后想加装一个独立电表**(主表外的厨房专用电表,方便核算厨房电费)。物业财务王主管要在系统建这张新表。 + +## 业务人员视角 + +### 第 1 步:确认装表信息 + +向陈先生 / 抄表员李师傅核实: + +- 房号 / 资产编号:12-3-501(对应 asset) +- 费用类型:电费(对应 FeeType) +- 物理表编号:E-501-K(物业自编,K 表示厨房) +- 倍率:1(普通家用单相表) +- 安装日期:今天(2026-05-26) +- **初始读数**:抄表员现场看物理表读数,假设 0(全新表) + +### 第 2 步:打开后台 + +后台 → 计量表 → 列表 → 右上角 **"新建"** 按钮 → 进 `CreateMeter` 页面。 + +### 第 3 步:填表单(`MeterForm`) + +| 字段 | 填什么 | +|---|---| +| **社区(community_id)** | 嘉禾花园 | +| **绑定房屋(asset_id)** | 12-3-501(下拉选)| +| **费用类型(fee_type_id)** | 电费(下拉选)| +| **表编号(code)** | `E-501-K` | +| **倍率(multiplier)** | 1.0 | +| **初始读数(initial_reading)** | 0.0 | +| **安装日期(installed_at)** | 2026-05-26 | +| **是否在役(is_active)** | ✅ 是(默认)| +| **替换上一代(replaced_meter_id)** | 留空(全新表,不是换表)| +| 备注 | "陈先生厨房分户表" | + +### 第 4 步:提交 + +系统: + +1. 校验 asset / fee_type 存在 +2. 校验 code 在该社区不重复(若有 unique 约束) +3. 建 Meter 记录(`is_active=true`, `replaced_meter_id=null`) +4. **可选**:是否同时建一条 `initial_reading` 的 MeterReading?看 `CreateMeter` 实现 —— 若 form 有"初始读数"字段(目前应该有),`installed_at` 当天会建一条 `MeterReading(current_reading=0)` 作为起点,这样下次抄表算用量有 previous 可对照 + +### 第 5 步:启用 + 抄表 + +新表建好后,下次抄表周期就纳入正常流程(`MetersNeedingReadingListWidget` 会显示)。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 王主管 + participant Filament + participant CreateMeter + participant 数据库 + + 王主管->>Filament: ListMeters → 新建按钮 + Filament->>CreateMeter: 渲染 form + 王主管->>CreateMeter: 填字段 + 提交 + CreateMeter->>数据库: 校验 asset / fee_type / code + CreateMeter->>数据库: 建 Meter(is_active=true) + alt form 含 initial_reading + CreateMeter->>数据库: 建 MeterReading(current=initial, source=manual, operated_by=王主管) + end + CreateMeter-->>Filament: 跳转 ViewMeter + Filament-->>王主管: 显示新表详情 +``` + +## 与批量导入的对比 + +| 维度 | [[init-new-community-batch|批量导入]] | **单录(本场景)** | +|---|---|---| +| 触发场景 | 新社区接管 / 一次性大批量 | 个别新装 / 后续补建 | +| 数量级 | 100+ | 1 | +| UI | Excel 导入 | Filament `CreateMeter` 表单 | +| 时长 | 几分钟 / 小时 | 1-2 分钟 | +| 出错容忍 | 单行失败 / 部分行可独立处理 | 单条提交,错就改了再交 | +| 业务人员熟练度 | 需熟悉 Excel 模板 | 任何人填表都行 | + +## 常见问题 + +> [!question] 业主已经有主电表,加装分表合规吗? +> **业务问题**,看物业政策 / 法律法规: +> +> - 国家电网通常**禁止**业主自己装"二次表"用于电费分摊 +> - 但**物业内部**核算可以(例如商铺租户共用一个主表,物业按业主装的分表算各自费用) +> +> 系统层面**只管记录**,不判断合规性。 + +> [!question] 同一房屋有主表也有分表怎么办? +> 系统允许同一 `asset_id` + 同一 `fee_type_id` 下有多张表(不像 prepaid 的"一户一账"约束)。每张表独立抄表 + 独立账单。 +> +> 但**业务上要清楚谁付谁的钱**: +> - 主表账单给业主 +> - 分表账单给租户 / 厨房承包人(看场景) +> +> 这要业务方明确**账单收方**,系统按 `community_asset_users` 关系找业户。 + +> [!question] 单录时填错 code 怎么办? +> 表创建后可走 `EditMeter`(`is_active=true` 时允许)修改。但若**已抄过表 / 生成 Bill**,改 code 会让"历史照片上的表号"与"系统 code"对不上 → 强烈不推荐改。详见 [[decommission-and-locking]]"为什么退役表不能改"段。 + +> [!question] 单录后没抄表就发现错了能删吗? +> 看 `MeterPolicy::delete()`:**仅允许"已退役 + 无任何读数"**的表被删。 +> +> 处理流程: +> 1. 先退役表(`is_active=false`, `decommission_reason=Removed`) +> 2. 走删除(若 Policy 允许 + 没读数) +> 3. 重新建正确的表 +> +> 详见 [[decommission-and-locking]]。 + +> [!question] 单录的表如何同步给抄表员? +> `MetersNeedingReadingListWidget` 会自动显示新表(下个抄表周期)。无需手工通知。 + +## 异常分支 + +- 大批量 → [[init-new-community-batch]] +- 换表(旧表退役 + 新表建)→ [[replace-broken-meter]] +- 错了删表 → [[decommission-without-replacement]] + +## 相关文档 + +- [[meter-vs-meter-reading]] +- [[init-new-community-batch]] +- [[replace-broken-meter]] +- [[decommission-and-locking]] diff --git a/prop-acc/scenarios/meter/replace-broken-meter.md b/prop-acc/scenarios/meter/replace-broken-meter.md new file mode 100644 index 0000000..ed34e2d --- /dev/null +++ b/prop-acc/scenarios/meter/replace-broken-meter.md @@ -0,0 +1,234 @@ +--- +title: prop-acc · meter · 场景 - 换表:旧表故障/退役,新表带 -R1 后缀 +aliases: + - 换表 + - 表更换 + - replace-broken-meter + - ReplaceMeterAction + - 场景-换计量表 +tags: + - 场景 + - prop-acc + - 计量表 + - 表管理 +audience: + - 业务人员 + - 抄表员 +status: 已发布 +sub_feature: meter +last_review: 2026-05-26 +code_version: 2026-05-22 +--- + +# 场景:换表,旧表故障/退役,新表带 -R1 后缀 + +物理表**老化 / 损坏 / 校验未过**,需要换新表。系统通过 `ReplaceMeterAction` **一步完成**:旧表退役 + 新表建立 + `replaced_meter_id` 关联 + 初始读数继承。新表自动编号 `<旧编号>-R1`。 + +## 典型情境 + +> [!example] 真实情境 +> 张阿姨家电表(编号 E-501)用了 8 年,2026 年 5 月例行校验未通过(读数漂移),物业要换新表。 +> +> 换表当天: +> - 抄表员李师傅现场读旧表:**5000 度** +> - 卸下旧表 + 装上新表 +> - 新表出厂读数:**0**(物理上) +> - 系统操作:`ReplaceMeterAction` +> +> 系统结果: +> - 旧表 E-501:`is_active=false`, `decommissioned_at=今天`, `decommission_reason=Replaced`, `final_reading=5000` +> - 新表 **E-501-R1**:`is_active=true`, `installed_at=今天`, `replaced_meter_id=旧表 ID`, **`initial_reading=5000`(继承)**, multiplier 继承 + +## 抄表员视角(李师傅) + +### 第 1 步:现场操作 + +到张阿姨家: + +1. 检查旧表状态(确认换表必要性) +2. **拍照存证**旧表当前读数(关键!后续争议时凭证) +3. 物理换表(断电 → 换表 → 通电) +4. 拍照新表初始状态(出厂 0) +5. 把信息回传业务人员(微信 / App / 当面) + +> [!warning] 拍照不能省 +> 旧表读数没拍照 = 系统里填的"5000" 没物理证据 = 业户事后质疑"我家明明只用了 4500" 时物业百口莫辩。 + +### 第 2 步:报回业务 + +抄表员把信息汇总给王主管(业务人员): + +- 旧表编号:E-501 +- 旧表最后读数:5000 +- 换表日期:2026-05-26 +- 退役原因:校验未过(`Replaced`) +- 新表型号:同型号(multiplier=1) + +## 业务人员视角 + +### 第 1 步:打开旧表 + +后台 → 计量表 → 找 E-501 → 进 `ViewMeter`。 + +### 第 2 步:点 `ReplaceMeterAction` + +右上角"换表"按钮(标签可能是"更换")。 + +> [!warning] 按钮可见性 +> 守护:`is_active=true` + Policy `->authorize('replace')`。已退役表此按钮灰化。 + +Modal 表单: + +| 字段 | 填什么 | +|---|---| +| **旧表最后读数(`final_reading`)** | 5000(抄表员现场读)| +| **退役原因(`decommission_reason`)** | `Replaced`(其他 4 种见 [[decommission-and-locking]]) | +| **退役日期** | 2026-05-26(默认今天)| +| **新表编号** | E-501-R1(系统自动生成,`nextReplacementCode()`,可改但不推荐)| +| **新表 multiplier** | 1.0(默认继承旧表)| +| **新表安装日期** | 2026-05-26(默认今天)| +| 备注 | "校验未通过,换新表" | + +### 第 3 步:提交 + +系统在**一个事务**内: + +1. 校验旧表 `is_active=true`(否则按钮就不该出现) +2. 旧表 update: + - `is_active = false` + - `decommissioned_at = 2026-05-26` + - `decommission_reason = Replaced` + - `final_reading = 5000` +3. 建新表: + - `code = E-501-R1`(`nextReplacementCode($oldCode)`) + - `is_active = true` + - `installed_at = 2026-05-26` + - `replaced_meter_id = 旧表 ID` + - **`initial_reading = 5000`**(继承自旧表 final_reading) + - `multiplier = 1.0`(继承) + - `community_id` / `asset_id` / `fee_type_id` 继承 + +### 第 4 步:验证 + 通知 + +后台 → 计量表 → 看新旧两张表 → 确认数据正确。 + +业户可不通知(业户对系统层无感)。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 抄表员 + participant 王主管 + participant Filament + participant ReplaceMeterAction + participant 数据库 + + 抄表员->>抄表员: 现场拍照 + 换表(物理) + 抄表员->>王主管: 旧表读数 5000,换表完成 + + 王主管->>Filament: ViewMeter(旧表) → ReplaceMeterAction + Filament->>ReplaceMeterAction: handle(oldMeter, finalReading=5000, reason=Replaced) + ReplaceMeterAction->>数据库: 开启事务 + ReplaceMeterAction->>数据库: 1. 旧表 update:is_active=false, decommissioned_at, decommission_reason=Replaced, final_reading=5000 + ReplaceMeterAction->>数据库: 2. 建新表:code=E-501-R1, is_active=true, replaced_meter_id=旧表id, initial_reading=5000, multiplier=1 + ReplaceMeterAction->>数据库: 提交事务 + Filament-->>王主管: 跳转新表 ViewMeter +``` + +## 旧表 / 新表数据对照 + +| 字段 | 旧表 E-501 | **新表 E-501-R1** | +|---|---|---| +| `code` | E-501 | **E-501-R1** | +| `is_active` | **false** | true | +| `installed_at` | 2018-XX-XX(原值) | 2026-05-26 | +| `decommissioned_at` | **2026-05-26** | null | +| `decommission_reason` | **Replaced** | null | +| `final_reading` | **5000** | null | +| `initial_reading` | (历史值不动)| **5000**(继承)| +| `multiplier` | 1 | 1(继承) | +| `replaced_meter_id` | null | **旧表 id** | +| `community_id`, `asset_id`, `fee_type_id` | 不动 | 继承 | + +## 5 月份的抄表 + 账单 + +换表那个月的账单: + +``` +本月用量 = current(新表第一次抄)+ initial(=5000) - previous(=5000) + = (50 + 5000) - 5000 + = 50 度 +``` + +新表 5 月底第一次抄读到 50(物理表头),系统存 `current_reading = 50 + 5000 = 5050`,`previous_reading = 5000`(继承),`consumption = 50`。账单按 50 度算,业户感觉不到换表。 + +> [!info] 抄表员录入逻辑 +> 抄表员现场看到新表是 50,**系统应自动加上 5000 存为 5050**(避免抄表员手动算)。或者抄表员录 50,系统在保存时自动加 5000。具体实现看 `MeterReadingsRelationManager` 的 form。 + +## 业户视角 + +业户**几乎感受不到** —— 只看到下月账单仍是正常用量。 + +唯一感知:换表当天可能短暂断电断水(物理操作)。物业应**提前通知**业户。 + +## 整链追溯 + +如果以后这张 E-501-R1 又出问题再换 → 新表 E-501-R2,`replaced_meter_id` 指 R1。如此累加: + +``` +E-501 (原生) → E-501-R1 (第 1 次换) → E-501-R2 (第 2 次换) → E-501-R3 ... +``` + +详见 [[replacement-chain]]"整条链的追溯"段。 + +## 常见问题 + +> [!question] 旧表 `final_reading` 填错了能改吗? +> 旧表的 `final_reading` 严格上属于"已退役表的字段",`MeterPolicy::update()` 在 `is_active=false` 时拒绝改([[decommission-and-locking]] 守护)。 +> +> 改错的话: +> - 通过 tinker 修(运维操作,留备注) +> - 或者把 `decommissioned_at = null` 让表"复活"(Policy 可能不允许),再走完整换表流程 +> +> **预防**:换表 Modal 提交前与抄表员书面确认 final_reading。 + +> [!question] 新表 multiplier 与旧表不同可以吗? +> 可以(form 上可改),但**强烈不推荐**。理由见 [[replacement-chain]]"常见问题"段:不同 multiplier 让用量计算公式变,业户对账困难。 + +> [!question] 新表编号 -R1 不喜欢能改成别的吗? +> Modal 表单允许改 `code`,但**强烈不推荐**: +> - `-R1` 是标准化命名,审计 / 报表 / 后续换表的 `-R2` 都基于这个 pattern +> - 改成自定义 code(如 "E-501-NEW")会破坏 `nextReplacementCode()` 算法,下次换表生成 `E-501-NEW-R1` 看着别扭 + +> [!question] 换表后业户对历史账单有异议怎么办? +> 历史 reading 都关联到旧表(`meter_id=旧表 id`),不会因换表丢失。审计可: +> +> - 后台找旧表 → 看历次 reading(只读) +> - 拿物理表照片(若有) +> - 拿换表前的累计读数(旧表 final_reading)对照 + +> [!question] 业户搬走永久弃用表,这种"换表"怎么处理? +> 那不是换表,是 [[decommission-without-replacement|退役不换表]]。`decommission_reason=Removed` 或 `Expired`,不建新表。 + +> [!question] 旧表是 active 但有未结账 reading,能换表吗? +> 系统**不阻止**(Action 不查未结账 reading)。但业务上: +> +> - 应先生成未结账 reading 的 Bill(走 [[bill-generation-pipeline]]) +> - 否则换表后那些 reading 永远不会被处理(它们关联旧表) +> +> 推荐流程:**换表前先把旧表当月抄表录入 + 生成 Bill** → 然后再换表。 + +## 异常分支 + +- 不换表只退役 → [[decommission-without-replacement]] +- 误换表想撤销 → 困难,见 [[replacement-chain]]"常见问题"段 +- 单纯换 multiplier 不换表 → 不推荐(应换表保留历史) + +## 相关文档 + +- [[meter-vs-meter-reading]] +- [[replacement-chain]] +- [[decommission-and-locking]] +- [[decommission-without-replacement]] +- [[register-single-meter]]