From a32e3ff6b2b265665503b71f552be4df3fcdb1af Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Sun, 14 Dec 2025 22:49:53 +0800 Subject: [PATCH] feat: 添加修改库存使用申请记录功能并修复重复数据问题 --- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationUpdateInput.cs | 29 +++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs | 1 + netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs | 1 + netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- sql/创建库存使用申请审批流程表.sql | 1 + 5 files changed, 279 insertions(+), 8 deletions(-) create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationUpdateInput.cs diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationUpdateInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationUpdateInput.cs new file mode 100644 index 0000000..15cb7cb --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationUpdateInput.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using NCC.Extend.Entitys.Dto.LqInventoryUsage; + +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication +{ + /// + /// 修改库存使用申请的使用记录输入 + /// + public class LqInventoryUsageApplicationUpdateInput + { + /// + /// 申请ID(必填) + /// + [Required(ErrorMessage = "申请ID不能为空")] + [StringLength(50, ErrorMessage = "申请ID长度不能超过50个字符")] + [Display(Name = "申请ID")] + public string ApplicationId { get; set; } + + /// + /// 新的使用记录列表(必填,至少需要一条记录) + /// + [Required(ErrorMessage = "使用记录列表不能为空")] + [MinLength(1, ErrorMessage = "至少需要添加一条使用记录")] + [Display(Name = "使用记录列表")] + public List UsageItems { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs index 07cc06b..09c438e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs @@ -65,3 +65,4 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application_node + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs index e4472cc..ffcc6ad 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs @@ -83,3 +83,4 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_approval_record + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs index 8d53f7c..40c5ea0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs @@ -710,9 +710,9 @@ namespace NCC.Extend throw NCCException.Oh("批次ID不能为空"); } - // 查询该批次的所有使用记录 + // 查询该批次的所有有效使用记录 var usageRecords = await _db.Queryable((u, product) => u.ProductId == product.Id) - .Where((u, product) => u.UsageBatchId == batchId) + .Where((u, product) => u.UsageBatchId == batchId && u.IsEffective == StatusEnum.有效.GetHashCode()) .Select((u, product) => new LqInventoryUsageListOutput { id = u.Id, @@ -776,8 +776,7 @@ namespace NCC.Extend // 获取批次基本信息(使用第一条记录的创建信息) var firstRecord = usageRecords.OrderBy(x => x.createTime).First(); - var effectiveRecords = usageRecords.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).ToList(); - var ineffectiveRecords = usageRecords.Where(x => x.isEffective == StatusEnum.无效.GetHashCode()).ToList(); + // 注意:usageRecords 已经只包含有效记录了,因为查询时已经过滤了 IsEffective // 查询申请记录(如果有) var application = await _db.Queryable() @@ -833,10 +832,10 @@ namespace NCC.Extend CreateUser = firstRecord.createUser, CreateUserName = firstRecord.createUserName, TotalCount = usageRecords.Count, - EffectiveCount = effectiveRecords.Count, - IneffectiveCount = ineffectiveRecords.Count, - TotalUsageQuantity = effectiveRecords.Sum(x => x.usageQuantity), - TotalUsageAmount = effectiveRecords.Sum(x => x.usageTotalValue), + EffectiveCount = usageRecords.Count, // 所有记录都是有效的(查询时已过滤) + IneffectiveCount = 0, // 无效记录不在查询结果中 + TotalUsageQuantity = usageRecords.Sum(x => x.usageQuantity), + TotalUsageAmount = usageRecords.Sum(x => x.usageTotalValue), UsageRecords = usageRecords, ApplicationInfo = applicationInfo }; @@ -1372,6 +1371,246 @@ namespace NCC.Extend } } + /// + /// 修改库存使用申请的使用记录 + /// + /// + /// 修改指定申请的使用记录,支持添加、删除、修改数量。修改后流程不变,主要是内容变更。 + /// 修改后会自动重新计算申请总金额,并检查库存是否充足、关联数据是否需要更新。 + /// 注意:只有待审批、审批中、已退回状态的申请才能修改,已通过或未通过的申请不能修改。 + /// + /// 示例请求: + /// ```json + /// PUT /api/Extend/LqInventoryUsage/UpdateApplicationUsageRecords + /// { + /// "applicationId": "申请ID", + /// "usageItems": [ + /// { + /// "productId": "产品ID", + /// "storeId": "门店ID", + /// "usageTime": "2024-01-01T10:00:00", + /// "usageQuantity": 10, + /// "relatedConsumeId": "关联消耗ID(可选)" + /// } + /// ] + /// } + /// ``` + /// + /// 参数说明: + /// - applicationId: 申请ID(必填) + /// - usageItems: 新的使用记录列表(必填,至少需要一条记录) + /// - productId: 产品ID(必填) + /// - storeId: 门店ID(必填) + /// - usageTime: 使用时间(必填) + /// - usageQuantity: 使用数量(必填,必须大于0) + /// - relatedConsumeId: 关联消耗ID(可选) + /// + /// 业务流程: + /// 1. 验证申请状态(只有待审批、已退回状态的申请才能修改) + /// 2. 验证库存是否充足 + /// 3. 逻辑删除旧的使用记录 + /// 4. 创建新的使用记录(自动计算单价和合计金额) + /// 5. 重新计算申请总金额 + /// 6. 检查关联数据是否需要更新 + /// + /// 修改输入 + /// 修改结果 + /// 修改成功 + /// 申请不存在、状态不正确或库存不足 + /// 服务器错误 + [HttpPut("UpdateApplicationUsageRecords")] + public async Task UpdateApplicationUsageRecordsAsync([FromBody] LqInventoryUsageApplicationUpdateInput input) + { + try + { + if (input == null || string.IsNullOrWhiteSpace(input.ApplicationId)) + { + throw NCCException.Oh("申请ID不能为空"); + } + + if (input.UsageItems == null || !input.UsageItems.Any()) + { + throw NCCException.Oh("使用记录列表不能为空,至少需要添加一条使用记录"); + } + + // 获取申请记录 + var application = await _db.Queryable() + .Where(x => x.Id == input.ApplicationId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (application == null) + { + throw NCCException.Oh("申请记录不存在或已失效"); + } + + // 验证申请状态(只有待审批、审批中、已退回状态的申请才能修改,已通过或未通过的申请不能修改) + if (application.ApprovalStatus != "待审批" && application.ApprovalStatus != "审批中" && application.ApprovalStatus != "已退回") + { + throw NCCException.Oh($"申请当前状态为{application.ApprovalStatus},只有待审批、审批中或已退回状态的申请才能修改使用记录"); + } + + // 验证是否已领取(已领取的申请不能修改) + if (application.IsReceived == 1) + { + throw NCCException.Oh("该申请已领取,无法修改使用记录"); + } + + // 获取该批次的所有旧使用记录 + var oldUsageRecords = await _db.Queryable() + .Where(x => x.UsageBatchId == application.UsageBatchId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .ToListAsync(); + + // 按产品ID分组,批量验证库存(在事务外先检查,避免不必要的回滚) + var productGroups = input.UsageItems.Select((item, index) => new { Item = item, Index = index }).GroupBy(x => x.Item.ProductId).ToList(); + + // 获取所有需要检查的产品ID + var productIds = productGroups.Select(x => x.Key).Distinct().ToList(); + + // 批量查询所有产品的库存信息(总库存) + var inventoryList = await _db.Queryable() + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.ProductId) + .Select(x => new { ProductId = x.ProductId, TotalInventory = SqlFunc.AggregateSum(x.Quantity) }) + .ToListAsync(); + var inventoryMap = inventoryList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalInventory)); + + // 批量查询所有产品的已使用数量(排除当前批次的使用记录,因为我们要替换它们) + var oldUsageRecordIds = oldUsageRecords.Select(x => x.Id).ToList(); + var allUsageList = await _db.Queryable() + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(oldUsageRecordIds.Any(), x => !oldUsageRecordIds.Contains(x.Id)) // 排除当前批次的使用记录 + .GroupBy(x => x.ProductId) + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum(x.UsageQuantity) }) + .ToListAsync(); + var usageMap = allUsageList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalUsage)); + + // 批量查询所有产品的名称和价格 + var productDict = await _db.Queryable() + .Where(x => productIds.Contains(x.Id)) + .Select(x => new { x.Id, x.ProductName, x.Price }) + .ToListAsync(); + var productInfoMap = productDict.ToDictionary(k => k.Id, v => new { v.ProductName, v.Price }); + + // 验证每个产品的库存是否充足 + foreach (var group in productGroups) + { + var productId = group.Key; + var totalNewQuantity = group.Sum(x => x.Item.UsageQuantity); + + if (!productInfoMap.ContainsKey(productId)) + { + throw NCCException.Oh($"产品ID {productId} 不存在"); + } + + var totalInventory = inventoryMap.GetValueOrDefault(productId, 0); + var totalUsage = usageMap.GetValueOrDefault(productId, 0); + var availableInventory = totalInventory - totalUsage; + + if (availableInventory < totalNewQuantity) + { + var productName = productInfoMap[productId].ProductName; + throw NCCException.Oh($"产品 {productName} 库存不足,当前可用库存:{availableInventory},需要数量:{totalNewQuantity}"); + } + } + + _db.Ado.BeginTran(); + + try + { + // 1. 逻辑删除旧的使用记录 + if (oldUsageRecords.Any()) + { + foreach (var oldRecord in oldUsageRecords) + { + oldRecord.IsEffective = StatusEnum.无效.GetHashCode(); + oldRecord.UpdateUser = _userManager.UserId; + oldRecord.UpdateTime = DateTime.Now; + } + await _db.Updateable(oldUsageRecords).ExecuteCommandAsync(); + } + + // 2. 批量计算新使用记录的平均价格(根据库存计算的加权平均价格) + var productAveragePriceMap = new Dictionary(); + foreach (var productId in productIds) + { + var product = productInfoMap[productId]; + var averagePrice = await CalculateAveragePriceFromInventoryAsync(productId, product.Price); + productAveragePriceMap[productId] = averagePrice; + } + + // 3. 创建新的使用记录 + var entitiesToInsert = new List(); + foreach (var item in input.UsageItems) + { + // 从平均价格字典获取单价(根据库存计算的加权平均价格) + var unitPrice = productAveragePriceMap.GetValueOrDefault(item.ProductId, 0); + var totalAmount = unitPrice * item.UsageQuantity; + + var usageEntity = new LqInventoryUsageEntity + { + Id = YitIdHelper.NextId().ToString(), + ProductId = item.ProductId, + StoreId = item.StoreId, + UsageTime = item.UsageTime, + UsageQuantity = item.UsageQuantity, + UnitPrice = unitPrice, + TotalAmount = totalAmount, + RelatedConsumeId = item.RelatedConsumeId, + UsageBatchId = application.UsageBatchId, // 使用相同的批次ID + CreateUser = _userManager.UserId, + CreateTime = DateTime.Now, + IsEffective = StatusEnum.有效.GetHashCode() + }; + + entitiesToInsert.Add(usageEntity); + } + + // 批量插入新使用记录 + if (entitiesToInsert.Any()) + { + await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); + } + + // 4. 重新计算申请总金额 + var newTotalAmount = entitiesToInsert.Sum(x => x.TotalAmount); + application.TotalAmount = newTotalAmount; + application.UpdateUser = _userManager.UserId; + application.UpdateTime = DateTime.Now; + + await _db.Updateable(application).ExecuteCommandAsync(); + + // 5. 检查关联数据是否需要更新 + // 如果有关联消耗ID,检查消耗记录是否存在 + var relatedConsumeIds = input.UsageItems + .Where(x => !string.IsNullOrWhiteSpace(x.RelatedConsumeId)) + .Select(x => x.RelatedConsumeId) + .Distinct() + .ToList(); + + if (relatedConsumeIds.Any()) + { + // 这里可以添加对关联消耗记录的检查逻辑 + // 例如:检查消耗记录是否存在,是否需要更新数量等 + // 由于不清楚具体的消耗记录表结构,这里只做记录 + _logger.LogInformation($"修改申请 {input.ApplicationId} 的使用记录,关联消耗ID:{string.Join(",", relatedConsumeIds)}"); + } + + _db.Ado.CommitTran(); + } + catch + { + _db.Ado.RollbackTran(); + throw; + } + } + catch (Exception ex) + { + _db.Ado.RollbackTran(); + _logger.LogError(ex, "修改申请使用记录失败"); + throw NCCException.Oh($"修改失败:{ex.Message}"); + } + } + #endregion #region 门店领取统计 diff --git a/sql/创建库存使用申请审批流程表.sql b/sql/创建库存使用申请审批流程表.sql index 4d22796..60d040a 100644 --- a/sql/创建库存使用申请审批流程表.sql +++ b/sql/创建库存使用申请审批流程表.sql @@ -109,3 +109,4 @@ WHERE u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL; + -- libgit2 0.21.4