All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 18s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
6.8 KiB
6.8 KiB
Spec: 存款明细计算核心算法
ADDED Requirements
Requirement: 月度计划存款计算公式
系统 SHALL 使用以下公式计算月度计划存款:
月度计划存款 = 收入预算 + 发生在本月的年度收入 - 支出预算 - 发生在本月的年度支出
其中:
- 收入预算:所有月度收入预算项的预算金额之和
- 发生在本月的年度收入:年度收入预算项在本月实际发生的金额(actual > 0)
- 支出预算:所有月度支出预算项的预算金额之和
- 发生在本月的年度支出:年度支出预算项在本月实际发生的金额(actual > 0)
Scenario: 纯月度预算计算
- WHEN 用户查询 2026年2月的月度存款,且只有月度预算(工资10000、奖金5000、房租3000、餐饮2000)
- THEN 系统返回计划存款 = 10000 + 5000 - 3000 - 2000 = 10000
Scenario: 月度预算 + 本月发生的年度预算
- WHEN 用户查询 2026年2月的月度存款,月度预算(工资10000、房租3000),且年度旅游支出在本月实际发生3000元
- THEN 系统返回计划存款 = 10000 - 3000 - 3000 = 4000
Scenario: 年度预算未在本月发生
- WHEN 用户查询 2026年2月的月度存款,月度预算(工资10000、房租3000),年度年终奖预算50000但本月实际为0
- THEN 系统返回计划存款 = 10000 - 3000 = 7000(年终奖不计入)
Requirement: 年度计划存款计算公式
系统 SHALL 使用以下公式计算年度计划存款:
年度计划存款 = 归档月已实收 + 未来月(包含本月)收入预算 - 归档月已实支 - 未来月(包含本月)支出预算
其中:
- 归档月已实收:已归档月份(1月~当前月-1)的实际收入金额之和,从
BudgetArchive读取 - 未来月收入预算:当前月及未来月份的月度收入预算 × 剩余月数
- 归档月已实支:已归档月份的实际支出金额之和,从
BudgetArchive读取 - 未来月支出预算:当前月及未来月份的月度支出预算 × 剩余月数
Scenario: 年初无归档数据
- WHEN 用户在 2026年1月查询年度存款,月度预算(工资10000/月、房租3000/月),无归档数据
- THEN 系统返回计划存款 = (10000 - 3000) × 12 = 84000
Scenario: 年中有归档数据
- WHEN 用户在 2026年3月查询年度存款,1月归档已实收15000、已实支4800,2月归档已实收14000、已实支5200,3~12月月度预算(工资10000、房租3000)
- THEN 系统返回计划存款 = (15000 + 14000) + (10000 × 10) - (4800 + 5200) - (3000 × 10) = 129000
Scenario: 归档数据包含年度预算
- WHEN 归档数据中包含年度预算的实际发生金额(如1月旅游支出3000)
- THEN 系统将其计入"归档月已实支",不重复计算
Requirement: 明细项计算用金额 - 收入规则
对于收入类预算项,系统 SHALL 根据以下规则计算"计算用金额":
- 如果实际金额 > 0,计算用金额 = 实际金额
- 如果实际金额 = 0,计算用金额 = 预算金额
Scenario: 收入已发生
- WHEN 工资预算10000,实际发生9500
- THEN 计算用金额 = 9500,标注为"使用实际"
Scenario: 收入未发生
- WHEN 奖金预算5000,实际发生0
- THEN 计算用金额 = 5000,标注为"使用预算"
Requirement: 明细项计算用金额 - 支出规则(普通)
对于非硬性支出类预算项,系统 SHALL 计算用金额 = MAX(预算金额, 实际金额)
Scenario: 支出未超预算
- WHEN 餐饮预算2000,实际发生1800
- THEN 计算用金额 = 2000,标注为"使用预算"
Scenario: 支出超预算
- WHEN 餐饮预算2000,实际发生2500
- THEN 计算用金额 = 2500,标注为"使用实际(超支)",高亮显示
Requirement: 明细项计算用金额 - 支出规则(硬性)
对于硬性支出(IsMandatoryExpense = true)且实际金额 = 0 的预算项,系统 SHALL 按天数折算计算用金额,不进行 MAX 比较。
月度折算公式:计算用金额 = 预算金额 / 当月天数 × 当前日期
年度折算公式:计算用金额 = 预算金额 / 当年天数 × 当前天数
Scenario: 硬性支出未发生(月度)
- WHEN 房租预算3000(硬性),实际为0,当前日期为2月15日,2月共28天
- THEN 计算用金额 = 3000 / 28 × 15 ≈ 1607.14,标注为"按天折算"
Scenario: 硬性支出已发生
- WHEN 房租预算3000(硬性),实际发生3000
- THEN 计算用金额 = MAX(3000, 3000) = 3000,标注为"使用实际"
Scenario: 硬性支出超预算
- WHEN 水电预算500(硬性),实际发生600
- THEN 计算用金额 = MAX(500, 600) = 600,标注为"使用实际(超支)"
Scenario: 硬性支出按天折算可能超预算
- WHEN 房租预算3000(硬性),实际为0,当前日期为2月29日,2月共28天
- THEN 计算用金额 = 3000 / 28 × 29 ≈ 3107.14(大于预算),标注为"按天折算"
Requirement: 归档月份数据处理
对于已归档月份的预算数据,系统 SHALL 直接使用归档中的实际金额(BudgetArchive.Content[].Actual),不重新计算。
Scenario: 读取归档数据
- WHEN 用户在3月查询年度存款,1月归档中工资实际10000、房租实际3000
- THEN 系统使用归档实际值10000和3000,不根据当前预算重新计算
Scenario: 归档后预算调整
- WHEN 1月归档时工资预算10000,实际10000;2月将工资预算调整为12000;用户在3月查询年度存款
- THEN 1月仍使用归档的实际10000,2月及以后使用新预算12000
Requirement: 闰年和月末边界处理
系统 SHALL 正确处理闰年和月末边界情况:
- 闰年判断:使用
DateTime.IsLeapYear(year)判断,闰年366天,平年365天 - 月末天数:使用
DateTime.DaysInMonth(year, month)获取
Scenario: 闰年2月硬性支出折算
- WHEN 2024年2月29日(闰年),房租预算3000(硬性),实际为0
- THEN 计算用金额 = 3000 / 29 × 29 = 3000
Scenario: 平年2月硬性支出折算
- WHEN 2026年2月28日(平年),房租预算3000(硬性),实际为0
- THEN 计算用金额 = 3000 / 28 × 28 = 3000
Scenario: 年度硬性支出闰年折算
- WHEN 2024年(闰年)第100天,年度保险预算12000(硬性),实际为0
- THEN 计算用金额 = 12000 / 366 × 100 ≈ 3278.69
Requirement: 不记额预算处理
对于不记额预算(NoLimit = true)的预算项,系统 SHALL 排除在计划存款计算之外,但在明细中显示为"不限额"。
Scenario: 不记额收入
- WHEN 存在不记额收入预算项(如意外收入),实际发生1000
- THEN 该项不计入"收入预算",明细中显示预算为"不限额",实际为1000