From e0ae9bdfc5b958352465a884a7de82967214cd2d Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Mon, 20 Oct 2025 15:42:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A1=E7=AE=97=E5=A4=84=E6=96=B9=E6=88=90?= =?UTF-8?q?=E6=9C=AC=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Medicinal/MedicinalService.cs | 3 +- .../Prescription/PrescriptionService.cs | 352 ++++++++++++++---- 2 files changed, 278 insertions(+), 77 deletions(-) diff --git a/SimpleAdmin.Application/Services/Medicinal/MedicinalService.cs b/SimpleAdmin.Application/Services/Medicinal/MedicinalService.cs index 0b20973..a7f8a5b 100644 --- a/SimpleAdmin.Application/Services/Medicinal/MedicinalService.cs +++ b/SimpleAdmin.Application/Services/Medicinal/MedicinalService.cs @@ -45,11 +45,12 @@ public class MedicinalService : DbRepository, IMedicinalService // 3. 批量计算:成本、剩余价值、总使用量 var medicinalIds = pageInfo.List.Select(dto => dto.Id).ToList(); var costAndUsageDict = await BatchCalculateCostAndValue(medicinalIds); - + //decimal all = 0; foreach (var item in pageInfo.List) { if (costAndUsageDict.TryGetValue(item.Id, out var metrics)) { + //all += metrics.usedCost; item.UsedCost = metrics.usedCost; // 使用成本 item.RemainingValue = metrics.remainingValue; // 剩余价值 item.TotalUsedWeight = metrics.totalUsedWeight; // 新增:总使用量 diff --git a/SimpleAdmin.Application/Services/Prescription/PrescriptionService.cs b/SimpleAdmin.Application/Services/Prescription/PrescriptionService.cs index 11733c6..a1156fc 100644 --- a/SimpleAdmin.Application/Services/Prescription/PrescriptionService.cs +++ b/SimpleAdmin.Application/Services/Prescription/PrescriptionService.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; +using Minio.DataModel; using NewLife.Reflection; using SimpleAdmin.Application.Entity; using SimpleAdmin.Application.Services.Prescription; @@ -19,9 +20,174 @@ using Yitter.IdGenerator; public class PrescriptionService : DbRepository, IPrescriptionService { + //public async Task> GetPrescriptionList(PrescriptionQueryInput input) + //{ + // var query = Context.Queryable() + // .LeftJoin((p, pe) => p.PersonId == pe.Id) + // .LeftJoin((p, pe, emp) => pe.EmployeeId != null && pe.EmployeeId == emp.Id) + // .WhereIF(!string.IsNullOrEmpty(input.PersonName), (p, pe, emp) => + // input.PersonName.Contains(pe.Name) || pe.Name.Contains(input.PersonName) || + // input.PersonName.Contains(emp.Name) || emp.Name.Contains(input.PersonName)) + // .WhereIF(input.PersonType.HasValue && input.PersonType == PersonType.Employee, (p, pe) => + // pe.EmployeeId == null) + // .WhereIF(input.PersonType.HasValue && input.PersonType != PersonType.Employee, (p, pe) => + // pe.EmployeeId != null); + + // try + // { + // // 获取处方基础信息(添加总成本属性) + // var prescriptionPage = await query.OrderByDescending((p, pe, emp) => p.CreateTime) + // .Select((p, pe, emp) => new PrescriptionDto + // { + // Id = p.Id, + // PersonId = p.PersonId, + // PersonName = pe.Name, + // PersonType = pe.EmployeeId != null ? PersonType.Relative : PersonType.Employee, + // VisitDate = p.VisitDate, + // CreateTime = p.CreateTime.Value, + // EmployeeName = emp.Name, + // CanEdit = false, + // TotalMedicineCost = 0 // 初始化总成本 + // }).ToPagedListAsync(input.PageNum, input.PageSize); + + // // 设置可编辑状态 + // foreach (var item in prescriptionPage.List) + // { + // if (item.CreateTime.Date == DateTime.Now.Date || UserManager.SuperAdmin) + // { + // item.CanEdit = true; + // } + // } + + // // 获取当前页所有处方的药材明细 + // var pidList = prescriptionPage.List.Select(a => a.Id).ToList(); + // var prescriptionHerbList = await Context.Queryable() + // .Where(a => pidList.Contains(a.PrescriptionId)) + // .ToListAsync(); + + // if (prescriptionHerbList.Any()) + // { + // // 1. 按【处方ID+药材ID】分组,统计每个处方中每种药材的总使用量 + // var prescriptionMedicinalUsage = prescriptionHerbList + // .GroupBy(h => new { h.PrescriptionId, h.MedicinalId }) + // .Select(g => new + // { + // PrescriptionId = g.Key.PrescriptionId, + // MedicinalId = g.Key.MedicinalId, + // TotalUsedWeight = g.Sum(h => h.AmountG) // 同一处方中同一药材的总用量 + // }) + // .ToList(); + + // // 2. 收集所有涉及的药材ID,批量获取FIFO计算所需数据 + // var medicinalIds = prescriptionMedicinalUsage.Select(u => u.MedicinalId).Distinct().ToList(); + // var fifoData = await GetMedicinalFifoData(medicinalIds); + + // // 3. 按处方ID分组,计算每个处方的总成本 + // var groupedByPrescription = prescriptionMedicinalUsage + // .GroupBy(u => u.PrescriptionId) + // .ToDictionary(g => g.Key, g => g.ToList()); + + // // 4. 遍历处方,计算总成本 + // foreach (var prescription in prescriptionPage.List) + // { + // if (groupedByPrescription.TryGetValue(prescription.Id, out var usages)) + // { + // foreach (var usage in usages) + // { + // // 基于FIFO计算当前药材的使用成本 + // var herbCost = CalculateHerbCostByFifo( + // fifoData, + // usage.MedicinalId, + // usage.TotalUsedWeight + // ); + + // // 累加到处方总成本 + // prescription.TotalMedicineCost += herbCost; + // } + // } + // } + // } + // foreach (var item in prescriptionPage.List) + // { + // //家属成本多十个点 + // if (item.PersonType== PersonType.Relative) + // { + // item.TotalMedicineCost = Math.Round(item.TotalMedicineCost * (decimal)1.1, 2, MidpointRounding.AwayFromZero); + // } + // } + // return prescriptionPage; + // } + // catch (Exception ex) + // { + // Console.WriteLine($"错误: {ex.Message}"); + // throw; + // } + //} + + ///// + ///// 获取药材FIFO计算所需的基础数据(入库记录) + ///// + //private async Task>> GetMedicinalFifoData(List medicinalIds) + //{ + // if (!medicinalIds.Any()) + // return new Dictionary>(); + + // // 批量查询所有涉及药材的入库记录(按时间排序) + // var addLogs = await Context.Queryable() + // .Where(l => medicinalIds.Contains(l.MedicinalId)) + // .OrderBy(l => l.CreateTime) + // .Select(l => new MedicinalFifoRecord + // { + // MedicinalId = l.MedicinalId, + // AddWeight = l.AddWeight, + // UnitPrice = l.UnitPrice + // }) + // .ToListAsync(); + + // // 按药材ID分组 + // return addLogs.GroupBy(l => l.MedicinalId) + // .ToDictionary(g => g.Key, g => g.ToList()); + //} + + ///// + ///// 按FIFO规则计算单种药材的使用成本 + ///// + //private decimal CalculateHerbCostByFifo( + // Dictionary> fifoData, + // long medicinalId, + // decimal usedWeight) + //{ + // if (usedWeight <= 0 || !fifoData.TryGetValue(medicinalId, out var records) || !records.Any()) + // return 0; + + // decimal remainingToCalculate = usedWeight; + // decimal totalCost = 0; + + // // 按入库顺序依次消耗 + // foreach (var record in records) + // { + // if (remainingToCalculate <= 0) + // break; + + // if (record.AddWeight <= remainingToCalculate) + // { + // // 当前批次全部消耗 + // totalCost += record.AddWeight * record.UnitPrice; + // remainingToCalculate -= record.AddWeight; + // } + // else + // { + // // 消耗当前批次的部分量 + // totalCost += remainingToCalculate * record.UnitPrice; + // remainingToCalculate = 0; + // } + // } + // return Math.Round(totalCost, 2, MidpointRounding.AwayFromZero); + //} + + public async Task> GetPrescriptionList(PrescriptionQueryInput input) { - // 原有查询逻辑保持不变 var query = Context.Queryable() .LeftJoin((p, pe) => p.PersonId == pe.Id) .LeftJoin((p, pe, emp) => pe.EmployeeId != null && pe.EmployeeId == emp.Id) @@ -35,7 +201,7 @@ public class PrescriptionService : DbRepository, IPrescriptionServ try { - // 获取处方基础信息(添加总成本属性) + // 获取处方基础信息 var prescriptionPage = await query.OrderByDescending((p, pe, emp) => p.CreateTime) .Select((p, pe, emp) => new PrescriptionDto { @@ -47,74 +213,99 @@ public class PrescriptionService : DbRepository, IPrescriptionServ CreateTime = p.CreateTime.Value, EmployeeName = emp.Name, CanEdit = false, - TotalMedicineCost = 0 // 初始化总成本 + TotalMedicineCost = 0 }).ToPagedListAsync(input.PageNum, input.PageSize); + if (!prescriptionPage.List.Any()) return prescriptionPage; + // 获取当前页所有处方的 Id + var pidList = prescriptionPage.List.Select(a => a.Id).ToList(); - // 设置可编辑状态 + // ------------------ “所有处方”按时间的消耗 ------------------ + + // 1) 取出所有处方 Id(按时间升序),用于按时间顺序模拟消耗 + var allPrescriptionIds = await Context.Queryable() + .OrderBy(p => p.CreateTime) + .Select(p => p.Id) + .ToListAsync(); + + // 2) 拉取所有处方对应的药材明细 + var allPrescriptionHerbList = await Context.Queryable() + .Where(ph => allPrescriptionIds.Contains(ph.PrescriptionId)) + .ToListAsync(); + + // 3) 按【处方ID+药材ID】分组,统计每个处方中每种药材的总使用量(全量) + var allUsageGroups = allPrescriptionHerbList + .GroupBy(h => new { h.PrescriptionId, h.MedicinalId }) + .Select(g => new + { + PrescriptionId = g.Key.PrescriptionId, + MedicinalId = g.Key.MedicinalId, + TotalUsedWeight = g.Sum(h => h.AmountG) + }) + .ToList(); + + // 4) 收集所有涉及的药材 ID,批量获取 FIFO 入库数据 + var allMedicinalIds = allUsageGroups.Select(u => u.MedicinalId).Distinct().ToList(); + var fifoData = await GetMedicinalFifoData(allMedicinalIds); + + // 5) 为每个处方建立快速查找:PrescriptionId -> usages + var groupedByPrescriptionAll = allUsageGroups + .GroupBy(u => u.PrescriptionId) + .ToDictionary(g => g.Key, g => g.ToList()); + + // 6) 初始化 FIFO 偏移(累计消耗量)字典偏移字典 + var fifoOffsets = new Dictionary(); + + // 7) 模拟按时间顺序消耗所有处方,并把每张处方的成本记录到字典 + var prescriptionCostDict = new Dictionary(); + + foreach (var pid in allPrescriptionIds) + { + decimal presCost = 0; + + if (groupedByPrescriptionAll.TryGetValue(pid, out var usages)) + { + foreach (var usage in usages) + { + var herbCost = CalculateHerbCostByFifoWithOffset( + fifoData, + fifoOffsets, + usage.MedicinalId, + usage.TotalUsedWeight + ); + + presCost += herbCost; + } + } + + // 四舍五入到 4 位(在分处方层面保留更多精度,最后显示再 round 2 位) + prescriptionCostDict[pid] = Math.Round(presCost, 4, MidpointRounding.AwayFromZero); + } + //decimal all = 0; + // 8) 将计算结果映射回当前页的处方 foreach (var item in prescriptionPage.List) { + if (prescriptionCostDict.TryGetValue(item.Id, out var cost)) + { + item.TotalMedicineCost = cost; + } + // 设置 CanEdit if (item.CreateTime.Date == DateTime.Now.Date || UserManager.SuperAdmin) { item.CanEdit = true; } - } - - // 获取当前页所有处方的药材明细 - var pidList = prescriptionPage.List.Select(a => a.Id).ToList(); - var prescriptionHerbList = await Context.Queryable() - .Where(a => pidList.Contains(a.PrescriptionId)) - .ToListAsync(); - - if (prescriptionHerbList.Any()) - { - // 1. 按【处方ID+药材ID】分组,统计每个处方中每种药材的总使用量 - var prescriptionMedicinalUsage = prescriptionHerbList - .GroupBy(h => new { h.PrescriptionId, h.MedicinalId }) - .Select(g => new - { - PrescriptionId = g.Key.PrescriptionId, - MedicinalId = g.Key.MedicinalId, - TotalUsedWeight = g.Sum(h => h.AmountG) // 同一处方中同一药材的总用量 - }) - .ToList(); - - // 2. 收集所有涉及的药材ID,批量获取FIFO计算所需数据 - var medicinalIds = prescriptionMedicinalUsage.Select(u => u.MedicinalId).Distinct().ToList(); - var fifoData = await GetMedicinalFifoData(medicinalIds); - - // 3. 按处方ID分组,计算每个处方的总成本 - var groupedByPrescription = prescriptionMedicinalUsage - .GroupBy(u => u.PrescriptionId) - .ToDictionary(g => g.Key, g => g.ToList()); - - // 4. 遍历处方,计算总成本 - foreach (var prescription in prescriptionPage.List) + //all += item.TotalMedicineCost; + // 家属加价 10% + if (item.PersonType == PersonType.Relative) { - if (groupedByPrescription.TryGetValue(prescription.Id, out var usages)) - { - foreach (var usage in usages) - { - // 基于FIFO计算当前药材的使用成本 - var herbCost = CalculateHerbCostByFifo( - fifoData, - usage.MedicinalId, - usage.TotalUsedWeight - ); - - // 累加到处方总成本 - prescription.TotalMedicineCost += herbCost; - } - } + item.TotalMedicineCost = Math.Round(item.TotalMedicineCost * 1.1m, 2, MidpointRounding.AwayFromZero); } - } - foreach (var item in prescriptionPage.List) - { - //家属成本多十个点 - if (item.PersonType== PersonType.Relative) + else { - item.TotalMedicineCost = Math.Round(item.TotalMedicineCost * (decimal)1.1, 2, MidpointRounding.AwayFromZero); + item.TotalMedicineCost = Math.Round(item.TotalMedicineCost, 2, MidpointRounding.AwayFromZero); } + } + return prescriptionPage; } catch (Exception ex) @@ -124,6 +315,7 @@ public class PrescriptionService : DbRepository, IPrescriptionServ } } + /// /// 获取药材FIFO计算所需的基础数据(入库记录) /// @@ -150,39 +342,47 @@ public class PrescriptionService : DbRepository, IPrescriptionServ } /// - /// 按FIFO规则计算单种药材的使用成本 + /// 按FIFO规则计算单种药材的使用成本(累计消耗) /// - private decimal CalculateHerbCostByFifo( + private decimal CalculateHerbCostByFifoWithOffset( Dictionary> fifoData, + Dictionary fifoOffsets, long medicinalId, decimal usedWeight) { if (usedWeight <= 0 || !fifoData.TryGetValue(medicinalId, out var records) || !records.Any()) return 0; - decimal remainingToCalculate = usedWeight; + decimal alreadyUsed = fifoOffsets.GetValueOrDefault(medicinalId, 0); + decimal remainingToUse = usedWeight; decimal totalCost = 0; - // 按入库顺序依次消耗 foreach (var record in records) { - if (remainingToCalculate <= 0) + if (remainingToUse <= 0) break; - if (record.AddWeight <= remainingToCalculate) + // 跳过已经消耗完的批次 + if (alreadyUsed >= record.AddWeight) { - // 当前批次全部消耗 - totalCost += record.AddWeight * record.UnitPrice; - remainingToCalculate -= record.AddWeight; - } - else - { - // 消耗当前批次的部分量 - totalCost += remainingToCalculate * record.UnitPrice; - remainingToCalculate = 0; + alreadyUsed -= record.AddWeight; + continue; } + + // 当前批次可用量 + decimal available = record.AddWeight - alreadyUsed; + decimal toConsume = Math.Min(available, remainingToUse); + + totalCost += toConsume * record.UnitPrice; + remainingToUse -= toConsume; + alreadyUsed = 0; + } - return Math.Round(totalCost, 2, MidpointRounding.AwayFromZero); + + // 更新累计消耗量 + fifoOffsets[medicinalId] = fifoOffsets.GetValueOrDefault(medicinalId, 0) + usedWeight; + + return Math.Round(totalCost, 4, MidpointRounding.AwayFromZero); } @@ -230,7 +430,7 @@ public class PrescriptionService : DbRepository, IPrescriptionServ Name = pe.Name }) .FirstAsync(); - + employee = empPerson; } var RelationShipName = await GetRelationTypes(); @@ -464,11 +664,11 @@ public class PrescriptionService : DbRepository, IPrescriptionServ var medicinal = medicinalList.FirstOrDefault(m => m.Id == herb.Id); if (medicinal == null) throw Oops.Bah($"药材[{herb.Id}]不存在"); - if (herb.AmountG<=0) + if (herb.AmountG <= 0) throw Oops.Bah($"请填写正确的消耗量,药材[{medicinal.MedicinalName}]"); if (medicinal.Number < herb.AmountG) throw Oops.Bah($"药材[{medicinal.MedicinalName}]库存不足,当前库存: {medicinal.Number}g,需要: {herb.AmountG}g"); - if(HerbsId.Contains(herb.Id)) throw Oops.Bah($"请勿重复添加同一个药材【{medicinal.MedicinalName}】"); + if (HerbsId.Contains(herb.Id)) throw Oops.Bah($"请勿重复添加同一个药材【{medicinal.MedicinalName}】"); HerbsId.Add(herb.Id); medicinal.Number -= herb.AmountG; }