413 lines
16 KiB
C#
413 lines
16 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.Linq.Dynamic.Core;
|
|
using SimpleAdmin.Application.Entity;
|
|
using Yitter.IdGenerator;
|
|
|
|
public class MedicinalService : DbRepository<Medicinal>, IMedicinalService
|
|
{
|
|
private readonly ISysUserService _sysUserService;
|
|
private readonly IDictService _dictService;
|
|
public MedicinalService(ISysUserService sysUserService, IDictService dictService)
|
|
{
|
|
_sysUserService = sysUserService;
|
|
_dictService = dictService;
|
|
}
|
|
public async Task<SqlSugarPagedList<MedicinalDto>> GetMedicinalList(string medicinalName, int page, int size)
|
|
{
|
|
// 1. 获取数据范围(原有逻辑不变)
|
|
//var dataScope = await _sysUserService.GetLoginUserApiDataScope();
|
|
//dataScope?.Remove(UserManager.OrgId);
|
|
|
|
// 2. 分页查询药材基本信息(1次查询,原有逻辑不变)
|
|
var query = this.AsQueryable()
|
|
//.WhereIF(dataScope.Any(), m => dataScope.Contains(m.Id))
|
|
.WhereIF(!string.IsNullOrEmpty(medicinalName), m => m.MedicinalName.Contains(medicinalName))
|
|
.OrderBy(p => p.Number)
|
|
.Select(m => new MedicinalDto
|
|
{
|
|
Id = m.Id,
|
|
MedicinalName = m.MedicinalName,
|
|
Stock = m.Number, // 当前库存
|
|
});
|
|
|
|
var pageInfo = await query.ToPagedListAsync(page, size);
|
|
if (!pageInfo.List.Any()) return pageInfo;
|
|
|
|
// 3. 批量计算:成本、剩余价值、总使用量(仅2次查询,无循环查询)
|
|
var medicinalIds = pageInfo.List.Select(dto => dto.Id).ToList();
|
|
var costAndUsageDict = await BatchCalculateCostAndValue(medicinalIds);
|
|
|
|
// 4. 内存中赋值所有指标(无数据库查询)
|
|
foreach (var item in pageInfo.List)
|
|
{
|
|
if (costAndUsageDict.TryGetValue(item.Id, out var metrics))
|
|
{
|
|
item.UsedCost = metrics.usedCost; // 使用成本
|
|
item.RemainingValue = metrics.remainingValue; // 剩余价值
|
|
item.TotalUsedWeight = metrics.totalUsedWeight; // 新增:总使用量
|
|
}
|
|
else
|
|
{
|
|
// 无数据时默认值
|
|
item.UsedCost = 0;
|
|
item.RemainingValue = 0;
|
|
item.TotalUsedWeight = 0;
|
|
}
|
|
}
|
|
|
|
return pageInfo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取药材下拉
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<List<MedicinalDto>> GetMedicinalDictList()
|
|
{
|
|
////获取数据范围
|
|
//var dataScope = await _sysUserService.GetLoginUserApiDataScope();
|
|
//dataScope?.Remove(UserManager.OrgId);
|
|
//药材查询
|
|
return await this.AsQueryable()
|
|
//.WhereIF(dataScope.Any(), m => dataScope.Contains(m.Id)) //机构ID查询
|
|
.OrderBy(p => p.Number)
|
|
.Select(m => new MedicinalDto
|
|
{
|
|
Id = m.Id,
|
|
MedicinalName = m.MedicinalName,
|
|
Stock = m.Number,
|
|
}).ToListAsync();
|
|
}
|
|
/// <summary>
|
|
/// 获取药材消耗记录
|
|
/// </summary>
|
|
/// <param name="medicinalId"></param>
|
|
/// <returns></returns>
|
|
/// <summary>
|
|
public async Task<SqlSugarPagedList<MedicinalConsumptionRecordDto>> GetConsumptionRecords(long medicinalId, int page, int size)
|
|
{
|
|
return await Context.Queryable<PrescriptionHerb>()
|
|
// 先关联处方
|
|
.LeftJoin<Prescription>((ph, p) => ph.PrescriptionId == p.Id)
|
|
// 再关联就诊人
|
|
.LeftJoin<Person>((ph, p, pe) => p.PersonId == pe.Id)
|
|
// 再关联员工
|
|
.LeftJoin<Person>((ph, p, pe, emp) => pe.EmployeeId != null && pe.EmployeeId == emp.Id)
|
|
// 条件也使用多参数
|
|
.Where((ph, p, pe, emp) => ph.MedicinalId == medicinalId)
|
|
.OrderByDescending((ph, p, pe, emp) => p.VisitDate)
|
|
// 投影使用所有表别名
|
|
.Select((ph, p, pe, emp) => new MedicinalConsumptionRecordDto
|
|
{
|
|
AmountG = ph.AmountG,
|
|
PersonName = pe.Name,
|
|
IsRelative = pe.EmployeeId != null,
|
|
EmployeeName = emp == null ? null : emp.Name,
|
|
UseTime = p.VisitDate
|
|
})
|
|
// 分页
|
|
.ToPagedListAsync(page, size);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取药材添加记录
|
|
/// </summary>
|
|
/// <param name="medicinalId"></param>
|
|
/// <returns></returns>
|
|
public async Task<SqlSugarPagedList<MedicinalAddLogDto>> GetAdditionRecords(long medicinalId, int page, int size)
|
|
{
|
|
var returnList = await Context.Queryable<MedicinalAddLog>()
|
|
.Where(log => log.MedicinalId == medicinalId)
|
|
.OrderByDescending(dto => dto.CreateTime)
|
|
.Select(log => new MedicinalAddLogDto
|
|
{
|
|
Id = log.Id,
|
|
AddWeight = log.AddWeight,
|
|
UnitPrice = log.UnitPrice,
|
|
AddTime = log.CreateTime.Value,
|
|
AddUserName = log.CreateUser,
|
|
TotalPrice = log.TotalPrice,
|
|
CanEdit = false
|
|
})
|
|
.ToPagedListAsync(page, size);
|
|
|
|
foreach (var item in returnList.List)
|
|
{
|
|
if (item.AddTime.Date == DateTime.Now.Date)
|
|
{
|
|
item.CanEdit = true;
|
|
}
|
|
}
|
|
return returnList;
|
|
}
|
|
/// <summary>
|
|
/// 添加药材
|
|
/// </summary>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> AddMedicinal(AddMedicinalInput input)
|
|
{
|
|
if (input.TotalPrice <= 0 || input.AddWeight <= 0) throw Oops.Bah("请填写正确的金额或者重量!");
|
|
// 开启事务
|
|
await Context.Ado.BeginTranAsync();
|
|
try
|
|
{
|
|
// 检查药材是否已存在
|
|
var existing = await Context.Queryable<Medicinal>()
|
|
.Where(m => m.Id == input.Id)
|
|
.FirstAsync();
|
|
|
|
if (existing != null)
|
|
{
|
|
// 更新库存
|
|
existing.Number += input.AddWeight;
|
|
await Context.Updateable(existing).ExecuteCommandAsync();
|
|
}
|
|
else
|
|
{
|
|
var medicinalModel = await Context.Queryable<Medicinal>()
|
|
.Where(m => m.MedicinalName == input.MedicinalName)
|
|
.FirstAsync();
|
|
if (medicinalModel != null) throw Oops.Bah("已存在该药材,请在下拉框选择!");
|
|
input.Id = YitIdHelper.NextId();
|
|
// 新增药材记录
|
|
await Context.Insertable(new Medicinal
|
|
{
|
|
Id = input.Id.Value,
|
|
Number = input.AddWeight,
|
|
MedicinalName = input.MedicinalName,
|
|
}).ExecuteCommandAsync();
|
|
}
|
|
decimal unitPrice = 0m;
|
|
if (input.AddWeight > 0)
|
|
{
|
|
unitPrice = Decimal.Round(input.TotalPrice / input.AddWeight, 2, MidpointRounding.AwayFromZero);
|
|
}
|
|
// 添加记录
|
|
await Context.Insertable(new MedicinalAddLog
|
|
{
|
|
MedicinalId = input.Id.Value,
|
|
AddWeight = input.AddWeight,
|
|
UnitPrice = unitPrice,
|
|
TotalPrice = input.TotalPrice
|
|
}).ExecuteCommandAsync();
|
|
|
|
await Context.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await Context.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// 修改药材添加记录
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="Exception"></exception>
|
|
public async Task<bool> UpdateAdditionRecord(UpdateMedicinalAddLogInput input)
|
|
{
|
|
if(input.TotalPrice<=0 || input.AddWeight<=0) throw Oops.Bah("请填写正确的金额或者重量!");
|
|
var log = await Context.Queryable<MedicinalAddLog>().Where(l => l.Id == input.Id).FirstAsync();
|
|
if (log == null)
|
|
{
|
|
throw Oops.Bah("记录不存在!");
|
|
}
|
|
|
|
// 检查是否是当天记录
|
|
if (log.CreateTime.Value.Date != DateTime.Now.Date)
|
|
{
|
|
throw Oops.Bah("只能修改当天的记录!");
|
|
}
|
|
|
|
// 计算重量变化
|
|
var weightDiff = input.AddWeight - log.AddWeight;
|
|
|
|
// 开启事务
|
|
await Context.Ado.BeginTranAsync();
|
|
|
|
try
|
|
{
|
|
// 更新记录
|
|
log.AddWeight = input.AddWeight;
|
|
log.TotalPrice = input.TotalPrice;
|
|
log.UnitPrice = Decimal.Round(input.TotalPrice / input.AddWeight, 2, MidpointRounding.AwayFromZero);
|
|
await Context.Updateable(log).ExecuteCommandAsync();
|
|
|
|
// 更新药材库存
|
|
var medicinal = await Context.Queryable<Medicinal>()
|
|
.Where(m => m.Id == log.MedicinalId)
|
|
.FirstAsync();
|
|
|
|
if (medicinal != null)
|
|
{
|
|
medicinal.Number += weightDiff;
|
|
if(medicinal.Number<0) throw Oops.Bah("无法修改,库存不足!");
|
|
await Context.Updateable(medicinal).ExecuteCommandAsync();
|
|
}
|
|
|
|
await Context.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await Context.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// 删除药材添加记录
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="Exception"></exception>
|
|
public async Task<bool> DeleteAdditionRecord(long id)
|
|
{
|
|
var log = await Context.Queryable<MedicinalAddLog>().Where(l => l.Id == id).FirstAsync();
|
|
if (log == null)
|
|
{
|
|
throw Oops.Bah("记录不存在!");
|
|
}
|
|
|
|
// 检查是否是当天记录
|
|
if (log.CreateTime.Value.Date != DateTime.Now.Date)
|
|
{
|
|
throw Oops.Bah("只能删除当天的记录!");
|
|
}
|
|
|
|
// 开启事务
|
|
await Context.Ado.BeginTranAsync();
|
|
|
|
try
|
|
{
|
|
// 删除记录
|
|
log.IsDelete = true;
|
|
await Context.Updateable(log).ExecuteCommandAsync();
|
|
|
|
|
|
// 更新药材库存
|
|
var medicinal = await Context.Queryable<Medicinal>()
|
|
.Where(m => m.Id == log.MedicinalId)
|
|
.FirstAsync();
|
|
|
|
if (medicinal != null)
|
|
{
|
|
medicinal.Number -= log.AddWeight;
|
|
if(medicinal.Number<0) throw Oops.Bah("无法删除,库存不足!");
|
|
await Context.Updateable(medicinal).ExecuteCommandAsync();
|
|
}
|
|
|
|
await Context.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await Context.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 批量计算:使用成本、剩余价值、总使用量(先进先出法)
|
|
/// </summary>
|
|
/// <param name="medicinalIds">需要计算的药材ID列表</param>
|
|
private async Task<Dictionary<long, (
|
|
decimal usedCost,//预估花费
|
|
decimal remainingValue,//预估剩余价值
|
|
decimal totalUsedWeight//使用总量
|
|
)>> BatchCalculateCostAndValue(List<long> medicinalIds)
|
|
{
|
|
var result = new Dictionary<long, (decimal, decimal, decimal)>();
|
|
if (!medicinalIds.Any()) return result;
|
|
|
|
// 1. 批量查询所有药材的【当前库存】(1次查询)
|
|
var medicinals = await Context.Queryable<Medicinal>()
|
|
.Where(m => medicinalIds.Contains(m.Id))
|
|
.Select(m => new { m.Id, m.Number }) // Number = 当前库存
|
|
.ToListAsync();
|
|
|
|
// 2. 批量查询所有药材的【入库记录】(1次查询,仅查需要的字段)
|
|
var allAddLogs = await Context.Queryable<MedicinalAddLog>()
|
|
.Where(l => medicinalIds.Contains(l.MedicinalId))
|
|
.OrderBy(l => l.CreateTime) // 先进先出:按入库时间排序
|
|
.Select(l => new {
|
|
l.MedicinalId,
|
|
l.AddWeight, // 入库量
|
|
l.UnitPrice, // 单价(用于计算成本)
|
|
l.TotalPrice // 总成本(用于计算成本)
|
|
})
|
|
.ToListAsync();
|
|
|
|
// 3. 按【药材ID】分组入库记录(内存处理,无数据库查询)
|
|
var groupedLogs = allAddLogs
|
|
.GroupBy(l => l.MedicinalId)
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
// 4. 遍历每个药材,内存中计算所有指标
|
|
foreach (var med in medicinals)
|
|
{
|
|
// 初始化默认值(无入库记录时)
|
|
decimal totalIn = 0; // 总入库量(AddWeight 总和)
|
|
decimal totalCost = 0; // 总入库成本
|
|
decimal totalUsedWeight = 0; // 总使用量
|
|
decimal usedCost = 0; // 使用成本
|
|
decimal remainingValue = 0;// 剩余价值
|
|
|
|
// 若有入库记录,计算总入库量、总成本
|
|
if (groupedLogs.TryGetValue(med.Id, out var addLogs) && addLogs.Any())
|
|
{
|
|
totalIn = addLogs.Sum(l => l.AddWeight); // 总入库量 = 所有 AddWeight 求和
|
|
totalCost = addLogs.Sum(l => l.TotalPrice); // 总入库成本
|
|
}
|
|
|
|
// 计算总使用量:总入库量 - 当前库存(若结果为负,说明数据异常,按0处理)
|
|
totalUsedWeight = Math.Max(0, totalIn - med.Number);
|
|
|
|
// 若有使用量,继续用先进先出法计算使用成本
|
|
if (totalUsedWeight > 0 && groupedLogs.TryGetValue(med.Id, out var validLogs))
|
|
{
|
|
decimal remainingToCalculate = totalUsedWeight;
|
|
foreach (var log in validLogs)
|
|
{
|
|
if (remainingToCalculate <= 0) break;
|
|
|
|
// 当前入库记录的量 <= 剩余未计算的使用量:全部计入成本
|
|
if (log.AddWeight <= remainingToCalculate)
|
|
{
|
|
usedCost += log.AddWeight * log.UnitPrice;
|
|
remainingToCalculate -= log.AddWeight;
|
|
}
|
|
// 当前入库记录的量 > 剩余未计算的使用量:仅计算剩余部分
|
|
else
|
|
{
|
|
usedCost += remainingToCalculate * log.UnitPrice;
|
|
remainingToCalculate = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 剩余价值 = 总入库成本 - 使用成本(若为负,按0处理)
|
|
remainingValue = Math.Max(0, totalCost - usedCost);
|
|
|
|
// 存入结果字典
|
|
result[med.Id] = (usedCost, remainingValue, totalUsedWeight);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|