diff --git a/Src/Dlhis.Canteen/Order/Dto/OrderCheckResultDto.cs b/Src/Dlhis.Canteen/Order/Dto/OrderCheckResultDto.cs new file mode 100644 index 00000000..8d0af378 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Dto/OrderCheckResultDto.cs @@ -0,0 +1,31 @@ +using Dlhis.Entity.Canteen.Enums; + +namespace Dlhis.Canteen.Order.Dto; + +public class OrderCheckResultDto +{ + /// + /// 消费厂区 + /// + public string Factory { get; set; } + + /// + /// 消费日期 + /// + public DateTime CostDate { get; set; } + + /// + /// 消费餐次 + /// + public ShiftEnumType ShiftType { get; set; } + + /// + /// 消费营业点 + /// + public Guid BusinessPlaceCode { get; set; } + + /// + /// 消费餐别 + /// + public Guid BusinessShiftCode { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs deleted file mode 100644 index c4f9c9dc..00000000 --- a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Dlhis.Entity.Canteen.Enums; - -namespace Dlhis.Canteen.Order.Dto; - -public class OrderPreCheckDto -{ - public string Factory { get; set; } - - public DateTime CostDate { get; set; } - - public ShiftEnumType ShiftType { get; set; } - - public Guid BusinessPlaceCode { get; set; } - - public Guid BusinessShiftCode { get; set; } -} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs b/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs index d9e904c4..f108f713 100644 --- a/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs +++ b/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs @@ -7,5 +7,5 @@ namespace Dlhis.Canteen.Order.Repository; public interface IOrderRepository : IScopeDependency { - Task CreateDeviceConsumeOrderAsync(OrderPreCheckDto checkData, ConsumeRequest request, GainfoEntity gainfo); + Task CreateDeviceConsumeOrderAsync(OrderCheckResultDto checkData, ConsumeRequest request, GainfoEntity gainfo); } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs b/Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs index a9e31997..23402c47 100644 --- a/Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs @@ -19,7 +19,7 @@ public class OrderRepository : IOrderRepository } /// - public async Task CreateDeviceConsumeOrderAsync(OrderPreCheckDto checkData, ConsumeRequest request, GainfoEntity gainfo) + public async Task CreateDeviceConsumeOrderAsync(OrderCheckResultDto checkData, ConsumeRequest request, GainfoEntity gainfo) { var order = new CanteenOrderEntity { diff --git a/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/BirthdayConsumeHandler.cs b/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/BirthdayConsumeHandler.cs new file mode 100644 index 00000000..dafafc7d --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/BirthdayConsumeHandler.cs @@ -0,0 +1,110 @@ +using Dlhis.Canteen.Order.Repository; +using Dlhis.Canteen.Order.Request; +using Dlhis.Canteen.Order.Response; +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Kurisu.AspNetCore.Utils; +using Kurisu.AspNetCore.Utils.Extensions; +using Mapster; + +namespace Dlhis.Canteen.Order.Services.ConsumeHandlers; + +/// +/// 生日券消费处理 +/// +[Service(nameof(BalanceRecordSourceEnumType.Birthday))] +public class BirthdayConsumeHandler : IConsumeHandler +{ + private readonly IDbContext _dbContext; + private readonly IOrderCommonHandler _orderCommonHandler; + private readonly IOrderRepository _orderRepository; + + public BirthdayConsumeHandler(IDbContext dbContext, + IOrderCommonHandler orderCommonHandler, + IOrderRepository orderRepository) + { + _dbContext = dbContext; + _orderCommonHandler = orderCommonHandler; + _orderRepository = orderRepository; + } + + public async Task> ConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) + { + var msg = string.Empty; + var ticketNo = request.UserUniqueNo; + + var exists = await _dbContext.Queryable() + .Where(x => x.Type == TicketEnumType.Birthday) + .Where(x => x.QrCode == ticketNo) + .AnyAsync(); + + exists.ThrowIfFalse("生日券无效"); + + try + { + var checkData = await _orderCommonHandler.PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime!.Value); + var isSupermarket = await _dbContext.Queryable() + .Where(x => x.Code == checkData.BusinessPlaceCode) + .Select(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) + .SingleAsync(); + + isSupermarket.ThrowIfFalse("请在超市消费"); + + var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request.Adapt(), gainfo); + + await _orderCommonHandler.TryConsumeAsync(order.Id, async () => + { + await CommonHelper.RetryAsync(async () => + { + var ticket = await _dbContext.Queryable() + .Where(x => x.Type == TicketEnumType.Birthday) + .Where(x => x.QrCode == ticketNo) + .SingleAsync(); + + //金额+- + ticket.Balance += order.Amount; + (ticket.Balance < 0).ThrowIfTrue("余额不足,无法消费"); + + await _dbContext.Updateable() + .SetColumns(x => x.Balance == ticket.Balance) + .SetColumns(x => x.Version == x.Version + 1) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .Where(x => x.QrCode == ticketNo && x.Version == ticket.Version) //必须与查询时的version一致 + .ExecuteCommandThrowVersionAsync(); + + //写入记录 + var balanceRecord = order.Adapt(); + balanceRecord.Remark = $"{ticket.QrCode}(生日券消费)"; + await _dbContext.InsertAsync(balanceRecord); + }, 5); + }); + } + catch (Exception ex) + { + msg = ex.Message; + } + + //获取餐券余额 + var data = await _dbContext.Queryable() + .LeftJoin((x, ga) => x.UserCode == ga.Code) + .Where(x => x.Type == TicketEnumType.Birthday && x.QrCode == ticketNo) + .Select((x, ga) => new OrderResultResponse + { + UserCode = x.UserCode, + UserName = ga.Name, + CurrentBalance = x.Balance + }) + .SingleAsync(); + + return new DefaultApiResult + { + Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = msg + }; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/EMealVoucherHandler.cs b/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/EMealVoucherHandler.cs new file mode 100644 index 00000000..86140edf --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/EMealVoucherHandler.cs @@ -0,0 +1,166 @@ +using Dlhis.Canteen.Order.Repository; +using Dlhis.Canteen.Order.Request; +using Dlhis.Canteen.Order.Response; +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Kurisu.AspNetCore.Utils; +using Kurisu.AspNetCore.Utils.Extensions; +using Mapster; + +namespace Dlhis.Canteen.Order.Services.ConsumeHandlers; + +/// +/// 电子餐券处理 +/// +[Service(nameof(BalanceRecordSourceEnumType.EMealVoucher))] +public class EMealVoucherHandler : IConsumeHandler +{ + private readonly IDbContext _dbContext; + private readonly IOrderRepository _orderRepository; + private readonly IOrderCommonHandler _orderCommonHandler; + + public EMealVoucherHandler(IDbContext dbContext, + IOrderRepository orderRepository, + IOrderCommonHandler orderCommonHandler) + { + _dbContext = dbContext; + _orderRepository = orderRepository; + _orderCommonHandler = orderCommonHandler; + } + + public async Task> ConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) + { + var msg = string.Empty; + var originData = await _dbContext.Queryable() + .Where(x => x.QrCode == request.UserUniqueNo) + .Select(x => x.OriginData) + .SingleAsync(); + + if (originData.IsEmpty()) + { + msg = "无法识别"; + return new DefaultApiResult + { + Code = ApiStateCode.Error, + Data = null, + Msg = msg + }; + } + + var originDataItems = originData.Split(":"); + var no = originDataItems[0]; + var idNo = originDataItems[3]; + + try + { + await EMealVoucherConsumeAsync(request.Adapt(), gainfo, originData, no, idNo); + } + catch (Exception ex) + { + msg = ex.Message; + } + + //获取餐券余额 + var data = await _dbContext.Queryable() + .LeftJoin((x, d) => x.No == d.No) + .Where((x, d) => x.No == no && d.IdNo == idNo) + .Select((x, d) => new OrderResultResponse + { + UserCode = d.IdNo, + UserName = d.Name, + CurrentBalance = d.Balance + }) + .SingleAsync(); + + try + { + var v = data.UserCode ?? string.Empty; + var vc = v.ToCharArray().Reverse().ToList(); + + for (int i = 0; i < vc.Count; i++) + { + if (i + 1 > 4) + { + vc[i] = '*'; + } + } + + vc.Reverse(); + data.UserCode = string.Join("", vc); + } + catch (Exception) + { + //ignore + } + + return new DefaultApiResult + { + Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = msg + }; + } + + + /// + /// 电子餐券消费 + /// + /// + /// + /// + /// + /// + private async Task EMealVoucherConsumeAsync(ConsumeRequest request, GainfoEntity gainfo, string originData, string no, string idNo) + { + var now = DateTime.Now; + + var current = await _dbContext.Queryable() + .Where(x => x.No == no && x.IdNo == idNo) + .Select(x => new { x.Name, x.Start, x.End }) + .SingleAsync(); + + (now >= current.Start && now <= current.End).ThrowIfFalse("餐券已过期"); + + var eMealVoucher = await _dbContext.Queryable().Where(x => x.No == no).SingleAsync(); + + var checkData = await _orderCommonHandler.PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); + var isSupermarket = await _dbContext.Queryable() + .Where(x => x.Code == checkData.BusinessPlaceCode) + .Select(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) + .SingleAsync(); + + isSupermarket.ThrowIfTrue("禁止在超市消费"); + + eMealVoucher.Factories.Contains(checkData.Factory).ThrowIfFalse("禁止在当前厂区消费"); + + gainfo.No = originData; + var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); + + await _orderCommonHandler.TryConsumeAsync(order.Id, async () => + { + await CommonHelper.RetryAsync(async () => + { + var detail = await _dbContext.Queryable().Where(x => x.No == no && x.IdNo == idNo).SingleAsync(); + //金额+- + detail.Balance += order.Amount; + (detail.Balance < 0).ThrowIfTrue("余额不足,无法消费"); + + await _dbContext.Updateable() + .SetColumns(x => x.Balance == detail.Balance) + .SetColumns(x => x.Version == x.Version + 1) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .Where(x => x.No == no && x.IdNo == idNo && x.Version == detail.Version) //必须与查询时的version一致 + .ExecuteCommandThrowVersionAsync(); + + //写入记录 + var balanceRecord = order.Adapt(); + balanceRecord.Remark = $"({no})" + current.Name; + await _dbContext.InsertAsync(balanceRecord); + }, 5); + }); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/OrderCommonHandler.cs b/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/OrderCommonHandler.cs new file mode 100644 index 00000000..21ac6801 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Services/ConsumeHandlers/OrderCommonHandler.cs @@ -0,0 +1,144 @@ +using Dlhis.Canteen.Order.Dto; +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 Dlhis.Entity.Device.Enums; +using Kurisu.AspNetCore.CustomClass; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Canteen.Order.Services.ConsumeHandlers; + +/// +/// 订单通用处理 +/// +public class OrderCommonHandler : IOrderCommonHandler +{ + private readonly IDbContext _dbContext; + private readonly ICanteenSettingService _canteenSettingService; + private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor; + + public OrderCommonHandler(IDbContext dbContext, + IHttpContextAccessor httpContextAccessor, + ICanteenSettingService canteenSettingService, + ILogger logger) + { + _dbContext = dbContext; + _canteenSettingService = canteenSettingService; + _httpContextAccessor = httpContextAccessor; + _logger = logger; + } + + /// + /// 设置设备ip + /// + /// + public async Task SetDeviceIpInfoAsync(string deviceNo) + { + try + { + if (_httpContextAccessor.HttpContext!.Request.Headers.TryGetValue("X-Requested-DeviceIp", out var ip)) + { + var device = await _dbContext.Queryable().Where(x => x.UniqueNo == deviceNo).SingleAsync(); + device.ConnectionInfo = new + { + ip = ip.ToString() + }.ToJson(); + + await _dbContext.UpdateAsync(device); + } + + _logger.LogInformation("设备ip获取:{deviceNo} {ip}", deviceNo, ip); + } + catch (Exception) + { + //ignore + } + } + + /// + /// 消费前预检 + /// + /// 设备号 + /// 消费时间 + /// + public async Task PreCheckAsync(string deviceUniqueNo, DateTime costDateTime) + { + var device = await _dbContext.Queryable().Where(x => x.UniqueNo == deviceUniqueNo).SingleAsync(); + device.ThrowIfNull("设备尚未登记"); + device.Enable.ThrowIfFalse("设备已被停用"); + (device.Type != DeviceTypeEnumType.Meal).ThrowIfTrue("设备类型有误"); + + var bindingInfo = await _dbContext.Queryable() + .LeftJoin((x, b) => x.BusinessPlaceCode == b.Code) + .LeftJoin((x, b, bs) => bs.PlaceCode == b.Code) + .Where(x => x.DeviceUniqueNo == deviceUniqueNo && x.BusinessPlaceCode.HasValue) + .Select((x, b, bs) => new + { + b.Code, + bs.ShiftCode, + Factory = x.TenantId + }) + .SingleAsync(); + + bindingInfo.ThrowIfNull("设备尚未登记营业点"); + + //获取当前消费餐次 + var shifts = await _dbContext.Queryable() + .Where(x => x.ShiftCode == bindingInfo.ShiftCode) + .Select(x => new { x.ShiftType, x.Start, x.End }) + .ToListAsync(); + shifts.ThrowIfEmpty("营业点餐次尚未设置"); + + var shift = shifts.FirstOrDefault(x => new TimePeriod + { + Start = x.Start, + End = x.End + }.IsPresent(costDateTime)); + shift.ThrowIfNull("非就餐时间"); + + var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, costDateTime); + + return new OrderCheckResultDto + { + Factory = bindingInfo.Factory, + CostDate = costDate, + ShiftType = shift!.ShiftType, + BusinessPlaceCode = bindingInfo.Code, + BusinessShiftCode = bindingInfo.ShiftCode + }; + } + + /// + /// 消费更新处理 + /// + /// + /// + public async Task TryConsumeAsync(long orderId, Func action) + { + try + { + await action(); + + await _dbContext.Updateable() + .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .Where(x => x.Id == orderId) + .ExecuteCommandAsync(); + } + catch (Exception ex) + { + await _dbContext.Updateable() + .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Failure) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .SetColumns(x => x.Result == ex.Message) + .Where(x => x.Id == orderId) + .ExecuteCommandAsync(); + + throw; + } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/IConsumeHandler.cs b/Src/Dlhis.Canteen/Order/Services/IConsumeHandler.cs new file mode 100644 index 00000000..4de14676 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Services/IConsumeHandler.cs @@ -0,0 +1,20 @@ +using Dlhis.Canteen.Order.Request; +using Dlhis.Canteen.Order.Response; +using Dlhis.Entity.Basic.Entity; +using Kurisu.AspNetCore.UnifyResultAndValidation; + +namespace Dlhis.Canteen.Order.Services; + +/// +/// 消费处理器 +/// +public interface IConsumeHandler : IScopeDependency +{ + /// + /// 消费 + /// + /// + /// + /// + Task> ConsumeAsync(OrderAddRequest request, GainfoEntity gainfo); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/IOrderCommonHandler.cs b/Src/Dlhis.Canteen/Order/Services/IOrderCommonHandler.cs new file mode 100644 index 00000000..c1762121 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Services/IOrderCommonHandler.cs @@ -0,0 +1,29 @@ +using Dlhis.Canteen.Order.Dto; + +namespace Dlhis.Canteen.Order.Services; + +public interface IOrderCommonHandler : IScopeDependency +{ + /// + /// 消费更新处理 + /// + /// + /// + /// + Task TryConsumeAsync(long orderId, Func action); + + /// + /// 设置设备ip + /// + /// + /// + Task SetDeviceIpInfoAsync(string deviceNo); + + /// + /// 消费前预检 + /// + /// 设备号 + /// 消费时间 + /// + Task PreCheckAsync(string deviceUniqueNo, DateTime costDateTime); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index d6284a13..22203026 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -1,19 +1,14 @@ using Dlhis.Basic.Api; -using Dlhis.Canteen.Order.Dto; using Dlhis.Canteen.Order.Repository; +using Dlhis.Canteen.Order.Request; +using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Setting.Services; +using Dlhis.Constants; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; using Dlhis.Entity.Canteen.Enums; -using Dlhis.Entity.Device.Entities; -using Kurisu.AspNetCore.Utils; -using Kurisu.AspNetCore.Utils.Extensions; -using Mapster; -using SqlSugar; -using Dlhis.Canteen.Order.Response; -using Dlhis.Canteen.Order.Request; using Dlhis.Entity.Device; -using Dlhis.Entity.Device.Enums; +using Dlhis.Entity.Device.Entities; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.Cache; using Kurisu.AspNetCore.CustomClass; @@ -21,7 +16,10 @@ using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.UnifyResultAndValidation; -using Dlhis.Constants; +using Kurisu.AspNetCore.Utils; +using Kurisu.AspNetCore.Utils.Extensions; +using Mapster; +using SqlSugar; namespace Dlhis.Canteen.Order.Services.Implements; @@ -30,31 +28,34 @@ public class OrderService : IOrderService private readonly ICanteenSettingService _canteenSettingService; private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; - private readonly IHttpContextAccessor _httpContextAccessor; private readonly IBalanceRepository _balanceRepository; private readonly IOrderRepository _orderRepository; private readonly RedisCache _redisCache; private readonly IGainfoPermissionApi _gainfoPermissionApi; + private readonly IOrderCommonHandler _orderCommonHandler; + private readonly INamedResolver _namedResolver; private readonly ILogger _logger; public OrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, ICurrentUser currentUser, - IHttpContextAccessor httpContextAccessor, IBalanceRepository balanceRepository, IOrderRepository orderRepository, RedisCache redisCache, IGainfoPermissionApi gainfoPermissionApi, + IOrderCommonHandler orderCommonHandler, + INamedResolver namedResolver, ILogger logger) { _canteenSettingService = canteenSettingService; _dbContext = dbContext; _currentUser = currentUser; - _httpContextAccessor = httpContextAccessor; _balanceRepository = balanceRepository; _orderRepository = orderRepository; _redisCache = redisCache; _gainfoPermissionApi = gainfoPermissionApi; + _orderCommonHandler = orderCommonHandler; + _namedResolver = namedResolver; _logger = logger; } @@ -75,7 +76,7 @@ public class OrderService : IOrderService /// public async Task> AddAsync(OrderAddRequest request) { - await SetDeviceIpInfoAsync(request.DeviceUniqueNo); + await _orderCommonHandler.SetDeviceIpInfoAsync(request.DeviceUniqueNo); await using var handler = await _redisCache.LockAsync($"consume:device:{request.UserUniqueNo}"); handler.Acquired.ThrowIfFalse("请勿频繁消费"); @@ -152,39 +153,47 @@ public class OrderService : IOrderService var s = long.TryParse(request.UserUniqueNo, out var userId); GainfoEntity gainfo; + //条码消费 if (request.Source == BalanceRecordSourceEnumType.QrCode) { - if (request.UserUniqueNo.StartsWith(QrCodeConstants.User)) + var flag = request.UserUniqueNo.Substring(0, 2); + switch (flag) { - gainfo = await GetUserByQrCodeAsync(request.UserUniqueNo); - await CheckPermissionAsync(gainfo.Code, request.DeviceUniqueNo); - } - else if (request.UserUniqueNo.StartsWith(QrCodeConstants.EMealVoucher)) - { - request.Source = BalanceRecordSourceEnumType.EMealVoucher; - var eMealVoucherQrCode = await _dbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); - eMealVoucherQrCode.ThrowIfNull("无法识别"); - var code = eMealVoucherQrCode.OriginData.Split(':')[2]; - gainfo = await _dbContext.Queryable().Where(x => x.Code == code).SingleAsync(); - } - else if (request.UserUniqueNo.StartsWith(QrCodeConstants.Birthday)) - { - var ticket = await _dbContext.Queryable().Where(x => x.Type == TicketEnumType.Birthday).Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); - ticket.ThrowIfNull("无法识别"); - gainfo = await _dbContext.Queryable().Where(x => x.Code == ticket.UserCode).SingleAsync(); - } - else - { - throw new UserFriendlyException($"无法识别{request.UserUniqueNo}"); + case QrCodeConstants.User: + { + gainfo = await GetUserByQrCodeAsync(request.UserUniqueNo); + await CheckPermissionAsync(gainfo.Code, request.DeviceUniqueNo); + } + break; + case QrCodeConstants.EMealVoucher: + { + request.Source = BalanceRecordSourceEnumType.EMealVoucher; + var eMealVoucherQrCode = await _dbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); + eMealVoucherQrCode.ThrowIfNull("无法识别"); + var code = eMealVoucherQrCode.OriginData.Split(':')[2]; + gainfo = await _dbContext.Queryable().Where(x => x.Code == code).SingleAsync(); + } + break; + case QrCodeConstants.Birthday: + { + request.Source = BalanceRecordSourceEnumType.Birthday; + var ticket = await _dbContext.Queryable().Where(x => x.Type == TicketEnumType.Birthday).Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); + ticket.ThrowIfNull("无法识别"); + gainfo = await _dbContext.Queryable().Where(x => x.Code == ticket.UserCode).SingleAsync(); + } + break; + + default: throw new UserFriendlyException($"无法识别{request.UserUniqueNo}"); } } else { //根据档案号 + //区分userId和code: 工号纯数字避免跟userId相同导致查询到的消费人错误 gainfo = await _dbContext.Queryable() - .WhereIF(request.Source == BalanceRecordSourceEnumType.Card, x => x.CardNo == request.UserUniqueNo) - .WhereIF(request.Source != BalanceRecordSourceEnumType.Card && !request.IsUserId, x => x.Code == request.UserUniqueNo) - .WhereIF(request.Source != BalanceRecordSourceEnumType.Card && request.IsUserId && s, x => x.Id == userId) + .WhereIF(request.Source == BalanceRecordSourceEnumType.Card, x => x.CardNo == request.UserUniqueNo) //卡号 + .WhereIF(request.Source != BalanceRecordSourceEnumType.Card && !request.IsUserId, x => x.Code == request.UserUniqueNo) //工号 + .WhereIF(request.Source != BalanceRecordSourceEnumType.Card && request.IsUserId && s, x => x.Id == userId) //user id .SingleAsync(); } @@ -230,7 +239,7 @@ public class OrderService : IOrderService //一天消费金额 var dayCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.DayCostMax, order.TenantId, order.UserNo); - if (dayCostMax != default) + if (dayCostMax != 0) { //消费记录一定会有营业点代码!.Value是安全用法 var cost = todayAllBalanceRecords @@ -242,7 +251,7 @@ public class OrderService : IOrderService //一次消费金额 var onceCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.OnceCostMax, order.TenantId, order.UserNo); - if (onceCostMax != default) + if (onceCostMax != 0) { (Math.Abs(order.Amount) > onceCostMax).ThrowIfTrue($"单次消费金额不能超出{onceCostMax}元"); } @@ -251,7 +260,7 @@ public class OrderService : IOrderService if (order.BusinessPlaceCode.HasValue && supermarketCodes.Contains(order.BusinessPlaceCode.Value)) { var dayMarketCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.DayMarketCostMax, order.TenantId, order.UserNo); - if (dayMarketCostMax != default) + if (dayMarketCostMax != 0) { var cost = todayAllBalanceRecords.Where(x => x.BusinessPlaceCode.HasValue) .Where(x => supermarketCodes.Contains(x.BusinessPlaceCode.Value)) @@ -354,7 +363,7 @@ public class OrderService : IOrderService /// private async Task ConsumeAsync(ConsumeRequest request, GainfoEntity gainfo) { - var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); + var checkData = await _orderCommonHandler.PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); if (request.Image.IsPresent()) @@ -367,7 +376,7 @@ public class OrderService : IOrderService }); } - await TryConsumeAsync(order.Id, async () => + await _orderCommonHandler.TryConsumeAsync(order.Id, async () => { //校验 await Verify(order); @@ -380,78 +389,11 @@ public class OrderService : IOrderService }); } - private async Task> EMealVoucherConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) - { - var msg = string.Empty; - var originData = await _dbContext.Queryable() - .Where(x => x.QrCode == request.UserUniqueNo) - .Select(x => x.OriginData) - .SingleAsync(); - if (originData.IsEmpty()) - { - msg = "无法识别"; - return new DefaultApiResult - { - Code = ApiStateCode.Error, - Data = null, - Msg = msg - }; - } - - var originDataItems = originData.Split(":"); - var no = originDataItems[0]; - var idNo = originDataItems[3]; - - try - { - await EMealVoucherConsumeAsync(request.Adapt(), gainfo, originData, no, idNo); - } - catch (Exception ex) - { - msg = ex.Message; - } - - //获取餐券余额 - var data = await _dbContext.Queryable() - .LeftJoin((x, d) => x.No == d.No) - .Where((x, d) => x.No == no && d.IdNo == idNo) - .Select((x, d) => new OrderResultResponse - { - UserCode = d.IdNo, - UserName = d.Name, - CurrentBalance = d.Balance - }) - .SingleAsync(); - - try - { - var v = data.UserCode ?? string.Empty; - var vc = v.ToCharArray().Reverse().ToList(); - - for (int i = 0; i < vc.Count; i++) - { - if (i + 1 > 4) - { - vc[i] = '*'; - } - } - - vc.Reverse(); - data.UserCode = string.Join("", vc); - } - catch (Exception) - { - //ignore - } - - return new DefaultApiResult - { - Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, - Data = data, - Msg = msg - }; - } + // private async Task> BirthdayConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) + // { + // + // } private async Task OfflineConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) @@ -490,8 +432,9 @@ public class OrderService : IOrderService { var message = string.Empty; - if (request.Source == BalanceRecordSourceEnumType.EMealVoucher) - return await EMealVoucherConsumeAsync(request, gainfo); + var otherTypes = new[] { BalanceRecordSourceEnumType.EMealVoucher, BalanceRecordSourceEnumType.Birthday }; + if (otherTypes.Contains(request.Source)) + return await _namedResolver.GetService(request.Source.ToString()).ConsumeAsync(request, gainfo); if (request.IsOffline) await OfflineConsumeAsync(request, gainfo); @@ -547,144 +490,6 @@ public class OrderService : IOrderService return await _dbContext.Queryable().Where(x => x.Code == gainfoQrCode.UserCode).SingleAsync(); } - /// - /// 电子餐券消费 - /// - /// - /// - /// - /// - /// - private async Task EMealVoucherConsumeAsync(ConsumeRequest request, GainfoEntity gainfo, string originData, string no, string idNo) - { - var now = DateTime.Now; - - var current = await _dbContext.Queryable() - .Where(x => x.No == no && x.IdNo == idNo) - .Select(x => new { x.Name, x.Start, x.End }) - .SingleAsync(); - - (now >= current.Start && now <= current.End).ThrowIfFalse("餐券已过期"); - - - var eMealVoucher = await _dbContext.Queryable().Where(x => x.No == no).SingleAsync(); - - var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); - var isSupermarket = await _dbContext.Queryable().Where(x => x.Code == checkData.BusinessPlaceCode) - .Select(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) - .SingleAsync(); - - isSupermarket.ThrowIfTrue("禁止在超市消费"); - - eMealVoucher.Factories.Contains(checkData.Factory).ThrowIfFalse("禁止在当前厂区消费"); - - gainfo.No = originData; - var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); - - await TryConsumeAsync(order.Id, async () => - { - await CommonHelper.RetryAsync(async () => - { - var detail = await _dbContext.Queryable().Where(x => x.No == no && x.IdNo == idNo).SingleAsync(); - //金额+- - detail.Balance += order.Amount; - (detail.Balance < 0).ThrowIfTrue("余额不足,无法消费"); - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == detail.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.No == no && x.IdNo == idNo && x.Version == detail.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - balanceRecord.Remark = $"({no})" + current.Name; - await _dbContext.InsertAsync(balanceRecord); - }, 5); - }); - } - - /// - /// 消费前预检 - /// - /// - /// - /// - private async Task PreCheckAsync(string deviceUniqueNo, DateTime costDateTime) - { - var device = await _dbContext.Queryable().Where(x => x.UniqueNo == deviceUniqueNo).SingleAsync(); - device.ThrowIfNull("设备尚未登记"); - device.Enable.ThrowIfFalse("设备已被停用"); - (device.Type != DeviceTypeEnumType.Meal).ThrowIfTrue("设备类型有误"); - - var bindingInfo = await _dbContext.Queryable() - .LeftJoin((x, b) => x.BusinessPlaceCode == b.Code) - .LeftJoin((x, b, bs) => bs.PlaceCode == b.Code) - .Where(x => x.DeviceUniqueNo == deviceUniqueNo && x.BusinessPlaceCode.HasValue) - .Select((x, b, bs) => new - { - b.Code, - bs.ShiftCode, - Factory = x.TenantId - }) - .SingleAsync(); - - bindingInfo.ThrowIfNull("设备尚未登记营业点"); - - //获取当前消费餐次 - var shifts = await _dbContext.Queryable() - .Where(x => x.ShiftCode == bindingInfo.ShiftCode) - .Select(x => new { x.ShiftType, x.Start, x.End }) - .ToListAsync(); - shifts.ThrowIfEmpty("营业点餐次尚未设置"); - - var shift = shifts.FirstOrDefault(x => new TimePeriod - { - Start = x.Start, - End = x.End - }.IsPresent(costDateTime)); - shift.ThrowIfNull("非就餐时间"); - - var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, costDateTime); - - return new OrderPreCheckDto - { - Factory = bindingInfo.Factory, - CostDate = costDate, - ShiftType = shift!.ShiftType, - BusinessPlaceCode = bindingInfo.Code, - BusinessShiftCode = bindingInfo.ShiftCode - }; - } - - /// - /// 设置设备ip - /// - /// - private async Task SetDeviceIpInfoAsync(string deviceNo) - { - try - { - if (_httpContextAccessor.HttpContext!.Request.Headers.TryGetValue("X-Requested-DeviceIp", out var ip)) - { - var device = await _dbContext.Queryable().Where(x => x.UniqueNo == deviceNo).SingleAsync(); - device.ConnectionInfo = new - { - ip = ip.ToString() - }.ToJson(); - - await _dbContext.UpdateAsync(device); - } - - _logger.LogInformation("设备ip获取:{deviceNo} {ip}", deviceNo, ip); - } - catch (Exception) - { - //ignore - } - } - /// /// 检查是否能够进行离线消费,处理hpt重复提交 /// @@ -700,34 +505,9 @@ public class OrderService : IOrderService if (orders.IsEmpty()) return true; - var order = orders.FirstOrDefault(); + var order = orders.FirstOrDefault()!; //存在在线order数据,但是状态未成功 return !order.IsOffline && order.OrderStatus != CanteenOrderStatusEnumType.Success; } - - private async Task TryConsumeAsync(long orderId, Func action) - { - try - { - await action(); - - await _dbContext.Updateable() - .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.Id == orderId) - .ExecuteCommandAsync(); - } - catch (Exception ex) - { - await _dbContext.Updateable() - .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Failure) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .SetColumns(x => x.Result == ex.Message) - .Where(x => x.Id == orderId) - .ExecuteCommandAsync(); - - throw; - } - } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Tickets/Services/Implements/BirthdayTicketService.cs b/Src/Dlhis.Canteen/Tickets/Services/Implements/BirthdayTicketService.cs index ba75b0e9..78bc5e07 100644 --- a/Src/Dlhis.Canteen/Tickets/Services/Implements/BirthdayTicketService.cs +++ b/Src/Dlhis.Canteen/Tickets/Services/Implements/BirthdayTicketService.cs @@ -40,8 +40,8 @@ public class BirthdayTicketService : BaseTicketService, IBirthdayTicketService { Type = TicketEnumType.Birthday, UserCode = item.Code, - OriginalAmount = amount, - Amount = amount, + OriginalBalance = amount, + Balance = amount, QrCode = qrCode, Remark = todayStr + "生日券", TenantId = item.Factory diff --git a/Src/Dlhis.Entity/Canteen/Entities/TicketEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/TicketEntity.cs index d3ca3c05..451b7ba4 100644 --- a/Src/Dlhis.Entity/Canteen/Entities/TicketEntity.cs +++ b/Src/Dlhis.Entity/Canteen/Entities/TicketEntity.cs @@ -10,7 +10,7 @@ namespace Dlhis.Entity.Canteen.Entities; /// 券 /// [SugarTable("Tickets", TableDescription = "券")] -public class TicketEntity : SugarBaseEntity, ITenantId +public class TicketEntity : SugarBaseEntity, ITenantId, IVersion { /// /// 类型 @@ -28,13 +28,13 @@ public class TicketEntity : SugarBaseEntity, ITenantId /// 初始余额 /// [SugarColumn(IsNullable = false, ColumnDescription = "初始余额")] - public decimal OriginalAmount { get; set; } + public decimal OriginalBalance { get; set; } /// /// 当前余额 /// [SugarColumn(IsNullable = false, ColumnDescription = "当前余额")] - public decimal Amount { get; set; } + public decimal Balance { get; set; } /// /// 消费码 @@ -49,4 +49,7 @@ public class TicketEntity : SugarBaseEntity, ITenantId public string Remark { get; set; } public string TenantId { get; set; } + + /// + public int Version { get; set; } } \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Enums/BalanceRecordSourceEnumType.cs b/Src/Dlhis.Entity/Canteen/Enums/BalanceRecordSourceEnumType.cs index 79d86f43..16f5a7fe 100644 --- a/Src/Dlhis.Entity/Canteen/Enums/BalanceRecordSourceEnumType.cs +++ b/Src/Dlhis.Entity/Canteen/Enums/BalanceRecordSourceEnumType.cs @@ -37,6 +37,14 @@ public enum BalanceRecordSourceEnumType /// [Description("电子餐券")] EMealVoucher, + + + /// + /// 生日券 + /// + [Description("生日券")] + Birthday, + //********************************************************************************************************* /// @@ -70,7 +78,7 @@ public enum BalanceRecordSourceEnumType /// [Description("其他基地餐补")] OtherTenantSubsidies, - + /// /// 余额写入 ///