# 财务计费领域技术方案 **领域编号**: 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 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 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 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 服务负责人维护