24. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--预算扣除、退回、补充

news/2025/1/16 18:47:07/

这篇文章我们一起来编写目前为止最为复杂的功能:预算扣除、退回、补充。预算回退有三种情况:修改后的支出金额小于修改前的支出金额支出记录删除后记录类型从支出改为收入。预算补充的情况有两种:记录类型从收入改为支出修改后的支出金额大于修改前的支出金额

一、需求

根据前面所说的需求如下:

编号需求说明
1预算回退1. 修改后的支出金额小于修改前的支出金额;2. 支出记录删除后;3. 记录类型从支出改为收入
2预算补充1. 记录类型从收入改为支出;2. 修改后的支出金额大于修改前的支出金额
3预算扣除新增支出记录后扣除预算

二、功能编写

我们一起来实现如何实现预算退回与预算补充功能。这里要注意的是,我们将引入EF Core 的事务机制,如果你不知道EF Core的事务机制请先去学习专栏《轻松学EntityFramework Core

2.1 预算扣除

预算扣除功能最简单,我们只需要在新增支出记录的同时扣除对应的预算即可。我们修改IncomeExpenditureRecordImp类下的Add方法,修改后的代码如下:

/// <summary>
/// 新增收支记录
/// </summary>
/// <param name="incomeExpenditureRecord"></param>
public void Add(IncomeExpenditureRecord incomeExpenditureRecord)
{//开启事务using (var transaction = _sporeAccountingDbContext.Database.BeginTransaction()){try{// 查找记录范围内的预算var budget = _sporeAccountingDbContext.Budgets.FirstOrDefault(x => x.UserId == incomeExpenditureRecord.UserId&& x.StartTime <= incomeExpenditureRecord.RecordDate &&x.EndTime >= incomeExpenditureRecord.RecordDate&& x.IncomeExpenditureClassificationId ==incomeExpenditureRecord.IncomeExpenditureClassificationId);if (budget != null){// 查询分类var classification = _sporeAccountingDbContext.IncomeExpenditureClassifications.FirstOrDefault(x => x.Id == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (classification.Type== IncomeExpenditureTypeEnmu.Income){budget.Remaining -= incomeExpenditureRecord.AfterAmount;}_sporeAccountingDbContext.Budgets.Update(budget);}_sporeAccountingDbContext.IncomeExpenditureRecords.Add(incomeExpenditureRecord);_sporeAccountingDbContext.SaveChanges();//提交事务transaction.Commit();}catch (Exception e){//回滚事务transaction.Rollback();throw;}}
}

Add 方法的核心部分包括事务处理、预算查找与更新、分类查询、记录添加和错误处理。首先,代码通过调用 _sporeAccountingDbContext.Database.BeginTransaction() 开启了一个数据库事务,以确保数据操作的原子性。如果操作成功,事务会提交;如果发生异常,事务将回滚,避免数据不一致。接着,代码从数据库中查找符合条件的预算记录。预算查找逻辑基于 UserId 和记录日期范围,同时匹配 IncomeExpenditureClassificationId。如果找到相关预算,代码会进一步查询该收支记录的分类信息。然后,根据分类的类型进行处理。如果分类类型为收入IncomeExpenditureTypeEnmu.Income,代码将从预算的剩余金额中扣除收支记录的金额AfterAmount。紧接着代码将新的收支记录添加到 IncomeExpenditureRecords 集合中,并调用 SaveChanges() 保存所有更改。最后,事务被提交。如果过程中出现任何异常,捕获到的异常会触发事务回滚,确保数据库保持一致性并避免部分操作被永久保存。回滚后,异常被重新抛出,以便调用者可以处理这个错误。通过这种方式,代码实现了对收支记录的安全、原子化操作,确保数据一致性和完整性。

2.2 预算回退与补充

这里我们要修改IncomeExpenditureRecordImp类中的两个方法:UpdateDeleteDelete方法很简单,我们直接将支出记录的金额再加回预算即可,代码如下:

