ether-docs/02-DESIGN/detail/DETAIL-FINANCE.md

811 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 财务与收费域 - 详细设计
**文档类型**: 详细设计文档
**生成日期**: 2026-05-18
**数据来源**: REVERSE-FINANCE.md反推文档+ 04-FINANCE.md原设计文档+ 实际代码分析
---
## 一、功能点清单
| 编号 | 功能模块 | 功能点 | 实现状态 | 说明 |
|------|---------|--------|---------|------|
| FIN-001 | 能耗计量管理 | 计量点CRUD | 已实现 | module-mdm 中 EnergyMeter |
| FIN-002 | 能耗计量管理 | 计量点编码自动生成 | 已实现 | EM + yyyyMMddHHmmss |
| FIN-003 | 能耗计量管理 | 计量点软删除 | 已实现 | 状态设为 INACTIVE |
| FIN-004 | 能耗录入 | 能耗数据录入 | 已实现 | 手动录入 + IoT预留 |
| FIN-005 | 能耗录入 | 读数递增校验 | 已实现 | 当前读数 >= 上次读数 |
| FIN-006 | 能耗录入 | 自动计算消耗量与费用 | 已实现 | consumption = current - previous; amount = consumption * unitPrice |
| FIN-007 | 能耗统计 | 按类型统计消耗 | 已实现(有缺陷) | 当前全部归入LIGHTING需修复 |
| FIN-008 | 能耗统计 | 单方能耗 | 已实现 | 遍历项目ACTIVE计量点累加 |
| FIN-009 | 收费项目管理 | 收费项目CRUD | 未实现 | FeeItem 实体不存在 |
| FIN-010 | 收费项目管理 | 周期性收费配置 | 未实现 | 按月/季/年收费 |
| FIN-011 | 收费项目管理 | 一次性收费配置 | 未实现 | 临时收费项目 |
| FIN-012 | 收费项目管理 | 按面积计费配置 | 未实现 | 面积 * 单价 |
| FIN-013 | 收费项目管理 | 按用量计费配置 | 未实现 | 用量 * 单价(对接能耗) |
| FIN-014 | 收费项目管理 | 固定金额计费配置 | 未实现 | 每月固定金额 |
| FIN-015 | 账单管理 | 账单自动生成 | 未实现 | 根据收费项目规则按账期生成 |
| FIN-016 | 账单管理 | 账单手动生成 | 未实现 | 人工创建单笔账单 |
| FIN-017 | 账单管理 | 批量账单生成 | 未实现 | 一次性为项目所有业主生成 |
| FIN-018 | 账单管理 | 账单状态流转 | 未实现 | UNPAID/PARTIAL_PAID/PAID/OVERDUE/CANCELLED |
| FIN-019 | 账单管理 | 催缴提醒 | 未实现 | 到期前3天提醒 + 逾期催缴 |
| FIN-020 | 账单管理 | 账单导出 | 未实现 | Excel/PDF 格式 |
| FIN-021 | 支付管理 | 线下收款登记 | 未实现 | 现金/银行转账/刷卡 |
| FIN-022 | 支付管理 | 线上支付对接 | 未实现 | 微信/支付宝 |
| FIN-023 | 支付管理 | 支付确认 | 未实现 | 支付状态确认 |
| FIN-024 | 支付管理 | 支付记录查询 | 未实现 | 按账单/业主/时间查询 |
| FIN-025 | 退款管理 | 退款申请 | 未实现 | 业主发起退款 |
| FIN-026 | 退款管理 | 退款审核 | 未实现 | 审批流程 |
| FIN-027 | 退款管理 | 退款执行 | 未实现 | 原路退回/线下退款 |
| FIN-028 | 滞纳金 | 滞纳金自动计算 | 未实现 | 逾期天数 * 日利率 |
| FIN-029 | 滞纳金 | 滞纳金上限控制 | 未实现 | 不超过 maxLateFee |
| FIN-030 | 能耗对接 | 能耗数据与账单对接 | 未实现 | EnergyConsumption -> FeeBill |
| FIN-031 | 能耗修复 | 修复按类型统计 | 未实现 | 按 meter.energyType 真正分项汇总 |
| FIN-032 | 能耗修复 | 统一前后端能源类型枚举 | 未实现 | 后端6种 vs 前端5种 |
| FIN-033 | 财务报表 | 收费统计 | 未实现 | 按项目/类型/时间维度 |
| FIN-034 | 财务报表 | 欠费分析 | 未实现 | 逾期/欠费金额统计 |
| FIN-035 | 财务报表 | 收入趋势 | 未实现 | 月度/季度趋势图 |
---
## 二、数据结构设计
### 2.1 已实现实体
#### 2.1.1 EnergyMeter能源计量点
**所在模块**: module-mdm
**数据库表**: `ops_energy_meter`
**源码**: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java`
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|--------|------|-----------|------|------|
| id | UUID | id | PK | 主键 |
| projectId | UUID | project_id | NOT NULL | 所属项目 |
| meterCode | String | meter_code | NOT NULL, UNIQUE | 计量点编码EM + yyyyMMddHHmmss |
| meterName | String | meter_name | NOT NULL | 计量点名称 |
| energyType | EnergyType | energy_type | NOT NULL | 能源类型枚举 |
| spaceNodeId | UUID | space_node_id | -- | 关联空间节点 |
| installationLocation | String | installation_location | -- | 安装位置 |
| ratedCapacity | BigDecimal(10,2) | rated_capacity | -- | 额定容量 |
| unitPrice | BigDecimal(10,4) | unit_price | -- | 单价4位小数精度 |
| status | Status | status | NOT NULL, 默认ACTIVE | 状态枚举 |
| createdAt | LocalDateTime | created_at | -- | 创建时间 |
| updatedAt | LocalDateTime | updated_at | -- | 更新时间 |
**枚举定义**:
```java
public enum EnergyType {
LIGHTING("照明插座用电"),
HVAC("空调用电"),
POWER("动力用电"),
SPECIAL("特殊用电"),
WATER("给排水"),
GAS("燃气");
}
public enum Status {
ACTIVE, INACTIVE
}
```
#### 2.1.2 EnergyConsumption能耗记录
**所在模块**: module-mdm
**数据库表**: `ops_energy_consumption`
**源码**: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java`
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|--------|------|-----------|------|------|
| id | UUID | id | PK | 主键 |
| projectId | UUID | project_id | NOT NULL | 所属项目 |
| meterId | UUID | meter_id | NOT NULL | 关联计量点 |
| consumptionDate | LocalDate | consumption_date | NOT NULL | 记录日期 |
| previousReading | BigDecimal(12,2) | previous_reading | -- | 上次读数 |
| currentReading | BigDecimal(12,2) | current_reading | -- | 当前读数 |
| consumption | BigDecimal(12,2) | consumption | NOT NULL | 消耗量 = current - previous |
| amount | BigDecimal(10,2) | amount | -- | 费用 = consumption * unitPrice |
| recordedBy | UUID | recorded_by | -- | 记录人 |
| recordMethod | RecordMethod | record_method | -- | 记录方式默认MANUAL |
| remarks | String(1000) | remarks | -- | 备注 |
| createdAt | LocalDateTime | created_at | -- | 创建时间 |
**数据库索引**:
```sql
idx_ec_meter_date -- (meter_id, consumption_date)
idx_ec_project_date -- (project_id, consumption_date)
```
**枚举定义**:
```java
public enum RecordMethod {
MANUAL, // 手动录入
IOT // IoT自动采集
}
```
---
### 2.2 待实现实体
#### 2.2.1 FeeItem收费项目
**目标模块**: module-finance待创建
**目标表**: `fin_fee_item`
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|--------|------|-----------|------|------|
| id | UUID | id | PK | 主键 |
| projectId | UUID | project_id | NOT NULL | 所属项目 |
| code | VARCHAR(50) | code | NOT NULL, UNIQUE | 收费项目编码FI + yyyyMMddHHmmss |
| name | VARCHAR(100) | name | NOT NULL | 收费项目名称 |
| type | VARCHAR(20) | type | NOT NULL | 收费类型枚举 |
| billingMethod | VARCHAR(20) | billing_method | NOT NULL | 计费方式枚举 |
| unitPrice | DECIMAL(12,4) | unit_price | -- | 单价(按用量/面积计费时使用) |
| unit | VARCHAR(20) | unit | -- | 计量单位kWh/吨/平方米/月/次) |
| fixedAmount | DECIMAL(12,2) | fixed_amount | -- | 固定金额FIXED计费方式 |
| billDay | INT | bill_day | -- | 出账日每月几号出账1-28 |
| dueDay | INT | due_day | -- | 到期日出账后第N天到期 |
| overdueDay | INT | overdue_day | -- | 逾期日到期后第N天开始计滞纳金 |
| enableLateFee | BOOLEAN | enable_late_fee | NOT NULL, 默认false | 是否启用滞纳金 |
| lateFeeRate | DECIMAL(8,6) | late_fee_rate | -- | 日滞纳金利率如0.0005 = 万分之五/天) |
| maxLateFee | DECIMAL(12,2) | max_late_fee | -- | 滞纳金上限金额 |
| description | VARCHAR(500) | description | -- | 收费项目说明 |
| status | VARCHAR(20) | status | NOT NULL, 默认ENABLED | 状态ENABLED/DISABLED |
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
| createdBy | UUID | created_by | -- | 创建人 |
**枚举定义**:
```java
public enum FeeType {
PROPERTY("物业费"),
PARKING("停车费"),
WATER("水费"),
ELECTRICITY("电费"),
GAS("燃气费"),
HEATING("供暖费"),
REPAIR_FUND("维修基金"),
OTHER("其他");
}
public enum BillingMethod {
FIXED("固定金额"), // 每月固定金额
AREA("按面积计费"), // 面积 * 单价
USAGE("按用量计费"), // 用量 * 单价(对接能耗)
CUSTOM("自定义"); // 自定义计费规则
}
```
**数据库索引**:
```sql
idx_fi_project_status -- (project_id, status)
idx_fi_code -- (code) -- UNIQUE约束自带索引
idx_fi_type -- (type, status)
```
**外键关系**:
```sql
fk_fi_project -- project_id REFERENCES mdm_project(id)
```
---
#### 2.2.2 FeeBill收费账单
**目标模块**: module-finance待创建
**目标表**: `fin_fee_bill`
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|--------|------|-----------|------|------|
| id | UUID | id | PK | 主键 |
| billNo | VARCHAR(30) | bill_no | NOT NULL, UNIQUE | 账单编号BL + yyyyMMdd + 4位序号 |
| feeItemId | UUID | fee_item_id | NOT NULL | 关联收费项目 |
| projectId | UUID | project_id | NOT NULL | 所属项目 |
| spaceNodeId | UUID | space_node_id | -- | 关联空间节点(房产) |
| ownerId | UUID | owner_id | -- | 关联业主ID |
| billPeriod | VARCHAR(20) | bill_period | NOT NULL | 账期如2026-05 |
| billDate | DATE | bill_date | NOT NULL | 出账日期 |
| dueDate | DATE | due_date | NOT NULL | 到期日期 |
| overdueDate | DATE | overdue_date | -- | 逾期日期 |
| usageAmount | DECIMAL(12,2) | usage_amount | -- | 用量(按用量计费时) |
| usageUnit | VARCHAR(20) | usage_unit | -- | 用量单位 |
| area | DECIMAL(12,2) | area | -- | 面积(按面积计费时) |
| amount | DECIMAL(12,2) | amount | NOT NULL | 应收金额 |
| lateFee | DECIMAL(12,2) | late_fee | 默认0 | 滞纳金 |
| discount | DECIMAL(12,2) | discount | 默认0 | 优惠金额 |
| payableAmount | DECIMAL(12,2) | payable_amount | NOT NULL | 应付金额 = amount + lateFee - discount |
| paidAmount | DECIMAL(12,2) | paid_amount | 默认0 | 已付金额 |
| status | VARCHAR(20) | status | NOT NULL, 默认UNPAID | 账单状态枚举 |
| sourceType | VARCHAR(20) | source_type | -- | 来源类型AUTO/MANUAL/IMPORT |
| sourceId | UUID | source_id | -- | 来源ID如能耗记录ID |
| remark | VARCHAR(500) | remark | -- | 备注 |
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
| createdBy | UUID | created_by | -- | 创建人 |
**枚举定义**:
```java
public enum BillStatus {
UNPAID("未支付"),
PARTIAL_PAID("部分支付"),
PAID("已支付"),
OVERDUE("已逾期"),
CANCELLED("已取消");
}
public enum BillSourceType {
AUTO("自动生成"),
MANUAL("手动创建"),
IMPORT("导入");
}
```
**数据库索引**:
```sql
idx_fb_bill_no -- (bill_no) -- UNIQUE约束自带索引
idx_fb_project_period -- (project_id, bill_period)
idx_fb_owner_status -- (owner_id, status)
idx_fb_fee_item -- (fee_item_id, bill_period)
idx_fb_space_node -- (space_node_id, bill_period)
idx_fb_due_date -- (due_date, status) -- 用于催缴查询
idx_fb_overdue -- (status, overdue_date) -- 用于滞纳金计算
```
**外键关系**:
```sql
fk_fb_fee_item -- fee_item_id REFERENCES fin_fee_item(id)
fk_fb_project -- project_id REFERENCES mdm_project(id)
fk_fb_space_node -- space_node_id REFERENCES mdm_space_node(id)
```
---
#### 2.2.3 FeePayment支付记录
**目标模块**: module-finance待创建
**目标表**: `fin_fee_payment`
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|--------|------|-----------|------|------|
| id | UUID | id | PK | 主键 |
| paymentNo | VARCHAR(30) | payment_no | NOT NULL, UNIQUE | 支付编号PY + yyyyMMddHHmmss |
| billId | UUID | bill_id | NOT NULL | 关联账单 |
| amount | DECIMAL(12,2) | amount | NOT NULL | 支付金额 |
| method | VARCHAR(20) | method | NOT NULL | 支付方式枚举 |
| thirdPartyNo | VARCHAR(100) | third_party_no | -- | 第三方交易号(微信/支付宝) |
| status | VARCHAR(20) | status | NOT NULL, 默认PENDING | 支付状态枚举 |
| failReason | VARCHAR(500) | fail_reason | -- | 失败原因 |
| paymentTime | TIMESTAMP | payment_time | -- | 实际支付时间 |
| operatorId | UUID | operator_id | -- | 操作人(线下收款时为收费员) |
| receiptNo | VARCHAR(30) | receipt_no | -- | 收据编号 |
| remark | VARCHAR(500) | remark | -- | 备注 |
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
**枚举定义**:
```java
public enum PaymentMethod {
WECHAT("微信支付"),
ALIPAY("支付宝"),
CASH("现金"),
BANK_TRANSFER("银行转账"),
CARD("刷卡");
}
public enum PaymentStatus {
PENDING("待支付"),
SUCCESS("支付成功"),
FAILED("支付失败"),
REFUNDED("已退款");
}
```
**数据库索引**:
```sql
idx_fp_payment_no -- (payment_no) -- UNIQUE约束自带索引
idx_fp_bill_id -- (bill_id)
idx_fp_status -- (status, payment_time)
idx_fp_method -- (method, payment_time)
idx_fp_operator -- (operator_id, payment_time)
```
**外键关系**:
```sql
fk_fp_bill -- bill_id REFERENCES fin_fee_bill(id)
```
---
#### 2.2.4 FeeRefund退款记录
**目标模块**: module-finance待创建
**目标表**: `fin_fee_refund`
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|--------|------|-----------|------|------|
| id | UUID | id | PK | 主键 |
| refundNo | VARCHAR(30) | refund_no | NOT NULL, UNIQUE | 退款编号RF + yyyyMMddHHmmss |
| paymentId | UUID | payment_id | NOT NULL | 关联支付记录 |
| billId | UUID | bill_id | NOT NULL | 关联账单 |
| amount | DECIMAL(12,2) | amount | NOT NULL | 退款金额 |
| reason | VARCHAR(500) | reason | NOT NULL | 退款原因 |
| status | VARCHAR(20) | status | NOT NULL, 默认PENDING | 退款状态枚举 |
| applicantId | UUID | applicant_id | NOT NULL | 申请人ID |
| approverId | UUID | approver_id | -- | 审批人ID |
| approveTime | TIMESTAMP | approve_time | -- | 审批时间 |
| approveRemark | VARCHAR(500) | approve_remark | -- | 审批备注 |
| thirdPartyRefundNo | VARCHAR(100) | third_party_refund_no | -- | 第三方退款单号 |
| refundTime | TIMESTAMP | refund_time | -- | 实际退款时间 |
| refundMethod | VARCHAR(20) | refund_method | -- | 退款方式ORIGINAL/OFFLINE |
| remark | VARCHAR(500) | remark | -- | 备注 |
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
**枚举定义**:
```java
public enum RefundStatus {
PENDING("待审批"),
APPROVED("已审批"),
REJECTED("已拒绝"),
REFUNDED("已退款"),
FAILED("退款失败");
}
public enum RefundMethod {
ORIGINAL("原路退回"),
OFFLINE("线下退款");
}
```
**数据库索引**:
```sql
idx_fr_refund_no -- (refund_no) -- UNIQUE约束自带索引
idx_fr_payment -- (payment_id)
idx_fr_bill -- (bill_id)
idx_fr_status -- (status)
idx_fr_applicant -- (applicant_id, status)
```
**外键关系**:
```sql
fk_fr_payment -- payment_id REFERENCES fin_fee_payment(id)
fk_fr_bill -- bill_id REFERENCES fin_fee_bill(id)
```
---
### 2.3 实体关系图
```
EnergyMeter (module-mdm) FeeItem (module-finance)
| |
v v
EnergyConsumption ---费用计算---> FeeBill (自动/手动生成)
(抄表数据) | |
| +---> FeePayment (支付记录)
| |
| v
| FeeRefund (退款记录)
|
+-- sourceType=AUTO, sourceId=EnergyConsumption.id
```
**跨模块引用关系**:
- FeeBill.sourceId 可指向 EnergyConsumption.id按用量计费时
- FeeBill.spaceNodeId 引用 mdm_space_node.id
- FeeBill.ownerId 引用 auth_user.id业主
- FeePayment.operatorId 引用 auth_user.id收费员
- FeeRefund.applicantId / approverId 引用 auth_user.id
---
## 三、API设计
### 3.1 已实现API能耗管理相关端点
**基础路径**: `/api/ops/energy`
**控制器**: `EnergyController`
#### 3.1.1 计量点管理
| 方法 | 路径 | 说明 | 请求参数 | 响应类型 |
|------|------|------|---------|---------|
| POST | `/meters` | 创建计量点 | EnergyMeter JSON | `ApiResponse<EnergyMeter>` |
| GET | `/meters` | 查询计量点列表 | projectId(必填), energyType(可选) | `ApiResponse<List<EnergyMeter>>` |
| GET | `/meters/{id}` | 获取计量点详情 | path: id | `ApiResponse<EnergyMeter>` |
| PUT | `/meters/{id}` | 更新计量点 | path: id, EnergyMeter JSON | `ApiResponse<EnergyMeter>` |
| DELETE | `/meters/{id}` | 删除计量点(软删除) | path: id | `ApiResponse<Void>` |
#### 3.1.2 能耗记录
| 方法 | 路径 | 说明 | 请求参数 | 响应类型 |
|------|------|------|---------|---------|
| POST | `/consumption` | 录入能耗数据 | `{meterId, currentReading, recordedBy}` | `ApiResponse<EnergyConsumption>` |
| GET | `/consumption/{meterId}` | 查询能耗记录 | path: meterId, startDate(可选), endDate(可选) | `ApiResponse<List<EnergyConsumption>>` |
#### 3.1.3 能耗统计
| 方法 | 路径 | 说明 | 请求参数 | 响应类型 |
|------|------|------|---------|---------|
| GET | `/statistics/by-type` | 按类型统计消耗 | projectId, month(yyyy-MM-dd) | `ApiResponse<Map<EnergyType, BigDecimal>>` |
| GET | `/statistics/unit-consumption` | 单方能耗 | projectId, month(yyyy-MM-dd) | `ApiResponse<BigDecimal>` |
---
### 3.2 待实现API收费项目/账单/支付/退款相关端点
#### 3.2.1 FeeItemController -- 收费项目管理
**基础路径**: `/api/finance/fee-items`
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|------|------|------|------------|---------|
| POST | `/` | 创建收费项目 | FeeItemForm JSON | `ApiResponse<FeeItem>` |
| GET | `/` | 查询收费项目列表 | projectId(必填), type(可选), status(可选), page, size | `ApiResponse<PageResponse<FeeItem>>` |
| GET | `/{id}` | 获取收费项目详情 | path: id | `ApiResponse<FeeItem>` |
| PUT | `/{id}` | 更新收费项目 | path: id, FeeItemForm JSON | `ApiResponse<FeeItem>` |
| PUT | `/{id}/status` | 启用/禁用收费项目 | `{status: "ENABLED"/"DISABLED"}` | `ApiResponse<Void>` |
| DELETE | `/{id}` | 删除收费项目 | path: id | `ApiResponse<Void>` |
**FeeItemForm**:
```json
{
"projectId": "UUID",
"name": "物业费",
"type": "PROPERTY",
"billingMethod": "AREA",
"unitPrice": 3.50,
"unit": "平方米/月",
"fixedAmount": null,
"billDay": 1,
"dueDay": 15,
"overdueDay": 5,
"enableLateFee": true,
"lateFeeRate": 0.0005,
"maxLateFee": 500.00,
"description": "住宅物业费"
}
```
#### 3.2.2 FeeBillController -- 账单管理
**基础路径**: `/api/finance/bills`
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|------|------|------|------------|---------|
| POST | `/` | 手动创建账单 | FeeBillForm JSON | `ApiResponse<FeeBill>` |
| POST | `/generate` | 按收费项目自动生成账单 | `{projectId, feeItemId, billPeriod}` | `ApiResponse<List<FeeBill>>` |
| POST | `/generate-batch` | 批量生成账单 | `{projectId, billPeriod, feeItemIds[]}` | `ApiResponse<BatchResult>` |
| GET | `/` | 查询账单列表 | projectId, ownerId, status, billPeriod, page, size | `ApiResponse<PageResponse<FeeBill>>` |
| GET | `/{id}` | 获取账单详情 | path: id | `ApiResponse<FeeBill>` |
| PUT | `/{id}` | 更新账单 | path: id, FeeBillForm JSON | `ApiResponse<FeeBill>` |
| POST | `/{id}/cancel` | 取消账单 | `{reason}` | `ApiResponse<Void>` |
| GET | `/overdue` | 查询逾期账单 | projectId, page, size | `ApiResponse<PageResponse<FeeBill>>` |
| GET | `/statistics` | 账单统计 | projectId, billPeriod | `ApiResponse<BillStatistics>` |
| GET | `/export` | 导出账单 | projectId, billPeriod, format | `Blob` |
**FeeBillForm**:
```json
{
"feeItemId": "UUID",
"projectId": "UUID",
"spaceNodeId": "UUID",
"ownerId": "UUID",
"billPeriod": "2026-05",
"usageAmount": 150.50,
"usageUnit": "kWh",
"area": 120.00,
"amount": 525.00,
"remark": ""
}
```
**BillStatistics**:
```json
{
"totalAmount": 150000.00,
"paidAmount": 120000.00,
"unpaidAmount": 30000.00,
"overdueAmount": 15000.00,
"lateFeeAmount": 500.00,
"totalCount": 200,
"paidCount": 160,
"unpaidCount": 30,
"overdueCount": 10
}
```
#### 3.2.3 FeePaymentController -- 支付管理
**基础路径**: `/api/finance/payments`
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|------|------|------|------------|---------|
| POST | `/` | 创建支付记录(线下收款) | PaymentForm JSON | `ApiResponse<FeePayment>` |
| GET | `/` | 查询支付记录 | billId, ownerId, method, status, startDate, endDate, page, size | `ApiResponse<PageResponse<FeePayment>>` |
| GET | `/{id}` | 获取支付详情 | path: id | `ApiResponse<FeePayment>` |
| POST | `/{id}/confirm` | 确认支付 | `{status: "SUCCESS"/"FAILED", failReason}` | `ApiResponse<FeePayment>` |
| POST | `/online/prepare` | 发起线上支付 | `{billId, method}` | `ApiResponse<PaymentPrepareResult>` |
| POST | `/online/callback` | 支付回调 | 第三方回调数据 | `ApiResponse<Void>` |
**PaymentForm**:
```json
{
"billId": "UUID",
"amount": 525.00,
"method": "CASH",
"receiptNo": "RCP20260518001",
"remark": ""
}
```
#### 3.2.4 FeeRefundController -- 退款管理
**基础路径**: `/api/finance/refunds`
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|------|------|------|------------|---------|
| POST | `/` | 申请退款 | RefundForm JSON | `ApiResponse<FeeRefund>` |
| GET | `/` | 查询退款记录 | billId, status, startDate, endDate, page, size | `ApiResponse<PageResponse<FeeRefund>>` |
| GET | `/{id}` | 获取退款详情 | path: id | `ApiResponse<FeeRefund>` |
| POST | `/{id}/approve` | 审批通过 | `{approveRemark}` | `ApiResponse<FeeRefund>` |
| POST | `/{id}/reject` | 审批拒绝 | `{approveRemark}` | `ApiResponse<FeeRefund>` |
| POST | `/{id}/execute` | 执行退款 | `{thirdPartyRefundNo, refundMethod}` | `ApiResponse<FeeRefund>` |
**RefundForm**:
```json
{
"paymentId": "UUID",
"billId": "UUID",
"amount": 525.00,
"reason": "重复缴费",
"refundMethod": "ORIGINAL"
}
```
#### 3.2.5 FeeReminderJob -- 催缴定时任务
| 任务 | Cron表达式 | 说明 |
|------|-----------|------|
| 到期提醒 | `0 0 9 * * ?` | 每天上午9点检查3天内到期账单发送提醒 |
| 逾期催缴 | `0 0 10 * * ?` | 每天上午10点检查逾期账单发送催缴通知 |
| 滞纳金计算 | `0 0 2 * * ?` | 每天凌晨2点计算逾期账单滞纳金 |
| 周汇总 | `0 0 18 ? * MON` | 每周一18点汇总逾期账单通知项目经理 |
---
## 四、业务规则
### 4.1 能耗计费规则(已实现 + 待完善)
#### 4.1.1 已实现规则
| 规则编号 | 规则名称 | 规则描述 | 实现位置 |
|---------|---------|---------|---------|
| E-001 | 仪表状态校验 | 只能对ACTIVE状态的仪表进行抄表 | EnergyConsumptionServiceImpl |
| E-002 | 读数递增校验 | 当前读数不能小于上次读数 | EnergyConsumptionServiceImpl |
| E-003 | 自动获取上次读数 | 从最近一条记录获取previousReading首次为0 | EnergyConsumptionServiceImpl |
| E-004 | 自动计算消耗量 | consumption = currentReading - previousReading | EnergyConsumptionServiceImpl |
| E-005 | 自动计算费用 | amount = consumption * meter.unitPrice单价为空时为0 | EnergyConsumptionServiceImpl |
| E-006 | 按月统计 | 根据month参数计算月份起止日期 | EnergyConsumptionServiceImpl |
| E-007 | 单方能耗 | 遍历项目所有ACTIVE计量点累加月度消耗量 | EnergyConsumptionServiceImpl |
#### 4.1.2 待修复缺陷
| 缺陷编号 | 描述 | 严重度 | 修复方案 |
|---------|------|--------|---------|
| E-BUG-001 | getConsumptionByType()将总消耗全部归入LIGHTING | 高 | 按 meter.energyType 真正分项汇总 |
| E-BUG-002 | 前后端能源类型枚举不一致 | 中 | 统一为后端6种枚举前端对齐 |
---
### 4.2 收费项目规则(待设计)
| 规则编号 | 规则名称 | 规则描述 | 适用计费方式 |
|---------|---------|---------|------------|
| FI-001 | 周期性收费 | 按月/季/年定期出账billDay指定出账日 | FIXED / AREA / USAGE |
| FI-002 | 一次性收费 | 临时创建,不参与自动出账 | CUSTOM |
| FI-003 | 临时收费 | 针对特定业主的临时性收费 | CUSTOM |
| FI-004 | 固定金额计费 | 每期收取固定金额如停车费300元/月) | FIXED |
| FI-005 | 按面积计费 | 面积 * 单价如物业费3.5元/平方米/月) | AREA |
| FI-006 | 按用量计费 | 用量 * 单价如电费0.85元/kWh对接能耗数据 | USAGE |
| FI-007 | 编码自动生成 | 格式FI + yyyyMMddHHmmss冲突时追加后缀 | 全部 |
| FI-008 | 禁用不删除 | 收费项目禁用后不再参与自动出账,已有账单不受影响 | 全部 |
| FI-009 | 项目隔离 | 收费项目属于特定项目,跨项目不可见 | 全部 |
---
### 4.3 账单生成规则(待设计)
| 规则编号 | 规则名称 | 规则描述 |
|---------|---------|---------|
| BL-001 | 自动生成 | 根据收费项目的billDay定时任务自动为项目下所有业主生成账单 |
| BL-002 | 手动生成 | 管理员手动为指定业主创建账单 |
| BL-003 | 批量生成 | 一次性为项目所有业主生成某类收费项目的账单 |
| BL-004 | 账单编号生成 | 格式BL + yyyyMMdd + 4位序号序号按天递增 |
| BL-005 | 到期日计算 | dueDate = billDate + feeItem.dueDay天 |
| BL-006 | 逾期日计算 | overdueDate = dueDate + feeItem.overdueDay天 |
| BL-007 | 按用量计费账单 | 从EnergyConsumption获取当期用量计算金额 |
| BL-008 | 按面积计费账单 | 从SpaceNode获取面积计算金额 = 面积 * 单价 |
| BL-009 | 固定金额账单 | 金额 = feeItem.fixedAmount |
| BL-010 | 防重复生成 | 同一收费项目 + 同一业主 + 同一账期不可重复生成 |
| BL-011 | 催缴提醒 | 到期前3天发送提醒逾期后发送催缴通知 |
| BL-012 | 账单取消 | 已支付/部分支付的账单不可取消,需先退款 |
---
### 4.4 支付流程(待设计)
| 规则编号 | 规则名称 | 规则描述 |
|---------|---------|---------|
| PY-001 | 线下收款 | 管理员登记现金/转账/刷卡收款,直接确认成功 |
| PY-002 | 线上支付 | 调用微信/支付宝SDK生成预支付单等待回调确认 |
| PY-003 | 支付确认 | 线上支付由回调确认,线下支付由操作员确认 |
| PY-004 | 部分支付 | 支持部分支付账单状态变为PARTIAL_PAID |
| PY-005 | 超额支付 | 支付金额不可超过应付金额payableAmount - paidAmount |
| PY-006 | 支付编号生成 | 格式PY + yyyyMMddHHmmss |
| PY-007 | 账单状态联动 | 支付成功后更新账单paidAmount和status |
| PY-008 | 收据编号 | 线下收款时生成收据编号 |
**支付状态流转**:
```
PENDING --(支付成功)--> SUCCESS
PENDING --(支付失败)--> FAILED
SUCCESS --(发起退款)--> REFUNDED
```
**账单状态联动**:
```
UNPAID --(部分支付)--> PARTIAL_PAID
UNPAID/PARTIAL_PAID --(全额支付)--> PAID
UNPAID --(超过逾期日)--> OVERDUE
任意状态 --(取消)--> CANCELLED需先退款
```
---
### 4.5 退款流程(待设计)
| 规则编号 | 规则名称 | 规则描述 |
|---------|---------|---------|
| RF-001 | 退款申请 | 业主或管理员发起退款申请,需指定退款原因 |
| RF-002 | 退款金额限制 | 退款金额不可超过原支付金额 |
| RF-003 | 退款审批 | 退款金额 > 1000元需审批否则自动通过 |
| RF-004 | 审批通过 | 审批人确认后退款状态变为APPROVED |
| RF-005 | 审批拒绝 | 审批人可拒绝退款,需填写拒绝原因 |
| RF-006 | 原路退回 | 线上支付的退款原路退回至支付账户 |
| RF-007 | 线下退款 | 现金/转账支付的退款通过线下处理 |
| RF-008 | 退款执行 | 记录第三方退款单号和实际退款时间 |
| RF-009 | 账单金额回退 | 退款成功后账单paidAmount减少状态可能回退 |
| RF-010 | 退款编号生成 | 格式RF + yyyyMMddHHmmss |
**退款状态流转**:
```
PENDING --(审批通过)--> APPROVED
PENDING --(审批拒绝)--> REJECTED
APPROVED --(退款成功)--> REFUNDED
APPROVED --(退款失败)--> FAILED
```
---
### 4.6 滞纳金计算(待设计)
| 规则编号 | 规则名称 | 规则描述 |
|---------|---------|---------|
| LF-001 | 计算触发 | 每天凌晨2点定时任务检查逾期账单 |
| LF-002 | 计算公式 | lateFee = 逾期天数 * payableAmount * lateFeeRate |
| LF-003 | 逾期天数 | 从overdueDate开始计算到当前日期 |
| LF-004 | 上限控制 | lateFee不超过feeItem.maxLateFee |
| LF-005 | 累加计算 | 每天累加,不覆盖之前的滞纳金 |
| LF-006 | 前提条件 | 仅当feeItem.enableLateFee = true时计算 |
| LF-007 | 已支付账单 | 已全额支付的账单不再计算滞纳金 |
| LF-008 | 已取消账单 | 已取消的账单不再计算滞纳金 |
| LF-009 | 部分支付 | 滞纳金基于剩余应付金额计算 |
**滞纳金计算示例**:
```
假设:
payableAmount = 1000元
lateFeeRate = 0.0005(万分之五/天)
maxLateFee = 500元
overdueDate = 2026-05-10
当前日期 = 2026-05-18
计算:
逾期天数 = 8天
每日滞纳金 = 1000 * 0.0005 = 0.5元
累计滞纳金 = 8 * 0.5 = 4元未超过上限500元
```
---
## 五、执行约束
| 约束编号 | 约束名称 | 约束描述 |
|---------|---------|---------|
| CON-001 | 金额精度 | 所有金额字段使用DECIMAL(12,2)单价使用DECIMAL(12,4)利率使用DECIMAL(8,6) |
| CON-002 | 事务一致性 | 账单生成、支付确认、退款执行必须在同一事务中完成账单状态更新 |
| CON-003 | 并发控制 | 支付操作使用乐观锁version字段或状态校验防止重复支付 |
| CON-004 | 审计日志 | 所有收费/支付/退款操作必须记录审计日志 |
| CON-005 | 项目隔离 | 所有查询必须按projectId过滤确保项目间数据隔离 |
| CON-006 | 软删除 | 收费项目使用启用/禁用替代物理删除,账单/支付/退款不允许删除 |
| CON-007 | 编码唯一性 | billNo/paymentNo/refundNo全局唯一生成时需处理冲突 |
| CON-008 | 定时任务幂等 | 催缴/滞纳金计算任务必须幂等,重复执行不产生副作用 |
---
## 六、权限控制
| 权限编码 | 权限名称 | 适用角色 | 说明 |
|---------|---------|---------|------|
| finance:fee-item:list | 查看收费项目 | 项目管理员/财务人员 | 查看项目下收费项目列表 |
| finance:fee-item:create | 创建收费项目 | 系统管理员 | 创建新的收费项目 |
| finance:fee-item:update | 修改收费项目 | 系统管理员 | 修改收费项目配置 |
| finance:fee-item:delete | 删除收费项目 | 系统管理员 | 删除/禁用收费项目 |
| finance:bill:list | 查看账单 | 项目管理员/财务人员 | 查看项目下账单列表 |
| finance:bill:create | 创建账单 | 财务人员 | 手动创建账单 |
| finance:bill:generate | 生成账单 | 财务人员 | 自动/批量生成账单 |
| finance:bill:cancel | 取消账单 | 财务主管 | 取消账单(需审批) |
| finance:bill:export | 导出账单 | 财务人员 | 导出账单Excel/PDF |
| finance:payment:create | 登记收款 | 财务人员 | 线下收款登记 |
| finance:payment:confirm | 确认支付 | 财务人员 | 确认支付状态 |
| finance:payment:list | 查看支付记录 | 财务人员 | 查看支付记录列表 |
| finance:refund:apply | 申请退款 | 财务人员/业主 | 发起退款申请 |
| finance:refund:approve | 审批退款 | 财务主管 | 审批退款申请 |
| finance:refund:execute | 执行退款 | 财务人员 | 执行退款操作 |
| finance:refund:list | 查看退款记录 | 财务人员 | 查看退款记录列表 |
| finance:statistics:view | 查看财务统计 | 项目管理员/财务主管 | 查看财务报表 |
| finance:late-fee:config | 配置滞纳金 | 系统管理员 | 配置滞纳金规则 |
**数据级权限**:
- 财务人员PROJECT数据范围只能查看/操作所属项目的财务数据
- 财务主管ALL数据范围可查看所有项目的财务数据
- 业主SELF数据范围只能查看自己的账单和支付记录
---
## 七、例外情况处理
| 例外编号 | 例外场景 | 处理策略 | 错误码 |
|---------|---------|---------|--------|
| EX-001 | 重复生成账单 | 检查feeItemId + ownerId + billPeriod唯一性已存在则跳过 | 7001 |
| EX-002 | 支付金额超过应付金额 | 校验支付金额 <= payableAmount - paidAmount | 7002 |
| EX-003 | 已支付账单取消 | 拒绝取消,提示需先退款 | 7003 |
| EX-004 | 退款金额超过支付金额 | 校验退款金额 <= 原支付金额 | 7004 |
| EX-005 | 重复支付 | 状态校验,已支付/已退款的支付记录不可再次确认 | 7005 |
| EX-006 | 能耗数据缺失 | 按用量计费时无对应能耗记录生成0元账单并标记异常 | 7006 |
| EX-007 | 业主无关联房产 | 生成账单时业主无房产关联,跳过并记录日志 | 7007 |
| EX-008 | 线上支付超时 | 支付状态保持PENDING由对账任务处理 | 7008 |
| EX-009 | 线上支付回调异常 | 记录原始回调数据,人工介入处理 | 7009 |
| EX-010 | 滞纳金计算溢出 | lateFee不超过maxLateFee上限 | 7010 |
| EX-011 | 收费项目被禁用后自动出账 | 跳过已禁用的收费项目 | 7011 |
| EX-012 | 批量生成部分失败 | 记录成功/失败数量和失败原因返回BatchResult | 7012 |