Files
WeiBingJuInterface/SimpleAdmin.Application/Services/Prescription/PrescriptionService.cs
T
2025-12-05 11:46:47 +08:00

865 lines
36 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 = "其他" }
};
}
}
}