添加项目文件。

This commit is contained in:
fuenmao
2025-09-29 13:48:36 +08:00
parent f61e922f0c
commit 5dc1780ef1
407 changed files with 63703 additions and 0 deletions
@@ -0,0 +1,412 @@
// 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;
}
}