diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 1e87033..358fb93 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -197,6 +197,9 @@ }, "active": "849c5ff8936a2b67", "lastOpenFiles": [ + "prop-acc/scenarios/meter/read-batch-via-excel-import.md", + "prop-acc/scenarios/meter/read-single-meter-manual.md", + "prop-acc/scenarios/meter/decommission-without-replacement.md", "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", @@ -222,9 +225,6 @@ "prop-acc/scenarios/prepaid/consume-meter-bill.md", "prop-acc/scenarios/prepaid/consume-multiple-bills-priority.md", "prop-acc/scenarios/prepaid/consume-monthly-property-bill.md", - "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/scenarios/prepaid", "prop-acc/concepts/prepaid", "prop-acc/scenarios/deposit", diff --git a/prop-acc/scenarios/meter/decommission-without-replacement.md b/prop-acc/scenarios/meter/decommission-without-replacement.md new file mode 100644 index 0000000..87d88c7 --- /dev/null +++ b/prop-acc/scenarios/meter/decommission-without-replacement.md @@ -0,0 +1,191 @@ +--- +title: prop-acc · meter · 场景 - 退役不换表(房屋拆除/业户永久弃用) +aliases: + - 退役不换表 + - 永久弃用 + - decommission-without-replacement + - 场景-计量表退役不换 +tags: + - 场景 + - prop-acc + - 计量表 + - 表管理 +audience: + - 业务人员 +status: 已发布 +sub_feature: meter +last_review: 2026-05-26 +code_version: 2026-05-22 +--- + +# 场景:退役不换表(房屋拆除/业户永久弃用) + +物理表**不再需要**(房屋拆除 / 业户永久搬走 / 商铺撤店 / 法定使用年限到不续装),系统**只退役不建新表**。`decommission_reason` 选 `Removed` / `Expired` 等,不走 `ReplaceMeterAction`。 + +## 典型情境 + +> [!example] 真实情境(一):房屋拆除 +> 嘉禾花园三期某栋老楼要拆迁重建,3 单元 1-6 层 30 户业主已全部搬走,准备拆楼。该单元所有水电气表(共 90 张)需要**永久退役**(房子拆了表也没了)。 + +> [!example] 真实情境(二):商铺撤店 +> 一楼商铺(原餐饮店)经营 5 年后结业,装修拆除,新承租人方向未定。原餐饮店专用商业电表 + 燃气表暂不需要,**退役归档**(等新承租人入驻再视情况建新表)。 + +> [!example] 真实情境(三):法定使用年限到 +> 一批 2010 年装的电表到 2026 年达到法定 15 年使用年限。物业评估: +> - 部分表性能仍好 + 业户无投诉 → 送校验,合格继续用([[replace-broken-meter|换表]] 的 `Calibration` reason) +> - 部分表频繁出问题 + 业户投诉 → 退役换新表(`Replaced`) +> - 个别表所在房屋已无人居住 → **退役不换表**(`Expired`) + +## 业务人员视角(王主管) + +### 第 1 步:确认场景与原因 + +选择正确的 `decommission_reason`: + +| 场景 | 推荐 reason | +|---|---| +| 房屋拆除 | `Removed` | +| 业户永久搬走且无新业户 | `Removed` | +| 法定年限到不续装 | `Expired` | +| 校验送检中暂停(后续可能恢复) | `Calibration`(暂停态,不算永久退役)| +| 损坏不修(整体弃用)| `Damaged` | +| (换新表替代) | `Replaced` — **走 [[replace-broken-meter]] 不是本场景** | + +### 第 2 步:打开表 + +后台 → 计量表 → 按 asset 查找 → 进 `ViewMeter`(`is_active=true`)。 + +### 第 3 步:`EditMeter` 改字段(或专用退役 Action,看 UI 设计) + +> [!info] UI 实现注意 +> 当前实现可能**没有专门的"退役不换表"按钮**,而是通过: +> - `EditMeter` 页直接改 `is_active=false` + 填 `decommissioned_at` + `decommission_reason` + `final_reading` +> - 或自定义"退役"Action(若实现了) +> +> 看具体 `MeterForm` 的字段开关。若没专用按钮,走 EditMeter。 + +填字段: + +| 字段 | 填什么 | +|---|---| +| `is_active` | **false**(取消勾选)| +| `decommissioned_at` | 2026-05-26 | +| `decommission_reason` | `Removed`(本场景一) / `Expired` / `Damaged` | +| `final_reading` | 退役那天的物理读数(若可读)/ 0(若表已被拆除无法读)| +| 备注 | 关键说明,如 "三期 3-1-101 拆迁,房屋已拆,表无法回读" | + +### 第 4 步:提交 + +系统: + +1. 校验旧表 `is_active=true`(`EditMeter` 守护 `->visible(is_active)`) +2. update Meter:`is_active=false`, `decommissioned_at`, `decommission_reason`, `final_reading` +3. **不建新表**(与 [[replace-broken-meter|换表]] 的关键区别) + +### 第 5 步:后续不抄表 + +`MetersNeedingReadingListWidget` 自动**不再显示**此表(`is_active=false` 过滤)。 + +历史 reading 保留,可查。 + +## 批量退役 + +如果是单元拆除(90 张表一起退役),逐张走太慢。可能的批量方案: + +| 方案 | 实现 | +|---|---| +| **List 页批量 Edit**(若有 BulkAction) | 选中多张 → 批量改 `is_active=false` | +| **Excel 导入"退役清单"** | 类似 `MeterInitializationImporter`,但当前没此功能(可补)| +| **tinker 脚本**(运维)| `Meter::whereIn('id', [...])->update([...])`,**留 audit log** | + +当前推荐:**业务量小的话逐张 EditMeter**;**业务量大的话联系运维**走 tinker(批量改字段 + 留事由记录)。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 王主管 + participant Filament + participant EditMeter + participant 数据库 + + 王主管->>Filament: 找到要退役的表 → ViewMeter → 编辑 + Filament->>EditMeter: 渲染 form + 王主管->>EditMeter: is_active=false + 填 decommissioned_at + reason + final_reading + 备注 + EditMeter->>EditMeter: 校验 is_active=true(原状态)+ Policy update 权限 + EditMeter->>数据库: update Meter 字段 + 数据库-->>Filament: ok + Filament-->>王主管: 跳回 ViewMeter,状态显示"已退役 - Removed - 2026-05-26" +``` + +## 退役后的状态 + +| 字段 | 退役后值 | +|---|---| +| `is_active` | false | +| `decommissioned_at` | 2026-05-26 | +| `decommission_reason` | `Removed` | +| `final_reading` | 退役当天读数(或 0)| +| `replaced_meter_id` | null(没有继任者)| +| 后续是否能改字段 | **几乎不能**(`EditMeter` `visible(is_active)` 守护 + Policy 拦截)| +| 后续是否能删 | 仅当**无任何 reading** 时(罕见)| + +详见 [[decommission-and-locking]]"退役后的行为"段。 + +## 与"换表"的关键差异 + +| 维度 | [[replace-broken-meter|换表(Replaced)]] | **退役不换表(本场景)** | +|---|---|---| +| `decommission_reason` | `Replaced` | `Removed` / `Expired` / `Damaged` | +| 是否建新表 | ✅ 是,带 `-R1` 后缀 | ❌ 否 | +| 业户后续是否要付水电费 | 是(用新表继续计费)| 否(无表无计费)| +| 房屋状态 | 仍有人住 | 通常拆 / 撤 / 弃用 | +| 触发 UI | `ReplaceMeterAction`(专用)| `EditMeter`(改字段)| + +## 业户视角 + +业户**通常感知不到**这条系统操作,因为业户本人也搬走 / 房屋已拆。 + +如果是商铺撤店 / 房屋暂时无人(后续可能有新业户),系统层面表已退役,**未来如有新业户入住要重新装表 → 建新表(走 [[register-single-meter]]),不是"复活"旧表**。 + +## 常见问题 + +> [!question] 退役表能"复活"吗(`is_active=false → true`)? +> Policy 设计上**不允许**(`EditMeter` 在 `is_active=false` 时隐藏所有编辑)。如果真需要复活: +> +> - tinker 改字段(运维,留事由) +> - 推荐做法:**建新表**替代,旧表保持退役状态(历史档案) + +> [!question] 退役表如何处理未付的历史 Bill? +> 表退役**不影响**已生成的 Bill(Bill 关联 reading 关联表,数据完整)。业户该付的还是要付。 +> +> 若业户也搬走联系不上:走逾期催收流程(不在本场景)。 + +> [!question] 退役不换表后,房屋还在但暂无业户用电怎么办? +> 房屋空置但有可能后续入驻 → **保留表 active 状态**,只是没有抄表数据(零用量)。每月账单可能仍生成(看 RatePlan 是否有 `min_amount`,详见 [[multiplier-and-tiered-pricing|min/max 封顶]])。 +> +> 真的永久不需要 → 退役。 + +> [!question] `decommission_reason` 选错了能改吗? +> 退役后修改字段被 Policy 拦截。若改错只能 tinker 修(运维 + 留事由)。 +> +> **预防**:退役前确认场景,选对 reason。 + +> [!question] 退役了但表上物理装着没拆走怎么办? +> 系统层面无区别(系统不管物理状态,只管"系统层面已退役")。物业人员需要**实际去现场拆表**(若房屋拆迁要拆楼)或**留存**(若只是商铺暂关)。系统不管理物理库存。 + +> [!question] 表的物理库存追踪? +> 当前系统**不涉及**。物业的"实物计量表"库存(回收、报废、复用)在 ERP / 资产管理系统里处理,不在本子模块。 + +## 异常分支 + +- 换表(有新表替代)→ [[replace-broken-meter]] +- 误退役想撤销 → 困难,见上方"复活"问题 +- 退役后无历史读数想删表 → 见 [[decommission-and-locking]]"退役表的物理删除"段 + +## 相关文档 + +- [[decommission-and-locking]] +- [[replace-broken-meter]] +- [[meter-vs-meter-reading]] +- [[multiplier-and-tiered-pricing]] diff --git a/prop-acc/scenarios/meter/read-batch-via-excel-import.md b/prop-acc/scenarios/meter/read-batch-via-excel-import.md new file mode 100644 index 0000000..df164d6 --- /dev/null +++ b/prop-acc/scenarios/meter/read-batch-via-excel-import.md @@ -0,0 +1,228 @@ +--- +title: prop-acc · meter · 场景 - 一次导入整月所有读数(Excel 批量) +aliases: + - 批量抄表 + - Excel 导入抄表 + - read-batch-via-excel-import + - MeterReadingsImporter + - 场景-Excel 批量抄表 +tags: + - 场景 + - prop-acc + - 计量表 + - 抄表 + - 批量导入 +audience: + - 业务人员 + - 抄表员 +status: 已发布 +sub_feature: meter +last_review: 2026-05-26 +code_version: 2026-05-22 +--- + +# 场景:一次导入整月所有读数(Excel 批量) + +中型物业(几百到上千张表)、未上集抄系统的,抄表员**月底一次性**把全社区抄表数据填入 Excel,业务人员**上传导入**。 + +## 典型情境 + +> [!example] 真实情境 +> 嘉禾花园 300 户 + 公共部位 + 商铺,合计 1,200 张表(水电气混)。未上集抄。抄表员李师傅 + 团队**每月最后一周**集中抄表 5-7 天,完成后: +> +> 1. 整理 Excel(1,200 行,每行一张表的本月读数) +> 2. 王主管下载"抄表读数模板" +> 3. 抄表数据填入模板对应列 +> 4. 上传导入 + +## 业务人员视角 + +### 第 1 步:下载抄表模板 + +后台 → 计量表 → 列表 → 顶部 **"下载抄表模板"** 按钮(`ExportMeterReadingsAction`,**注意**:命名误导,实际是下载模板)。 + +> [!info] 命名问题(issue.md Q5) +> `ExportMeterReadingsAction` 的名字让人以为是"导出读数"(把数据从系统导出来),实际是**"下载抄表模板"**(给抄表员填的空模板,预填上次读数)。issue.md Q5 待补:重命名为 `DownloadMeterReadingsTemplateAction`。 + +下载的 Excel 包含: + +| 列 | 说明 | 预填 | +|---|---|---| +| 房号 | asset 编号 | ✅ 系统填(对应每张已建表) | +| 表编号 | meter code | ✅ 系统填 | +| 费用类型 | 水/电/燃气 | ✅ 系统填 | +| 上次读数 | 上月该表 reading | ✅ 系统填(供抄表员对比)| +| **本次读数** | 本月该表 reading | **❌ 空,抄表员填**| +| 抄表日期 | 本次 read_at | **❌ 空,抄表员填**(或默认月底)| +| 备注 | 选填 | ❌ | + +预填上次读数让抄表员**对比时有参考**(本次 > 上次 才合理),也防止漏填行(看清楚每行该填什么)。 + +### 第 2 步:抄表员填本月数据 + +抄表员李师傅团队**整月**(或月末几天)做这事: + +- 现场抄表 + 拍照(用 App / 纸质本) +- 数据填入模板 +- 完成后交回王主管 + +### 第 3 步:上传导入 + +后台 → 计量表 → 列表 → 顶部 **"导入抄表数据"** 按钮(`ImportActionWithExcel` + `MeterReadingsImporter`)→ 选 asset_type → 上传 → 提交。 + +系统: + +1. 解析 Excel(走 `BaseImporter`) +2. 每行校验(meter 存在?asset 匹配?读数合法?) +3. 批量建 MeterReading(每行一条,`source=manual`,`operated_by=导入操作员`,无 photo_url —— 走批量没拍照) +4. 算 consumption +5. 报告:成功 N 条,失败 M 条 + +> [!warning] 批量导入无 photo_url +> Excel 模板没法批量上传照片。批量导入的 reading **photo_url 为空**。 +> +> 业务上推荐:**抄表员独立留照片**(手机相册按月归档),业户事后争议时翻照片(虽然不在系统里)。或者: +> - 高争议表(住宅)走 [[read-single-meter-manual|单录 + 拍照]] +> - 低争议表(商铺 / 公共)走批量导入(节省时间) + +### 第 4 步:核对 + +导入后: + +- `MetersNeedingReadingListWidget` 显示"本月未抄表"清单 → 此时应 0 条(或个别遗漏) +- 抽样验证几张表的 reading 数据正确 +- `HighConsumptionReadingsListWidget` 看是否有异常用量 + +### 第 5 步:触发账单生成 + +导入完成后: + +- **自动触发**(`MeterReadingsImporter` 完成后默认调 `GenerateBillsFromMeterReadingsAction`) +- 或**手动触发**(`ListMeters` 上的"生成账单"按钮,选刚导入的 readings) + +详见 [[bill-generation-pipeline]]。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 李师傅 + participant 王主管 + participant Filament + participant MeterReadingsImporter + participant GenerateBills[GenerateBillsFromMeterReadingsAction] + participant 数据库 + + 王主管->>Filament: 下载抄表模板(预填上次读数) + Filament-->>王主管: 模板.xlsx + 王主管->>李师傅: 转发模板 + 李师傅->>李师傅: 现场抄表 + 填模板 + 李师傅->>王主管: 已填模板.xlsx + + 王主管->>Filament: 导入抄表数据 → 选 asset_type + 上传 + Filament->>MeterReadingsImporter: parse + chunk 处理(100/批) + + loop 每批 + MeterReadingsImporter->>数据库: 开启事务 + loop 每行 + MeterReadingsImporter->>数据库: 校验 meter + asset + alt 通过 + MeterReadingsImporter->>数据库: 建 MeterReading + else 失败 + MeterReadingsImporter->>MeterReadingsImporter: 记失败行 + end + end + alt 全成功 + MeterReadingsImporter->>数据库: 提交 + else 任一失败 + MeterReadingsImporter->>数据库: 回滚整批 + end + end + + MeterReadingsImporter->>GenerateBills: handle(刚建的 readings) + GenerateBills->>数据库: 批量建 Bill + 回写 reading.bill_id + + MeterReadingsImporter-->>Filament: 报告 + Filament-->>王主管: 通知 + 失败行下载 +``` + +## 抄表员视角(李师傅) + +整月抄表流程: + +1. **第 1-5 周(月初)**:正常工作 + 部分非紧急表抄(若有时间) +2. **第 25-30 日(月末)**:集中抄表 + - 按楼栋 + 单元顺序(避免漏) + - 每户:开门(若有人)/ 看公共表(若装在外) + - 读表 → 拍照 → 填模板 +3. **30 日 / 月底**:整理完整 Excel → 交回王主管 + +工作量:1,200 张表 / 月 / 1 抄表员 → 约 40 张 / 天 / 5 天工作。比单录(后台一张张点)快 3-5 倍。 + +## 业户视角 + +业户**无感知** —— 抄表员上门时业户可能在家也可能不在(公共表 / 燃气表通常装在楼道,不用进户)。 + +## `MeterReadingsImporter` 双义列名问题(issue.md Q5) + +> [!warning] 已知 silent corruption 风险 +> 当前 `MeterReadingsImporter` 用**一个 Importer 处理两种 Excel layout**(住宅单元 vs 商铺/公共),列 label 是"双义"形式(如 `'层编号/费用类型'`),靠 `$this->options['asset_type']` 决定列含义。 +> +> **风险**:导入时用户选错 `asset_type`: +> - 系统不报错(列存在,数据有值,看着像合法) +> - 但数据写到**错误字段**(silent corruption) +> - 业务人员事后核对才发现数据错位 +> +> **修复**(issue.md Q5 待补):拆成 `MeterReadingsImporterForUnit` + `MeterReadingsImporterForShop`,每个列含义固定。 +> +> **当前预防**:导入时**务必仔细确认** asset_type 选项 + 抽样核对前几行数据。 + +## 常见问题 + +> [!question] 导入失败的常见原因? +> - meter 不存在(asset 或 code 找不到对应表) +> - meter 已退役(`is_active=false`) +> - 读数倒走(本次 < 上次) +> - 读数格式错(非数字 / 含中文) +> - asset_type 选错(silent corruption,不报错但数据错) + +> [!question] 已导入想撤销? +> Reading 不可改 / 不可删(若已生成 Bill 更严)。撤销 = 走 [[exception-readings-locked-after-bill]] 流程,复杂。 +> +> **预防**:导入前抽样核对 Excel + 选对 asset_type + 小批量先试。 + +> [!question] 同一张表本月被重复导入(填了两次 Excel)? +> 看 `MeterReadingsImporter` 是否有"同 meter + 同 read_at 不允许"的守护。如果没有 → 系统建两条 reading → 两条都算用量 → 业户被算两遍。**严重 bug**,需要业务人员核对避免重复导入。 + +> [!question] 部分表本月没抄怎么办? +> `MetersNeedingReadingListWidget` 会显示"本月未抄"清单。业务人员可: +> - 让抄表员补抄(走 [[read-single-meter-manual|单录]]) +> - 让业户自报读数(部分物业接受,但需核对) +> - 跳过本月(下月一起算,业户账单可能突然变高) + +> [!question] 商铺 / 公共表与住宅表能合并导入吗? +> 看 Excel 模板设计。当前**asset_type 是必选项**,即一次导入只能处理一种类型。要分两次导入(住宅一次,商铺一次,公共一次)。 + +> [!question] 导入完成后立即生成账单还是等月底? +> 取决于业务流程: +> - **导入即生成**:`MeterReadingsImporter` 完成后自动调 GenerateBills(默认推荐) +> - **手动触发**:业务人员后续审核 reading 后再触发生成 +> +> 当前实现应是"自动生成"模式(看 Importer 配置)。 + +## 异常分支 + +- 单张表录入 → [[read-single-meter-manual]] +- 集抄自动 → [[read-via-iot-remote-source]] +- 已导入发现错 → [[exception-readings-locked-after-bill]] +- 高用量异常 → [[exception-high-consumption]] +- 抄表完成率审计 → [[audit-meters-needing-reading]] + +## 相关文档 + +- [[meter-vs-meter-reading]] +- [[reading-source-and-photo-proof]] +- [[bill-generation-pipeline]] +- [[read-single-meter-manual]] +- [[read-via-iot-remote-source]] +- [[init-new-community-batch]] diff --git a/prop-acc/scenarios/meter/read-single-meter-manual.md b/prop-acc/scenarios/meter/read-single-meter-manual.md new file mode 100644 index 0000000..3dd0151 --- /dev/null +++ b/prop-acc/scenarios/meter/read-single-meter-manual.md @@ -0,0 +1,170 @@ +--- +title: prop-acc · meter · 场景 - 单张表后台手动录入 +aliases: + - 手动抄表单录 + - 单张表录入 + - read-single-meter-manual + - 场景-单张表手动抄表 +tags: + - 场景 + - prop-acc + - 计量表 + - 抄表 +audience: + - 业务人员 + - 抄表员 +status: 已发布 +sub_feature: meter +last_review: 2026-05-26 +code_version: 2026-05-22 +--- + +# 场景:单张表后台手动录入 + +抄表员**单张表**录入读数。最基础的抄表方式 —— 适合小规模物业 / 个别补抄 / 集抄掉线的兜底。 + +## 典型情境 + +> [!example] 真实情境 +> 嘉禾花园抄表员李师傅本月集抄系统**有 5 户掉线**(IoT 表故障 / 信号不好)。这 5 户他亲自上门读表,然后回办公室后台单条录入。 +> +> 本场景:其中一户(张阿姨 12-3-501 的电表 E-501,本月读数 5,280)。 + +## 抄表员视角 + +### 第 1 步:现场读表 + +到张阿姨家: + +1. 找到电表(通常装在玄关 / 门外配电箱) +2. **看清表头数字**:5280 +3. **拍照存证**(`photo_url`,见 [[reading-source-and-photo-proof]] + [[read-with-photo-proof]]) +4. 记录(写在抄表本子上或手机备忘) + +### 第 2 步:回办公室录入 + +打开手机 App / 浏览器后台 → 计量表 → 找 E-501 → 进 `ViewMeter` → 滚动到下方"抄表读数"(`MeterReadingsRelationManager`)→ 点 **"新增"** 按钮。 + +> 替代路径:抄表员手机 App 直接现场录入,无需回办公室。当前若有此 App 走集成接口,无此 App 走后台。 + +### 第 3 步:填表单 + +| 字段 | 填什么 | +|---|---| +| **抄表日期(`read_at`)** | 2026-05-26(默认今天)| +| **当前读数(`current_reading`)** | 5280(物理表头数字)| +| **来源(`source`)** | `manual`(默认)| +| **拍照(`photo_url`)** | 上传现场照片(若有要求)| +| **操作员(`operated_by`)** | 自动填李师傅(当前登录用户)| +| **备注(`memo`)** | 选填,如 "集抄掉线手动补抄" | + +> [!info] previous_reading 不用填 +> 系统自动从该 meter 最近一条 reading 取(或从 `Meter.initial_reading` 取)作为 previous,自动算 `consumption = (current - previous) × multiplier`。 +> +> 例:上次抄表 5050 → previous=5050 → consumption = (5280 - 5050) × 1 = 230 度 + +### 第 4 步:提交 + +系统: + +1. 校验 `meter.is_active=true`(退役表不能抄,详见 [[decommission-and-locking]]) +2. 校验 `current_reading >= previous_reading`(若有此守护;否则即"读数倒走"异常,见 [[exception-high-consumption]] 相关) +3. 算 consumption +4. 建 MeterReading(`bill_id=null`,即未生成账单) +5. (可选)若配置了"抄表即生成账单",自动调 `GenerateBillsFromMeterReadingsAction`(详见 [[bill-generation-pipeline]]) + +提交后跳回 `ViewMeter`,新 reading 显示在列表第一条。 + +## 业务人员视角 + +业务人员**通常不直接抄表**,但会做: + +- **审核**抄表员录入的数据(看高用量异常,见 [[exception-high-consumption]]) +- **生成账单**(批量,月底统一,走 `GenerateBillsFromMeterReadingsAction`) +- **核对**抄表完成率(`MetersNeedingReadingListWidget`) + +## 系统流程 + +```mermaid +sequenceDiagram + participant 李师傅[抄表员] + participant Filament + participant MeterReadingsRelationManager + participant 数据库 + + Note over 李师傅: 现场读表 5280 + + 李师傅->>Filament: ViewMeter(E-501) → 新增 reading + Filament->>MeterReadingsRelationManager: 渲染 form + 李师傅->>MeterReadingsRelationManager: 填 current=5280 + 上传照片 + 提交 + MeterReadingsRelationManager->>数据库: 校验 meter.is_active=true + MeterReadingsRelationManager->>数据库: 查 previous_reading(上次 5050) + MeterReadingsRelationManager->>数据库: 算 consumption=(5280-5050)*1=230 + MeterReadingsRelationManager->>数据库: 建 MeterReading(source=manual, operated_by=李师傅, photo_url, bill_id=null) + Filament-->>李师傅: 显示新 reading 230 度 +``` + +## 业户视角 + +业户**无感知** —— 张阿姨可能根本不知道李师傅上门读了表。 + +下个月物业账单出来,显示"5 月电费:用电 230 度,¥XXX",业户看明白即可。如果有异议 → 物业拿出 `photo_url` 照片证明。 + +## 何时用本场景 + +| 场景 | 用本场景? | +|---|---| +| 集抄系统正常运行 | ❌ 走 [[read-via-iot-remote-source]] | +| 集抄系统某户掉线 | ✅ 个别补抄 | +| 小规模物业未上集抄(全靠人工)| ✅ 但建议升级到 [[read-batch-via-excel-import]](效率高)| +| 抄表数据需要事后修正 | ❌ Reading 不可改,见 [[exception-readings-locked-after-bill]] | +| 业户家暂时无人,无法抄 | 抄表员标"未抄",月底再补 | + +## 常见问题 + +> [!question] 读数倒走(current < previous)怎么办? +> 看 Form 是否有守护。若**允许提交**:系统建 reading,consumption 是负数 → 后续生成 Bill 时 Calculator 可能抛错 / 给 0 / 给 min_amount。 +> +> **业务上**读数倒走通常意味着: +> - 表故障(乱跳)→ 走 [[replace-broken-meter|换表]] +> - 抄表录错(可能是本月录上月数 / 笔误)→ 立即改(若没生成 Bill 可改;若有 Bill 见 [[exception-readings-locked-after-bill]]) +> - 业户偷电 / 物理改表 → 严重事件,法务介入 + +> [!question] 读完一户表才发现没拍照怎么办? +> 看物业政策严格度: +> - **必须拍照**:回去补拍 → 上传(理论上事后照片也是凭据,但不如当场) +> - **建议拍照**:本次录入时备注"未拍照" → 业户事后无异议则 OK +> +> 长期不拍照 = 业户争议时无凭证 = 物业被动。 + +> [!question] 抄表日期填错了能改吗? +> Reading 不可改([[decommission-and-locking]] 第 2 锁)。若 `read_at` 填错且**没生成 Bill** → 见 Policy 是否允许删 + 重建。若**已生成 Bill** → 复杂,走作废 Bill 流程([[exception-readings-locked-after-bill]])。 +> +> **预防**:Form 默认带入今天日期,改之前确认。 + +> [!question] 一次录多张表(几十户)用单录效率太低? +> 是的。**走 [[read-batch-via-excel-import]] 批量导入**或 [[read-via-iot-remote-source]] IoT 自动。本场景适合 1-10 张表的个别情况。 + +> [!question] 录入后立即生成 Bill 吗? +> 看配置: +> - **抄表即生成 Bill**:Form 提交后自动调 `GenerateBillsFromMeterReadingsAction`(每条 reading 立即变 Bill) +> - **月底批量生成 Bill**:Form 提交后只建 reading,月底业务人员统一触发批量生成 +> +> 当前实现看具体 Filament 配置,两种都支持。 + +## 异常分支 + +- 批量录入 → [[read-batch-via-excel-import]] +- 集抄自动 → [[read-via-iot-remote-source]] +- 拍照存证 → [[read-with-photo-proof]] +- 读完发现是错的 → 立即改(无 Bill)/ [[exception-readings-locked-after-bill]](有 Bill) +- 异常用量 → [[exception-high-consumption]] + +## 相关文档 + +- [[meter-vs-meter-reading]] +- [[reading-source-and-photo-proof]] +- [[bill-generation-pipeline]] +- [[read-batch-via-excel-import]] +- [[read-via-iot-remote-source]] +- [[exception-high-consumption]]