865 lines
36 KiB
C#
865 lines
36 KiB
C#
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||
//
|
||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||
|
||
|
||
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;
|
||
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)
|
||
.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);
|
||
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;
|
||
}
|
||
//all += item.TotalMedicineCost;
|
||
// 家属加价 10%
|
||
if (item.PersonType == PersonType.Relative)
|
||
{
|
||
item.TotalMedicineCost = Math.Round(item.TotalMedicineCost * 1.1m, 2, MidpointRounding.AwayFromZero);
|
||
}
|
||
else
|
||
{
|
||
item.TotalMedicineCost = Math.Round(item.TotalMedicineCost, 2, MidpointRounding.AwayFromZero);
|
||
}
|
||
|
||
}
|
||
//var all= prescriptionPage.List.Sum(a => a.TotalMedicineCost);
|
||
//var cc = await GetAllPrescriptionTotalCost();
|
||
return prescriptionPage;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"错误: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 获取所有处方的总药材成本
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public async Task<decimal> GetAllPrescriptionTotalCost()
|
||
{
|
||
// 1) 获取所有处方的 ID(按创建时间升序),并明确类型为 long
|
||
var allPrescriptionIds = await Context.Queryable<Prescription>()
|
||
.OrderBy(p => p.CreateTime)
|
||
.Select(p => p.Id) // 确保这里的 Id 类型是 long 或 long?
|
||
.ToListAsync();
|
||
|
||
if (!allPrescriptionIds.Any())
|
||
return 0m;
|
||
|
||
// 把 id 列表规范为 long 列表(处理可能的 nullable)
|
||
var allIdsLong = allPrescriptionIds.Select(id => Convert.ToInt64(id)).ToList();
|
||
|
||
// 2) 获取所有处方的药材使用明细
|
||
var allPrescriptionHerbList = await Context.Queryable<PrescriptionHerb>()
|
||
.Where(ph => allIdsLong.Contains(Convert.ToInt64(ph.PrescriptionId)))
|
||
.ToListAsync();
|
||
|
||
var allUsageGroups = allPrescriptionHerbList
|
||
.GroupBy(h => new { PrescriptionId = Convert.ToInt64(h.PrescriptionId), h.MedicinalId })
|
||
.Select(g => new
|
||
{
|
||
PrescriptionId = g.Key.PrescriptionId,
|
||
g.Key.MedicinalId,
|
||
TotalUsedWeight = g.Sum(h => h.AmountG)
|
||
})
|
||
.ToList();
|
||
|
||
// 3) 获取所有药材的 FIFO 入库记录
|
||
var allMedicinalIds = allUsageGroups.Select(u => u.MedicinalId).Distinct().ToList();
|
||
var fifoData = await GetMedicinalFifoData(allMedicinalIds);
|
||
|
||
// 4) 按处方构建使用分组(key 为 long)
|
||
var groupedByPrescriptionAll = allUsageGroups
|
||
.GroupBy(u => u.PrescriptionId)
|
||
.ToDictionary(g => g.Key, g => g.ToList());
|
||
|
||
// 5) FIFO 偏移量
|
||
var fifoOffsets = new Dictionary<long, decimal>();
|
||
|
||
decimal totalSum = 0m;
|
||
|
||
// 6) 获取每张处方的人员类型(IsRelative: bool),并把 key 明确为 long,避免类型不匹配
|
||
var personTmp = await Context.Queryable<Prescription>()
|
||
.LeftJoin<Person>((p, pe) => p.PersonId == pe.Id)
|
||
.Where(p => allIdsLong.Contains(Convert.ToInt64(p.Id)))
|
||
.Select((p, pe) => new
|
||
{
|
||
Id = p.Id,
|
||
IsRelative = pe.EmployeeId != null
|
||
})
|
||
.ToListAsync();
|
||
|
||
// 明确转换 key 为 long(处理 nullable)
|
||
var personInfoDict = personTmp
|
||
.ToDictionary(x => Convert.ToInt64(x.Id), x => x.IsRelative);
|
||
|
||
// 7) FIFO 成本计算(按时间顺序已由 allIdsLong 保持)
|
||
foreach (var pidObj in allIdsLong)
|
||
{
|
||
long pid = pidObj; // 明确 pid 是 long
|
||
|
||
decimal presCost = 0m;
|
||
|
||
if (groupedByPrescriptionAll.TryGetValue(pid, out var usages))
|
||
{
|
||
foreach (var usage in usages)
|
||
{
|
||
var cost = CalculateHerbCostByFifoWithOffset(
|
||
fifoData,
|
||
fifoOffsets,
|
||
usage.MedicinalId,
|
||
usage.TotalUsedWeight
|
||
);
|
||
|
||
presCost += cost;
|
||
}
|
||
}
|
||
|
||
// 处方级别保留 4 位小数
|
||
presCost = Math.Round(presCost, 4, MidpointRounding.AwayFromZero);
|
||
|
||
// 家属加价 10%(若字典缺失,默认不是家属)
|
||
var isRelative = personInfoDict.TryGetValue(pid, out var rel) && rel;
|
||
if (isRelative)
|
||
{
|
||
presCost = Math.Round(presCost * 1.1m, 2, MidpointRounding.AwayFromZero);
|
||
}
|
||
else
|
||
{
|
||
presCost = Math.Round(presCost, 2, MidpointRounding.AwayFromZero);
|
||
}
|
||
|
||
totalSum += presCost;
|
||
}
|
||
|
||
return totalSum;
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <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 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 alreadyUsed = fifoOffsets.GetValueOrDefault(medicinalId, 0);
|
||
decimal remainingToUse = usedWeight;
|
||
decimal totalCost = 0;
|
||
|
||
foreach (var record in records)
|
||
{
|
||
if (remainingToUse <= 0)
|
||
break;
|
||
|
||
// 跳过已经消耗完的批次
|
||
if (alreadyUsed >= record.AddWeight)
|
||
{
|
||
alreadyUsed -= record.AddWeight;
|
||
continue;
|
||
}
|
||
|
||
// 当前批次可用量
|
||
decimal available = record.AddWeight - alreadyUsed;
|
||
decimal toConsume = Math.Min(available, remainingToUse);
|
||
|
||
totalCost += toConsume * record.UnitPrice;
|
||
remainingToUse -= toConsume;
|
||
alreadyUsed = 0;
|
||
|
||
}
|
||
|
||
// 更新累计消耗量
|
||
fifoOffsets[medicinalId] = fifoOffsets.GetValueOrDefault(medicinalId, 0) + usedWeight;
|
||
|
||
return Math.Round(totalCost, 4, MidpointRounding.AwayFromZero);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 获取处方详情
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="Exception"></exception>
|
||
public async Task<PrescriptionDetailDto> GetPrescriptionDetail(long id)
|
||
{
|
||
var prescription = await Context.Queryable<Prescription>()
|
||
.Where(p => p.Id == id)
|
||
.FirstAsync();
|
||
|
||
if (prescription == null)
|
||
{
|
||
throw Oops.Bah("处方不存在");
|
||
}
|
||
|
||
var person = await Context.Queryable<Person>()
|
||
.Where(pe => pe.Id == prescription.PersonId)
|
||
.FirstAsync();
|
||
|
||
var herbs = await Context.Queryable<PrescriptionHerb, Medicinal>((ph, mt) => ph.MedicinalId == mt.Id)
|
||
.Where(ph => ph.PrescriptionId == id)
|
||
.Select((ph, mt) => new PrescriptionHerbDto
|
||
{
|
||
MedicinalId = mt.Id,
|
||
MedicinalName = mt.MedicinalName,
|
||
AmountG = ph.AmountG,
|
||
Note = ph.Note
|
||
})
|
||
.ToListAsync();
|
||
|
||
// 判断是否是员工还是家属
|
||
PersonDto employee = null;
|
||
if (person.EmployeeId.HasValue)
|
||
{
|
||
var empPerson = await Context.Queryable<Person>()
|
||
.Where(pe => pe.Id == person.EmployeeId)
|
||
.Select(pe => new PersonDto
|
||
{
|
||
Id = pe.Id,
|
||
Name = pe.Name
|
||
})
|
||
.FirstAsync();
|
||
|
||
employee = empPerson;
|
||
}
|
||
var RelationShipName = await GetRelationTypes();
|
||
return new PrescriptionDetailDto
|
||
{
|
||
Id = prescription.Id,
|
||
Person = new PersonDetailDto
|
||
{
|
||
Id = person.Id,
|
||
Name = person.Name,
|
||
Birthday = person.Birthday,
|
||
Weight = prescription.Weight,
|
||
Height = person.Height,
|
||
IsEmployee = !person.EmployeeId.HasValue,
|
||
EmployeeId = person.EmployeeId,
|
||
EmployeeName = employee?.Name,
|
||
RelationShip = person.RelationShip,
|
||
RelationShipName = RelationShipName.FirstOrDefault(t => t.Id == person.RelationShip)?.Name
|
||
},
|
||
DiagnosisResult = prescription.DiagnosisResult,
|
||
UserMedicinaAdvice = prescription.UserMedicinaAdvice,
|
||
VisitDate = prescription.VisitDate,
|
||
Herbs = herbs,
|
||
CanEdit = prescription.CreateTime.Value.Date == DateTime.Now.Date
|
||
};
|
||
}
|
||
/// <summary>
|
||
/// 添加编辑处方记录
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
public async Task<long> SavePrescription(SavePrescriptionInput input)
|
||
{
|
||
// 基础参数验证
|
||
if (input == null)
|
||
throw Oops.Bah("输入参数不能为空");
|
||
|
||
if (input.Herbs == null || !input.Herbs.Any())
|
||
throw Oops.Bah("处方药材不能为空");
|
||
|
||
await Context.Ado.BeginTranAsync();
|
||
try
|
||
{
|
||
// 用于在方法内记录新创建的员工 id(如果需要)
|
||
long? createdEmployeeId = null;
|
||
|
||
// ---------- 1. 处理前端在 person 中携带的“临时员工”(如果存在) ----------
|
||
if (input.Person == null)
|
||
throw Oops.Bah("人员信息不能为空");
|
||
|
||
// 场景 A: 病人是家属(IsEmployee == false),但关联的员工是前端临时新增
|
||
if (!input.Person.IsEmployee)
|
||
{
|
||
// 如果前端明确标记关联员工为临时新增(EmployeeIsTemp),需要先创建员工
|
||
if (input.Person.EmployeeIsTemp)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(input.Person.EmployeeName))
|
||
throw Oops.Bah("标记了临时关联员工,但未提供员工姓名");
|
||
|
||
// 创建新员工记录(作为 Person 表的一条记录,RelationShip=null, EmployeeId=null)
|
||
var newEmp = new Person
|
||
{
|
||
Id = YitIdHelper.NextId(),
|
||
Name = input.Person.EmployeeName?.Trim(),
|
||
};
|
||
await Context.Insertable(newEmp).ExecuteCommandAsync();
|
||
createdEmployeeId = newEmp.Id;
|
||
}
|
||
else if (input.Person.EmployeeId.HasValue && input.Person.EmployeeId.Value > 0)
|
||
{
|
||
// 如果前端传的是已有员工 ID,校验该员工是否存在
|
||
var exists = await Context.Queryable<Person>()
|
||
.AnyAsync(p => p.Id == input.Person.EmployeeId.Value);
|
||
if (!exists)
|
||
throw Oops.Bah($"关联的员工 ID={input.Person.EmployeeId.Value} 不存在");
|
||
}
|
||
else if (!string.IsNullOrWhiteSpace(input.Person.EmployeeName))
|
||
{
|
||
var newEmp = new Person
|
||
{
|
||
Id = YitIdHelper.NextId(),
|
||
Name = input.Person.EmployeeName.Trim(),
|
||
};
|
||
await Context.Insertable(newEmp).ExecuteCommandAsync();
|
||
createdEmployeeId = newEmp.Id;
|
||
}
|
||
else
|
||
{
|
||
// 没有绑定员工信息(既不是 temp,也没有 employeeId),则无法继续
|
||
if (input.Person.Id == null || input.Person.Id <= 0)
|
||
throw Oops.Bah("家属未关联员工,请选择或新增关联员工");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 病人是公司员工(IsEmployee == true)
|
||
// 如果没有 Id,但前端同时将该员工标记为临时新增,或者传了 Name 而无 Id,则创建员工
|
||
if ((!input.Person.Id.HasValue || input.Person.Id.Value <= 0) &&
|
||
(!string.IsNullOrWhiteSpace(input.Person.Name) || input.Person.EmployeeIsTemp))
|
||
{
|
||
// 如果前端对员工也标记了 EmployeeIsTemp 且提供了 EmployeeName,则优先使用 EmployeeName(兼容性)
|
||
string empNameForCreate = !string.IsNullOrWhiteSpace(input.Person.Name) ? input.Person.Name.Trim() : input.Person.EmployeeName?.Trim();
|
||
if (string.IsNullOrWhiteSpace(empNameForCreate))
|
||
throw Oops.Bah("新增员工时姓名不能为空");
|
||
|
||
var newEmp = new Person
|
||
{
|
||
Id = YitIdHelper.NextId(),
|
||
Name = empNameForCreate,
|
||
Birthday = input.Person.Birthday,
|
||
Height = input.Person.Height,
|
||
};
|
||
await Context.Insertable(newEmp).ExecuteCommandAsync();
|
||
// 将新创建的员工 id 作为主 person id(病人本身)
|
||
input.Person.Id = newEmp.Id;
|
||
}
|
||
else if (input.Person.Id.HasValue && input.Person.Id.Value > 0)
|
||
{
|
||
// 校验存在性
|
||
var exists = await Context.Queryable<Person>().AnyAsync(p => p.Id == input.Person.Id.Value);
|
||
if (!exists)
|
||
throw Oops.Bah($"指定的人员 ID={input.Person.Id.Value} 不存在");
|
||
}
|
||
}
|
||
|
||
// ---------- 2. 确定主 personId(如果是临时创建的员工, input.Person.Id 已在上一步设置) ----------
|
||
long personId;
|
||
if (input.Person.Id.HasValue && input.Person.Id.Value > 0)
|
||
{
|
||
personId = input.Person.Id.Value;
|
||
// 已存在的人员(或上一步创建的员工),我们仍然可以继续使用 Storageable 更新信息
|
||
}
|
||
else
|
||
{
|
||
// 没有 Id,需新建主人员(可能是家属或员工)
|
||
if (string.IsNullOrWhiteSpace(input.Person.Name))
|
||
throw Oops.Bah("新增人员信息不完整,姓名为必填项");
|
||
|
||
personId = YitIdHelper.NextId();
|
||
}
|
||
|
||
// ---------- 3. 保存/更新主人员信息(使用 Storageable,兼容插入或更新) ----------
|
||
var mappedEmployeeId = input.Person.EmployeeId;
|
||
|
||
// 如果之前创建了员工(createdEmployeeId),优先使用该 id 作为家属的 EmployeeId
|
||
if (createdEmployeeId.HasValue)
|
||
mappedEmployeeId = createdEmployeeId.Value;
|
||
|
||
// 如果 person.IsEmployee == true,则 person.EmployeeId 应为 null(员工不需要关联员工)
|
||
if (input.Person.IsEmployee)
|
||
mappedEmployeeId = null;
|
||
|
||
var person = new Person
|
||
{
|
||
Id = personId,
|
||
Name = input.Person?.Name,
|
||
Birthday = input.Person?.Birthday,
|
||
Height = input.Person?.Height,
|
||
RelationShip = input.Person?.RelationShip,
|
||
EmployeeId = mappedEmployeeId
|
||
};
|
||
await Context.Storageable(person).ExecuteCommandAsync();
|
||
|
||
// ---------- 4. 处方保存/编辑逻辑----------
|
||
Prescription prescription;
|
||
if (input.Id.HasValue) // 编辑
|
||
{
|
||
prescription = await Context.Queryable<Prescription>()
|
||
.FirstAsync(p => p.Id == input.Id.Value);
|
||
|
||
if (prescription == null)
|
||
throw Oops.Bah("处方不存在");
|
||
|
||
if (prescription.CreateTime.Value.Date != DateTime.Now.Date && !UserManager.SuperAdmin)
|
||
throw Oops.Bah("只能编辑当天的处方");
|
||
|
||
// 查询原有消耗记录并恢复库存
|
||
var oldHerbs = await Context.Queryable<PrescriptionHerb>()
|
||
.Where(ph => ph.PrescriptionId == input.Id.Value)
|
||
.ToListAsync();
|
||
|
||
if (oldHerbs.Count > 0)
|
||
{
|
||
foreach (var g in oldHerbs.GroupBy(x => x.MedicinalId))
|
||
{
|
||
// 恢复库存(加回已消耗)
|
||
await Context.Updateable<Medicinal>()
|
||
.SetColumns(m => new Medicinal { Number = m.Number + g.Sum(x => x.AmountG) })
|
||
.Where(m => m.Id == g.Key)
|
||
.ExecuteCommandAsync();
|
||
}
|
||
}
|
||
|
||
// 删除原有处方药材
|
||
await Context.Deleteable<PrescriptionHerb>()
|
||
.Where(ph => ph.PrescriptionId == input.Id.Value)
|
||
.ExecuteCommandAsync();
|
||
|
||
// 更新处方
|
||
prescription.PersonId = personId;
|
||
prescription.Weight = input.Person.Weight;
|
||
prescription.DiagnosisResult = input.DiagnosisResult;
|
||
prescription.UserMedicinaAdvice = input.UserMedicinaAdvice;
|
||
prescription.VisitDate = input.VisitDate ?? DateTime.Now;
|
||
await Context.Updateable(prescription).ExecuteCommandAsync();
|
||
}
|
||
else // 新增
|
||
{
|
||
var prescripId = YitIdHelper.NextId();
|
||
prescription = new Prescription
|
||
{
|
||
Id = prescripId,
|
||
Weight = input.Person.Weight,
|
||
PersonId = personId,
|
||
DiagnosisResult = input.DiagnosisResult,
|
||
UserMedicinaAdvice = input.UserMedicinaAdvice,
|
||
VisitDate = input.VisitDate ?? DateTime.Now,
|
||
};
|
||
await Context.Insertable(prescription).ExecuteCommandAsync();
|
||
input.Id = prescripId;
|
||
}
|
||
|
||
// ---------- 5. 校验库存 & 扣减 & 插入消耗记录 ----------
|
||
var herbIds = input.Herbs.Select(h => h.Id).ToList();
|
||
var medicinalList = await Context.Queryable<Medicinal>()
|
||
.Where(m => herbIds.Contains(m.Id))
|
||
.ToListAsync();
|
||
List<long> HerbsId = new List<long>();
|
||
foreach (var herb in input.Herbs)
|
||
{
|
||
var medicinal = medicinalList.FirstOrDefault(m => m.Id == herb.Id);
|
||
if (medicinal == null)
|
||
throw Oops.Bah($"药材[{herb.Id}]不存在");
|
||
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}】");
|
||
HerbsId.Add(herb.Id);
|
||
medicinal.Number -= herb.AmountG;
|
||
}
|
||
|
||
// 批量更新药材库存
|
||
await Context.Updateable(medicinalList).ExecuteCommandAsync();
|
||
|
||
// 批量插入处方药材
|
||
var prescriptionHerbs = input.Herbs.Select(h => new PrescriptionHerb
|
||
{
|
||
PrescriptionId = input.Id.Value,
|
||
MedicinalId = h.Id,
|
||
AmountG = h.AmountG,
|
||
Note = h.Note
|
||
}).ToList();
|
||
await Context.Insertable(prescriptionHerbs).ExecuteCommandAsync();
|
||
|
||
await Context.Ado.CommitTranAsync();
|
||
return input.Id.Value;
|
||
}
|
||
catch
|
||
{
|
||
await Context.Ado.RollbackTranAsync();
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 人员下拉
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
/// <param name="employeeId"></param>
|
||
/// <returns></returns>
|
||
public async Task<List<PersonDto>> GetPersonList(PersonType? type, long? employeeId)
|
||
{
|
||
var query = Context.Queryable<Person>();
|
||
|
||
// 筛选员工或家属
|
||
if (type.HasValue)
|
||
{
|
||
if (type == PersonType.Employee)
|
||
{
|
||
query = query.Where(pe => pe.EmployeeId == null);
|
||
}
|
||
else if (type == PersonType.Relative && employeeId.HasValue)
|
||
{
|
||
query = query.Where(pe => pe.EmployeeId == employeeId);
|
||
}
|
||
}
|
||
|
||
return await query.Select(pe => new PersonDto
|
||
{
|
||
Id = pe.Id,
|
||
Name = pe.Name,
|
||
Birthday = pe.Birthday,
|
||
Height = pe.Height,
|
||
RelationShip = pe.RelationShip
|
||
}).ToListAsync();
|
||
}
|
||
/// <summary>
|
||
/// 获取关系下拉
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public async Task<List<RelationTypeDto>> GetRelationTypes()
|
||
{
|
||
var sysmodel = await Context.Queryable<SysDict>().FirstAsync(a => a.DictValue == "Relation");
|
||
if (sysmodel != null)
|
||
{
|
||
return await Context.Queryable<SysDict>().Where(a => a.ParentId == sysmodel.Id).Select(pe => new RelationTypeDto
|
||
{
|
||
Id = int.Parse(pe.DictValue),
|
||
Name = pe.DictLabel,
|
||
}).ToListAsync();
|
||
}
|
||
else
|
||
{
|
||
return new List<RelationTypeDto>
|
||
{
|
||
new RelationTypeDto { Id = 1, Name = "配偶" },
|
||
new RelationTypeDto { Id = 2, Name = "子女" },
|
||
new RelationTypeDto { Id = 3, Name = "父母" },
|
||
new RelationTypeDto { Id = 4, Name = "兄弟姐妹" },
|
||
new RelationTypeDto { Id = 5, Name = "其他" }
|
||
};
|
||
}
|
||
}
|
||
}
|