vault backup: 2026-05-26 00:13:05
This commit is contained in:
10
.obsidian/workspace.json
vendored
10
.obsidian/workspace.json
vendored
@@ -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"
|
||||
]
|
||||
}
|
||||
185
prop-acc/scenarios/meter/init-new-community-batch.md
Normal file
185
prop-acc/scenarios/meter/init-new-community-batch.md
Normal file
@@ -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]]
|
||||
153
prop-acc/scenarios/meter/register-single-meter.md
Normal file
153
prop-acc/scenarios/meter/register-single-meter.md
Normal file
@@ -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]]
|
||||
234
prop-acc/scenarios/meter/replace-broken-meter.md
Normal file
234
prop-acc/scenarios/meter/replace-broken-meter.md
Normal file
@@ -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]]
|
||||
Reference in New Issue
Block a user