# 财务与收费域 - 详细设计

**文档类型**: 详细设计文档
**生成日期**: 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 |
