ether-docs/_archive/domains-old/04-FINANCE.md

644 lines
21 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.

# 财务计费领域技术方案
**领域编号**: 4.4
**微服务**: ether-finance
**最后更新**: 2026-04-26
> **更新记录**:
> - 2026-04-26: 反向同步实际代码实现到文档。主要变更:修正 FeeItem/FeeBill/FeePayment 的实现状态标注为"未实现"(原标注为"已实现"与实际代码不符);修正费用催缴/滞纳金的实现状态标注为"未实现"新增能耗计量管理章节EnergyMeter/EnergyConsumption当前在 module-mdm 中实现新增能源类型枚举EnergyType新增记录方式枚举RecordMethod明确能耗管理与财务计费的边界。
---
## 一、领域概述
### 1.1 领域职责
财务计费领域负责物业费用管理全生命周期:
- 收费项目管理(物业费、停车费、水电费等)
- 账单生成与管理
- 支付处理与对账
- 费用催缴通知
- 财务报表与统计
### 1.2 核心概念
| 概念 | 说明 | 对应实体 | 实现状态 |
|------|------|----------|----------|
| **收费项目** | 可收费的项目定义 | FeeItem | 未实现 |
| **账单** | 应收费用单据 | FeeBill | 未实现 |
| **支付记录** | 支付流水记录 | FeePayment | 未实现 |
| **退款** | 退款申请与处理 | FeeRefund | 未实现 |
| **能源计量点** | 能耗数据采集点 | EnergyMeter | 已实现(module-mdm) |
| **能耗记录** | 抄表数据与费用计算 | EnergyConsumption | 已实现(module-mdm) |
---
## 二、领域模型
### 2.1 聚合根设计
#### FeeItem收费项目
```java
@Entity
@Table(name = "fin_fee_item")
@Data
public class FeeItem {
@Id
private UUID id;
private UUID projectId;
private String code; // 项目编码
private String name; // 项目名称
private FeeType type; // PROPERTY/PARKING/WATER/ELECTRICITY/GAS/OTHER
// 计费规则
private BillingMethod billingMethod; // FIXED/AREA/USAGE/CUSTOM
private BigDecimal unitPrice; // 单价
private String unit; // 单位: 元/月、元/㎡·月、元/度
// 账期
private Integer billDay; // 账单日(每月几号)
private Integer dueDay; // 到期日(每月几号)
private Integer overdueDay; // 逾期日(每月几号)
// 滞纳金
private Boolean enableLateFee;
private BigDecimal lateFeeRate; // 滞纳金比例(日利率)
private BigDecimal maxLateFee; // 滞纳金上限
// 状态
private Boolean enabled;
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
// 收费类型枚举
public enum FeeType {
PROPERTY("物业费"),
PARKING("停车费"),
WATER("水费"),
ELECTRICITY("电费"),
GAS("燃气费"),
HEATING("供暖费"),
REPAIR_FUND("维修基金"),
OTHER("其他");
}
// 计费方式枚举
public enum BillingMethod {
FIXED("固定金额"),
AREA("按面积"),
USAGE("按用量"),
CUSTOM("自定义");
}
```
#### FeeBill收费账单
```java
@Entity
@Table(name = "fin_fee_bill")
@Data
public class FeeBill {
@Id
private UUID id;
private UUID projectId;
private String billNo; // 账单编号
// 关联信息
private UUID feeItemId; // 收费项目
private UUID spaceNodeId; // 关联房产
private UUID ownerId; // 关联业主
// 账期
private String billPeriod; // 账期: 2024-02
private LocalDate billDate; // 账单日期
private LocalDate dueDate; // 到期日期
private LocalDate overdueDate; // 逾期日期
// 用量(按用量计费)
private BigDecimal usageAmount; // 用量
private String usageUnit; // 用量单位
// 金额
private BigDecimal amount; // 应收金额
private BigDecimal lateFee; // 滞纳金
private BigDecimal discount; // 优惠金额
private BigDecimal payableAmount; // 应付金额 = amount + lateFee - discount
private BigDecimal paidAmount; // 已付金额
// 状态
private BillStatus status; // UNPAID/PARTIAL_PAID/PAID/OVERDUE/CANCELLED
// 关联支付记录
@OneToMany(mappedBy = "bill")
private List<FeePayment> payments;
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
// 账单状态枚举
public enum BillStatus {
UNPAID("未缴费"),
PARTIAL_PAID("部分缴费"),
PAID("已缴费"),
OVERDUE("已逾期"),
CANCELLED("已取消");
}
```
#### FeePayment支付记录
```java
@Entity
@Table(name = "fin_fee_payment")
@Data
public class FeePayment {
@Id
private UUID id;
private UUID billId;
private String paymentNo; // 支付流水号
private BigDecimal amount; // 支付金额
// 支付方式
private PaymentMethod method; // WECHAT/ALIPAY/CASH/BANK_TRANSFER/CARD
private String thirdPartyNo; // 第三方支付流水号
// 支付状态
private PaymentStatus status; // PENDING/SUCCESS/FAILED/REFUNDED
private String failReason; // 失败原因
// 时间
private LocalDateTime paymentTime;
private String remark;
// 操作人
private UUID operatorId;
private String operatorName;
// 审计字段
private LocalDateTime createdAt;
}
// 支付方式枚举
public enum PaymentMethod {
WECHAT("微信支付"),
ALIPAY("支付宝"),
CASH("现金"),
BANK_TRANSFER("银行转账"),
CARD("刷卡");
}
```
#### FeeRefund退款记录
```java
@Entity
@Table(name = "fin_fee_refund")
@Data
public class FeeRefund {
@Id
private UUID id;
private UUID projectId;
private UUID billId;
private UUID paymentId; // 关联支付记录
private String refundNo; // 退款单号
private BigDecimal amount; // 退款金额
private String reason; // 退款原因
// 状态
private RefundStatus status; // PENDING/APPROVED/REJECTED/REFUNDED
// 审批
private UUID approverId;
private String approverName;
private LocalDateTime approveTime;
private String approveRemark;
// 退款执行
private String thirdPartyRefundNo;
private LocalDateTime refundTime;
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
```
---
## 三、费用催缴
### 3.1 定时任务
```java
@Component
public class FeeReminderJob {
@Autowired
private FeeBillRepository feeBillRepository;
@Autowired
private NotificationService notificationService;
// 每天9点执行提醒即将到期
@Scheduled(cron = "0 0 9 * * ?")
public void remindUpcomingDue() {
// 查询3天内到期的账单
LocalDate targetDate = LocalDate.now().plusDays(3);
List<FeeBill> upcomingBills = feeBillRepository
.findByDueDateBetweenAndStatus(LocalDate.now(), targetDate, BillStatus.UNPAID);
for (FeeBill bill : upcomingBills) {
notificationService.sendFeeReminder(bill, "FEE_UPCOMING_DUE");
}
}
// 每天9点和18点执行催缴逾期
@Scheduled(cron = "0 0 9,18 * * ?")
public void remindOverdue() {
// 查询已逾期的账单
List<FeeBill> overdueBills = feeBillRepository
.findByOverdueDateBeforeAndStatus(LocalDate.now(), BillStatus.UNPAID);
for (FeeBill bill : overdueBills) {
// 计算滞纳金
calculateLateFee(bill);
notificationService.sendFeeReminder(bill, "FEE_OVERDUE");
}
}
// 每周一9点执行发送汇总
@Scheduled(cron = "0 0 9 ? * MON")
public void sendWeeklySummary() {
// 发送本周待缴费汇总
}
private void calculateLateFee(FeeBill bill) {
if (!bill.getEnableLateFee()) {
return;
}
// 计算逾期天数
long overdueDays = ChronoUnit.DAYS.between(bill.getOverdueDate(), LocalDate.now());
if (overdueDays <= 0) {
return;
}
// 计算滞纳金
BigDecimal lateFee = bill.getAmount()
.multiply(bill.getLateFeeRate())
.multiply(BigDecimal.valueOf(overdueDays));
// 不超过上限
if (bill.getMaxLateFee() != null && lateFee.compareTo(bill.getMaxLateFee()) > 0) {
lateFee = bill.getMaxLateFee();
}
bill.setLateFee(lateFee);
bill.setPayableAmount(bill.getAmount().add(lateFee).subtract(bill.getDiscount()));
feeBillRepository.save(bill);
}
}
```
---
## 四、能耗计量管理(已实现,当前在 module-mdm 中)
> **边界说明**: 能耗计量管理当前在 `module-mdm` 中实现属于基础数据管理MDM范畴。能耗数据采集和简单费用计算是已实现功能但正式的账单生成、支付处理等财务功能属于本领域module-finance的待实现功能。两者通过 EnergyConsumption.amount 对接:能耗模块提供用量和计算值,财务模块将其作为按用量计费的账单数据来源。
### 4.1 EnergyMeter能源计量点
```java
@Entity
@Table(name = "ops_energy_meter")
@Data
public class EnergyMeter {
@Id
private UUID id;
private UUID projectId;
private String meterCode; // 计量点编码自动生成EM + yyyyMMddHHmmss
private String meterName; // 计量点名称
private EnergyType energyType; // 能源类型枚举
private UUID spaceNodeId; // 关联空间节点
private String installationLocation;// 安装位置
private BigDecimal ratedCapacity; // 额定容量
private BigDecimal unitPrice; // 单价精度4位小数用于费用计算
private Status status; // ACTIVE/INACTIVE
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
```
**能源类型枚举**:
```java
public enum EnergyType {
LIGHTING("照明插座用电"),
HVAC("空调用电"),
POWER("动力用电"),
SPECIAL("特殊用电"),
WATER("给排水"),
GAS("燃气");
}
```
> **已知问题**: 前后端能源类型枚举不一致。后端为 LIGHTING/HVAC/POWER/SPECIAL/WATER/GAS前端为 ELECTRICITY/WATER/GAS/CENTRAL_HEATING/CENTRAL_COOLING需对齐。
### 4.2 EnergyConsumption能耗记录
```java
@Entity
@Table(name = "ops_energy_consumption")
@Data
public class EnergyConsumption {
@Id
private UUID id;
private UUID projectId;
private UUID meterId; // 关联计量点
private LocalDate consumptionDate; // 记录日期
private BigDecimal previousReading; // 上次读数
private BigDecimal currentReading; // 当前读数
private BigDecimal consumption; // 消耗量 = current - previous
private BigDecimal amount; // 费用 = consumption x unitPrice
private UUID recordedBy; // 记录人
private RecordMethod recordMethod; // 记录方式
private String remarks; // 备注
private LocalDateTime createdAt;
}
```
**记录方式枚举**:
```java
public enum RecordMethod {
MANUAL("手动录入"),
IOT("IoT自动采集");
}
```
### 4.3 能耗业务规则
| 规则 | 说明 |
|------|------|
| 编码自动生成 | 格式EM + yyyyMMddHHmmss冲突时追加后缀 |
| 仪表状态校验 | 只能对 ACTIVE 状态的仪表进行抄表 |
| 读数递增校验 | 当前读数不能小于上次读数 |
| 自动获取上次读数 | 从最近一条记录获取 previousReading首次为 0 |
| 自动计算消耗量 | consumption = currentReading - previousReading |
| 自动计算费用 | amount = consumption x meter.unitPrice单价为空时为 0 |
| 默认手动录入 | recordMethod 默认设为 MANUAL |
| 软删除 | 删除操作将状态设为 INACTIVE |
### 4.4 能耗 API 接口
```
基础路径: /api/ops/energy
计量点管理:
POST /meters 创建计量点
GET /meters 查询计量点列表projectId必填energyType可选
GET /meters/{id} 获取计量点详情
PUT /meters/{id} 更新计量点
DELETE /meters/{id} 删除计量点(软删除)
能耗记录:
POST /consumption 录入能耗数据
GET /consumption/{meterId} 查询能耗记录startDate/endDate可选
能耗统计:
GET /statistics/by-type 按类型统计消耗
GET /statistics/unit-consumption 单方能耗
```
> **已知缺陷**: `getConsumptionByType()` 当前将项目总消耗全部归入 LIGHTING 类型,未按 meter.energyType 真正分项汇总,需修复。
### 4.5 能耗与财务的边界
```
EnergyMeter (module-mdm) FeeItem (module-finance待实现)
| |
v v
EnergyConsumption --费用计算--> FeeBill (自动/手动生成,待实现)
(抄表数据) |
v
FeePayment (支付记录,待实现)
|
v
FeeRefund (退款记录,待实现)
```
- **能耗模块module-mdm职责**: 计量点管理、抄表录入、消耗量计算、简单费用计算consumption x unitPrice
- **财务模块module-finance待实现职责**: 收费项目管理、账单生成、支付处理、催缴通知、滞纳金计算、退款处理
---
## 五、实现状态与差异
### 5.1 实现状态
| 功能模块 | 实现状态 | 备注 |
|---------|---------|------|
| FeeItem | 🔴 未实现 | 无 module-finance 模块,无 fin_* 表 |
| FeeBill | 🔴 未实现 | 无 FeeBill 实体/服务/控制器 |
| FeePayment | 🔴 未实现 | 无 FeePayment 实体/服务/控制器 |
| FeeRefund | 🔴 未实现 | 原设计即标注未实现 |
| 费用催缴 | 🔴 未实现 | 无 FeeReminderJob 定时任务 |
| 滞纳金计算 | 🔴 未实现 | 无计算逻辑 |
| EnergyMeter | 🟢 已实现 | 在 module-mdm 中实现 |
| EnergyConsumption | 🟢 已实现 | 在 module-mdm 中实现,含简单费用计算 |
| 能耗统计分析 | 🟢 已实现(有缺陷) | 按类型统计未真正分项汇总 |
| 退款功能 | 🔴 未实现 | 待开发 |
| 财务报表 | 🔴 未实现 | 待开发 |
| 支付网关 | 🔴 未实现 | 待对接 |
### 5.2 与设计方案的差异
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|--------|----------|----------|----------|
| **FeeItem** | 完整实体定义 | 不存在 | 完全缺失,需新建 module-finance |
| **FeeBill** | 完整实体定义 | 不存在 | 完全缺失 |
| **FeePayment** | 完整实体定义 | 不存在 | 完全缺失 |
| **FeeRefund** | 完整实体定义 | 不存在 | 完全缺失 |
| **费用催缴** | FeeReminderJob | 不存在 | 完全缺失 |
| **滞纳金** | 自动计算逻辑 | 不存在 | 完全缺失 |
| **能耗管理** | 未在设计文档中 | module-mdm中已实现 | 设计文档遗漏,需补充 |
| **能源类型** | FeeType(8种) | EnergyType(6种) | 实际更细分(按用电分项),但缺少供暖/物业费 |
| **前后端枚举** | 统一 | 不一致 | 后端LIGHTING/HVAC/POWER/SPECIAL/WATER/GAS vs 前端ELECTRICITY/WATER/GAS/CENTRAL_HEATING/CENTRAL_COOLING |
### 5.3 改进计划
| 优先级 | 改进项 | 说明 |
|--------|--------|------|
| P0 | 创建 module-finance 模块 | 独立财务模块,包含 entity/service/controller/repository |
| P0 | 实现 FeeItem 收费项目 | 含完整字段和 CRUD API |
| P0 | 实现 FeeBill 账单 | 含账单自动生成和状态流转 |
| P0 | 实现 FeePayment 支付记录 | 线下收款登记、支付记录查询 |
| P0 | 能耗数据与账单对接 | 将 EnergyConsumption 的 amount 作为按用量计费的账单数据来源 |
| P1 | 修复能耗按类型统计 | getConsumptionByType() 应按 meter.energyType 真正分项汇总 |
| P1 | 统一前后端能源类型枚举 | 对齐后端和前端的 EnergyType 定义 |
| P1 | 实现费用催缴定时任务 | 到期提醒、逾期催缴、周汇总 |
| P1 | 实现滞纳金自动计算 | 逾期天数 x 日利率,不超过上限 |
| P1 | 实现退款功能 | 退款申请、审批、执行流程 |
| P2 | 对接支付网关 | 集成微信支付、支付宝 SDK |
| P2 | 财务报表 | 收费统计、欠费分析、收入趋势 |
---
## 六、数据库表结构
```sql
-- 收费项目表
CREATE TABLE fin_fee_item (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
code VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL,
billing_method VARCHAR(20) NOT NULL,
unit_price NUMERIC(12,2),
unit VARCHAR(20),
bill_day INTEGER,
due_day INTEGER,
overdue_day INTEGER,
enable_late_fee BOOLEAN DEFAULT FALSE,
late_fee_rate NUMERIC(5,4),
max_late_fee NUMERIC(12,2),
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(project_id, code)
);
-- 账单表
CREATE TABLE fin_fee_bill (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
bill_no VARCHAR(32) NOT NULL,
fee_item_id UUID NOT NULL,
space_node_id UUID,
owner_id UUID,
bill_period VARCHAR(10) NOT NULL,
bill_date DATE NOT NULL,
due_date DATE NOT NULL,
overdue_date DATE,
usage_amount NUMERIC(12,2),
usage_unit VARCHAR(20),
amount NUMERIC(12,2) NOT NULL,
late_fee NUMERIC(12,2) DEFAULT 0,
discount NUMERIC(12,2) DEFAULT 0,
payable_amount NUMERIC(12,2) NOT NULL,
paid_amount NUMERIC(12,2) DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'UNPAID',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(project_id, bill_no)
);
-- 支付记录表
CREATE TABLE fin_fee_payment (
id UUID PRIMARY KEY,
bill_id UUID NOT NULL,
payment_no VARCHAR(64) NOT NULL,
amount NUMERIC(12,2) NOT NULL,
method VARCHAR(20) NOT NULL,
third_party_no VARCHAR(100),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
fail_reason VARCHAR(500),
payment_time TIMESTAMP,
remark VARCHAR(500),
operator_id UUID,
operator_name VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 退款记录表
CREATE TABLE fin_fee_refund (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
bill_id UUID NOT NULL,
payment_id UUID NOT NULL,
refund_no VARCHAR(64) NOT NULL,
amount NUMERIC(12,2) NOT NULL,
reason VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
approver_id UUID,
approver_name VARCHAR(100),
approve_time TIMESTAMP,
approve_remark VARCHAR(500),
third_party_refund_no VARCHAR(100),
refund_time TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 创建索引
CREATE INDEX idx_fee_item_project ON fin_fee_item(project_id);
CREATE INDEX idx_fee_bill_project ON fin_fee_bill(project_id);
CREATE INDEX idx_fee_bill_status ON fin_fee_bill(status);
CREATE INDEX idx_fee_bill_owner ON fin_fee_bill(owner_id);
CREATE INDEX idx_fee_bill_due_date ON fin_fee_bill(due_date);
CREATE INDEX idx_fee_payment_bill ON fin_fee_payment(bill_id);
-- ============================================================
-- 以下为已实现的能耗计量相关表(在 module-mdm 中)
-- ============================================================
-- 能源计量点表(已实现)
CREATE TABLE ops_energy_meter (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
meter_code VARCHAR(50) NOT NULL,
meter_name VARCHAR(100) NOT NULL,
energy_type VARCHAR(20) NOT NULL,
space_node_id UUID,
installation_location VARCHAR(255),
rated_capacity NUMERIC(10,2),
unit_price NUMERIC(10,4),
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(project_id, meter_code)
);
-- 能耗记录表(已实现)
CREATE TABLE ops_energy_consumption (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
meter_id UUID NOT NULL REFERENCES ops_energy_meter(id),
consumption_date DATE NOT NULL,
previous_reading NUMERIC(12,2),
current_reading NUMERIC(12,2),
consumption NUMERIC(12,2) NOT NULL,
amount NUMERIC(10,2),
recorded_by UUID,
record_method VARCHAR(20) DEFAULT 'MANUAL',
remarks VARCHAR(1000),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 能耗表索引
CREATE INDEX idx_energy_meter_project ON ops_energy_meter(project_id);
CREATE INDEX idx_energy_meter_status ON ops_energy_meter(status);
CREATE INDEX idx_ec_meter_date ON ops_energy_consumption(meter_id, consumption_date);
CREATE INDEX idx_ec_project_date ON ops_energy_consumption(project_id, consumption_date);
```
---
**文档维护**: 本领域技术方案由 ether-finance 服务负责人维护