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