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,
-
+
///
/// 余额写入
///