using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; using Dlhis.Entity.Canteen.Enums; using Dlhis.Entity.Device.Entities; using Dlhis.Middle.Entities; using Kurisu.AspNetCore.Utils; using Kurisu.AspNetCore.Utils.Extensions; using Mapster; using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.UnifyResultAndValidation; using Dlhis.Middle.Response; using Dlhis.Middle.Request; namespace Dlhis.Middle.Services.Implements; public class OrderService : IOrderService { private readonly ICanteenSettingService _canteenSettingService; private readonly IRemoteOrderService _remoteOrderService; private readonly string _factory; private readonly IDbContext _dbContext; private Dictionary _settings; public OrderService(ICanteenSettingService canteenSettingService, IRemoteOrderService remoteOrderService, IConfiguration configuration, IDbContext dbContext) { _canteenSettingService = canteenSettingService; _remoteOrderService = remoteOrderService; _factory = configuration.GetSection("Factory").Value; _dbContext = dbContext; } public async Task> AddAsync(OrderAddRequest request) { if (await _remoteOrderService.AbleRemoteCallAsync()) { var remoteResult = await _remoteOrderService.AddAsync(request); if (remoteResult.Code == ApiStateCode.Success) { var d = remoteResult.Data; await _dbContext.Updateable() .SetColumns(x => x.Balance == d.Balance) .Where(x => x.UserCode == d.UserCode) .ExecuteCommandAsync(); } remoteResult.Msg = "在线消费:" + remoteResult.Msg; return remoteResult; } //根据档案号 var gainfo = await _dbContext.Queryable() .Where(x => x.Code == request.UserUniqueNo || x.CardNo == request.UserUniqueNo) .FirstAsync(); var message = "离线消费:"; gainfo.ThrowIfNull("无消费权限"); var isError = false; try { (await _dbContext.Queryable().Where(x => x.UserNo == gainfo.No).AnyAsync()) .ThrowIfFalse("余额不足,无法消费"); await ConsumeAsync(request.Adapt(), gainfo); } catch (Exception ex) { message += ex.Message; isError = true; } //获取人员余额 var data = await _dbContext.Queryable() .LeftJoin((ga, department) => ga.DepartmentNo == department.No) .LeftJoin((ga, department, mealUser) => ga.No == mealUser.UserNo) .Where(ga => ga.Id == gainfo.Id) .Select((ga, department, mealUser) => new OrderResultResponse { UserCode = ga.Code, UserName = ga.Name, DepartmentNo = ga.DepartmentNo, DepartmentName = department.Name, Balance = mealUser.Balance }) .SingleAsync(); return new DefaultApiResult { Code = isError ? ApiStateCode.Error : ApiStateCode.Success, Data = data, Msg = message }; } /// /// 消费 /// /// /// /// private async Task ConsumeAsync(ConsumeRequest request, LocalGainfoEntity gainfo) { var registry = await _dbContext.Queryable().Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo).SingleAsync(); registry.ThrowIfNull("设备尚未登记"); var device = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).SingleAsync(); device.ThrowIfNull("设备尚未登记"); device.Enable.ThrowIfFalse("设备已被停用"); var businessPlaceInfo = await _dbContext.Queryable() .LeftJoin((x, sr) => x.Code == sr.PlaceCode) .Where(x => x.Code == registry.BusinessPlaceCode) .Select((x, sr) => new { x.Code, sr.ShiftCode, x.TenantId }) .FirstAsync(); businessPlaceInfo.ThrowIfNull("设备尚未登记营业点"); //获取当前消费餐次 var shifts = await _dbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); shifts.ThrowIfEmpty("营业点餐次尚未设置"); var shift = shifts.FirstOrDefault(x => new TimePeriod { Start = TimeOnly.FromDateTime(x.Start), End = TimeOnly.FromDateTime(x.End) }.IsPresent1(request.CostDateTime)); shift.ThrowIfNull("非就餐时间,无法消费"); var costDate = await GetCostDateAsync(shift, request.CostDateTime); var balanceRecord = new LocalBalanceRecordEntity { // RequestJson = request.ToJson(), // ThirdNo = request.ThirdNo, Type = BalanceRecordTypeEnumType.Out, ApplicationType = CanteenApplicationTypeEnumType.Device, Name = gainfo.Name, Source = request.Source, UserNo = gainfo.No, UserCode = gainfo.Code, Amount = -Math.Abs(request.Amount), BusinessPlaceCode = businessPlaceInfo?.Code, BusinessShiftCode = shift?.ShiftCode, ShiftType = shift?.ShiftType, CostDate = costDate.Date, CostId = gainfo.CostId, DepartmentNo = gainfo.DepartmentNo, DeviceUniqueNo = request.DeviceUniqueNo, No = _factory + "-" + SnowFlakeHelper.Instance.NextId(), SubsidiesFromMonth = request.CostDateTime.ToString("yyyyMM"), // IsOffline = request.IsOffline, IsCostCrossTenant = await GetSettingAsync(businessPlaceInfo!.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, //餐别异常处理 Status = shift == null ? BalanceRecordStatusEnumType.Doubtful : BalanceRecordStatusEnumType.Valid, //OrderStatus = CanteenOrderStatusEnumType.Wait, CostDateTime = request.CostDateTime, //标识当前消费的基地 TenantId = businessPlaceInfo.TenantId, }; //校验 await Verify(balanceRecord); var allowNoBalance = await GetSettingAsync(balanceRecord.TenantId, CanteenSettingItemEnumType.AllowNoBalance); await CommonHelper.RetryAsync(async () => { if (!allowNoBalance) { var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserNo == balanceRecord.UserNo).SingleAsync(); //金额+- gainfoMeal.Balance -= Math.Abs(balanceRecord.Amount); (gainfoMeal.Balance < 0).ThrowIfTrue("余额不足,无法消费"); await _dbContext.Updateable() .SetColumns(x => x.Balance == gainfoMeal.Balance) .SetColumns(x => x.Version == x.Version + 1) .SetColumns(x => x.ModifiedTime == DateTime.Now) .Where(x => x.UserNo == balanceRecord.UserNo && x.Version == gainfoMeal.Version) //必须与查询时的version一致 .ExecuteCommandThrowVersionAsync(); } await _dbContext.InsertAsync(balanceRecord); }, 5); } //******************************************************* help methods ************************************************************/ /// /// 获取成本日期 /// /// /// /// private async Task GetCostDateAsync(LocalBusinessShiftMealRelationshipEntity shift, DateTime costDateTime) { var switchTime = await GetSettingAsync(shift.TenantId, CanteenSettingItemEnumType.BusinessSwitchTime); var costDate = costDateTime.Date; if (switchTime.HasValue) { var beginOfDay = costDateTime.Date; var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); if (costDateTime < setTime) costDate = costDateTime.Date.AddDays(-1); } return costDate; } /// /// 验证参数 /// /// /// /// private async Task Verify(LocalBalanceRecordEntity order) { var beginOfDay = DateTime.Today; var endOfDay = beginOfDay.AddDays(1).AddSeconds(-1); //今天所有消费记录 var todayAllBalanceRecords = await _dbContext.Queryable() .Where(x => x.UserNo == order.UserNo) .Where(x => x.Type == BalanceRecordTypeEnumType.Out) .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) .Where(x => x.CreateTime >= beginOfDay && x.CreateTime <= endOfDay) .ToListAsync(); //超市营业点 var supermarketCodes = await _dbContext.Queryable() .Where(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) .Select(x => x.Code) .ToListAsync(); //一天消费金额 var dayCostMax = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.DayCostMax); if (dayCostMax != default) { //must has business place code, use .Value is safe var cost = todayAllBalanceRecords .Where(x => !supermarketCodes.Contains(x.BusinessPlaceCode.Value)) //filter supermarket .Sum(x => Math.Abs(x.Amount)); (cost + Math.Abs(order.Amount) > dayCostMax).ThrowIfTrue($"超出当天消费限制{dayCostMax}元,无法消费"); } //一次消费金额 var onceCostMax = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.OnceCostMax); if (onceCostMax != default) { (Math.Abs(order.Amount) > onceCostMax).ThrowIfTrue($"单次消费金额不能超出{onceCostMax}元"); } //超时消费限额 var dayMarketCostMax = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.DayMarketCostMax); if (dayMarketCostMax != default) { //current businessplace is supermarket if (order.BusinessPlaceCode.HasValue && supermarketCodes.Contains(order.BusinessPlaceCode.Value)) { var cost = todayAllBalanceRecords.Where(x => x.BusinessPlaceCode.HasValue) .Where(x => supermarketCodes.Contains(x.BusinessPlaceCode.Value)) .Sum(x => Math.Abs(x.Amount)); (cost + Math.Abs(order.Amount) > dayMarketCostMax).ThrowIfTrue($"超出当天超市消费限制{dayMarketCostMax},无法消费"); } } //重复餐次消费 var allowRepeatInOneShift = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.AllowRepeatInOneShift, true); if (!allowRepeatInOneShift) { var costCount = todayAllBalanceRecords.Count(x => x.ShiftType == order.ShiftType); (costCount > 1).ThrowIfTrue($"{order?.ShiftType?.GetDescription()}不允许多次消费"); } } /// /// 获取参数配置 /// /// /// /// /// /// private async Task GetSettingAsync(string tenantId, CanteenSettingItemEnumType item, T defaultValue = default) { _settings ??= await _canteenSettingService.GetUserSettingAsync(string.Empty, tenantId); if (_settings.TryGetValue(item, out var v)) { try { return int.TryParse(v, out var n) ? n.Adapt() : v.Adapt(); } catch (Exception) { return defaultValue; } } return defaultValue; } }