using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Setting.Services; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; using Dlhis.Entity.Canteen.Enums; using Dlhis.Entity.Device.Entities; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.AspNetCore.Utils; using Kurisu.AspNetCore.Utils.Extensions; using Kurisu.Core.CustomClass; using Kurisu.Core.DataAccess.Dto; using Kurisu.SqlSugar.Attributes; using Kurisu.SqlSugar.Extensions; using Kurisu.SqlSugar.Services; using Mapster; using SqlSugar; namespace Dlhis.Canteen.Order.Services.Implements; public class OrderService : IOrderService { private readonly IDbContext _dbContext; private readonly ICanteenSettingService _canteenSettingService; private readonly ILogger _logger; public OrderService(IDbContext dbContext, ICanteenSettingService canteenSettingService, ILogger logger) { _dbContext = dbContext; _canteenSettingService = canteenSettingService; _logger = logger; } public async Task> GetPageAsync(OrderPageRequest request) { var page = await _dbContext.Queryable() .Select() .ToPageListAsync(request); return page; } [IgnoreTenant] public async Task AddAsync(OrderAddRequest request) { var device = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).Where(x => x.Enable).FirstAsync(); device.ThrowIfNull("设备不存在或者尚未启用"); var registry = await _dbContext.Queryable().Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo).FirstAsync(); registry.ThrowIfNull("设备尚未注册"); var businessPlace = await _dbContext.Queryable().Where(x => x.Code == registry.BusinessPlaceCode).FirstAsync(); businessPlace.ThrowIfNull("营业点尚未设置"); var shifts = await _dbContext.Queryable().Where(x => x.BusinessPlaceCode == registry.BusinessPlaceCode).ToListAsync(); var shift = shifts.FirstOrDefault(x => new TimePeriod() { Start = x.Start, End = x.End, }.IsPresent1(request.CostDateTime)); //根据档案号 var canteenUser = await _dbContext.Queryable() .Where(x => x.UserNo == request.UserUniqueNo || x.UserCode == request.UserUniqueNo) .FirstAsync(); canteenUser.ThrowIfNull($"{request.DeviceUniqueNo}不存在,无法就餐"); var gafinfo = await _dbContext.Queryable() .Where(x => x.No == canteenUser.UserNo) .FirstAsync(); var settings = await _canteenSettingService.GetUserSettingAsync(gafinfo.No); var switchTime = GetSetting(settings, CanteenSettingItemEnumType.BusinessSwitchTime); //金额处理 request.Amount = request.Type == BalanceRecordTypeEnumType.In ? Math.Abs(request.Amount) : -Math.Abs(request.Amount); var shiftTimePeriod = new TimePeriod() { Start = shift?.Start, End = shift?.End }; //成本日期 todo 优化 var costDate = DateTime.Now; if (shift == null) { costDate = DateTime.Now; } else { if (shiftTimePeriod.IsCrossDay()) costDate = DateTime.Now.AddDays(-1); var beginOfDay = DateTime.Today; if (switchTime.HasValue) { var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); if (DateTime.Now < setTime) costDate = DateTime.Now.AddDays(-1); } } var order = new CanteenOrderEntity { RequestJson = request.ToJson(), ThirdNo = request.ThirdNo, Type = request.Type.Value, OrderStatus = CanteenOrderStatusEnumType.Wait, Name = gafinfo.Name, Source = request.Source, UserId = canteenUser.UserNo, UserCode = canteenUser.UserCode, Amount = request.Amount, BusinessPlaceCode = businessPlace.Code, BusinessShiftCode = shift?.Code, CostDate = costDate.Date, CostId = canteenUser.CostId, DepartmentNo = gafinfo.DepartmentNo, DeviceInfo = device.Code.ToString(), No = "O" + SnowFlakeHelper.Instance.NextId(), SubsidiesFromMonth = DateTime.Now.ToString("yyyyMM"), IsOffline = request.IsOffline, IsCostCrossTenant = GetSetting(settings, CanteenSettingItemEnumType.AllowCrossTenant), OriginTenantId = canteenUser.TenantId,//人员可能跨基地消费 Status = BalanceRecordStatusEnumType.Doubtful, TenantId = device.TenantId, //设备才能标识当前消费的基地 }; var id = await _dbContext.InsertReturnIdentityAsync(order); await GenerateChargeRecordAsync(id, settings, shifts, request.CostDateTime); } private static T GetSetting(Dictionary settings, CanteenSettingItemEnumType item, T defaultValue = default) { if (settings.TryGetValue(item, out var v)) { return v.Adapt(); } return defaultValue; } public async Task GenerateChargeRecordAsync(long orderId, Dictionary settings, List shifts, DateTime costDateTime) { var order = await _dbContext.Queryable().Where(x => x.Id == orderId).FirstAsync(); await Verify(settings, shifts, costDateTime, order);//校验 for (int i = 0; i < 5; i++) { try { var canteenUser = await _dbContext.Queryable().Where(x => x.UserNo == order.UserId).FirstAsync(); //金额+- canteenUser.Balance += order.Amount; if (order.Type == BalanceRecordTypeEnumType.Out) { if (canteenUser.Balance < 0) { throw new UserFriendlyException("余额不足,无法消费"); } } var effect = await _dbContext.Updateable() .SetColumns(x => x.Balance == canteenUser.Balance + order.Amount) .SetColumns(x => x.Version == x.Version + 1) .Where(x => x.UserNo == order.UserId && x.Version == canteenUser.Version)//必须与查询时的version一致 .ExecuteCommandAsync(); if (effect <= 0) { throw new VersionExceptions("更新版本冲突"); } var balanceRecord = order.Adapt(); await _dbContext.InsertAsync(balanceRecord); order.Status = BalanceRecordStatusEnumType.Valid; //订单改为有效 break; } catch (Exception ex) when (ex is VersionExceptions) { _logger.LogError(ex, ex.Message); } catch (Exception) { throw; } } } private async Task Verify(Dictionary settings, List shifts, DateTime costDateTime, CanteenOrderEntity order) { //在线 if (!order.IsOffline) { shifts.FirstOrDefault(x => new TimePeriod() { Start = x.Start, End = x.End, }.IsPresent1(costDateTime)).ThrowIfNull("当前非就餐时间,无法消费"); } var beginOfDay = DateTime.Today; var endOfDay = beginOfDay.AddHours(23).AddMinutes(59).AddSeconds(59); var balanceRecords = await _dbContext.Queryable() .Where(x => x.UserId == order.UserId) .Where(x => x.CreateTime <= beginOfDay && x.CreateTime <= endOfDay) //今天消费记录 .ToListAsync(); var dayMarketCostMax = GetSetting(settings, CanteenSettingItemEnumType.DayMarketCostMax); if (dayMarketCostMax != default) { var businessPlaceCodes = await _dbContext.Queryable().Where(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket).Select(x => x.Code).ToListAsync(); var cost = balanceRecords.Where(x => businessPlaceCodes.Contains(x.BusinessPlaceCode)).Sum(x => x.Amount); if (cost + Math.Abs(order.Amount) > dayMarketCostMax) { throw new UserFriendlyException($"超出当天超市消费限制({dayMarketCostMax}),无法消费"); } } var allowRepeatInOneShift = GetSetting(settings, CanteenSettingItemEnumType.AllowRepeatInOneShift, true); if (!allowRepeatInOneShift) { //todo 餐次问题 var costCount = balanceRecords.Where(x => x.BusinessShiftCode == order.BusinessShiftCode).Count(); if (costCount > 1) { throw new UserFriendlyException($"当前餐次不允许多次消费"); } } var onceCostMax = GetSetting(settings, CanteenSettingItemEnumType.OnceCostMax); if (onceCostMax != default) { if (Math.Abs(order.Amount) > onceCostMax) { throw new UserFriendlyException($"一次消费金额不能超出{onceCostMax}"); } } var dayCostMax = GetSetting(settings, CanteenSettingItemEnumType.DayCostMax); if (dayCostMax != default) { var cost = balanceRecords.Where(x => x.Type == BalanceRecordTypeEnumType.Out).Sum(x => Math.Abs(x.Amount)); if (cost + Math.Abs(order.Amount) > dayCostMax) { throw new UserFriendlyException($"超出当天消费限制({dayCostMax}),无法消费"); } } } }