# 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