- 添加 BudgetItemCalculator 辅助类,实现明细项计算规则 - 收入:实际>0取实际,否则取预算 - 支出:取MAX(预算, 实际) - 硬性支出未发生:按天数折算 - 归档数据:直接使用实际值 - 实现月度和年度存款核心公式 - 月度:收入预算 + 本月年度收入 - 支出预算 - 本月年度支出 - 年度:归档已实收 + 未来收入预算 - 归档已实支 - 未来支出预算 - 定义存款明细数据结构 - SavingsDetail: 包含收入/支出明细列表和汇总 - BudgetDetailItem: 预算明细项(含计算用金额、计算说明等) - SavingsCalculationSummary: 计算汇总信息 - 新增单元测试 - BudgetItemCalculatorTest: 11个测试覆盖所有计算规则 - BudgetSavingsCalculationTest: 6个测试验证核心公式 测试结果:所有测试通过 (366 passed, 0 failed)
122 lines
3.7 KiB
C#
122 lines
3.7 KiB
C#
namespace Service.Budget;
|
||
|
||
/// <summary>
|
||
/// 预算明细项计算辅助类
|
||
/// 用于计算单个预算项的有效金额(计算用金额)
|
||
/// </summary>
|
||
public static class BudgetItemCalculator
|
||
{
|
||
/// <summary>
|
||
/// 计算预算项的有效金额
|
||
/// </summary>
|
||
/// <param name="category">预算类别(收入/支出)</param>
|
||
/// <param name="budgetLimit">预算金额</param>
|
||
/// <param name="actualAmount">实际金额</param>
|
||
/// <param name="isMandatory">是否为硬性消费</param>
|
||
/// <param name="isArchived">是否为归档数据</param>
|
||
/// <param name="referenceDate">参考日期</param>
|
||
/// <param name="periodType">预算周期类型(月度/年度)</param>
|
||
/// <returns>有效金额(用于计算的金额)</returns>
|
||
public static decimal CalculateEffectiveAmount(
|
||
BudgetCategory category,
|
||
decimal budgetLimit,
|
||
decimal actualAmount,
|
||
bool isMandatory,
|
||
bool isArchived,
|
||
DateTime referenceDate,
|
||
BudgetPeriodType periodType)
|
||
{
|
||
// 归档数据直接返回实际值
|
||
if (isArchived)
|
||
{
|
||
return actualAmount;
|
||
}
|
||
|
||
// 收入:实际>0取实际,否则取预算
|
||
if (category == BudgetCategory.Income)
|
||
{
|
||
return actualAmount > 0 ? actualAmount : budgetLimit;
|
||
}
|
||
|
||
// 支出(硬性且实际=0):按天数折算
|
||
if (category == BudgetCategory.Expense && isMandatory && actualAmount == 0)
|
||
{
|
||
return CalculateMandatoryAmount(budgetLimit, referenceDate, periodType);
|
||
}
|
||
|
||
// 支出(普通):取MAX
|
||
if (category == BudgetCategory.Expense)
|
||
{
|
||
return Math.Max(budgetLimit, actualAmount);
|
||
}
|
||
|
||
return budgetLimit;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算硬性消费按天数折算的金额
|
||
/// </summary>
|
||
private static decimal CalculateMandatoryAmount(
|
||
decimal limit,
|
||
DateTime date,
|
||
BudgetPeriodType periodType)
|
||
{
|
||
if (periodType == BudgetPeriodType.Month)
|
||
{
|
||
var daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);
|
||
return limit / daysInMonth * date.Day;
|
||
}
|
||
else // Year
|
||
{
|
||
var daysInYear = DateTime.IsLeapYear(date.Year) ? 366 : 365;
|
||
return limit / daysInYear * date.DayOfYear;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成计算说明
|
||
/// </summary>
|
||
/// <param name="category">预算类别</param>
|
||
/// <param name="budgetLimit">预算金额</param>
|
||
/// <param name="actualAmount">实际金额</param>
|
||
/// <param name="effectiveAmount">有效金额</param>
|
||
/// <param name="isMandatory">是否为硬性消费</param>
|
||
/// <param name="isArchived">是否为归档数据</param>
|
||
/// <returns>计算说明文本</returns>
|
||
public static string GenerateCalculationNote(
|
||
BudgetCategory category,
|
||
decimal budgetLimit,
|
||
decimal actualAmount,
|
||
decimal effectiveAmount,
|
||
bool isMandatory,
|
||
bool isArchived)
|
||
{
|
||
if (isArchived)
|
||
{
|
||
return "归档实际";
|
||
}
|
||
|
||
if (category == BudgetCategory.Income)
|
||
{
|
||
return actualAmount > 0 ? "使用实际" : "使用预算";
|
||
}
|
||
|
||
if (category == BudgetCategory.Expense)
|
||
{
|
||
if (isMandatory && actualAmount == 0)
|
||
{
|
||
return "按天折算";
|
||
}
|
||
|
||
if (actualAmount > budgetLimit)
|
||
{
|
||
return "使用实际(超支)";
|
||
}
|
||
|
||
return effectiveAmount == actualAmount ? "使用实际" : "使用预算";
|
||
}
|
||
|
||
return "使用预算";
|
||
}
|
||
}
|