计算处方成本调整
This commit is contained in:
@@ -45,11 +45,12 @@ public class MedicinalService : DbRepository<Medicinal>, 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; // 新增:总使用量
|
||||
|
||||
@@ -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<Prescription>, IPrescriptionService
|
||||
{
|
||||
//public async Task<SqlSugarPagedList<PrescriptionDto>> GetPrescriptionList(PrescriptionQueryInput input)
|
||||
//{
|
||||
// var query = Context.Queryable<Prescription>()
|
||||
// .LeftJoin<Person>((p, pe) => p.PersonId == pe.Id)
|
||||
// .LeftJoin<Person>((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<PrescriptionHerb>()
|
||||
// .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;
|
||||
// }
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// 获取药材FIFO计算所需的基础数据(入库记录)
|
||||
///// </summary>
|
||||
//private async Task<Dictionary<long, List<MedicinalFifoRecord>>> GetMedicinalFifoData(List<long> medicinalIds)
|
||||
//{
|
||||
// if (!medicinalIds.Any())
|
||||
// return new Dictionary<long, List<MedicinalFifoRecord>>();
|
||||
|
||||
// // 批量查询所有涉及药材的入库记录(按时间排序)
|
||||
// var addLogs = await Context.Queryable<MedicinalAddLog>()
|
||||
// .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());
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// 按FIFO规则计算单种药材的使用成本
|
||||
///// </summary>
|
||||
//private decimal CalculateHerbCostByFifo(
|
||||
// Dictionary<long, List<MedicinalFifoRecord>> 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<SqlSugarPagedList<PrescriptionDto>> GetPrescriptionList(PrescriptionQueryInput input)
|
||||
{
|
||||
// 原有查询逻辑保持不变
|
||||
var query = Context.Queryable<Prescription>()
|
||||
.LeftJoin<Person>((p, pe) => p.PersonId == pe.Id)
|
||||
.LeftJoin<Person>((p, pe, emp) => pe.EmployeeId != null && pe.EmployeeId == emp.Id)
|
||||
@@ -35,7 +201,7 @@ public class PrescriptionService : DbRepository<Prescription>, 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<Prescription>, 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<Prescription>()
|
||||
.OrderBy(p => p.CreateTime)
|
||||
.Select(p => p.Id)
|
||||
.ToListAsync();
|
||||
|
||||
// 2) 拉取所有处方对应的药材明细
|
||||
var allPrescriptionHerbList = await Context.Queryable<PrescriptionHerb>()
|
||||
.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<long, decimal>();
|
||||
|
||||
// 7) 模拟按时间顺序消耗所有处方,并把每张处方的成本记录到字典
|
||||
var prescriptionCostDict = new Dictionary<long, decimal>();
|
||||
|
||||
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<PrescriptionHerb>()
|
||||
.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<Prescription>, IPrescriptionServ
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取药材FIFO计算所需的基础数据(入库记录)
|
||||
/// </summary>
|
||||
@@ -150,39 +342,47 @@ public class PrescriptionService : DbRepository<Prescription>, IPrescriptionServ
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按FIFO规则计算单种药材的使用成本
|
||||
/// 按FIFO规则计算单种药材的使用成本(累计消耗)
|
||||
/// </summary>
|
||||
private decimal CalculateHerbCostByFifo(
|
||||
private decimal CalculateHerbCostByFifoWithOffset(
|
||||
Dictionary<long, List<MedicinalFifoRecord>> fifoData,
|
||||
Dictionary<long, decimal> 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<Prescription>, IPrescriptionServ
|
||||
Name = pe.Name
|
||||
})
|
||||
.FirstAsync();
|
||||
|
||||
|
||||
employee = empPerson;
|
||||
}
|
||||
var RelationShipName = await GetRelationTypes();
|
||||
@@ -464,11 +664,11 @@ public class PrescriptionService : DbRepository<Prescription>, 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user