--- 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]]