From 4f7b984dfc3ade88456bfa7a866f8d6fc3b0d899 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Wed, 14 Aug 2024 15:07:08 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E7=94=B5=E5=AD=90=E9=A4=90=E5=88=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implements/DeviceConnectService.cs | 4 +- .../Services/Implements/GainfoMealService.cs | 2 - Src/Dlhis.Basic/Program.cs | 1 + Src/Dlhis.Canteen/Dlhis.Canteen.csproj | 6 - .../Controllers/EMealVoucherController.cs | 65 ++++ .../Request/EMealVoucherPageRequest.cs | 7 + .../Response/EMealVoucherDetailResponse.cs | 31 ++ .../Response/EMealVoucherPageResponse.cs | 12 + .../Services/IEMealVoucherService.cs | 18 ++ .../Implements/EMealVoucherService.cs | 106 +++++++ .../Implements/MealSupplementRecordService.cs | 1 - .../Order/Dto/OrderPreCheckDto.cs | 16 + .../Services/Implements/BalanceService.cs | 1 - .../Implements/BookingOrderService.cs | 1 - .../Implements/HainingBalanceService.cs | 1 - .../Order/Services/Implements/OrderService.cs | 300 ++++++++++++++---- .../Canteen/Entities/BalanceRecordEntity.cs | 6 - .../Entities/EMealVoucherDetailEntity.cs | 23 +- .../EMealVoucherDetailQrCodeEntity.cs | 29 ++ .../Enums/BalanceRecordSourceEnumType.cs | 2 +- .../Request/EMealVoucherReceiveRequest.cs | 5 + .../Implements/EMealVoucherService.cs | 2 +- .../Services/Implements/OrderService.cs | 1 - .../Jobs/MealSupplementHandlerJob.cs | 1 - Src/Gateway/Program.cs | 14 +- Src/Gateway/appsettings.json | 1 - Src/Gateway/ocelot.Development.json | 2 +- 27 files changed, 552 insertions(+), 106 deletions(-) create mode 100644 Src/Dlhis.Canteen/EMealVoucher/Request/EMealVoucherPageRequest.cs create mode 100644 Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherDetailResponse.cs create mode 100644 Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherPageResponse.cs create mode 100644 Src/Dlhis.Canteen/EMealVoucher/Services/IEMealVoucherService.cs create mode 100644 Src/Dlhis.Canteen/EMealVoucher/Services/Implements/EMealVoucherService.cs create mode 100644 Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs create mode 100644 Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailQrCodeEntity.cs diff --git a/Src/Dlhis.Basic/Device/Services/Implements/DeviceConnectService.cs b/Src/Dlhis.Basic/Device/Services/Implements/DeviceConnectService.cs index 4b767cc6..30671f82 100644 --- a/Src/Dlhis.Basic/Device/Services/Implements/DeviceConnectService.cs +++ b/Src/Dlhis.Basic/Device/Services/Implements/DeviceConnectService.cs @@ -17,10 +17,12 @@ namespace Dlhis.Basic.Device.Services.Implements; public class DeviceConnectService : IDeviceConnectService { private readonly IDbContext _dbContext; + private readonly IHttpContextAccessor _httpContextAccessor; - public DeviceConnectService(IDbContext dbContext) + public DeviceConnectService(IDbContext dbContext, IHttpContextAccessor httpContextAccessor) { _dbContext = dbContext; + _httpContextAccessor = httpContextAccessor; } diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs index 93249cff..587c3fb2 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs @@ -96,7 +96,6 @@ public class GainfoMealService : IGainfoMealService ShiftType = null, CostDate = now.Date, DeviceUniqueNo = null, - SubsidiesFromMonth = now.ToString("yyyyMM"), IP = null, CostId = gainfoLeave.CostId, OriginTenantId = gainfoLeave.Factory, @@ -141,7 +140,6 @@ public class GainfoMealService : IGainfoMealService ShiftType = null, CostDate = now.Date, DeviceUniqueNo = null, - SubsidiesFromMonth = now.ToString("yyyyMM"), IP = null, CostId = gainfo.CostId, OriginTenantId = gainfo.Factory, diff --git a/Src/Dlhis.Basic/Program.cs b/Src/Dlhis.Basic/Program.cs index 882db25c..6bd00925 100644 --- a/Src/Dlhis.Basic/Program.cs +++ b/Src/Dlhis.Basic/Program.cs @@ -22,6 +22,7 @@ public class Startup : DefaultStartup public override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); + services.AddHttpContextAccessor(); services.AddRedis(); } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Dlhis.Canteen.csproj b/Src/Dlhis.Canteen/Dlhis.Canteen.csproj index fc8473bf..0c6bb0f3 100644 --- a/Src/Dlhis.Canteen/Dlhis.Canteen.csproj +++ b/Src/Dlhis.Canteen/Dlhis.Canteen.csproj @@ -28,10 +28,4 @@ - - - - - - diff --git a/Src/Dlhis.Canteen/EMealVoucher/Controllers/EMealVoucherController.cs b/Src/Dlhis.Canteen/EMealVoucher/Controllers/EMealVoucherController.cs index 98e2f3ed..40b9483d 100644 --- a/Src/Dlhis.Canteen/EMealVoucher/Controllers/EMealVoucherController.cs +++ b/Src/Dlhis.Canteen/EMealVoucher/Controllers/EMealVoucherController.cs @@ -1,3 +1,7 @@ +using Dlhis.Canteen.EMealVoucher.Request; +using Dlhis.Canteen.EMealVoucher.Response; +using Dlhis.Canteen.EMealVoucher.Services; +using Kurisu.AspNetCore.DataAccess.Dto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -8,4 +12,65 @@ namespace Dlhis.Canteen.EMealVoucher.Controllers; [ApiController] public class EMealVoucherController : ControllerBase { + private readonly IEMealVoucherService _eMealVoucherService; + + public EMealVoucherController(IEMealVoucherService eMealVoucherService) + { + _eMealVoucherService = eMealVoucherService; + } + + /// + /// 获取我的电子餐券申请 + /// + /// + /// + [HttpGet("my")] + public async Task> GetMyEMealVoucherPageAsync([FromQuery] EMealVoucherPageRequest request) + { + return await _eMealVoucherService.GetMyEMealVoucherPageAsync(request); + } + + /// + /// 查看电子餐券 + /// + /// + /// + [HttpGet("no")] + public async Task> GetDetailsByNoAsync(string no) + { + return await _eMealVoucherService.GetDetailsByNoAsync(no); + } + + /// + /// 获取url签名 + /// + /// + /// + [HttpGet("signature")] + public async Task GetQrCodeSignatureAsync(long detailId) + { + return await _eMealVoucherService.GetQrCodeSignatureAsync(detailId); + } + + /// + /// 刷新url签名 + /// + /// + [HttpPost("signature/refresh")] + public async Task RefreshSignatureAsync(long detailId) + { + await _eMealVoucherService.RefreshSignatureAsync(detailId); + } + + /// + /// 获取二维码 + /// + /// + /// + [AllowAnonymous] + [HttpGet("qrcode")] + public async Task GetQrCodeAsync(string signature) + { + return await _eMealVoucherService.GetQrCodeAsync(signature); + } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/EMealVoucher/Request/EMealVoucherPageRequest.cs b/Src/Dlhis.Canteen/EMealVoucher/Request/EMealVoucherPageRequest.cs new file mode 100644 index 00000000..d23ddad0 --- /dev/null +++ b/Src/Dlhis.Canteen/EMealVoucher/Request/EMealVoucherPageRequest.cs @@ -0,0 +1,7 @@ +using Kurisu.AspNetCore.DataAccess.Dto; + +namespace Dlhis.Canteen.EMealVoucher.Request; + +public class EMealVoucherPageRequest : PageDto +{ +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherDetailResponse.cs b/Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherDetailResponse.cs new file mode 100644 index 00000000..03656071 --- /dev/null +++ b/Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherDetailResponse.cs @@ -0,0 +1,31 @@ +namespace Dlhis.Canteen.EMealVoucher.Response; + +public class EMealVoucherDetailResponse +{ + public long Id { get; set; } + + /// + /// 用餐人 + /// + public string Name { get; set; } + + /// + /// 手机号 + /// + public string Phone { get; set; } + + /// + /// 就餐开始 + /// + public DateTime Start { get; set; } + + /// + /// 就餐结束 + /// + public DateTime End { get; set; } + + /// + /// 金额 + /// + public decimal Balance { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherPageResponse.cs b/Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherPageResponse.cs new file mode 100644 index 00000000..c35caac3 --- /dev/null +++ b/Src/Dlhis.Canteen/EMealVoucher/Response/EMealVoucherPageResponse.cs @@ -0,0 +1,12 @@ +namespace Dlhis.Canteen.EMealVoucher.Response; + +public class EMealVoucherPageResponse +{ + public long Id { get; set; } + + public string No { get; set; } + + public string Title { get; set; } + + public DateTime Date { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/EMealVoucher/Services/IEMealVoucherService.cs b/Src/Dlhis.Canteen/EMealVoucher/Services/IEMealVoucherService.cs new file mode 100644 index 00000000..ef41f70a --- /dev/null +++ b/Src/Dlhis.Canteen/EMealVoucher/Services/IEMealVoucherService.cs @@ -0,0 +1,18 @@ +using Dlhis.Canteen.EMealVoucher.Request; +using Dlhis.Canteen.EMealVoucher.Response; +using Kurisu.AspNetCore.DataAccess.Dto; + +namespace Dlhis.Canteen.EMealVoucher.Services; + +public interface IEMealVoucherService : IScopeDependency +{ + Task> GetMyEMealVoucherPageAsync(EMealVoucherPageRequest request); + + Task> GetDetailsByNoAsync(string no); + + Task GetQrCodeSignatureAsync(long detailId); + + Task RefreshSignatureAsync(long detailId); + + Task GetQrCodeAsync(string signature); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/EMealVoucher/Services/Implements/EMealVoucherService.cs b/Src/Dlhis.Canteen/EMealVoucher/Services/Implements/EMealVoucherService.cs new file mode 100644 index 00000000..c00c2fa1 --- /dev/null +++ b/Src/Dlhis.Canteen/EMealVoucher/Services/Implements/EMealVoucherService.cs @@ -0,0 +1,106 @@ +using System.Text; +using Dlhis.Canteen.EMealVoucher.Request; +using Dlhis.Canteen.EMealVoucher.Response; +using Dlhis.Entity.Canteen.Entities; +using Kurisu.AspNetCore.Authentication.Abstractions; +using Kurisu.AspNetCore.DataAccess.Dto; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils.Extensions; +using SqlSugar; + +namespace Dlhis.Canteen.EMealVoucher.Services.Implements; + +public class EMealVoucherService : IEMealVoucherService +{ + private readonly IDbContext _dbContext; + private readonly ICurrentUser _currentUser; + + public EMealVoucherService(IDbContext dbContext, ICurrentUser currentUser) + { + _dbContext = dbContext; + _currentUser = currentUser; + } + + + public async Task> GetMyEMealVoucherPageAsync(EMealVoucherPageRequest request) + { + var code = _currentUser.GetUserClaim("jinko-code"); + code = "0123456"; + return await _dbContext.Queryable() + .Where(x => x.Code == code) + .OrderBy(x => x.Id, OrderByType.Desc) + .Select() + .ToPageListAsync(request); + } + + + public async Task> GetDetailsByNoAsync(string no) + { + return await _dbContext.Queryable() + .Where(x => x.No == no) + .Select() + .ToListAsync(); + } + + + public async Task GetQrCodeSignatureAsync(long detailId) + { + return await GenerateSignatureAsync(detailId, false); + } + + public async Task RefreshSignatureAsync(long detailId) + { + await GenerateSignatureAsync(detailId, true); + } + + private async Task GenerateSignatureAsync(long detailId, bool isRefresh) + { + var eMealVoucherDetail = await _dbContext.Queryable().Where(x => x.Id == detailId).SingleAsync(); + eMealVoucherDetail.ThrowIfNull("数据不存在"); + if (eMealVoucherDetail.Signature.IsPresent()) + { + if (isRefresh) + { + await _dbContext.Deleteable().Where(x => x.Signature == eMealVoucherDetail.Signature).ExecuteCommandAsync(); + } + else + { + return eMealVoucherDetail.Signature; + } + } + + eMealVoucherDetail.Signature = Guid.NewGuid().ToString().Replace("-", "") + DateTimeOffset.Now.ToUnixTimeMilliseconds(); + await _dbContext.UpdateAsync(eMealVoucherDetail); + + return eMealVoucherDetail.Signature; + } + + /// + public async Task GetQrCodeAsync(string signature) + { + var eMealVoucherDetail = await _dbContext.Queryable().Where(x => x.Signature == signature).SingleAsync(); + eMealVoucherDetail.ThrowIfNull("无效地址"); + var eMealVoucher = await _dbContext.Queryable().Where(x => x.No == eMealVoucherDetail.No).SingleAsync(); + + var ts = DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString().Reverse().Join(string.Empty); + var originData = string.Concat(eMealVoucher.No, ":", ts, ":", eMealVoucher.Code, ":", eMealVoucherDetail.IdNo); + + var qrCode = "02" + originData.ToArray().Select(x => + { + var b = Encoding.ASCII.GetBytes(x.ToString()); + int i = b[0]; + return Convert.ToString(i); + }).Join(string.Empty); + + + await _dbContext.InsertAsync(new EMealVoucherDetailQrCodeEntity + { + Signature = signature, + OriginData = originData, + QrCode = qrCode + }); + + return qrCode; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs b/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs index 70e94176..6bb57af2 100644 --- a/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs +++ b/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs @@ -174,7 +174,6 @@ public class MealSupplementRecordService : IMealSupplementRecordService Source = BalanceRecordSourceEnumType.LocalTenantSubsidies, Remark = "期初导入", CostId = gainfo.CostId, - SubsidiesFromMonth = today.ToString("yyyyMM"), OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, Name = gainfo.Name, diff --git a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs new file mode 100644 index 00000000..6667755e --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs @@ -0,0 +1,16 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Canteen.Order.Dto +{ + public class OrderPreCheckDto + { + public DateTime CostDate { get; set; } + public BusinessShiftMealRelationshipEntity Shift { get; set; } + + public string BusinessPlaceTenantId { 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/Services/Implements/BalanceService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs index cd1cc6a6..a83c3107 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs @@ -482,7 +482,6 @@ public class BalanceService : IBalanceService ShiftType = null, CostDate = DateTime.Now.Date, DeviceUniqueNo = null, - SubsidiesFromMonth = DateTime.Now.ToString("yyyyMM"), IP = null, CostId = gainfo.CostId, OriginTenantId = gainfo.Factory, diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs index 17054b7b..afff04cd 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs @@ -71,7 +71,6 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService DeliveryAddress = request.DeliveryAddress, No = SnowFlakeHelper.Instance.NextId().ToString(), - SubsidiesFromMonth = now.ToString("yyyyMM"), IsOffline = false, IsCostCrossTenant = await GetSettingAsync(gainfo.No, businessPlace.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/HainingBalanceService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/HainingBalanceService.cs index fd0e9fea..46ac4779 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/HainingBalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/HainingBalanceService.cs @@ -75,7 +75,6 @@ public class HainingBalanceService : IHainingBalanceService Source = request.Source, Remark = request.Remark, CostId = gainfo.CostId, - SubsidiesFromMonth = request.CostDate.ToString("yyyyMM"), TenantId = businessPlaceInfo?.TenantId ?? request.Factory, OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index 8edc15fa..e94823f6 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Dlhis.Canteen.Order.Dto; using Dlhis.Canteen.Setting.Services; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; @@ -161,7 +162,11 @@ public class OrderService : BaseOrderService, IOrderService } else if (request.UserUniqueNo.StartsWith("02")) { - request.Source = BalanceRecordSourceEnumType.EVoucher; + 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 @@ -180,8 +185,7 @@ public class OrderService : BaseOrderService, IOrderService { gainfo.ThrowIfNull("无法识别,无权消费"); } - - if (request.IsOffline) + else { //兼容hpt尚未删除人员后产生的消费数据 gainfo ??= await DbContext.Queryable().AS("GainfoLeave") @@ -305,7 +309,6 @@ public class OrderService : BaseOrderService, IOrderService _logs.Add("消费请求校验:" + _sw.ElapsedMilliseconds); _sw.Restart(); - await CommonHelper.RetryAsync(async () => { var gainfoMeal = await DbContext.Queryable().Where(x => x.UserNo == order.UserNo).SingleAsync(); @@ -329,42 +332,6 @@ public class OrderService : BaseOrderService, IOrderService _sw.Restart(); } - /// - /// 根据二维码获取消费用户 - /// - /// - /// - private async Task GetUserByQrCodeAsync(string qrCode) - { - var gainfoQrCode = await DbContext.Queryable().Where(x => x.QrCode == qrCode).SingleAsync(); - gainfoQrCode.ThrowIfNull("消费码已过期"); - (gainfoQrCode.ModifiedTime.AddMinutes(60) < DateTime.Now).ThrowIfTrue("消费码已过期"); - - return await DbContext.Queryable().Where(x => x.Code == gainfoQrCode.UserCode).SingleAsync(); - } - - - /// - /// 检查是否可以离线消费 - /// - /// - /// - private async Task ToCheckAbleOfflineConsumeAsync(string thirdNo) - { - var order = await DbContext.Queryable() - .Where(x => x.ThirdNo == thirdNo) - .OrderBy(x => x.CreateTime, OrderByType.Desc) - .FirstAsync(); - - if (order == null) - { - return true; - } - - //存在在线order数据,但是状态未成功 - return !order.IsOffline && order.OrderStatus != CanteenOrderStatusEnumType.Success; - } - /// /// 离线消费处理 /// @@ -418,7 +385,6 @@ public class OrderService : BaseOrderService, IOrderService DeviceUniqueNo = request.DeviceUniqueNo, No = SnowFlakeHelper.Instance.NextId().ToString(), - SubsidiesFromMonth = request.CostDateTime.ToString("yyyyMM"), IsOffline = request.IsOffline, IsCostCrossTenant = businessPlaceInfo!.TenantId != gainfo.Factory, @@ -492,8 +458,6 @@ public class OrderService : BaseOrderService, IOrderService device.ThrowIfNull("设备尚未登记"); device.Enable.ThrowIfFalse("设备已被停用"); - (request.Source == BalanceRecordSourceEnumType.QrCode && device.TenantId != gainfo.Factory).ThrowIfTrue("二维码在此设备禁止消费"); - var businessPlaceInfo = await DbContext.Queryable() .LeftJoin((x, sr) => x.Code == sr.PlaceCode) .Where(x => x.Code == registry.BusinessPlaceCode) @@ -523,6 +487,8 @@ public class OrderService : BaseOrderService, IOrderService var costDate = await GetCostDateAsync(shift, request.CostDateTime); + (request.Source == BalanceRecordSourceEnumType.QrCode && businessPlaceInfo.TenantId != gainfo.Factory).ThrowIfTrue("禁止跨厂区消费"); + var order = new CanteenOrderEntity { RequestJson = request.ToJson(), @@ -543,9 +509,8 @@ public class OrderService : BaseOrderService, IOrderService DeviceUniqueNo = request.DeviceUniqueNo, No = SnowFlakeHelper.Instance.NextId().ToString(), - SubsidiesFromMonth = request.CostDateTime.ToString("yyyyMM"), IsOffline = request.IsOffline, - IsCostCrossTenant = await GetSettingAsync(gainfo.No, businessPlaceInfo!.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), + IsCostCrossTenant = gainfo.Factory != businessPlaceInfo!.TenantId, OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, @@ -610,17 +575,29 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task ToDoAsync(OrderAddRequest request, GainfoEntity gainfo) { - var message = ""; - if (request.IsOffline) - { - if (request.ThirdNo.IsPresent()) - { - var redisCache = _serviceProvider.GetService(); + var message = string.Empty; - await using var handler = await redisCache.LockAsync($"consume:device:{request.ThirdNo}"); - if (handler.Acquired) + if (request.Source == BalanceRecordSourceEnumType.EMealVoucher) + { + try + { + await EMealVoucherConsumeAsync(request.Adapt(), gainfo); + } + catch (Exception ex) + { + message = ex.Message; + } + } + else + { + if (request.IsOffline) + { + if (request.ThirdNo.IsPresent()) { - if (await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) + var redisCache = _serviceProvider.GetService(); + + await using var handler = await redisCache.LockAsync($"consume:device:{request.ThirdNo}"); + if (handler.Acquired && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) { try { @@ -632,32 +609,219 @@ public class OrderService : BaseOrderService, IOrderService } } } + else + { + try + { + await HandleOfflineAsync(request.Adapt(), gainfo); + } + catch (Exception ex) + { + Logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); + } + } } else { try { - await HandleOfflineAsync(request.Adapt(), gainfo); + (await DbContext.Queryable().Where(x => x.UserCode == gainfo.Code).AnyAsync()).ThrowIfFalse("就餐余额不足"); + await ConsumeAsync(request.Adapt(), gainfo); } catch (Exception ex) { - Logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); + message = ex.Message; } } } - else - { - try - { - (await DbContext.Queryable().Where(x => x.UserCode == gainfo.Code).AnyAsync()).ThrowIfFalse("就餐余额不足"); - await ConsumeAsync(request.Adapt(), gainfo); - } - catch (Exception ex) - { - message = ex.Message; - } - } return message; + + async Task ToCheckAbleOfflineConsumeAsync(string thirdNo) + { + var order = await DbContext.Queryable() + .Where(x => x.ThirdNo == thirdNo) + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .FirstAsync(); + + if (order == null) + { + return true; + } + + //存在在线order数据,但是状态未成功 + return !order.IsOffline && order.OrderStatus != CanteenOrderStatusEnumType.Success; + } + } + + /// + /// 根据二维码获取消费用户 + /// + /// + /// + private async Task GetUserByQrCodeAsync(string qrCode) + { + var gainfoQrCode = await DbContext.Queryable().Where(x => x.QrCode == qrCode).SingleAsync(); + gainfoQrCode.ThrowIfNull("消费码已过期"); + (gainfoQrCode.ModifiedTime.AddMinutes(60) < DateTime.Now).ThrowIfTrue("消费码已过期"); + + return await DbContext.Queryable().Where(x => x.Code == gainfoQrCode.UserCode).SingleAsync(); + } + + + private async Task EMealVoucherConsumeAsync(ConsumeRequest request, GainfoEntity gainfo) + { + var eMealVoucherQrCode = await DbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); + var originData = eMealVoucherQrCode.OriginData.Split(':'); + + var no = originData[0]; + var idNo = originData[3]; + var nowDate = DateTime.Now.Date; + var current = await DbContext.Queryable() + .Where(x => x.No == no && x.IdNo == idNo).SingleAsync(); + (nowDate >= current.Start && nowDate <= current.End).ThrowIfFalse("餐券已过期"); + + var time = DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64(originData[1].Reverse().Join(string.Empty))).ToLocalTime().DateTime; + (time.AddMinutes(60) >= DateTime.Now).ThrowIfFalse("二维码已过期"); + + var eMealVoucher = await DbContext.Queryable().Where(x => x.No == no).SingleAsync(); + + var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); + eMealVoucher.Factories.Contains(checkData.BusinessPlaceTenantId).ThrowIfFalse("禁止在当前厂区消费"); + + _logs.Add("检验设备和营业点参数:" + _sw.ElapsedMilliseconds); + _sw.Restart(); + + var order = new CanteenOrderEntity + { + RequestJson = request.ToJson(), + ThirdNo = request.ThirdNo, + Type = BalanceRecordTypeEnumType.Out, + ApplicationType = CanteenApplicationTypeEnumType.Device, + Name = gainfo.Name, + Source = request.Source, + UserNo = eMealVoucherQrCode.OriginData, //记录为当前消费券 + UserCode = gainfo.Code, + Amount = request.Amount, + BusinessPlaceCode = checkData.BusinessPlaceCode, + BusinessShiftCode = checkData.BusinessShiftCode, + ShiftType = checkData.Shift.ShiftType, + CostDate = checkData.CostDate, + CostId = gainfo.CostId, + DepartmentNo = gainfo.DepartmentNo, + DeviceUniqueNo = request.DeviceUniqueNo, + + No = SnowFlakeHelper.Instance.NextId().ToString(), + IsOffline = false, + IsCostCrossTenant = gainfo.Factory != checkData.BusinessPlaceTenantId, + + OriginTenantId = gainfo.Factory, + Werks = gainfo.Werks, + Status = BalanceRecordStatusEnumType.Valid, + OrderStatus = CanteenOrderStatusEnumType.Wait, + CostDateTime = request.CostDateTime, + //标识当前消费的基地 + TenantId = checkData.BusinessPlaceTenantId, + }; + + var orderId = await DbContext.InsertReturnIdentityAsync(order); + _logs.Add("临时order写入:" + _sw.ElapsedMilliseconds); + _sw.Restart(); + + try + { + 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(); + await DbContext.InsertAsync(balanceRecord); + }, 5); + + _logs.Add("消费写入:" + _sw.ElapsedMilliseconds); + _sw.Restart(); + + await DbContext.Updateable() + .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .Where(x => x.Id == orderId) + .ExecuteCommandAsync(); + + _logs.Add("更新image和订单信息:" + _sw.ElapsedMilliseconds); + _sw.Restart(); + } + catch (Exception ex) + { + await DbContext.Updateable() + .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Failure) + .SetColumns(x => x.Result == ex.Message) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .Where(x => x.Id == orderId) + .ExecuteCommandAsync(); + + throw; + } + } + + /// + /// 消费前预检 + /// + /// + /// + /// + private async Task PreCheckAsync(string deviceUniqueNo, DateTime costDateTime) + { + var registry = await DbContext.Queryable().Where(x => x.DeviceUniqueNo == deviceUniqueNo).SingleAsync(); + registry.ThrowIfNull("设备尚未登记"); + + var device = await DbContext.Queryable().Where(x => x.UniqueNo == 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 + }) + .SingleAsync(); + + businessPlaceInfo.ThrowIfNull("设备尚未登记营业点"); + + //获取当前消费餐次 + var shifts = await DbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); + shifts.ThrowIfEmpty("营业点餐次尚未设置"); + + var shift = shifts.FirstOrDefault(x => new TimePeriod + { + Start = x.Start, + End = x.End + }.IsPresent(costDateTime)); + shift.ThrowIfNull("非就餐时间,无法消费"); + + var costDate = await GetCostDateAsync(shift, costDateTime); + + return new OrderPreCheckDto + { + CostDate = costDate, + Shift = shift, + BusinessPlaceTenantId = businessPlaceInfo.TenantId, + BusinessPlaceCode = businessPlaceInfo.Code, + BusinessShiftCode = businessPlaceInfo.ShiftCode + }; } } \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Entities/BalanceRecordEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/BalanceRecordEntity.cs index b5874947..a3fdedda 100644 --- a/Src/Dlhis.Entity/Canteen/Entities/BalanceRecordEntity.cs +++ b/Src/Dlhis.Entity/Canteen/Entities/BalanceRecordEntity.cs @@ -91,12 +91,6 @@ public class BalanceRecordEntity : SugarBaseEntity, ISoftDeleted, ITenantId [SugarColumn(ColumnDescription = "消费机器唯一信息", IsNullable = true)] public string DeviceUniqueNo { get; set; } - /// - /// 餐补月份 - /// - [SugarColumn(ColumnDescription = "餐补月份")] - public string SubsidiesFromMonth { get; set; } - /// /// Ip /// diff --git a/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailEntity.cs index b41f0533..35a963b2 100644 --- a/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailEntity.cs +++ b/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailEntity.cs @@ -1,4 +1,5 @@ -using Kurisu.AspNetCore.DataAccess.SqlSugar; +using Kurisu.AspNetCore.DataAccess.Entity; +using Kurisu.AspNetCore.DataAccess.SqlSugar; using SqlSugar; namespace Dlhis.Entity.Canteen.Entities; @@ -7,7 +8,7 @@ namespace Dlhis.Entity.Canteen.Entities; /// 电子餐券明细 /// [SugarTable(TableName = "EMealVoucherDetail", TableDescription = "电子餐券明细")] -public class EMealVoucherDetailEntity : SugarBaseEntity +public class EMealVoucherDetailEntity : SugarBaseEntity, IVersion { /// /// 主表id @@ -18,7 +19,7 @@ public class EMealVoucherDetailEntity : SugarBaseEntity /// /// 流水号 /// - [SugarColumn(ColumnDescription = "流水号")] + [SugarColumn(ColumnDescription = "流水号", UniqueGroupNameList = new[] { "idx_EMealVoucherDetail_no_idno" })] public string No { get; set; } /// @@ -30,7 +31,7 @@ public class EMealVoucherDetailEntity : SugarBaseEntity /// /// 身份证 /// - [SugarColumn(ColumnDescription = "序号")] + [SugarColumn(ColumnDescription = "证件号", UniqueGroupNameList = new[] { "idx_EMealVoucherDetail_no_idno" })] public string IdNo { get; set; } /// @@ -60,6 +61,16 @@ public class EMealVoucherDetailEntity : SugarBaseEntity /// /// 金额 /// - [SugarColumn(ColumnDescription = "金额", ColumnDataType = "decimal(16,2)")] - public decimal Amount { get; set; } + [SugarColumn(ColumnDescription = "余额", ColumnDataType = "decimal(16,2)")] + public decimal Balance { get; set; } + + /// + /// 版本 + /// + [SugarColumn(ColumnDescription = "版本")] + public int Version { get; set; } + + + [SugarColumn(ColumnDataType = "varchar(255)")] + public string Signature { get; set; } } \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailQrCodeEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailQrCodeEntity.cs new file mode 100644 index 00000000..b1a6b505 --- /dev/null +++ b/Src/Dlhis.Entity/Canteen/Entities/EMealVoucherDetailQrCodeEntity.cs @@ -0,0 +1,29 @@ +using Kurisu.AspNetCore.DataAccess.SqlSugar; +using SqlSugar; + +namespace Dlhis.Entity.Canteen.Entities; + +/// +/// 电子餐券明细QrCode +/// +[SugarTable(TableName = "EMealVoucherDetailQrCode", TableDescription = "电子餐券明细QrCode")] +public class EMealVoucherDetailQrCodeEntity : SugarBaseEntity +{ + /// + /// url签名 + /// + [SugarColumn(ColumnDataType = "varchar(255)", IndexGroupNameList = new[] { "idx_EMealVoucherDetailQrCode_signature" })] + public string Signature { get; set; } + + /// + /// 原始数据 + /// + [SugarColumn(ColumnDescription = "原始数据")] + public string OriginData { get; set; } + + /// + /// 二维码 + /// + [SugarColumn(ColumnDescription = "二维码", UniqueGroupNameList = new[] { "idx_EMealVoucherDetailQrCode_qrcode" })] + public string QrCode { 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 587169b8..79d86f43 100644 --- a/Src/Dlhis.Entity/Canteen/Enums/BalanceRecordSourceEnumType.cs +++ b/Src/Dlhis.Entity/Canteen/Enums/BalanceRecordSourceEnumType.cs @@ -36,7 +36,7 @@ public enum BalanceRecordSourceEnumType /// 电子餐券 /// [Description("电子餐券")] - EVoucher, + EMealVoucher, //********************************************************************************************************* /// diff --git a/Src/Dlhis.External/EMealVoucher/Request/EMealVoucherReceiveRequest.cs b/Src/Dlhis.External/EMealVoucher/Request/EMealVoucherReceiveRequest.cs index 86f3aa3f..76597552 100644 --- a/Src/Dlhis.External/EMealVoucher/Request/EMealVoucherReceiveRequest.cs +++ b/Src/Dlhis.External/EMealVoucher/Request/EMealVoucherReceiveRequest.cs @@ -82,6 +82,11 @@ public class EMealVoucherReceiveRequest : IValidatableObject { yield return new ValidationResult("来宾数量不匹配", new[] { nameof(Number) }); } + + if (Details.DistinctBy(x => x.IdNo).Count() != Details.Count) + { + yield return new ValidationResult("证件信息重复", new[] { "IdNo" }); + } } } diff --git a/Src/Dlhis.External/EMealVoucher/Services/Implements/EMealVoucherService.cs b/Src/Dlhis.External/EMealVoucher/Services/Implements/EMealVoucherService.cs index 9e12372a..a956a612 100644 --- a/Src/Dlhis.External/EMealVoucher/Services/Implements/EMealVoucherService.cs +++ b/Src/Dlhis.External/EMealVoucher/Services/Implements/EMealVoucherService.cs @@ -43,7 +43,7 @@ public class EMealVoucherService : IEMealVoucherService Phone = x.Phone, Start = x.Start, End = x.End, - Amount = x.Amount + Balance = x.Amount }).ToList(); var id = await _dbContext.InsertReturnIdentityAsync(voucher); diff --git a/Src/Dlhis.Middle/Services/Implements/OrderService.cs b/Src/Dlhis.Middle/Services/Implements/OrderService.cs index 98126066..a632b1c5 100644 --- a/Src/Dlhis.Middle/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Middle/Services/Implements/OrderService.cs @@ -171,7 +171,6 @@ public class OrderService : IOrderService DeviceUniqueNo = request.DeviceUniqueNo, No = _factory + "-" + SnowFlakeHelper.Instance.NextId(), - SubsidiesFromMonth = request.CostDateTime.ToString("yyyyMM"), // IsOffline = request.IsOffline, IsCostCrossTenant = await GetSettingAsync(businessPlaceInfo!.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), diff --git a/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs b/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs index 0f288bd5..1a3698ca 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs @@ -156,7 +156,6 @@ public class MealSupplementHandlerJob : BaseJob Source = BalanceRecordSourceEnumType.LocalTenantSubsidies, Remark = mealSupplement.UniqueId + ":" + mealSupplement.Remark, CostId = gainfo.CostId, - SubsidiesFromMonth = mealSupplement.Date.ToString("yyyyMM"), OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, Name = gainfo.Name, diff --git a/Src/Gateway/Program.cs b/Src/Gateway/Program.cs index e96a2582..c8fd2e8b 100644 --- a/Src/Gateway/Program.cs +++ b/Src/Gateway/Program.cs @@ -1,4 +1,3 @@ - using Gateway; public class Program @@ -8,13 +7,14 @@ public class Program Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, builder) => { - builder.AddJsonFile("ocelot.json", true, true); + if (context.HostingEnvironment.IsProduction()) + { + builder.AddJsonFile("ocelot.json", true, true); + } + builder.AddJsonFile($"ocelot.{context.HostingEnvironment.EnvironmentName}.json", true, true); }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) .Build().Run(); } -} +} \ No newline at end of file diff --git a/Src/Gateway/appsettings.json b/Src/Gateway/appsettings.json index 508d6f2a..d0e733e1 100644 --- a/Src/Gateway/appsettings.json +++ b/Src/Gateway/appsettings.json @@ -11,7 +11,6 @@ "Common", "Canteen", "Scheduled", - "User", "Sys", "Visitor", "Message" diff --git a/Src/Gateway/ocelot.Development.json b/Src/Gateway/ocelot.Development.json index e1aaacf0..45f461c0 100644 --- a/Src/Gateway/ocelot.Development.json +++ b/Src/Gateway/ocelot.Development.json @@ -72,7 +72,7 @@ "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { - "Host": "localhost", + "Host": "192.168.199.115", "Port": 6000 } ]