/// <summary>
/// 删除收支记录
/// </summary>
/// <param name="incomeExpenditureRecordId"></param>
/// <returns></returns>
public void Delete(string incomeExpenditureRecordId)
{//开启事务using (var transaction = _sporeAccountingDbContext.Database.BeginTransaction()){try{var incomeExpenditureRecord = _sporeAccountingDbContext.IncomeExpenditureRecords.FirstOrDefault(x => x.Id == incomeExpenditureRecordId);if (incomeExpenditureRecord != null){// 查找记录范围内的预算var budget = _sporeAccountingDbContext.Budgets.FirstOrDefault(x => x.UserId == incomeExpenditureRecord.UserId&& x.StartTime <= incomeExpenditureRecord.RecordDate &&x.EndTime >= incomeExpenditureRecord.RecordDate&& x.IncomeExpenditureClassificationId == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (budget != null){// 查询分类var classification = _sporeAccountingDbContext.IncomeExpenditureClassifications.FirstOrDefault(x => x.Id == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (classification.Type== IncomeExpenditureTypeEnmu.Income){budget.Remaining += incomeExpenditureRecord.AfterAmount;}_sporeAccountingDbContext.Budgets.Update(budget);}_sporeAccountingDbContext.IncomeExpenditureRecords.Remove(incomeExpenditureRecord);_sporeAccountingDbContext.SaveChanges();//提交事务transaction.Commit();}}catch (Exception e){//回滚事务transaction.Rollback();throw;}}
}

Delete 方法首先开启一个数据库事务,确保在整个删除操作过程中,如果出现任何问题,可以回滚所有更改以保持数据一致性。然后,通过查询 IncomeExpenditureRecords 集合查找与给定 incomeExpenditureRecordId 匹配的收支记录。如果找到相应的记录,代码继续执行下一步。接下来,代码查找与该收支记录相关的预算信息。预算查询的条件包括用户 ID、记录日期范围,以及收支分类 ID。如果找到匹配的预算,代码会进一步查找该收支记录的分类信息,以确定该记录是否属于收入类型。如果分类类型是收入,代码将从预算的剩余金额中增加这笔收支记录的金额。这一步骤的目的是在删除收支记录时,恢复预算中的剩余金额,从而保持预算数据的准确性。
完成预算调整后,代码将目标收支记录从 IncomeExpenditureRecords 集合中移除,并调用 SaveChanges() 方法将所有更改保存到数据库。最后,事务被提交,确认所有操作成功完成。

Update 方法的修改需要将数据兼顾记录类分类的类型是否发生变更,代码如下:

