229 lines
8.6 KiB
Markdown
229 lines
8.6 KiB
Markdown
---
|
|
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]]
|