/// <summary>
/// 修改收支记录
/// </summary>
/// <param name="incomeExpenditureRecord"></param>
/// <returns></returns>
public void Update(IncomeExpenditureRecord incomeExpenditureRecord)
{using (var transaction = _sporeAccountingDbContext.Database.BeginTransaction()){try{// 查询原记录var oldIncomeExpenditureRecord = _sporeAccountingDbContext.IncomeExpenditureRecords.FirstOrDefault(x => x.Id == incomeExpenditureRecord.Id);// 查找记录范围内的预算var budget = _sporeAccountingDbContext.Budgets.FirstOrDefault(x => x.UserId == incomeExpenditureRecord.UserId&& x.StartTime <= incomeExpenditureRecord.RecordDate &&x.EndTime >= incomeExpenditureRecord.RecordDate&& x.IncomeExpenditureClassificationId ==incomeExpenditureRecord.IncomeExpenditureClassificationId);if (budget != null){// 查询分类var classification = _sporeAccountingDbContext.IncomeExpenditureClassifications.FirstOrDefault(x => x.Id == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (classification.Type== IncomeExpenditureTypeEnmu.Income){//如果是支出,需要减去原来的金额budget.Remaining = (budget.Amount - incomeExpenditureRecord.AfterAmount);}else{//如果是收入,需要加上原来的金额budget.Remaining = (budget.Amount + incomeExpenditureRecord.AfterAmount);}_sporeAccountingDbContext.Budgets.Update(budget);}oldIncomeExpenditureRecord.AfterAmount = incomeExpenditureRecord.AfterAmount;oldIncomeExpenditureRecord.BeforAmount = incomeExpenditureRecord.BeforAmount;oldIncomeExpenditureRecord.RecordDate = incomeExpenditureRecord.RecordDate;oldIncomeExpenditureRecord.Remark = incomeExpenditureRecord.Remark;oldIncomeExpenditureRecord.IncomeExpenditureClassificationId =incomeExpenditureRecord.IncomeExpenditureClassificationId;_sporeAccountingDbContext.IncomeExpenditureRecords.Update(oldIncomeExpenditureRecord);_sporeAccountingDbContext.SaveChanges();//提交事务transaction.Commit();}catch (Exception e){//回滚事务transaction.Rollback();throw;}}
}

Update 方法通过 IncomeExpenditureRecords 集合查找与提供的记录 ID 匹配的原始收支记录。找到后,代码继续查找与这条记录相关的预算信息。预算查询条件包括用户 ID、记录日期范围和收支分类 ID。如果找到匹配的预算,代码接着通过 IncomeExpenditureClassifications 集合查询记录的分类信息。根据分类类型(收入或支出),代码调整预算的剩余金额。如果分类是收入,代码从预算金额中减去更新后的金额;如果是支出,代码将金额加回预算。这确保了预算在更新记录后仍然准确。之后,代码更新原始收支记录的相关属性,包括金额、日期、备注和分类 ID。将修改后的记录通过 Update 方法标记为需要更新,并调用 SaveChanges() 保存所有更改。

三、总结

文章介绍了预算管理中复杂功能的实现,包括预算扣除、退回和补充。预算回退涉及三种情况:修改后的支出金额小于修改前的金额、删除支出记录、记录类型从支出改为收入;预算补充包括记录类型从收入改为支出以及修改后的支出金额大于修改前的金额。文章详细描述了如何在新增、删除和修改收支记录时,使用 EF Core 事务机制确保操作的原子性和数据一致性。具体实现方法包括调整预算金额、根据收支记录类型动态更新预算、处理异常并回滚事务,以保证数据库的完整性和正确性。文章通过实际代码示例清晰地阐述了这些操作的细节。
下一篇文章,我们将结合主币种设置和预算来实现预算金额的币种转换。


http://www.ppmy.cn/news/1563681.html

相关文章

你喜欢用什么编辑器?

电脑工作者和程序员所使用的文本编辑器通常需要具备高效率、易用性以及对代码友好等特点&#xff0c;包括语法高亮、自动完成、多文件同时编辑、查找替换、版本控制集成等功能。以下是几个广受开发者欢迎且实用性较强的文本编辑器&#xff1a; Visual Studio Code&#xff08;V…

Thrustmaster Hotas Warthog飞行操作杆开发

目录 0 摘 要 &#xff1a;简单说一下这篇文章在搞啥 1 背 景 &#xff1a;什么需求以及对开发的背景调查 2 环境配置 &#xff1a;具体需要什么环境&#xff0c;对软件层面的需求 3 硬件测试 &#xff1a;测试遥感器…

【C语言】线程----同步、互斥、条件变量

目录 3. 同步 3.1 概念 3.2 同步机制 3.3 函数接口 1. 同步 1.1 概念 同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情 1.2 同步机制 通过信号量实现线程间的同步 信号量&#xff1a;通过信号量实现同步操作&#xff1b;由信号量来决定…

数据结构《MapSet哈希表》

文章目录 一、搜索树1.1 定义1.2 模拟实现搜索 二、Map2.1 定义2.2 Map.Entry2.3 TreeMap的使用2.4 Map的常用方法 三、Set3.1 定义3.2 TreeSet的使用3.3 Set的常用方法 四、哈希表4.1 哈希表的概念4.2 冲突4.2.1 冲突的概念4.2.2 冲突的避免1. 选择合适的哈希函数2. 负载因子调…

【寒假逆袭计划】自学黑客计划(网络安全)

CSDN大礼包&#xff1a;&#x1f449;基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&…

记录一个v-if与自定义指令的BUG

在做某个系统的时候因为element自带的v-input和v-input-number不能满足所需要的功能&#xff0c;例如限制小数位数&#xff0c;最大值最小值和值是否允许存在非0之类的状态&#xff0c;写了一个自定义指令v-onlyNumber来满足需求(v-onlyNumber在我其他文章内有直接copy就行)&am…

Ubuntu 磁盘修复

Ubuntu 磁盘修复 在 ubuntu 文件系统变成只读模式&#xff0c;该处理呢&#xff1f; 文件系统内部的错误&#xff0c;如索引错误、元数据损坏等&#xff0c;也可能导致系统进入只读状态。磁盘坏道或硬件故障也可能引发文件系统只读的问题。/etc/fstab配置错误&#xff0c;可能…

全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之一维数组(应用一)

实战训练1—小明摘桃子 问题描述&#xff1a; 小明家院子里有棵桃树&#xff0c;桃子成熟的时候&#xff0c;小明就会带着30厘米高的板凳跑去摘桃子&#xff0c;当他不能直接用手摘到桃子的时候&#xff0c;就会踩到板凳上再试试。桃树上每次都是10个桃子&#xff0c;现在已知…