From 7d322ef5f60b39d28ee1c267c9461070ed83728c Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 24 Oct 2024 16:51:17 +0800 Subject: [PATCH 01/23] =?UTF-8?q?fix=20=E5=8F=82=E6=95=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=B9=E6=B3=95=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Order/Services/BaseOrderService.cs | 38 ------------------- .../Implements/BookingOrderService.cs | 8 ++-- .../Order/Services/Implements/OrderService.cs | 12 +++--- .../Services/ICanteenSettingService.cs | 25 +++++++++++- .../Implements/CanteenSettingService.cs | 25 ++++++++++++ 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs b/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs index 16ed5fee..df521389 100644 --- a/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs @@ -1,16 +1,12 @@ using Dlhis.Canteen.Setting.Services; -using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Enums; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using Kurisu.AspNetCore.Utils.Extensions; -using Mapster; namespace Dlhis.Canteen.Order.Services; public abstract class BaseOrderService { - private readonly ICanteenSettingService _canteenSettingService; private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; private readonly ILogger _logger; @@ -21,7 +17,6 @@ public abstract class BaseOrderService ICurrentUser currentUser, ILogger logger) { - _canteenSettingService = canteenSettingService; _dbContext = dbContext; _currentUser = currentUser; _logger = logger; @@ -32,37 +27,4 @@ public abstract class BaseOrderService protected IDbContext DbContext => _dbContext; protected ICurrentUser CurrentUser => _currentUser; - - /// - /// 获取参数配置 - /// - /// - /// - /// - /// - /// - /// - protected async Task GetSettingAsync(string userNo, string tenantId, CanteenSettingItemEnumType item, T defaultValue = default) - { - _settings ??= await _canteenSettingService.GetUserSettingAsync(userNo, tenantId); - - if (_settings.TryGetValue(item, out var v)) - { - try - { - if (int.TryParse(v, out var n)) - { - return n.Adapt(); - } - - return v.Adapt(); - } - catch (Exception) - { - return defaultValue; - } - } - - return defaultValue; - } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs index e149c83b..a39482e4 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs @@ -19,6 +19,7 @@ namespace Dlhis.Canteen.Order.Services.Implements; public class BookingOrderService : BaseOrderService, IBookingOrderService { + private readonly ICanteenSettingService _canteenSettingService; private readonly IDishesMenuApi _dishesMenuApi; private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; @@ -28,6 +29,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService ICurrentUser currentUser, ILogger logger) : base(canteenSettingService, dbContext, currentUser, logger) { + _canteenSettingService = canteenSettingService; _dishesMenuApi = dishesMenuApi; _businessPlacePermissionsApi = businessPlacePermissionsApi; } @@ -41,7 +43,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService //预定时间 var now = DateTime.Now; - var bookingDeadline = await GetSettingAsync(gainfo.No, businessPlace.TenantId, CanteenSettingItemEnumType.BookingDeadline) ?? new TimeOnly(20, 30, 00); + var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, businessPlace.TenantId, gainfo.No, new TimeOnly(20, 30, 00)); var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); (now >= dealLine).ThrowIfTrue("超出订餐时间"); @@ -72,7 +74,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService No = SnowFlakeHelper.Instance.NextId().ToString(), IsOffline = false, - IsCostCrossTenant = await GetSettingAsync(gainfo.No, businessPlace.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), + IsCostCrossTenant = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowCrossTenant, businessPlace.TenantId, gainfo.No), OriginTenantId = gainfo.Factory, //人员可能跨基地消费 @@ -212,7 +214,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); order.ThrowIfNull("订单不存在"); - var bookingDeadline = await GetSettingAsync(order.No, order.TenantId, CanteenSettingItemEnumType.BookingDeadline) ?? new TimeOnly(20, 30, 00); + var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, order.TenantId, order.No, new TimeOnly(20, 30, 00)); var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); (DateTime.Now >= dealLine).ThrowIfTrue("不在订单取消允许范围内,不可操作"); diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index 98e9356c..4b9f43bc 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -22,6 +22,7 @@ namespace Dlhis.Canteen.Order.Services.Implements; public class OrderService : BaseOrderService, IOrderService { + private readonly ICanteenSettingService _canteenSettingService; private readonly IServiceProvider _serviceProvider; private readonly IHttpContextAccessor _httpContextAccessor; @@ -33,6 +34,7 @@ public class OrderService : BaseOrderService, IOrderService ILogger logger) : base(canteenSettingService, dbContext, currentUser, logger) { + _canteenSettingService = canteenSettingService; _serviceProvider = serviceProvider; _httpContextAccessor = httpContextAccessor; } @@ -198,7 +200,7 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task GetCostDateAsync(BusinessShiftMealRelationshipEntity shift, DateTime costDateTime) { - var switchTime = await GetSettingAsync(string.Empty, shift.TenantId, CanteenSettingItemEnumType.BusinessSwitchTime); + var switchTime = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BusinessSwitchTime, shift.TenantId, string.Empty); var costDate = costDateTime.Date; @@ -243,7 +245,7 @@ public class OrderService : BaseOrderService, IOrderService .ToListAsync(); //一天消费金额 - var dayCostMax = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.DayCostMax); + var dayCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.DayCostMax, order.TenantId, order.UserNo); if (dayCostMax != default) { //消费记录一定会有营业点代码!.Value是安全用法 @@ -255,7 +257,7 @@ public class OrderService : BaseOrderService, IOrderService } //一次消费金额 - var onceCostMax = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.OnceCostMax); + var onceCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.OnceCostMax, order.TenantId, order.UserNo); if (onceCostMax != default) { (Math.Abs(order.Amount) > onceCostMax).ThrowIfTrue($"单次消费金额不能超出{onceCostMax}元"); @@ -264,7 +266,7 @@ public class OrderService : BaseOrderService, IOrderService //超市消费限额 if (order.BusinessPlaceCode.HasValue && supermarketCodes.Contains(order.BusinessPlaceCode.Value)) { - var dayMarketCostMax = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.DayMarketCostMax); + var dayMarketCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.DayMarketCostMax, order.TenantId, order.UserNo); if (dayMarketCostMax != default) { var cost = todayAllBalanceRecords.Where(x => x.BusinessPlaceCode.HasValue) @@ -276,7 +278,7 @@ public class OrderService : BaseOrderService, IOrderService } //重复餐次消费 - var allowRepeatInOneShift = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.AllowRepeatInOneShift, true); + var allowRepeatInOneShift = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowRepeatInOneShift, order.TenantId, order.UserNo, true); if (!allowRepeatInOneShift) { var costCount = todayAllBalanceRecords.Count(x => x.ShiftType == order.ShiftType); diff --git a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs index ecd8d69d..9872d134 100644 --- a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs @@ -7,10 +7,25 @@ namespace Dlhis.Canteen.Setting.Services; public interface ICanteenSettingService : IScopeDependency { + /// + /// 获取配置分页数据 + /// + /// + /// Task> GetPageAsync(CanteenSettingPageRequest request); + /// + /// 保存配置 + /// + /// + /// Task SaveSettingAsync(CanteenSettingSaveRequest request); + /// + /// 设置配置启用开关 + /// + /// + /// Task SetEnableAsync(CanteenSettingSetEnableRequest request); /// @@ -21,7 +36,13 @@ public interface ICanteenSettingService : IScopeDependency /// Task> GetUserSettingAsync(string userNo, string tenantId); - + /// + /// 获取营业点切换时间 + /// + /// + /// Task GetBusinessSwitchTimeAsync(string tenantId); -} + + Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs index 39300afe..41f3791b 100644 --- a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs @@ -114,6 +114,31 @@ public class CanteenSettingService : ICanteenSettingService } } + /// + public async Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo, T defaultValue = default) + { + var settings = await GetUserSettingAsync(userNo, factory); + + if (settings.TryGetValue(item, out var v)) + { + try + { + if (int.TryParse(v, out var n)) + { + return n.Adapt(); + } + + return v.Adapt(); + } + catch (Exception) + { + return defaultValue; + } + } + + return defaultValue; + } + //*********************************************** help methods ***************************************************// private async Task CheckDuplicateSettingAsync(CanteenSettingSaveRequest request) From 5cf11e51287395ad4d0fa5d2bf963fa62dbfd3c0 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Fri, 25 Oct 2024 17:10:18 +0800 Subject: [PATCH 02/23] =?UTF-8?q?add=20=E6=B6=88=E8=B4=B9=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Implements/SysDataService.cs | 4 +- .../Controllers/GainfoMealController.cs | 12 - .../Response/GainfoCurrentMealInfoResponse.cs | 9 - .../Response/GainfoCurrentResponse.cs | 5 - .../Response/GainfoMealResponse.cs | 2 +- .../Services/IGainfoMealService.cs | 2 - .../Services/Implements/GainfoMealService.cs | 160 +--- .../Services/Implements/GainfoService.cs | 9 +- .../Implements/MealSupplementRecordService.cs | 31 - .../Order/Controllers/BalancesController.cs | 32 - .../Order/Controllers/OrdersController.cs | 106 ++- .../Order/Dto/OrderPreCheckDto.cs | 20 +- .../Order/Repository/IBalanceRepository.cs | 40 + .../Order/Repository/IOrderRepository.cs | 11 + .../Implements/BalanceRepository.cs | 57 ++ .../Repository/Implements/OrderRepository.cs | 68 ++ .../Order/Services/IBalanceService.cs | 4 +- .../Services/Implements/BalanceService.cs | 91 +-- .../Implements/BookingOrderService.cs | 604 +++++++-------- .../Services/Implements/MiddleService.cs | 18 +- .../Order/Services/Implements/OrderService.cs | 697 +++++++----------- .../Services/ICanteenSettingService.cs | 2 + .../Implements/CanteenSettingService.cs | 18 + .../BusinessShiftMealRelationshipEntity.cs | 12 +- .../Basic/Entity/GainfoMealEntity.cs | 86 +-- .../Canteen/Entities/WalletEntity.cs | 29 + .../Enums/CanteenApplicationTypeEnumType.cs | 3 + .../Entities/LocalGainfoMealEntity.cs | 41 ++ .../Common/Services/IJinkoJipService.cs | 36 + .../Services/Implements/HangfireService.cs | 4 +- .../Services/Implements/JinkoJipService.cs | 213 ++++++ .../Implements/SapBusinessTripService.cs | 4 +- .../Jobs/MealSupplementHandlerJob.cs | 36 +- Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs | 4 +- .../Jobs/SyncOriginCardInfoJob.cs | 4 +- Src/Dlhis.ScheduledTask/Program.cs | 2 +- 36 files changed, 1273 insertions(+), 1203 deletions(-) delete mode 100644 Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/IBalanceRepository.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs create mode 100644 Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs create mode 100644 Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs create mode 100644 Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index 53ec663d..3b35e692 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -39,7 +39,9 @@ public class SysDataService : ISysDataService case "GainfoMeal": { var codes = await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => x.Code).ToListAsync(); - return await _dbContext.Queryable().Where(x => codes.Contains(x.UserCode)).Select(x => (object)x).ToListAsync(); + return null; + //todo 余额 + ///return await _dbContext.Queryable().Where(x => codes.Contains(x.UserCode)).Select(x => (object)x).ToListAsync(); } default: return await _dbContext.Client.SqlQueryable(sql).ToListAsync(); diff --git a/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs b/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs index 24b50fc6..d511a2ca 100644 --- a/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs +++ b/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs @@ -2,7 +2,6 @@ using Dlhis.Basic.Organization.Response; using Dlhis.Basic.Organization.Services; using Kurisu.AspNetCore.DataAccess.Dto; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Aop; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -33,15 +32,4 @@ public class GainfoMealController : ControllerBase { return await _mealUserService.GetPageAsync(request); } - - /// - /// 迁移 - /// - /// - [Transactional] - [HttpPost("migration")] - public async Task MigrationAsync(GainfoBalanceMigrationRequest request) - { - await _mealUserService.MigrationAsync(request); - } } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs deleted file mode 100644 index 4ccf513f..00000000 --- a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Dlhis.Basic.Organization.Response; - -public class GainfoCurrentMealInfoResponse -{ - /// - /// 余额 - /// - public decimal Balance { get; set; } -} diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs index 06f3b3db..b2fb54b8 100644 --- a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs +++ b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs @@ -62,9 +62,4 @@ public class GainfoCurrentResponse /// 卡类型名称 /// public string CardTypeName { get; set; } - - /// - /// 就餐信息 - /// - public GainfoCurrentMealInfoResponse MealInfo { get; set; } } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs index 33c7472d..f2bc5509 100644 --- a/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs +++ b/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs @@ -41,7 +41,7 @@ public class GainfoMealResponse /// /// 餐补总额 /// - public decimal? MealSupplementAmount { get; set; } + public decimal MealSupplementAmount { get; set; } /// /// 档案状态 diff --git a/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs index f657d625..3d2505cc 100644 --- a/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs @@ -7,6 +7,4 @@ namespace Dlhis.Basic.Organization.Services; public interface IGainfoMealService : IScopeDependency { Task> GetPageAsync(GainfoMealPageRequest request); - - Task MigrationAsync(GainfoBalanceMigrationRequest request); } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs index a97b3a01..f9a354e7 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs @@ -9,7 +9,6 @@ using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using Kurisu.AspNetCore.Utils; using Kurisu.AspNetCore.Utils.Extensions; using SqlSugar; @@ -28,138 +27,47 @@ public class GainfoMealService : IGainfoMealService public async Task> GetPageAsync(GainfoMealPageRequest request) { - var tenantId = _currentUser.GetStringTenantId(); + var factory = _currentUser.GetStringTenantId(); + var factories = await GetWalletFactoriesAsync(_currentUser.GetStringTenantId()); - var page = await _dbContext.Queryable() - .LeftJoin((meal, gainfo) => meal.UserCode == gainfo.Code) - .LeftJoin((meal, gainfo, gainfoLeave) => meal.UserCode == gainfoLeave.Code) - .LeftJoin((meal, gainfo, gainfoLeave, dept1) => dept1.No == gainfo.DepartmentNo) - .LeftJoin((meal, gainfo, gainfoLeave, dept1, dept2) => dept2.No == gainfoLeave.DepartmentNo) - .WhereIF(request.Search.IsPresent(), (meal, gainfo, gainfoLeave, dept1, dept2) => meal.UserCode == request.Search - || gainfo.Name == request.Search - || gainfoLeave.Name == request.Search) - .Where((meal, gainfo, gainfoLeave) => gainfo.Factory == tenantId || gainfoLeave.Factory == tenantId) - .WhereIF(request.IsLeave, (meal, gainfo, gainfoLeave) => SqlFunc.Subqueryable().Where(x => x.Code == meal.UserCode).Any()) - .WhereIF(request.IsLeave && request.SearchDateTimePeriod.HasValue(), (meal, gainfo, gainfoLeave) => gainfoLeave.ResignationDate >= request.SearchDateTimePeriod.Start && gainfoLeave.ResignationDate <= request.SearchDateTimePeriod.End) - .Select((meal, gainfo, gainfoLeave, dept1, dept2) => new GainfoMealResponse + return await _dbContext.Queryable().AS(request.IsLeave ? "GainfoLeave" : "Gainfo") + .LeftJoin((x, dept) => dept.No == x.DepartmentNo) + .WhereIF(request.Search.IsPresent(), x => x.Code == request.Search || x.Name == request.Search) + .Where(x => x.Factory == factory) + .WhereIF(request.IsLeave && request.SearchDateTimePeriod.HasValue(), x => x.ResignationDate >= request.SearchDateTimePeriod.Start && + x.ResignationDate <= request.SearchDateTimePeriod.End) + .Select((x, dept) => new GainfoMealResponse { - Balance = meal.Balance, - UserCode = meal.UserCode, - UserName = SqlFunc.IsNull(gainfo.Name, gainfoLeave.Name), - UserNo = SqlFunc.IsNull(gainfo.No, gainfoLeave.No), - DepartmentName = SqlFunc.IsNull(dept1.Name, dept2.Name), - Status = SqlFunc.IsNullOrEmpty(gainfo.No) ? GainfoStatusEnumType.Leave : GainfoStatusEnumType.Working + UserCode = x.Code, + UserName = x.Name, + UserNo = x.No, + DepartmentName = dept.Name, + Status = request.IsLeave + ? GainfoStatusEnumType.Leave + : GainfoStatusEnumType.Working, + MealSupplementAmount = SqlFunc.Subqueryable() + .Where(p => p.Code == x.Code) + .Where(p => factories.Contains(p.Factory)) + .WhereIF(request.SearchDateTimePeriod.HasValue(), p => p.Date >= request.SearchDateTimePeriod.Start && p.Date <= request.SearchDateTimePeriod.End) + .Sum(p => p.Amount), + Balance = SqlFunc.Subqueryable() + .Where(p => factories.Contains(p.TenantId)) + .Where(p => p.UserCode == x.Code && p.Status == BalanceRecordStatusEnumType.Valid && p.IsDeleted == false) + .Where(p => p.Source != BalanceRecordSourceEnumType.EMealVoucher) + .Sum(p => p.Amount) }) .ToPageListAsync(request); - - var userCodes = page.Data.Select(x => x.UserCode).ToList(); - var summary = await _dbContext.Queryable() - .Where(x => userCodes.Contains(x.Code)) - .WhereIF(request.SearchDateTimePeriod.HasValue(), x => x.Date >= request.SearchDateTimePeriod.Start && x.Date <= request.SearchDateTimePeriod.End) - .GroupBy(x => x.Code) - .Select(x => new - { - x.Code, - Amount = SqlFunc.AggregateSum(x.Amount) - }) - .ToListAsync(); - - page.Data.ForEach(x => { x.MealSupplementAmount = summary.FirstOrDefault(item => item.Code == x.UserCode)?.Amount ?? 0; }); - - return page; } - public async Task MigrationAsync(GainfoBalanceMigrationRequest request) + + //*********************************************************************************************************************************************************************** + + + public async Task> GetWalletFactoriesAsync(string factory) { - //只能针对离职人员 - var gainfoLeave = await _dbContext.Queryable().Where(x => x.Code == request.BeforeCode).SingleAsync(); - gainfoLeave.ThrowIfNull("需要对已离职人员迁移"); + var wallets = await _dbContext.Queryable().ToListAsync(); + var code = wallets.First(x => x.Factory == factory).Code; - var beforeBalance = await _dbContext.Queryable().Where(x => x.UserCode == request.BeforeCode).Select(x => x.Balance).SingleAsync(); - (beforeBalance <= 0).ThrowIfTrue("不可操作,余额不足"); - - var now = DateTime.Now; - - await _dbContext.InsertAsync(new BalanceRecordEntity - { - No = SnowFlakeHelper.Instance.NextId().ToString(), - UserNo = gainfoLeave.No, - Name = gainfoLeave.Name, - UserCode = gainfoLeave.Code, - DepartmentNo = gainfoLeave.DepartmentNo, - Source = BalanceRecordSourceEnumType.BalanceMigration, - Type = BalanceRecordTypeEnumType.In, - Amount = -Math.Abs(beforeBalance), - BusinessPlaceCode = null, - BusinessShiftCode = null, - ShiftType = null, - CostDate = now.Date, - DeviceUniqueNo = null, - IP = null, - CostId = gainfoLeave.CostId, - OriginTenantId = gainfoLeave.Factory, - IsCostCrossTenant = false, - Status = BalanceRecordStatusEnumType.Valid, - CostDateTime = DateTime.Now, - Remark = $"{gainfoLeave.Code}:余额转出", - ApplicationType = CanteenApplicationTypeEnumType.BalanceMigration, - TenantId = gainfoLeave.Factory, - Werks = gainfoLeave.Werks, - IsDeleted = false, - }); - - var beforeBalanceAmount = await _dbContext.Queryable() - .Where(x => x.UserCode == gainfoLeave.Code) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == beforeBalanceAmount) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .SetColumns(x => x.Version == x.Version + 1) - .Where(x => x.UserCode == request.BeforeCode) //不需要判断version - .ExecuteCommandAsync(); - - //转移到现有人员 - var gainfo = await _dbContext.Queryable().Where(x => x.Code == request.NowCode).SingleAsync(); - gainfo.ThrowIfNull("目标人员不存在"); - - await _dbContext.InsertAsync(new BalanceRecordEntity - { - No = SnowFlakeHelper.Instance.NextId().ToString(), - UserNo = gainfo.No, - Name = gainfo.Name, - UserCode = gainfo.Code, - DepartmentNo = gainfo.DepartmentNo, - Source = BalanceRecordSourceEnumType.BalanceMigration, - Type = BalanceRecordTypeEnumType.In, - Amount = beforeBalance, - BusinessPlaceCode = null, - BusinessShiftCode = null, - ShiftType = null, - CostDate = now.Date, - DeviceUniqueNo = null, - IP = null, - CostId = gainfo.CostId, - OriginTenantId = gainfo.Factory, - IsCostCrossTenant = false, - Status = BalanceRecordStatusEnumType.Valid, - CostDateTime = DateTime.Now, - Remark = $"{gainfoLeave.Code}:余额转入", - ApplicationType = CanteenApplicationTypeEnumType.MealSupplement, - TenantId = gainfo.Factory, - Werks = gainfo.Werks, - IsDeleted = false, - }); - - var nowGainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == request.NowCode).SingleAsync(); - var nowBalance = nowGainfoMeal.Balance + beforeBalance; - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == nowBalance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .SetColumns(x => x.Version == x.Version + 1) - .Where(x => x.UserCode == request.NowCode && x.Version == nowGainfoMeal.Version) - .ExecuteCommandThrowVersionAsync("转入失败"); + return wallets.Where(x => x.Code == code).Select(x => x.Factory).ToList(); } } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs index 63317086..b76ef920 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs @@ -197,10 +197,9 @@ public class GainfoService : IGainfoService var info = await _gainfoStore.Queryable(GainfoStatusEnumType.Working) .LeftJoin((ga, department) => ga.DepartmentNo == department.No) - .LeftJoin((ga, department, mealUser) => ga.Code == mealUser.UserCode) - .LeftJoin((ga, department, mealUser, type) => type.Code == ga.CardType.ToString() && type.Type == "timeperiod") + .LeftJoin((ga, department, type) => type.Code == ga.CardType.ToString() && type.Type == "timeperiod") .Where((ga, department) => ga.Id == gainfo.Id) - .Select((ga, department, mealUser, type) => new GainfoCurrentResponse + .Select((ga, department, type) => new GainfoCurrentResponse { UserNo = ga.No, Birthday = ga.Birthday, @@ -210,10 +209,6 @@ public class GainfoService : IGainfoService UserCode = ga.Code, UserName = ga.Name, Factory = ga.Factory, - MealInfo = new GainfoCurrentMealInfoResponse - { - Balance = mealUser.Balance - }, CardType = ga.CardType, CardTypeName = type.Display }).SingleAsync(); diff --git a/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs b/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs index c334c0cc..ddeb8fca 100644 --- a/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs +++ b/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs @@ -187,37 +187,6 @@ public class MealSupplementRecordService : IMealSupplementRecordService DepartmentNo = gainfo.DepartmentNo, DeviceUniqueNo = null }); - - await CommonHelper.RetryAsync(async () => - { - var mealUser = await _dbContext.Queryable().Where(x => x.UserCode == request.Code).FirstAsync(); - if (mealUser == null) - { - mealUser = new GainfoMealEntity - { - UserCode = gainfo.Code, - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0 - }; - - await _dbContext.InsertAsync(mealUser); - } - - //重新核算 - var balance = await _dbContext.Queryable() - .Where(x => x.UserCode == request.Code) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == mealUser.UserCode && x.Version == mealUser.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); } } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs b/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs index dcf8df4c..baa4deb1 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs @@ -167,36 +167,4 @@ public class BalancesController : ControllerBase { await _balanceService.ManuallyImportBalanceAndReCalcAsync(requests); } - - - /// - /// 重算 - /// - /// - [Authorize(Roles = "admin")] - [HttpPost("recalc")] - [IgnoreTenant] - [Transactional(IsolationLevel.ReadCommitted)] - public async Task ReCalcAsync(BalanceReCalcRequest request) - { - await _balanceService.ReCalcAsync(request.UserCode); - } - - [HttpPost("recalc1")] - [Transactional(IsolationLevel.ReadCommitted)] - [IgnoreTenant] - public async Task ReC5() - { - var sp = App.GetServiceProvider(); - var db = sp.GetService(); - var middleService = sp.GetService(); - var others = await db.Queryable().AS("LocalBalanceRecordEntity") - .Where(x => SqlFunc.Subqueryable().Where(p => p.No == x.No).NotAny()) - .ToListAsync(); - - foreach (var item in others) - { - await middleService.MiddleUploadAsync(item); - } - } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs index 59ab64b3..1949cb5b 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs @@ -19,12 +19,10 @@ namespace Dlhis.Canteen.Order.Controllers; public class OrdersController : ControllerBase { private readonly IOrderService _orderService; - private readonly IBookingOrderService _bookingOrderService; - public OrdersController(IOrderService orderService, IBookingOrderService bookingOrderService) + public OrdersController(IOrderService orderService) { _orderService = orderService; - _bookingOrderService = bookingOrderService; } /// @@ -66,56 +64,56 @@ public class OrdersController : ControllerBase return await _orderService.GetOrdersAggregationAsync(page); } - /// - /// 取消订单 - /// - /// - /// - [HttpPut("user-cancel")] - [Transactional] - [IgnoreTenant] - public async Task CancelOrderAsync(OrderCancelRequest request) - { - await _bookingOrderService.UserCancelOrderAsync(request); - } + // /// + // /// 取消订单 + // /// + // /// + // /// + // [HttpPut("user-cancel")] + // [Transactional] + // [IgnoreTenant] + // public async Task CancelOrderAsync(OrderCancelRequest request) + // { + // await _bookingOrderService.UserCancelOrderAsync(request); + // } - - /// - /// 取消订单 - /// - /// - /// - [HttpPut("manager-cancel")] - [Transactional] - [IgnoreTenant] - public async Task ManagerCancelOrderAsync(OrderCancelRequest request) - { - await _bookingOrderService.ManagerCancelOrderAsync(request); - } - - //************************************************************** 订餐功能 ********************************************************************************** - - /// - /// 取消订单 - /// - /// - /// - [HttpPost("booking")] - [Transactional] - [IgnoreTenant] - public async Task Booking(BookingMealRequest request) - { - return await _bookingOrderService.BookingAsync(request); - } - - /// - /// 订单分页 - /// - /// - /// - [HttpGet("booking/page")] - public async Task> GetBookingPageAsync([FromQuery] OrderBookingPageRequest request) - { - return await _bookingOrderService.GetBookingPageAsync(request); - } + // + // /// + // /// 取消订单 + // /// + // /// + // /// + // [HttpPut("manager-cancel")] + // [Transactional] + // [IgnoreTenant] + // public async Task ManagerCancelOrderAsync(OrderCancelRequest request) + // { + // await _bookingOrderService.ManagerCancelOrderAsync(request); + // } + // + // //************************************************************** 订餐功能 ********************************************************************************** + // + // /// + // /// 取消订单 + // /// + // /// + // /// + // [HttpPost("booking")] + // [Transactional] + // [IgnoreTenant] + // public async Task Booking(BookingMealRequest request) + // { + // return await _bookingOrderService.BookingAsync(request); + // } + // + // /// + // /// 订单分页 + // /// + // /// + // /// + // [HttpGet("booking/page")] + // public async Task> GetBookingPageAsync([FromQuery] OrderBookingPageRequest request) + // { + // return await _bookingOrderService.GetBookingPageAsync(request); + // } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs index 6667755e..c4f9c9dc 100644 --- a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs +++ b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs @@ -1,16 +1,16 @@ -using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Enums; -namespace Dlhis.Canteen.Order.Dto +namespace Dlhis.Canteen.Order.Dto; + +public class OrderPreCheckDto { - public class OrderPreCheckDto - { - public DateTime CostDate { get; set; } - public BusinessShiftMealRelationshipEntity Shift { get; set; } + public string Factory { get; set; } - public string BusinessPlaceTenantId { get; set; } + public DateTime CostDate { get; set; } - public Guid BusinessPlaceCode { get; set; } + public ShiftEnumType ShiftType { get; set; } - public Guid BusinessShiftCode { 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/IBalanceRepository.cs b/Src/Dlhis.Canteen/Order/Repository/IBalanceRepository.cs new file mode 100644 index 00000000..0c1e321c --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/IBalanceRepository.cs @@ -0,0 +1,40 @@ +using Dlhis.Entity.Canteen.Entities; + +namespace Dlhis.Canteen.Order.Repository; + +public interface IBalanceRepository : IScopeDependency +{ + /// + /// 获取余额 + /// + /// + /// + /// + Task GetBalanceAsync(string userCode, string factory); + + /// + /// 获取消费明细 + /// + /// + /// + /// + /// + /// + Task> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime); + + /// + /// 获取钱包厂区 + /// + /// + /// + Task> GetWalletFactoriesAsync(string factory); + + /// + /// 检测余额是否足够 + /// + /// + /// + /// + /// + Task CheckConsumableAsync(string userCode, string factory, decimal cost); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs b/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs new file mode 100644 index 00000000..d9e904c4 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs @@ -0,0 +1,11 @@ +using Dlhis.Canteen.Order.Dto; +using Dlhis.Canteen.Order.Request; +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; + +namespace Dlhis.Canteen.Order.Repository; + +public interface IOrderRepository : IScopeDependency +{ + Task CreateDeviceConsumeOrderAsync(OrderPreCheckDto checkData, ConsumeRequest request, GainfoEntity gainfo); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs new file mode 100644 index 00000000..5de3e2f9 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs @@ -0,0 +1,57 @@ +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Canteen.Order.Repository.Implements; + +public class BalanceRepository : IBalanceRepository +{ + private readonly IDbContext _dbContext; + + public BalanceRepository(IDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task GetBalanceAsync(string userCode, string factory) + { + var factories = await GetWalletFactoriesAsync(factory); + + return await _dbContext.Queryable() + .Where(x => x.UserCode == userCode && x.Status == BalanceRecordStatusEnumType.Valid) + .Where(x => factories.Contains(x.TenantId)) + .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) + .SumAsync(x => x.Amount); + } + + + public async Task> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime) + { + var factories = await GetWalletFactoriesAsync(factory); + + return await _dbContext.Queryable() + .Where(x => x.UserCode == userCode) + .Where(x => factories.Contains(x.TenantId)) + .Where(x => x.Type == BalanceRecordTypeEnumType.Out) + .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) + .Where(x => x.CostDateTime >= startTime && x.CostDateTime <= endTime) + .ToListAsync(); + } + + public async Task> GetWalletFactoriesAsync(string factory) + { + var wallets = await _dbContext.Queryable().ToListAsync(); + var code = wallets.First(x => x.Factory == factory).Code; + + return wallets.Where(x => x.Code == code).Select(x => x.Factory).ToList(); + } + + + public async Task CheckConsumableAsync(string userCode, string factory, decimal cost) + { + var balance = await GetBalanceAsync(userCode, factory); + var isEnough = balance - Math.Abs(cost) >= 0; + isEnough.ThrowIfFalse($"钱包余额不足({balance}元)"); + } +} \ 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 new file mode 100644 index 00000000..a9e31997 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs @@ -0,0 +1,68 @@ +using Dlhis.Canteen.Order.Dto; +using Dlhis.Canteen.Order.Request; +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Canteen.Order.Repository.Implements; + +public class OrderRepository : IOrderRepository +{ + private readonly IDbContext _dbContext; + + public OrderRepository(IDbContext dbContext) + { + _dbContext = dbContext; + } + + /// + public async Task CreateDeviceConsumeOrderAsync(OrderPreCheckDto checkData, ConsumeRequest request, GainfoEntity gainfo) + { + var order = new CanteenOrderEntity + { + RequestJson = request.ToJson(), + IsOffline = request.IsOffline, + No = SnowFlakeHelper.Instance.NextId().ToString(), + ThirdNo = request.ThirdNo, + Type = BalanceRecordTypeEnumType.Out, + ApplicationType = CanteenApplicationTypeEnumType.Device, + Name = gainfo.Name, + Source = request.Source, + UserNo = gainfo.No, + UserCode = gainfo.Code, + Amount = request.Amount, + BusinessPlaceCode = checkData.BusinessPlaceCode, + BusinessShiftCode = checkData.BusinessShiftCode, + ShiftType = checkData.ShiftType, + CostDate = checkData.CostDate, + CostDateTime = request.CostDateTime, + CostId = gainfo.CostId, + DepartmentNo = gainfo.DepartmentNo, + DeviceUniqueNo = request.DeviceUniqueNo, + + Remark = null, + DeliveryAddress = null, + Result = null, + IP = null, + + IsCostCrossTenant = gainfo.Factory != checkData.Factory, + + Werks = gainfo.Werks, + OriginTenantId = gainfo.Factory, + //标识当前消费的基地 + TenantId = checkData.Factory, + + Status = BalanceRecordStatusEnumType.Valid, + OrderStatus = CanteenOrderStatusEnumType.Wait, + + IsDeleted = false, + }; + + var orderId = await _dbContext.InsertReturnIdentityAsync(order); + order.Id = orderId; + return order; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs b/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs index bedc7a72..4efc0902 100644 --- a/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs @@ -14,7 +14,7 @@ public interface IBalanceService : IScopeDependency Task> GetUserBalanceGroupAsync(BalanceRecordUserGroupPageRequest request); - + Task SupplementBalanceAsync(BalanceSupplementRequest request); @@ -40,6 +40,4 @@ public interface IBalanceService : IScopeDependency Task ManuallyImportBalanceAndReCalcAsync(List requests); - - Task ReCalcAsync(string userCode); } \ 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 24e9fb11..3bedcca8 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs @@ -122,11 +122,7 @@ public class BalanceService : IBalanceService balanceRecord.Remark = "退款"; await _dbContext.UpdateAsync(balanceRecord); - if (balanceRecord.Source is BalanceRecordSourceEnumType.Face or BalanceRecordSourceEnumType.QrCode) - { - await ReCalcAsync(balanceRecord.UserCode); - } - else if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) + if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) { var originData = balanceRecord.UserNo.Split(":"); var eNo = originData[0]; @@ -323,19 +319,6 @@ public class BalanceService : IBalanceService await _dbContext.Client.Ado.RollbackTranAsync(); throw; } - - await CommonHelper.RetryAsync(async () => - { - var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == balanceInfo.UserCode).SingleAsync(); - var balance = gainfoMeal.Balance + Math.Abs(balanceInfo.Amount) + request.Amount; - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == balanceInfo.UserCode && x.Version == gainfoMeal.Version) - .ExecuteCommandThrowVersionAsync(); - }, 10); } public async Task GetPageByUserAsync(BalanceRecordUserPageRequest request) @@ -542,82 +525,10 @@ public class BalanceService : IBalanceService Werks = gainfo.Werks, IsDeleted = false }); - - await CommonHelper.RetryAsync(async () => - { - var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == gainfo.Code).FirstAsync(); - if (gainfoMeal == null) - { - gainfoMeal = new GainfoMealEntity - { - UserCode = gainfo.Code, - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0 - }; - - await _dbContext.InsertAsync(gainfoMeal); - } - - //重新核算 - var balance = await _dbContext.Queryable() - .Where(x => x.UserCode == gainfo.Code) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == gainfoMeal.UserCode && x.Version == gainfoMeal.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); } } - public async Task ReCalcAsync(string userCode) - { - await CommonHelper.RetryAsync(async () => - { - var gainfo = await _dbContext.Queryable().Where(x => x.Code == userCode).SingleAsync() - ?? await _dbContext.Queryable().Where(x => x.Code == userCode).SingleAsync(); - - gainfo.ThrowIfNull("档案不存在"); - - var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == gainfo.Code).FirstAsync(); - if (gainfoMeal == null) - { - gainfoMeal = new GainfoMealEntity - { - UserCode = gainfo.Code, - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0 - }; - - await _dbContext.InsertAsync(gainfoMeal); - } - - //重新核算 - var balance = await _dbContext.Queryable() - .Where(x => x.UserCode == gainfo.Code) - .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == gainfoMeal.UserCode && x.Version == gainfoMeal.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); - } - - /// /// 获取离线设备 /// diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs index a39482e4..e71257ed 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs @@ -1,302 +1,302 @@ -using Dlhis.Canteen.Dishes.Request; -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.Utils.Extensions; -using Kurisu.AspNetCore.Utils; -using Dlhis.Canteen.Setting.Services; -using Dlhis.Basic.Api; -using Kurisu.AspNetCore.Authentication.Abstractions; -using Kurisu.AspNetCore.DataAccess.Dto; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using SqlSugar; -using Mapster; - -namespace Dlhis.Canteen.Order.Services.Implements; - -public class BookingOrderService : BaseOrderService, IBookingOrderService -{ - private readonly ICanteenSettingService _canteenSettingService; - private readonly IDishesMenuApi _dishesMenuApi; - private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; - - public BookingOrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, - IDishesMenuApi dishesMenuApi, - IBusinessPlacePermissionsApi businessPlacePermissionsApi, - ICurrentUser currentUser, ILogger logger) - : base(canteenSettingService, dbContext, currentUser, logger) - { - _canteenSettingService = canteenSettingService; - _dishesMenuApi = dishesMenuApi; - _businessPlacePermissionsApi = businessPlacePermissionsApi; - } - - public async Task BookingAsync(BookingMealRequest request) - { - var gainfo = await CheckExistsMyBookingsAsync(request.BookingDate, request.ShiftType); - - var businessPlace = await DbContext.Queryable().Where(x => x.Code == request.BusinessPlaceCode).FirstAsync(); - - //预定时间 - var now = DateTime.Now; - - var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, businessPlace.TenantId, gainfo.No, new TimeOnly(20, 30, 00)); - var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); - (now >= dealLine).ThrowIfTrue("超出订餐时间"); - - //菜单存在 - var menuDetailResult = await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); - var menuDetail = menuDetailResult.Data; - menuDetail.ThrowIfNull("预定失败,菜品已经下架"); - - var order = new CanteenOrderEntity - { - RequestJson = request.ToJson(), - ThirdNo = string.Empty, - Type = BalanceRecordTypeEnumType.Out, - ApplicationType = CanteenApplicationTypeEnumType.Booking, - Name = gainfo.Name, - Source = request.Source, - UserNo = gainfo.No, - UserCode = gainfo.Code, - Amount = -Math.Abs(menuDetail.Price * request.Number), - BusinessPlaceCode = request.BusinessPlaceCode, - BusinessShiftCode = null, - ShiftType = request.ShiftType, - CostDate = now.Date, - CostId = gainfo.CostId, - DepartmentNo = gainfo.DepartmentNo, - DeviceUniqueNo = string.Empty, - DeliveryAddress = request.DeliveryAddress, - - No = SnowFlakeHelper.Instance.NextId().ToString(), - IsOffline = false, - IsCostCrossTenant = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowCrossTenant, businessPlace.TenantId, gainfo.No), - - OriginTenantId = gainfo.Factory, //人员可能跨基地消费 - - Status = BalanceRecordStatusEnumType.Valid, - OrderStatus = CanteenOrderStatusEnumType.Success, - - CostDateTime = now, - - //标识当前消费的基地 - TenantId = businessPlace.TenantId, - }; - - await DbContext.InsertAsync(order); - await CommonHelper.RetryAsync(async () => - { - await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); - menuDetail = menuDetailResult.Data; - menuDetail.ThrowIfNull("预定失败,菜品已经下架"); - menuDetail.Number -= request.Number; - - (menuDetail.Number < 0).ThrowIfTrue($"{menuDetail.DishesName}剩余数量不足"); - - //餐补 - if (request.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) - { - var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); - mealInfo.ThrowIfNull("余额不足,无法消费"); - - //金额+- - mealInfo.Balance += order.Amount; - (mealInfo.Balance < 0).ThrowIfTrue("余额不足,无法消费"); - - await DbContext.Updateable() - .SetColumns(x => x.Balance == mealInfo.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == order.UserCode && x.Version == mealInfo.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - } - - await DbContext.Updateable() - .SetColumns(x => x.Number == menuDetail.Number) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == now) - .Where(x => x.StartDate <= menuDetail.Date && menuDetail.Date <= x.EndDate - && x.ShiftType == request.ShiftType - && x.BusinessPlaceCode == request.BusinessPlaceCode - && x.DishesCode == request.DishesCode - && x.Version == menuDetail.Version - ) - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); - - await DbContext.InsertAsync(new CanteenOrderDetailEntity - { - OrderNo = order.No, - BookingDate = request.BookingDate, - DishesCode = request.DishesCode, - Price = menuDetail.Price, - Unit = request.Unit, - Number = request.Number, - TenantId = order.TenantId, - }); - }, 10); - - - return new BookingResponse - { - No = order.No, - CreateTime = order.CreateTime, - Source = order.Source, - Amount = Math.Abs(order.Amount) - }; - } - - - public async Task> GetBookingPageAsync(OrderBookingPageRequest request) - { - var myCodes = (await _businessPlacePermissionsApi.GetMyAsync()).Data.Select(x => (Guid?)x.Code).ToList(); - - var page = await DbContext.Queryable() - .LeftJoin((x, ga) => x.UserNo == ga.No) - .LeftJoin((x, ga, dept) => dept.No == ga.DepartmentNo) - .Where(x => myCodes.Contains(x.BusinessPlaceCode)) - .WhereIF(request.OrderStatus.HasValue, x => x.OrderStatus == request.OrderStatus) - .WhereIF(request.Source.HasValue, x => x.Source == request.Source) - .WhereIF(request.Search.IsPresent(), (x, ga) => x.UserCode == request.Search || ga.Name.Contains(request.Search)) - .WhereIF(request.BookingPeriod?.Start.HasValue == true && request.BookingPeriod?.End.HasValue == true, x => - SqlFunc.Subqueryable() - .Where(p => p.OrderNo == x.No) - .Where(p => p.BookingDate >= request.BookingPeriod.Start) - .Where(p => p.BookingDate <= request.BookingPeriod.End) - .Any() - ) - .WhereIF(request.ShiftType.HasValue, x => x.ShiftType == request.ShiftType) - .Select((x, ga, dept) => new OrderBookingResponse - { - No = x.No, - DeliveryAddress = x.DeliveryAddress, - Remark = x.Remark, - Amount = SqlFunc.Abs(x.Amount), - CreateTime = x.CreateTime, - ShiftType = x.ShiftType, - Status = x.OrderStatus, - UserCode = x.UserCode, - UserName = ga.Name, - Source = x.Source, - DepartmentName = dept.Name, - }) - .ToPageListAsync(request); - - var nos = page.Data.Select(x => x.No).ToList(); - var details = await DbContext.Queryable() - .LeftJoin((x, dishes) => x.DishesCode == dishes.CustomCode) - .Where(x => nos.Contains(x.OrderNo)) - .Select((x, dishes) => new { x.OrderNo, x.BookingDate, x.Number, dishes.Name }).ToListAsync(); - - page.Data.ForEach(x => - { - x.BookingDate = details.First(item => item.OrderNo == x.No).BookingDate; - x.TotalNumber = details.Where(item => item.OrderNo == x.No).Sum(item => item.Number); - x.DishesInfos = details.Where(item => item.OrderNo == x.No).Select(item => new - { - item.Name - }) - .ToList(); - }); - - return page; - } - - public async Task UserCancelOrderAsync(OrderCancelRequest request) - { - var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); - order.ThrowIfNull("订单不存在"); - - var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, order.TenantId, order.No, new TimeOnly(20, 30, 00)); - var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); - (DateTime.Now >= dealLine).ThrowIfTrue("不在订单取消允许范围内,不可操作"); - - await ManagerCancelOrderAsync(new OrderCancelRequest - { - No = request.No, - Reason = request.Reason, - }); - } - - - public async Task ManagerCancelOrderAsync(OrderCancelRequest request) - { - var now = DateTime.Now; - var userId = CurrentUser.GetIntSubjectId(); - - var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); - order.ThrowIfNull("订单不存在"); - - await DbContext.Updateable() - .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Cancel) - .SetColumns(x => x.Remark == request.Reason) - .SetColumns(x => x.ModifiedTime == now) - .SetColumns(x => x.ModifiedBy == userId) - .Where(x => x.No == request.No) - .ExecuteCommandAsync(); - - await DbContext.Updateable() - .SetColumns(x => x.Status == BalanceRecordStatusEnumType.Obsolute) - .SetColumns(x => x.Remark == request.Reason) - .SetColumns(x => x.ModifiedTime == now) - .SetColumns(x => x.ModifiedBy == userId) - .Where(x => x.No == request.No) - .ExecuteCommandAsync(); - - if (order.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) - { - await CommonHelper.RetryAsync(async () => - { - var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); - var setBalance = mealInfo.Balance + Math.Abs(order.Amount); - await DbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == setBalance) - .SetColumns(x => x.ModifiedTime == now) - .SetColumns(x => x.ModifiedBy == userId) - .Where(x => x.Id == mealInfo.Id && x.Version == mealInfo.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); - } - } - - - /// - /// 检查我的订餐是否存在,存在抛出异常 - /// - /// - /// - /// - private async Task CheckExistsMyBookingsAsync(DateTime bookingDate, ShiftEnumType shiftType) - { - var subjectId = CurrentUser.GetIntSubjectId(); - var gainfo = await DbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); - var jinkoCode = CurrentUser.GetUserClaim("jinko-code") ?? string.Empty; - if (jinkoCode.IsPresent()) - { - gainfo = await DbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); - } - - var start = bookingDate.Date.AddDays(-1); - var end = start.AddDays(1).AddSeconds(-1); - - var exists = await DbContext.Queryable() - .Where(x => x.ApplicationType == CanteenApplicationTypeEnumType.Booking) - .Where(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) - .Where(x => x.ShiftType == shiftType) - .Where(x => x.UserNo == gainfo.No) - .Where(x => x.CreateTime >= start && x.CreateTime <= end) - .AnyAsync(); - - exists.ThrowIfTrue("不可重复订餐"); - - return gainfo; - } -} \ No newline at end of file +// using Dlhis.Canteen.Dishes.Request; +// 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.Utils.Extensions; +// using Kurisu.AspNetCore.Utils; +// using Dlhis.Canteen.Setting.Services; +// using Dlhis.Basic.Api; +// using Kurisu.AspNetCore.Authentication.Abstractions; +// using Kurisu.AspNetCore.DataAccess.Dto; +// using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +// using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +// using SqlSugar; +// using Mapster; +// +// namespace Dlhis.Canteen.Order.Services.Implements; +// +// public class BookingOrderService : BaseOrderService, IBookingOrderService +// { +// private readonly ICanteenSettingService _canteenSettingService; +// private readonly IDishesMenuApi _dishesMenuApi; +// private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; +// +// public BookingOrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, +// IDishesMenuApi dishesMenuApi, +// IBusinessPlacePermissionsApi businessPlacePermissionsApi, +// ICurrentUser currentUser, ILogger logger) +// : base(canteenSettingService, dbContext, currentUser, logger) +// { +// _canteenSettingService = canteenSettingService; +// _dishesMenuApi = dishesMenuApi; +// _businessPlacePermissionsApi = businessPlacePermissionsApi; +// } +// +// public async Task BookingAsync(BookingMealRequest request) +// { +// var gainfo = await CheckExistsMyBookingsAsync(request.BookingDate, request.ShiftType); +// +// var businessPlace = await DbContext.Queryable().Where(x => x.Code == request.BusinessPlaceCode).FirstAsync(); +// +// //预定时间 +// var now = DateTime.Now; +// +// var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, businessPlace.TenantId, gainfo.No, new TimeOnly(20, 30, 00)); +// var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); +// (now >= dealLine).ThrowIfTrue("超出订餐时间"); +// +// //菜单存在 +// var menuDetailResult = await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); +// var menuDetail = menuDetailResult.Data; +// menuDetail.ThrowIfNull("预定失败,菜品已经下架"); +// +// var order = new CanteenOrderEntity +// { +// RequestJson = request.ToJson(), +// ThirdNo = string.Empty, +// Type = BalanceRecordTypeEnumType.Out, +// ApplicationType = CanteenApplicationTypeEnumType.Booking, +// Name = gainfo.Name, +// Source = request.Source, +// UserNo = gainfo.No, +// UserCode = gainfo.Code, +// Amount = -Math.Abs(menuDetail.Price * request.Number), +// BusinessPlaceCode = request.BusinessPlaceCode, +// BusinessShiftCode = null, +// ShiftType = request.ShiftType, +// CostDate = now.Date, +// CostId = gainfo.CostId, +// DepartmentNo = gainfo.DepartmentNo, +// DeviceUniqueNo = string.Empty, +// DeliveryAddress = request.DeliveryAddress, +// +// No = SnowFlakeHelper.Instance.NextId().ToString(), +// IsOffline = false, +// IsCostCrossTenant = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowCrossTenant, businessPlace.TenantId, gainfo.No), +// +// OriginTenantId = gainfo.Factory, //人员可能跨基地消费 +// +// Status = BalanceRecordStatusEnumType.Valid, +// OrderStatus = CanteenOrderStatusEnumType.Success, +// +// CostDateTime = now, +// +// //标识当前消费的基地 +// TenantId = businessPlace.TenantId, +// }; +// +// await DbContext.InsertAsync(order); +// await CommonHelper.RetryAsync(async () => +// { +// await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); +// menuDetail = menuDetailResult.Data; +// menuDetail.ThrowIfNull("预定失败,菜品已经下架"); +// menuDetail.Number -= request.Number; +// +// (menuDetail.Number < 0).ThrowIfTrue($"{menuDetail.DishesName}剩余数量不足"); +// +// //餐补 +// if (request.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) +// { +// var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); +// mealInfo.ThrowIfNull("余额不足,无法消费"); +// +// //金额+- +// mealInfo.Balance += order.Amount; +// (mealInfo.Balance < 0).ThrowIfTrue("余额不足,无法消费"); +// +// await DbContext.Updateable() +// .SetColumns(x => x.Balance == mealInfo.Balance) +// .SetColumns(x => x.Version == x.Version + 1) +// .SetColumns(x => x.ModifiedTime == DateTime.Now) +// .Where(x => x.UserCode == order.UserCode && x.Version == mealInfo.Version) //必须与查询时的version一致 +// .ExecuteCommandThrowVersionAsync(); +// } +// +// await DbContext.Updateable() +// .SetColumns(x => x.Number == menuDetail.Number) +// .SetColumns(x => x.Version == x.Version + 1) +// .SetColumns(x => x.ModifiedTime == now) +// .Where(x => x.StartDate <= menuDetail.Date && menuDetail.Date <= x.EndDate +// && x.ShiftType == request.ShiftType +// && x.BusinessPlaceCode == request.BusinessPlaceCode +// && x.DishesCode == request.DishesCode +// && x.Version == menuDetail.Version +// ) +// .ExecuteCommandThrowVersionAsync(); +// +// //写入记录 +// var balanceRecord = order.Adapt(); +// await DbContext.InsertAsync(balanceRecord); +// +// await DbContext.InsertAsync(new CanteenOrderDetailEntity +// { +// OrderNo = order.No, +// BookingDate = request.BookingDate, +// DishesCode = request.DishesCode, +// Price = menuDetail.Price, +// Unit = request.Unit, +// Number = request.Number, +// TenantId = order.TenantId, +// }); +// }, 10); +// +// +// return new BookingResponse +// { +// No = order.No, +// CreateTime = order.CreateTime, +// Source = order.Source, +// Amount = Math.Abs(order.Amount) +// }; +// } +// +// +// public async Task> GetBookingPageAsync(OrderBookingPageRequest request) +// { +// var myCodes = (await _businessPlacePermissionsApi.GetMyAsync()).Data.Select(x => (Guid?)x.Code).ToList(); +// +// var page = await DbContext.Queryable() +// .LeftJoin((x, ga) => x.UserNo == ga.No) +// .LeftJoin((x, ga, dept) => dept.No == ga.DepartmentNo) +// .Where(x => myCodes.Contains(x.BusinessPlaceCode)) +// .WhereIF(request.OrderStatus.HasValue, x => x.OrderStatus == request.OrderStatus) +// .WhereIF(request.Source.HasValue, x => x.Source == request.Source) +// .WhereIF(request.Search.IsPresent(), (x, ga) => x.UserCode == request.Search || ga.Name.Contains(request.Search)) +// .WhereIF(request.BookingPeriod?.Start.HasValue == true && request.BookingPeriod?.End.HasValue == true, x => +// SqlFunc.Subqueryable() +// .Where(p => p.OrderNo == x.No) +// .Where(p => p.BookingDate >= request.BookingPeriod.Start) +// .Where(p => p.BookingDate <= request.BookingPeriod.End) +// .Any() +// ) +// .WhereIF(request.ShiftType.HasValue, x => x.ShiftType == request.ShiftType) +// .Select((x, ga, dept) => new OrderBookingResponse +// { +// No = x.No, +// DeliveryAddress = x.DeliveryAddress, +// Remark = x.Remark, +// Amount = SqlFunc.Abs(x.Amount), +// CreateTime = x.CreateTime, +// ShiftType = x.ShiftType, +// Status = x.OrderStatus, +// UserCode = x.UserCode, +// UserName = ga.Name, +// Source = x.Source, +// DepartmentName = dept.Name, +// }) +// .ToPageListAsync(request); +// +// var nos = page.Data.Select(x => x.No).ToList(); +// var details = await DbContext.Queryable() +// .LeftJoin((x, dishes) => x.DishesCode == dishes.CustomCode) +// .Where(x => nos.Contains(x.OrderNo)) +// .Select((x, dishes) => new { x.OrderNo, x.BookingDate, x.Number, dishes.Name }).ToListAsync(); +// +// page.Data.ForEach(x => +// { +// x.BookingDate = details.First(item => item.OrderNo == x.No).BookingDate; +// x.TotalNumber = details.Where(item => item.OrderNo == x.No).Sum(item => item.Number); +// x.DishesInfos = details.Where(item => item.OrderNo == x.No).Select(item => new +// { +// item.Name +// }) +// .ToList(); +// }); +// +// return page; +// } +// +// public async Task UserCancelOrderAsync(OrderCancelRequest request) +// { +// var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); +// order.ThrowIfNull("订单不存在"); +// +// var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, order.TenantId, order.No, new TimeOnly(20, 30, 00)); +// var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); +// (DateTime.Now >= dealLine).ThrowIfTrue("不在订单取消允许范围内,不可操作"); +// +// await ManagerCancelOrderAsync(new OrderCancelRequest +// { +// No = request.No, +// Reason = request.Reason, +// }); +// } +// +// +// public async Task ManagerCancelOrderAsync(OrderCancelRequest request) +// { +// var now = DateTime.Now; +// var userId = CurrentUser.GetIntSubjectId(); +// +// var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); +// order.ThrowIfNull("订单不存在"); +// +// await DbContext.Updateable() +// .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Cancel) +// .SetColumns(x => x.Remark == request.Reason) +// .SetColumns(x => x.ModifiedTime == now) +// .SetColumns(x => x.ModifiedBy == userId) +// .Where(x => x.No == request.No) +// .ExecuteCommandAsync(); +// +// await DbContext.Updateable() +// .SetColumns(x => x.Status == BalanceRecordStatusEnumType.Obsolute) +// .SetColumns(x => x.Remark == request.Reason) +// .SetColumns(x => x.ModifiedTime == now) +// .SetColumns(x => x.ModifiedBy == userId) +// .Where(x => x.No == request.No) +// .ExecuteCommandAsync(); +// +// if (order.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) +// { +// await CommonHelper.RetryAsync(async () => +// { +// var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); +// var setBalance = mealInfo.Balance + Math.Abs(order.Amount); +// await DbContext.Updateable() +// .SetColumns(x => x.Version == x.Version + 1) +// .SetColumns(x => x.Balance == setBalance) +// .SetColumns(x => x.ModifiedTime == now) +// .SetColumns(x => x.ModifiedBy == userId) +// .Where(x => x.Id == mealInfo.Id && x.Version == mealInfo.Version) +// .ExecuteCommandThrowVersionAsync(); +// }, 5); +// } +// } +// +// +// /// +// /// 检查我的订餐是否存在,存在抛出异常 +// /// +// /// +// /// +// /// +// private async Task CheckExistsMyBookingsAsync(DateTime bookingDate, ShiftEnumType shiftType) +// { +// var subjectId = CurrentUser.GetIntSubjectId(); +// var gainfo = await DbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); +// var jinkoCode = CurrentUser.GetUserClaim("jinko-code") ?? string.Empty; +// if (jinkoCode.IsPresent()) +// { +// gainfo = await DbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); +// } +// +// var start = bookingDate.Date.AddDays(-1); +// var end = start.AddDays(1).AddSeconds(-1); +// +// var exists = await DbContext.Queryable() +// .Where(x => x.ApplicationType == CanteenApplicationTypeEnumType.Booking) +// .Where(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) +// .Where(x => x.ShiftType == shiftType) +// .Where(x => x.UserNo == gainfo.No) +// .Where(x => x.CreateTime >= start && x.CreateTime <= end) +// .AnyAsync(); +// +// exists.ThrowIfTrue("不可重复订餐"); +// +// return gainfo; +// } +// } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs index 213b4c83..55530c2f 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs @@ -25,21 +25,19 @@ public class MiddleService : IMiddleService if (!await _dbContext.Queryable().AnyAsync(x => x.No == entity.No)) { await _dbContext.InsertAsync(entity); - await _balanceService.ReCalcAsync(entity.UserCode); } //获取人员余额 - return await _dbContext.Queryable() - .LeftJoin((x, ga) => x.UserCode == ga.Code) - .LeftJoin((x, ga, department) => ga.DepartmentNo == department.No) - .Where(x => x.UserCode == entity.UserCode) - .Select((x, ga, department) => new OrderResultResponse + return await _dbContext.Queryable() + .LeftJoin((x, department) => x.DepartmentNo == department.No) + .Where(x => x.Code == entity.UserCode) + .Select((x, department) => new OrderResultResponse { - UserCode = ga.Code, - UserName = ga.Name, - DepartmentNo = ga.DepartmentNo, + UserCode = x.Code, + UserName = x.Name, + DepartmentNo = x.DepartmentNo, DepartmentName = department.Name, - CurrentBalance = x.Balance + //CurrentBalance = x.Balance todo 余额 }) .SingleAsync(); } diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index 4b9f43bc..791dac7f 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -1,4 +1,5 @@ using Dlhis.Canteen.Order.Dto; +using Dlhis.Canteen.Order.Repository; using Dlhis.Canteen.Setting.Services; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; @@ -11,6 +12,7 @@ using SqlSugar; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Order.Request; using Dlhis.Entity.Device; +using Dlhis.Entity.Device.Enums; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; @@ -20,23 +22,34 @@ using Kurisu.AspNetCore.UnifyResultAndValidation; namespace Dlhis.Canteen.Order.Services.Implements; -public class OrderService : BaseOrderService, IOrderService +public class OrderService : IOrderService { private readonly ICanteenSettingService _canteenSettingService; - private readonly IServiceProvider _serviceProvider; + 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 ILogger _logger; public OrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, ICurrentUser currentUser, - IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor, + IBalanceRepository balanceRepository, + IOrderRepository orderRepository, + RedisCache redisCache, ILogger logger) - : base(canteenSettingService, dbContext, currentUser, logger) { _canteenSettingService = canteenSettingService; - _serviceProvider = serviceProvider; + _dbContext = dbContext; + _currentUser = currentUser; _httpContextAccessor = httpContextAccessor; + _balanceRepository = balanceRepository; + _orderRepository = orderRepository; + _redisCache = redisCache; + _logger = logger; } /// @@ -56,28 +69,12 @@ public class OrderService : BaseOrderService, IOrderService /// public async Task AddAsync(OrderAddRequest request) { - try - { - if (_httpContextAccessor.HttpContext!.Request.Headers.TryGetValue("X-Requested-DeviceIp", out var ip)) - { - var device = await DbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).SingleAsync(); - device.ConnectionInfo = new - { - ip = ip.ToString() - }.ToJson(); + await SetDeviceIpInfoAsync(request.DeviceUniqueNo); - await DbContext.UpdateAsync(device); - } - - Logger.LogInformation("设备IP获取:{deviceNo} {ip}", request.DeviceUniqueNo, ip); - } - catch (Exception) - { - //ignore - } + await using var handler = await _redisCache.LockAsync($"consume:device:{request.UserUniqueNo}"); + handler.Acquired.ThrowIfFalse("请勿频繁消费"); var gainfo = await GetUserAsync(request); - return new OrderResultResponseWithLogs { Response = await ToDoAsync(request, gainfo) @@ -87,15 +84,15 @@ public class OrderService : BaseOrderService, IOrderService public async Task> GetOrdersAggregationAsync(PageDto page) { - var subjectId = CurrentUser.GetIntSubjectId(); - var gainfo = await DbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); - var jinkoCode = CurrentUser.GetUserClaim("jinko-code") ?? string.Empty; + var subjectId = _currentUser.GetIntSubjectId(); + var gainfo = await _dbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); + var jinkoCode = _currentUser.GetUserClaim("jinko-code") ?? string.Empty; if (jinkoCode.IsPresent()) { - gainfo = await DbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); + gainfo = await _dbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); } - var pageResult = await DbContext.Queryable().Where(x => x.UserNo == gainfo.No) + var pageResult = await _dbContext.Queryable().Where(x => x.UserNo == gainfo.No) .LeftJoin((x, bPlace) => x.BusinessPlaceCode == bPlace.Code) .Where(x => x.ApplicationType == CanteenApplicationTypeEnumType.Booking) .OrderBy(x => x.CreateTime, OrderByType.Desc) @@ -110,7 +107,7 @@ public class OrderService : BaseOrderService, IOrderService .ToPageListAsync(page); var nos = pageResult.Data.Select(x => x.No).ToList(); - var dtos = await DbContext.Queryable() + var dtos = await _dbContext.Queryable() .LeftJoin((x, d) => x.DishesCode == d.CustomCode) .Where(x => nos.Contains(x.OrderNo)) .Select((x, d) => new DishesDto @@ -152,10 +149,10 @@ public class OrderService : BaseOrderService, IOrderService else if (request.UserUniqueNo.StartsWith("02")) { request.Source = BalanceRecordSourceEnumType.EMealVoucher; - var eMealVoucherQrCode = await DbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); + 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(); + gainfo = await _dbContext.Queryable().Where(x => x.Code == code).SingleAsync(); } else { @@ -165,7 +162,7 @@ public class OrderService : BaseOrderService, IOrderService else { //根据档案号 - gainfo = await DbContext.Queryable() + 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) @@ -181,7 +178,7 @@ public class OrderService : BaseOrderService, IOrderService else { //兼容hpt尚未删除人员后产生的消费数据 - gainfo ??= await DbContext.Queryable().AS("GainfoLeave") + gainfo ??= await _dbContext.Queryable().AS("GainfoLeave") .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.IdentityUserId == userId) @@ -192,33 +189,6 @@ public class OrderService : BaseOrderService, IOrderService } - /// - /// 获取成本日期 - /// - /// - /// - /// - private async Task GetCostDateAsync(BusinessShiftMealRelationshipEntity shift, DateTime costDateTime) - { - var switchTime = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BusinessSwitchTime, shift.TenantId, string.Empty); - - var costDate = costDateTime.Date; - - if (!switchTime.HasValue) - { - return costDate; - } - - var beginOfDay = costDateTime.Date; - - var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); - if (costDateTime < setTime) - costDate = costDateTime.Date.AddDays(-1); - - return costDate; - } - - /// /// 验证参数 /// @@ -231,15 +201,10 @@ public class OrderService : BaseOrderService, IOrderService var endOfDay = beginOfDay.AddDays(1).AddSeconds(-1); //今天所有消费记录 - var todayAllBalanceRecords = await DbContext.Queryable() - .Where(x => x.UserNo == order.UserNo) - .Where(x => x.Type == BalanceRecordTypeEnumType.Out) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .Where(x => x.CreateTime >= beginOfDay && x.CreateTime <= endOfDay) - .ToListAsync(); + var todayAllBalanceRecords = await _balanceRepository.GetBalanceRecordsAsync(order.UserCode, order.TenantId, beginOfDay, endOfDay); //超市营业点 - var supermarketCodes = await DbContext.Queryable() + var supermarketCodes = await _dbContext.Queryable() .Where(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) .Select(x => x.Code) .ToListAsync(); @@ -286,40 +251,6 @@ public class OrderService : BaseOrderService, IOrderService } } - - /// - /// 生成充值记录 - /// - /// - /// - /// - /// - private async Task GenerateConsumeRecordAsync(long orderId) - { - var order = await DbContext.Queryable().Where(x => x.Id == orderId).SingleAsync(); - //校验 - await Verify(order); - - await CommonHelper.RetryAsync(async () => - { - var gainfoMeal = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).SingleAsync(); - //金额+- - gainfoMeal.Balance += order.Amount; - (gainfoMeal.Balance < 0).ThrowIfTrue("余额不足,无法消费"); - - await DbContext.Updateable() - .SetColumns(x => x.Balance == gainfoMeal.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == order.UserCode && x.Version == gainfoMeal.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); - }, 5); - } - /// /// 离线消费处理 /// @@ -327,36 +258,35 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task HandleOfflineAsync(ConsumeRequest request, GainfoEntity gainfo) { - var registry = await DbContext.Queryable() - .Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo) - .FirstAsync(); - - var businessPlaceInfo = await DbContext.Queryable() - .LeftJoin((x, sr) => x.Code == sr.PlaceCode) - .Where(x => x.Code == registry.BusinessPlaceCode) - .Select((x, sr) => new + var bindingInfo = await _dbContext.Queryable() + .LeftJoin((x, b) => x.BusinessPlaceCode == b.Code) + .LeftJoin((x, b, bs) => bs.PlaceCode == b.Code) + .Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo && x.BusinessPlaceCode.HasValue) + .Select((x, b, bs) => new { - x.Code, - sr.ShiftCode, - x.TenantId + b.Code, bs.ShiftCode, Factory = x.TenantId }) - .FirstAsync(); + .SingleAsync(); + //获取当前消费餐次 - var shifts = await DbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); + var shifts = await _dbContext.Queryable() + .Where(x => x.ShiftCode == bindingInfo.ShiftCode).ToListAsync(); - var shift = shifts.FirstOrDefault(x => new TimePeriod() + var shift = shifts.FirstOrDefault(x => new TimePeriod { Start = x.Start, End = x.End }.IsPresent(request.CostDateTime)); - var costDate = await GetCostDateAsync(shift, request.CostDateTime); + var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, request.CostDateTime); var order = new CanteenOrderEntity { RequestJson = request.ToJson(), + No = SnowFlakeHelper.Instance.NextId().ToString(), ThirdNo = request.ThirdNo, + IsOffline = request.IsOffline, Type = BalanceRecordTypeEnumType.Out, ApplicationType = CanteenApplicationTypeEnumType.Device, Name = gainfo.Name, @@ -364,7 +294,7 @@ public class OrderService : BaseOrderService, IOrderService UserNo = gainfo.No, UserCode = gainfo.Code, Amount = request.Amount, - BusinessPlaceCode = businessPlaceInfo?.Code, + BusinessPlaceCode = bindingInfo.Code, BusinessShiftCode = shift?.ShiftCode, ShiftType = shift?.ShiftType, CostDate = costDate.Date, @@ -372,9 +302,7 @@ public class OrderService : BaseOrderService, IOrderService DepartmentNo = gainfo.DepartmentNo, DeviceUniqueNo = request.DeviceUniqueNo, - No = SnowFlakeHelper.Instance.NextId().ToString(), - IsOffline = request.IsOffline, - IsCostCrossTenant = businessPlaceInfo!.TenantId != gainfo.Factory, + IsCostCrossTenant = bindingInfo.Factory != gainfo.Factory, OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, @@ -386,48 +314,12 @@ public class OrderService : BaseOrderService, IOrderService CostDateTime = request.CostDateTime, //标识当前消费的基地 - TenantId = businessPlaceInfo!.TenantId, + TenantId = bindingInfo.Factory, }; - - await DbContext.InsertReturnIdentityAsync(order); - - await CommonHelper.RetryAsync(async () => - { - var redisCache = _serviceProvider.GetService(); - await using var handler = await redisCache.LockAsync($"gainfo:meal:{order.UserNo}", retryInterval: TimeSpan.FromSeconds(1)); - if (handler.Acquired) - { - var gainfoMeal = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); - if (gainfoMeal == null) - { - gainfoMeal = new GainfoMealEntity - { - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0, - UserCode = order.UserCode, - }; - - await DbContext.InsertAsync(gainfoMeal); - } - - //金额+- - gainfoMeal.Balance += order.Amount; - - await DbContext.Updateable() - .SetColumns(x => x.Balance == gainfoMeal.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == order.UserCode && x.Version == gainfoMeal.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); - } - }, 5); + await _dbContext.InsertReturnIdentityAsync(order); + var balanceRecord = order.Adapt(); + await _dbContext.InsertAsync(balanceRecord); } /// @@ -439,79 +331,83 @@ public class OrderService : BaseOrderService, IOrderService private async Task ConsumeAsync(ConsumeRequest request, GainfoEntity gainfo) { var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); + var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); - //(request.Source == BalanceRecordSourceEnumType.QrCode && checkData.BusinessPlaceTenantId != gainfo.Factory).ThrowIfTrue("禁止跨厂区消费"); - - var order = new CanteenOrderEntity + if (request.Image.IsPresent()) { - RequestJson = request.ToJson(), - ThirdNo = request.ThirdNo, - Type = BalanceRecordTypeEnumType.Out, - ApplicationType = CanteenApplicationTypeEnumType.Device, - Name = gainfo.Name, - Source = request.Source, - UserNo = gainfo.No, - UserCode = gainfo.Code, - Amount = request.Amount, - BusinessPlaceCode = checkData.BusinessPlaceCode, - BusinessShiftCode = checkData.BusinessShiftCode, - ShiftType = checkData.Shift.ShiftType, - CostDate = checkData.CostDate, - CostId = gainfo.CostId, - DepartmentNo = gainfo.DepartmentNo, - DeviceUniqueNo = request.DeviceUniqueNo, + await _dbContext.InsertAsync(new ImageRecordEntity + { + No = order.No, + TableName = "B", + Image = request.Image + }); + } - No = SnowFlakeHelper.Instance.NextId().ToString(), - IsOffline = request.IsOffline, - IsCostCrossTenant = gainfo.Factory != checkData.BusinessPlaceTenantId, + await TryConsumeAsync(order.Id, async () => + { + //校验 + await Verify(order); - OriginTenantId = gainfo.Factory, - Werks = gainfo.Werks, - Status = BalanceRecordStatusEnumType.Valid, + await _balanceRepository.CheckConsumableAsync(order.UserCode, order.TenantId, order.Amount); - OrderStatus = CanteenOrderStatusEnumType.Wait, + //写入记录 + var balanceRecord = order.Adapt(); + await _dbContext.InsertAsync(balanceRecord); + }); + } - CostDateTime = request.CostDateTime, + 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(); - //标识当前消费的基地 - TenantId = checkData.BusinessPlaceTenantId - }; + if (originData.IsEmpty()) + { + msg = "无法识别"; + return new DefaultApiResult + { + Code = ApiStateCode.Error, + Data = null, + Msg = msg + }; + } - var orderId = await DbContext.InsertReturnIdentityAsync(order); + var originDataItems = originData.Split(":"); + var no = originDataItems[0]; + var idNo = originDataItems[3]; try { - await GenerateConsumeRecordAsync(orderId); - - if (request.Image.IsPresent()) - { - await DbContext.InsertAsync(new ImageRecordEntity - { - No = order.No, - TableName = "B", - Image = request.Image - }); - } - - await DbContext.Updateable() - .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.Id == orderId) - .ExecuteCommandAsync(); + await EMealVoucherConsumeAsync(request.Adapt(), gainfo, originData, no, idNo); } 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; + msg = ex.Message; } - } + //获取餐券余额 + var data = await _dbContext.Queryable() + .LeftJoin((x, d) => x.No == d.No) + .LeftJoin((x, d, u) => u.IdNo == d.IdNo) + .Where((x, d) => x.No == no && d.IdNo == idNo) + .Select((x, d, u) => new OrderResultResponse + { + UserCode = u.Code, + UserName = u.Name, + CurrentBalance = d.Balance + }) + .SingleAsync(); + + return new DefaultApiResult + { + Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = msg + }; + } /// /// 分支处理在线/离线消费 @@ -524,135 +420,68 @@ public class OrderService : BaseOrderService, IOrderService var message = string.Empty; if (request.Source == BalanceRecordSourceEnumType.EMealVoucher) + return await EMealVoucherConsumeAsync(request, gainfo); + + if (request.IsOffline) { - var eMealVoucherQrCode = await DbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); - if (eMealVoucherQrCode == null) - { - message = "无法识别"; - return new DefaultApiResult - { - Code = ApiStateCode.Error, - Data = null, - Msg = message - }; - } - - var originData = eMealVoucherQrCode.OriginData.Split(':'); - var no = originData[0]; - var idNo = originData[3]; - try { - await EMealVoucherConsumeAsync(request.Adapt(), gainfo, eMealVoucherQrCode.OriginData, no, idNo); + if (request.ThirdNo.IsPresent() && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) + { + await using var handler = await _redisCache.LockAsync($"consume:device-offline:{request.ThirdNo}"); + if (handler.Acquired) + { + await HandleOfflineAsync(request.Adapt(), gainfo); + } + } + else + { + await HandleOfflineAsync(request.Adapt(), gainfo); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); + } + } + else + { + try + { + await ConsumeAsync(request.Adapt(), gainfo); } catch (Exception ex) { message = 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 - { - CurrentBalance = d.Balance - }) - .SingleAsync(); - - return new DefaultApiResult - { - Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, - Data = data, - Msg = message - }; } - else + + var currentFactory = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).Select(x => x.TenantId).SingleAsync(); + var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory); + + //获取人员余额 + var data = await _dbContext.Queryable() + .LeftJoin((x, department) => x.DepartmentNo == department.No) + .Where(x => x.Code == gainfo.Code) + .Select((x, department) => new OrderResultResponse + { + UserCode = x.Code, + UserName = x.Name, + DepartmentNo = x.DepartmentNo, + DepartmentName = department.Name, + CurrentBalance = balance + }) + .SingleAsync(); + + return new DefaultApiResult { - if (request.IsOffline) - { - if (request.ThirdNo.IsPresent()) - { - var redisCache = _serviceProvider.GetService(); - - await using var handler = await redisCache.LockAsync($"consume:device:{request.ThirdNo}"); - if (handler.Acquired && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) - { - 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); - } - catch (Exception ex) - { - Logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, 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; - } - } - - //获取人员余额 - var data = await DbContext.Queryable() - .LeftJoin((x, ga) => x.UserCode == ga.Code) - .LeftJoin((x, ga, department) => ga.DepartmentNo == department.No) - .Where(x => x.UserCode == gainfo.Code) - .Select((x, ga, department) => new OrderResultResponse - { - UserCode = ga.Code, - UserName = ga.Name, - DepartmentNo = ga.DepartmentNo, - DepartmentName = department.Name, - CurrentBalance = x.Balance - }) - .SingleAsync(); - - return new DefaultApiResult - { - Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, - Data = data, - Msg = 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; - } + Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = message + }; } + /// /// 根据二维码获取消费用户 /// @@ -660,74 +489,56 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task GetUserByQrCodeAsync(string qrCode) { - var gainfoQrCode = await DbContext.Queryable().Where(x => x.QrCode == qrCode).SingleAsync(); + 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(); + 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 nowDate = DateTime.Now.Date; - var current = await DbContext.Queryable() - .Where(x => x.No == no && x.IdNo == idNo).SingleAsync(); + + var current = await _dbContext.Queryable() + .Where(x => x.No == no && x.IdNo == idNo) + .Select(x => new { x.Start, x.End }) + .SingleAsync(); + (nowDate >= current.Start && nowDate <= current.End).ThrowIfFalse("餐券已过期"); - var eMealVoucher = await DbContext.Queryable().Where(x => x.No == no).SingleAsync(); + 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) + var isSupermarket = await _dbContext.Queryable().Where(x => x.Code == checkData.BusinessPlaceCode) .Select(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) .SingleAsync(); + isSupermarket.ThrowIfTrue("禁止在超市消费"); - eMealVoucher.Factories.Contains(checkData.BusinessPlaceTenantId).ThrowIfFalse("禁止在当前厂区消费"); + eMealVoucher.Factories.Contains(checkData.Factory).ThrowIfFalse("禁止在当前厂区消费"); - var order = new CanteenOrderEntity - { - RequestJson = request.ToJson(), - ThirdNo = request.ThirdNo, - Type = BalanceRecordTypeEnumType.Out, - ApplicationType = CanteenApplicationTypeEnumType.Device, - Name = gainfo.Name, - Source = request.Source, - UserNo = 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, + gainfo.No = originData; + var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); - 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); - - try + await TryConsumeAsync(order.Id, async () => { await CommonHelper.RetryAsync(async () => { - var detail = await DbContext.Queryable().Where(x => x.No == no && x.IdNo == idNo).SingleAsync(); + 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() + await _dbContext.Updateable() .SetColumns(x => x.Balance == detail.Balance) .SetColumns(x => x.Version == x.Version + 1) .SetColumns(x => x.ModifiedTime == DateTime.Now) @@ -736,26 +547,9 @@ public class OrderService : BaseOrderService, IOrderService //写入记录 var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); + await _dbContext.InsertAsync(balanceRecord); }, 5); - - 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.Result == ex.Message) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.Id == orderId) - .ExecuteCommandAsync(); - - throw; - } + }); } /// @@ -766,28 +560,28 @@ public class OrderService : BaseOrderService, IOrderService /// 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(); + var device = await _dbContext.Queryable().Where(x => x.UniqueNo == deviceUniqueNo).SingleAsync(); device.ThrowIfNull("设备尚未登记"); device.Enable.ThrowIfFalse("设备已被停用"); + (device.Type != DeviceTypeEnumType.Meal).ThrowIfTrue("设备类型有误"); - var businessPlaceInfo = await DbContext.Queryable() - .LeftJoin((x, sr) => x.Code == sr.PlaceCode) - .Where(x => x.Code == registry.BusinessPlaceCode) - .Select((x, sr) => new + 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 { - x.Code, - sr.ShiftCode, - x.TenantId + b.Code, bs.ShiftCode, Factory = x.TenantId }) .SingleAsync(); - businessPlaceInfo.ThrowIfNull("设备尚未登记营业点"); + bindingInfo.ThrowIfNull("设备尚未登记营业点"); //获取当前消费餐次 - var shifts = await DbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); + 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 @@ -795,17 +589,88 @@ public class OrderService : BaseOrderService, IOrderService Start = x.Start, End = x.End }.IsPresent(costDateTime)); - shift.ThrowIfNull("非就餐时间,无法消费"); + shift.ThrowIfNull("非就餐时间"); - var costDate = await GetCostDateAsync(shift, costDateTime); + var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, costDateTime); return new OrderPreCheckDto { + Factory = bindingInfo.Factory, CostDate = costDate, - Shift = shift, - BusinessPlaceTenantId = businessPlaceInfo.TenantId, - BusinessPlaceCode = businessPlaceInfo.Code, - BusinessShiftCode = businessPlaceInfo.ShiftCode + 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重复提交 + /// + /// + /// + private async Task ToCheckAbleOfflineConsumeAsync(string thirdNo) + { + var order = await _dbContext.Queryable() + .Where(x => x.ThirdNo == thirdNo) + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .SingleAsync(); + + if (order == null) + return true; + + //存在在线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/Setting/Services/ICanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs index 9872d134..2376b87c 100644 --- a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs @@ -45,4 +45,6 @@ public interface ICanteenSettingService : IScopeDependency Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default); + + Task GetCostDateAsync(string factory, DateTime costDateTime); } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs index 41f3791b..c5654f0d 100644 --- a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs @@ -139,6 +139,24 @@ public class CanteenSettingService : ICanteenSettingService return defaultValue; } + /// + public async Task GetCostDateAsync(string factory, DateTime costDateTime) + { + var switchTime = await GetSettingAsync(CanteenSettingItemEnumType.BusinessSwitchTime, factory, string.Empty); + + var costDate = costDateTime.Date; + + if (!switchTime.HasValue) return costDate; + + var beginOfDay = costDateTime.Date; + + var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); + if (costDateTime < setTime) + costDate = costDateTime.Date.AddDays(-1); + + return costDate; + } + //*********************************************** help methods ***************************************************// private async Task CheckDuplicateSettingAsync(CanteenSettingSaveRequest request) diff --git a/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs b/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs index d2cc2328..0928de41 100644 --- a/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs +++ b/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs @@ -10,37 +10,37 @@ namespace Dlhis.Entity.Basic.Entity; public class BusinessShiftMealRelationshipEntity : SugarBaseEntity, ITenantId { /// - /// 餐别Code + /// 餐别Code /// [SugarColumn(ColumnDescription = "餐别Code", UniqueGroupNameList = new[] { "idx_CanteenBusinessShiftRelationship_shiftcode_shifttype" })] public Guid ShiftCode { get; set; } /// - /// 餐别手工号 + /// 餐别手工号 /// [SugarColumn(ColumnDescription = "餐别编码")] public string ShiftCustomCode { get; set; } /// - /// 餐次 + /// 餐次 /// [SugarColumn(ColumnDescription = "餐次", ColumnDataType = "varchar(50)", SqlParameterDbType = typeof(EnumToStringConvert), UniqueGroupNameList = new[] { "idx_CanteenBusinessShiftRelationship_shiftcode_shifttype" })] public ShiftEnumType ShiftType { get; set; } /// - /// 经营开始时间 + /// 经营开始时间 /// [SugarColumn(ColumnDescription = "经营开始时间")] public TimeOnly Start { get; set; } /// - /// 经营结束时间 + /// 经营结束时间 /// [SugarColumn(ColumnDescription = "经营结束时间")] public TimeOnly End { get; set; } /// - /// 序号 + /// 序号 /// [SugarColumn(ColumnDescription = "序号")] public int Sequence { get; set; } diff --git a/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs b/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs index 1d311bc6..5e779cd9 100644 --- a/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs +++ b/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs @@ -1,43 +1,43 @@ -using Kurisu.AspNetCore.DataAccess.Entity; -using Kurisu.AspNetCore.DataAccess.SqlSugar; -using SqlSugar; - -namespace Dlhis.Entity.Basic.Entity -{ - /// - /// 就餐人员 - /// - [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] - public class GainfoMealEntity : SugarBaseEntity, IVersion - { - /// - /// 工号 - /// - [SugarColumn(IsNullable = false, ColumnDescription = "工号", UniqueGroupNameList = new[] { "idx_CanteenUser_UserCode" })] - public string UserCode { get; set; } - - /// - /// 余额 - /// - [SugarColumn(ColumnDescription = "余额")] - public decimal Balance { get; set; } - - /// - /// 余额(充值) - /// - [SugarColumn(ColumnDescription = "余额(充值)")] - public decimal BalanceOfRecharge { get; set; } - - /// - /// 余额(餐补) - /// - [SugarColumn(ColumnDescription = "余额(餐补)")] - public decimal BalanceOfSubsidies { get; set; } - - /// - /// 版本号 - /// - [SugarColumn(ColumnDescription = "版本号")] - public int Version { get; set; } - } -} \ No newline at end of file +// using Kurisu.AspNetCore.DataAccess.Entity; +// using Kurisu.AspNetCore.DataAccess.SqlSugar; +// using SqlSugar; +// +// namespace Dlhis.Entity.Basic.Entity +// { +// /// +// /// 就餐人员 +// /// +// [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] +// public class GainfoMealEntity : SugarBaseEntity, IVersion +// { +// /// +// /// 工号 +// /// +// [SugarColumn(IsNullable = false, ColumnDescription = "工号", UniqueGroupNameList = new[] { "idx_CanteenUser_UserCode" })] +// public string UserCode { get; set; } +// +// /// +// /// 余额 +// /// +// [SugarColumn(ColumnDescription = "余额")] +// public decimal Balance { get; set; } +// +// /// +// /// 余额(充值) +// /// +// [SugarColumn(ColumnDescription = "余额(充值)")] +// public decimal BalanceOfRecharge { get; set; } +// +// /// +// /// 余额(餐补) +// /// +// [SugarColumn(ColumnDescription = "余额(餐补)")] +// public decimal BalanceOfSubsidies { get; set; } +// +// /// +// /// 版本号 +// /// +// [SugarColumn(ColumnDescription = "版本号")] +// public int Version { get; set; } +// } +// } \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs new file mode 100644 index 00000000..8f698195 --- /dev/null +++ b/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs @@ -0,0 +1,29 @@ +using Kurisu.AspNetCore.DataAccess.SqlSugar; +using SqlSugar; + +namespace Dlhis.Entity.Canteen.Entities; + +/// +/// 钱包 +/// +[SugarTable(TableName = "Wallets", TableDescription = "钱包")] +public class WalletEntity : SugarBaseEntity +{ + /// + /// 钱包编码 + /// + [SugarColumn(IsNullable = false)] + public string Code { get; set; } + + /// + /// 钱包名称 + /// + [SugarColumn(IsNullable = false)] + public string Name { get; set; } + + /// + /// 厂区 + /// + [SugarColumn(IsNullable = false, UniqueGroupNameList = new[] { "idx_wallets_facotry" })] + public string Factory { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs b/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs index 54cc684e..d3ab0bd7 100644 --- a/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs +++ b/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs @@ -8,6 +8,9 @@ namespace Dlhis.Entity.Canteen.Enums; [Description("餐饮应用类型")] public enum CanteenApplicationTypeEnumType { + /// + /// 设备消费 + /// [Description("设备消费")] Device = 100, diff --git a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs index e21572d2..bb9809ff 100644 --- a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs +++ b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs @@ -1,4 +1,7 @@ using Dlhis.Entity.Basic.Entity; +using Kurisu.AspNetCore.DataAccess.Entity; +using Kurisu.AspNetCore.DataAccess.SqlSugar; +using SqlSugar; namespace Dlhis.Middle.Entities { @@ -6,4 +9,42 @@ namespace Dlhis.Middle.Entities { public new int Id { get; set; } } + + + /// + /// 就餐人员 + /// + [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] + public class GainfoMealEntity : SugarBaseEntity, IVersion + { + /// + /// 工号 + /// + [SugarColumn(IsNullable = false, ColumnDescription = "工号", UniqueGroupNameList = new[] { "idx_CanteenUser_UserCode" })] + public string UserCode { get; set; } + + /// + /// 余额 + /// + [SugarColumn(ColumnDescription = "余额")] + public decimal Balance { get; set; } + + /// + /// 余额(充值) + /// + [SugarColumn(ColumnDescription = "余额(充值)")] + public decimal BalanceOfRecharge { get; set; } + + /// + /// 余额(餐补) + /// + [SugarColumn(ColumnDescription = "余额(餐补)")] + public decimal BalanceOfSubsidies { get; set; } + + /// + /// 版本号 + /// + [SugarColumn(ColumnDescription = "版本号")] + public int Version { get; set; } + } } \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs new file mode 100644 index 00000000..6b91adbc --- /dev/null +++ b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs @@ -0,0 +1,36 @@ +using Dlhis.ScheduledTask.Common.Request; +using Dlhis.ScheduledTask.Common.Response; + +namespace Dlhis.ScheduledTask.Common.Services; + +public interface IJinkoJipService : ISingletonDependency +{ + Task UploadAttendancesAsync(List requests); + + Task SyncMealSupplementRecordsAsync(List requests); + + + /// + /// 获取出差信息 + /// + /// + /// + /// + /// + Task GetTravelInfosByFactoryAsync(DateTime begin, DateTime end, string fatory); + + /// + /// 获取出差信息 + /// + /// + /// + /// + /// + Task GetTravelInfosByUserCodeAsync(DateTime begin, DateTime end, string userCode); + + + Task SyncFaceToSapAsync(string userCode, string base64); + + + string IssueToken(); +} \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs index 6f177b4e..51d56928 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs @@ -12,9 +12,9 @@ public class HangfireService : IHangfireService { private readonly RedisCache _redisCache; private readonly IDbContext _dbContext; - private readonly IJinkoJIPService _jinkoJipService; + private readonly IJinkoJipService _jinkoJipService; - public HangfireService(RedisCache redisCache, IDbContext dbContext, IJinkoJIPService jinkoJipService) + public HangfireService(RedisCache redisCache, IDbContext dbContext, IJinkoJipService jinkoJipService) { _redisCache = redisCache; _dbContext = dbContext; diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs new file mode 100644 index 00000000..88d207b2 --- /dev/null +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs @@ -0,0 +1,213 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using Dlhis.ScheduledTask.Common.Request; +using Dlhis.ScheduledTask.Common.Response; +using Kurisu.AspNetCore.Utils.Extensions; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json.Linq; + +namespace Dlhis.ScheduledTask.Common.Services.Implements; + +public class JinkoJipService : IJinkoJipService +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public JinkoJipService(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + _configuration = configuration; + } + + private HttpClient GetHttpClient() + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_configuration.GetSection("JinkoOptions:Jip:BaseUrl").Value); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", IssueToken()); + + return httpClient; + } + + public async Task UploadAttendancesAsync(List requests) + { + var requestUrl = "/JIP/HCM/HCM_SWIPE_DATA_SAVE"; + + var body = new + { + T_DATA = requests, + }; + + var json = body.ToJson(); + + //_logger.LogInformation("上传考勤记录请求:{json}", json); + + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(requestUrl, content); + var responseJson = await response.Content.ReadAsStringAsync(); + + _logger.LogInformation("上传考勤记录响应:{json}", responseJson); + + try + { + return responseJson.ToObject(); + } + catch (Exception) + { + return new UploadAttendanceResponse + { + OLX = "99", + OMS = "接口响应数据异常,无法反序列化" + }; + } + } + + + public async Task SyncMealSupplementRecordsAsync(List requests) + { + var requestUrl = "/JIP/DHR/DHR_DL_CALLBACK"; + + var json = requests.ToJson(Kurisu.AspNetCore.Utils.Extensions.JsonExtensions.DefaultSetting); + + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(requestUrl, content); + var responseJson = await response.Content.ReadAsStringAsync(); + + _logger.LogInformation("餐补状态同步响应:{json}", responseJson); + + var jobj = JObject.Parse(responseJson); + if (jobj.TryGetValue("exp", out var exp)) + { + true.ThrowIfTrue(exp.Value()); + } + + if (jobj.TryGetValue("message", out var message) && !jobj.ContainsKey("code")) + { + true.ThrowIfTrue(message.Value()); + } + + (jobj.GetValue("code").Value() != 200).ThrowIfTrue(jobj.GetValue("message").Value()); + + return responseJson; + } + + + public async Task GetTravelInfosByFactoryAsync(DateTime begin, DateTime end, string factory) + { + const string apiUrl = "/JIP/HCM/HCM_TRAVELINFO"; + + var beginDay = begin.ToString("yyyyMMdd"); + var endDay = end.ToString("yyyyMMdd"); + + var obj = new + { + I_PAGE = 1, + I_LINE = 500, + IV_BEGDA = beginDay, + IV_ENDDA = endDay, + IT_CCMDD = new List + { + new + { + IV_CCMDD = factory + } + } + }; + + var body = obj.ToJson(); + + var content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(apiUrl, content); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + return jsonResponse.ToObject(); + } + + public async Task GetTravelInfosByUserCodeAsync(DateTime begin, DateTime end, string userCode) + { + const string apiUrl = "/JIP/HCM/HCM_TRAVELINFO"; + + var beginDay = begin.ToString("yyyyMMdd"); + var endDay = end.ToString("yyyyMMdd"); + + //IV_GONGH IV_ORGID + //IT_2002GH + var obj = new + { + IV_BEGDA = beginDay, + IV_ENDDA = endDay, + IT_2002GH = new List + { + new + { + IV_GONGH = userCode + } + } + }; + + var body = obj.ToJson(); + + var content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(apiUrl, content); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + return jsonResponse.ToObject(); + } + + public async Task SyncFaceToSapAsync(string userCode, string base64) + { + const string apiUrl = "/JIP/HCM/HCM_I_PHOTO"; + var obj = new + { + I_WORKCODE = userCode, + I_PHOTO_STR = base64 + }; + + var body = obj.ToJson(); + + var content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(apiUrl, content); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + return jsonResponse.ToObject(); + } + + + public string IssueToken() + { + var jwtKey = _configuration.GetSection("JinkoOptions:Jip:Key").Value; + var jwtSecret = _configuration.GetSection("JinkoOptions:Jip:Secret").Value; + const int min = 5; + + var now = DateTime.Now.AddMinutes(-min); + var expires = DateTime.Now.AddMinutes(min); + + var claims = new List + { + new(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(now).ToUnixTimeSeconds()}"), + new(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(expires).ToUnixTimeSeconds()}"), + new(JwtRegisteredClaimNames.Iss, jwtKey), + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)); + var descriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims), + Issuer = jwtKey, + IssuedAt = now, + NotBefore = now, + Expires = expires, + SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256) + }; + + var jwtHandler = new JwtSecurityTokenHandler(); + var securityToken = jwtHandler.CreateToken(descriptor); + return jwtHandler.WriteToken(securityToken); + } +} \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs index e4282e7d..e3ec2c19 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs @@ -15,12 +15,12 @@ namespace Dlhis.ScheduledTask.Common.Services.Implements; public class SapBusinessTripService : ISapBusinessTripService { - private readonly IJinkoJIPService _jinkoJipService; + private readonly IJinkoJipService _jinkoJipService; private readonly IDbContext _dbContext; private readonly IDevicePermissionApi _devicePermissionApi; private readonly ILogger _logger; - public SapBusinessTripService(IJinkoJIPService jinkoJipService, IDbContext dbContext, IDevicePermissionApi devicePermissionApi, ILogger logger) + public SapBusinessTripService(IJinkoJipService jinkoJipService, IDbContext dbContext, IDevicePermissionApi devicePermissionApi, ILogger logger) { _jinkoJipService = jinkoJipService; _dbContext = dbContext; diff --git a/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs b/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs index 8a67cd70..d81ca771 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs @@ -6,8 +6,6 @@ using Dlhis.ScheduledTask.Attributes; using Dlhis.ScheduledTask.Common.Request; using Dlhis.ScheduledTask.Common.Services; using Hangfire; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; -using Kurisu.AspNetCore.Utils; namespace Dlhis.ScheduledTask.Jobs; @@ -17,10 +15,10 @@ namespace Dlhis.ScheduledTask.Jobs; [RecurringJob(nameof(MealSupplementHandlerJob), "0 0/30 * * * ?", Name = "餐补核算")] public class MealSupplementHandlerJob : BaseJob { - private readonly IJinkoJIPService _jinkoJIPService; + private readonly IJinkoJipService _jinkoJIPService; public MealSupplementHandlerJob(IServiceProvider serviceProvider, - IJinkoJIPService jinkoJIPService) : base(serviceProvider) + IJinkoJipService jinkoJIPService) : base(serviceProvider) { _jinkoJIPService = jinkoJIPService; } @@ -85,23 +83,6 @@ public class MealSupplementHandlerJob : BaseJob } } - - //创建余额数据 - var mealUser = await DbContext.Queryable().Where(x => x.UserCode == gainfo.Code).SingleAsync(); - if (mealUser == null) - { - mealUser = new GainfoMealEntity - { - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - UserCode = gainfo.Code, - Version = 0, - }; - - await DbContext.InsertAsync(mealUser); - } - await SetMealSupplementAsync(gainfo, item); } } @@ -111,19 +92,6 @@ public class MealSupplementHandlerJob : BaseJob await DbContext.Client.Ado.BeginTranAsync(); try { - await CommonHelper.RetryAsync(async () => - { - var mealUser = await DbContext.Queryable().Where(x => x.UserCode == mealSupplement.Code).FirstAsync(); - var b = mealUser.Balance += mealSupplement.Amount; - - await DbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == b) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == mealUser.UserCode && x.Version == mealUser.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); - await DbContext.InsertAsync(new BalanceRecordEntity { Amount = mealSupplement.Amount, diff --git a/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs b/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs index 0475ec33..d71c18be 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs @@ -7,10 +7,10 @@ namespace Dlhis.ScheduledTask.Jobs; [RecurringJob(nameof(SyncFaceJob), "0 0/15 * * * ?", Name = "face数据同步sap")] public class SyncFaceJob : BaseJob { - private readonly IJinkoJIPService _jinkoJipService; + private readonly IJinkoJipService _jinkoJipService; /// - public SyncFaceJob(IServiceProvider serviceProvider, IJinkoJIPService jinkoJipService) : base(serviceProvider) + public SyncFaceJob(IServiceProvider serviceProvider, IJinkoJipService jinkoJipService) : base(serviceProvider) { _jinkoJipService = jinkoJipService; } diff --git a/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs b/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs index 173ada85..1d7412b9 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs @@ -14,9 +14,9 @@ namespace Dlhis.ScheduledTask.Jobs; [RecurringJob(nameof(SyncOriginCardInfoJob), "0 0/1 * * * ?", Name = "上传考勤记录")] public class SyncOriginCardInfoJob : BaseJob { - private readonly IJinkoJIPService _jinkoJIPService; + private readonly IJinkoJipService _jinkoJIPService; - public SyncOriginCardInfoJob(IJinkoJIPService jinkoJIPService, IServiceProvider serviceProvider) : base(serviceProvider) + public SyncOriginCardInfoJob(IJinkoJipService jinkoJIPService, IServiceProvider serviceProvider) : base(serviceProvider) { _jinkoJIPService = jinkoJIPService; } diff --git a/Src/Dlhis.ScheduledTask/Program.cs b/Src/Dlhis.ScheduledTask/Program.cs index 999976ce..5289eb45 100644 --- a/Src/Dlhis.ScheduledTask/Program.cs +++ b/Src/Dlhis.ScheduledTask/Program.cs @@ -26,6 +26,6 @@ public class Startup : DefaultStartup { base.Configure(app, env); - var token = app.ApplicationServices.GetService().IssueToken(); + var token = app.ApplicationServices.GetService().IssueToken(); } } \ No newline at end of file From 71b563546c27c69bc7a2b3e803cd3f9bd1283645 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Mon, 28 Oct 2024 10:27:16 +0800 Subject: [PATCH 03/23] rename . --- Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs | 2 +- .../Common/Services/Implements/JinkoJIPService.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs index 5533267d..6b91adbc 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs @@ -3,7 +3,7 @@ using Dlhis.ScheduledTask.Common.Response; namespace Dlhis.ScheduledTask.Common.Services; -public interface IJinkoJIPService : ISingletonDependency +public interface IJinkoJipService : ISingletonDependency { Task UploadAttendancesAsync(List requests); diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs index 9ed40b41..88d207b2 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs @@ -10,13 +10,13 @@ using Newtonsoft.Json.Linq; namespace Dlhis.ScheduledTask.Common.Services.Implements; -public class JinkoJIPService : IJinkoJIPService +public class JinkoJipService : IJinkoJipService { private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfiguration _configuration; - public JinkoJIPService(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) + public JinkoJipService(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) { _httpClientFactory = httpClientFactory; _logger = logger; From 727e49f72e128aa19155ac31c51d8dfb0a58a23e Mon Sep 17 00:00:00 2001 From: liguoyang Date: Mon, 28 Oct 2024 16:28:31 +0800 Subject: [PATCH 04/23] feat middleHealthz --- .../BusinessPlaceShiftTinyResponse.cs | 2 +- .../SysData/Controller/MiddlesController.cs | 29 +++++++++++++++++++ .../SysData/Request/HealthCheckRequest.cs | 9 ++++++ .../Request/MiddleHealthCheckRequest.cs | 6 ++++ .../SysData/Services/IHealthCheckService.cs | 6 ++++ .../Services/Implements/HealthCheckService.cs | 10 +++++++ .../Services/Implements/SysDataService.cs | 25 ++++++++++++++-- 7 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs diff --git a/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs b/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs index 359754c7..40217d10 100644 --- a/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs +++ b/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs @@ -3,7 +3,7 @@ public class BusinessPlaceShiftTinyResponse { /// - /// 无用字段,暂时不删除 //todo + /// 无用字段,暂时不删除 /// public string CustomCode { get; set; } diff --git a/Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs b/Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs new file mode 100644 index 00000000..c3485490 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs @@ -0,0 +1,29 @@ +using Dlhis.Basic.Common.SysData.Request; +using Dlhis.Basic.Common.SysData.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Dlhis.Basic.Common.SysData.Controller; + +[Authorize] +[Route("api/[controller]")] +[ApiController] +public class MiddlesController : ControllerBase +{ + private readonly IHealthCheckService _healthCheckService; + + public MiddlesController(IHealthCheckService healthCheckService) + { + _healthCheckService = healthCheckService; + } + + /// + /// 健康检查 + /// + /// + [HttpPost("healthz")] + public async Task CheckAsync(MiddleHealthCheckRequest request) + { + await _healthCheckService.CheckAsync(request.Factories); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs b/Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs new file mode 100644 index 00000000..4e530661 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace Dlhis.Basic.Common.SysData.Request; + +public class HealthCheckRequest +{ + [MinLength(1)] + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs b/Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs new file mode 100644 index 00000000..c08b33c0 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Basic.Common.SysData.Request; + +public class MiddleHealthCheckRequest +{ + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs b/Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs new file mode 100644 index 00000000..489f8e5b --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Basic.Common.SysData.Services; + +public interface IHealthCheckService : IScopeDependency +{ + Task CheckAsync(List factories); +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs new file mode 100644 index 00000000..90203b63 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs @@ -0,0 +1,10 @@ +namespace Dlhis.Basic.Common.SysData.Services.Implements; + +public class HealthCheckService : IHealthCheckService +{ + public async Task CheckAsync(List factories) + { + //todo 状态显示 + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index 3b35e692..f413900e 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -1,5 +1,7 @@ using System.Reflection; using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; using Kurisu.AspNetCore; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; @@ -34,17 +36,34 @@ public class SysDataService : ISysDataService switch (tableName) { case "Gainfo": - //sql = $"select * from Gainfo where factory = '{factory}'"; return await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => (object)x).ToListAsync(); case "GainfoMeal": { + var factories = await GetWalletFactoriesAsync(factory); + var codes = await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => x.Code).ToListAsync(); + // return await _dbContext.Queryable() + // .Where(x => codes.Contains(x.UserCode) && factories.Contains(x.TenantId)) + // .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) + // .GroupBy(x => x.UserCode) + // .Select(x => new + // { + // x.UserCode, + // Balance = SqlFunc.AggregateSum(x.Amount) + // }) + // .ToListAsync(); return null; - //todo 余额 - ///return await _dbContext.Queryable().Where(x => codes.Contains(x.UserCode)).Select(x => (object)x).ToListAsync(); } default: return await _dbContext.Client.SqlQueryable(sql).ToListAsync(); } } + + public async Task> GetWalletFactoriesAsync(string factory) + { + var wallets = await _dbContext.Queryable().ToListAsync(); + var code = wallets.First(x => x.Factory == factory).Code; + + return wallets.Where(x => x.Code == code).Select(x => x.Factory).ToList(); + } } \ No newline at end of file From 43189e1e573e4ead40fc54e4d94827c299c7ebd1 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Mon, 28 Oct 2024 17:37:13 +0800 Subject: [PATCH 05/23] =?UTF-8?q?fix=20gainfomeal=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Implements/SysDataService.cs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index f413900e..1650fa30 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -24,35 +24,42 @@ public class SysDataService : ISysDataService var type = App.ActiveTypes .Where(x => x.IsDefined(typeof(SugarTable), false)) .Where(x => !x.IsDefined(typeof(SkipScanAttribute), false)) - .First(x => x.GetCustomAttribute()!.TableName == tableName); + .FirstOrDefault(x => x.GetCustomAttribute()!.TableName == tableName); - var table = type.GetCustomAttribute()!.TableName; + var table = type?.GetCustomAttribute()!.TableName; var sql = $"select * from {table}"; - if (type.IsAssignableTo(typeof(ITenantId))) + + if (type == null) { - sql += $" where tenantId = '{factory}'"; + table = tableName; + } + else + { + if (type.IsAssignableTo(typeof(ITenantId))) + { + sql += $" where tenantId = '{factory}'"; + } } - switch (tableName) + switch (table) { case "Gainfo": return await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => (object)x).ToListAsync(); case "GainfoMeal": { - var factories = await GetWalletFactoriesAsync(factory); + var r = await _dbContext.Queryable() + .LeftJoin((x, b) => x.Code == b.UserCode) + .Where(x => x.Factory == factory) + .Where((x, b) => b.Source != BalanceRecordSourceEnumType.EMealVoucher && b.Status == BalanceRecordStatusEnumType.Valid && b.IsDeleted == false) + .GroupBy(x => x.Code) + .Select((x, b) => new + { + UserCode = x.Code, + Balance = SqlFunc.AggregateSum(b.Amount) + }) + .ToListAsync(); - var codes = await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => x.Code).ToListAsync(); - // return await _dbContext.Queryable() - // .Where(x => codes.Contains(x.UserCode) && factories.Contains(x.TenantId)) - // .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - // .GroupBy(x => x.UserCode) - // .Select(x => new - // { - // x.UserCode, - // Balance = SqlFunc.AggregateSum(x.Amount) - // }) - // .ToListAsync(); - return null; + return r.Select(x => (object)x).ToList(); } default: return await _dbContext.Client.SqlQueryable(sql).ToListAsync(); From 7ab7c61a293ebe23a4778297b2b5bb92108e7ca3 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Tue, 29 Oct 2024 12:03:19 +0800 Subject: [PATCH 06/23] =?UTF-8?q?fix=20=E6=9F=A5=E8=AF=A2=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/SysData/Services/Implements/SysDataService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index 1650fa30..976e0244 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -48,14 +48,14 @@ public class SysDataService : ISysDataService case "GainfoMeal": { var r = await _dbContext.Queryable() - .LeftJoin((x, b) => x.Code == b.UserCode) .Where(x => x.Factory == factory) - .Where((x, b) => b.Source != BalanceRecordSourceEnumType.EMealVoucher && b.Status == BalanceRecordStatusEnumType.Valid && b.IsDeleted == false) .GroupBy(x => x.Code) - .Select((x, b) => new + .Select(x => new { UserCode = x.Code, - Balance = SqlFunc.AggregateSum(b.Amount) + Balance = SqlFunc.Subqueryable().Where(p => p.UserCode == x.Code) + .Where(p => p.Source != BalanceRecordSourceEnumType.EMealVoucher && p.Status == BalanceRecordStatusEnumType.Valid && p.IsDeleted == false) + .Sum(p => p.Amount) }) .ToListAsync(); From 8cf195eb772b6a1e0179a1a5ad2b5a14e8b7dc5b Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 24 Oct 2024 16:51:17 +0800 Subject: [PATCH 07/23] =?UTF-8?q?fix=20=E5=8F=82=E6=95=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=B9=E6=B3=95=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Order/Services/BaseOrderService.cs | 38 ------------------- .../Implements/BookingOrderService.cs | 8 ++-- .../Order/Services/Implements/OrderService.cs | 12 +++--- .../Services/ICanteenSettingService.cs | 25 +++++++++++- .../Implements/CanteenSettingService.cs | 25 ++++++++++++ 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs b/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs index 16ed5fee..df521389 100644 --- a/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/BaseOrderService.cs @@ -1,16 +1,12 @@ using Dlhis.Canteen.Setting.Services; -using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Enums; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using Kurisu.AspNetCore.Utils.Extensions; -using Mapster; namespace Dlhis.Canteen.Order.Services; public abstract class BaseOrderService { - private readonly ICanteenSettingService _canteenSettingService; private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; private readonly ILogger _logger; @@ -21,7 +17,6 @@ public abstract class BaseOrderService ICurrentUser currentUser, ILogger logger) { - _canteenSettingService = canteenSettingService; _dbContext = dbContext; _currentUser = currentUser; _logger = logger; @@ -32,37 +27,4 @@ public abstract class BaseOrderService protected IDbContext DbContext => _dbContext; protected ICurrentUser CurrentUser => _currentUser; - - /// - /// 获取参数配置 - /// - /// - /// - /// - /// - /// - /// - protected async Task GetSettingAsync(string userNo, string tenantId, CanteenSettingItemEnumType item, T defaultValue = default) - { - _settings ??= await _canteenSettingService.GetUserSettingAsync(userNo, tenantId); - - if (_settings.TryGetValue(item, out var v)) - { - try - { - if (int.TryParse(v, out var n)) - { - return n.Adapt(); - } - - return v.Adapt(); - } - catch (Exception) - { - return defaultValue; - } - } - - return defaultValue; - } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs index e149c83b..a39482e4 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs @@ -19,6 +19,7 @@ namespace Dlhis.Canteen.Order.Services.Implements; public class BookingOrderService : BaseOrderService, IBookingOrderService { + private readonly ICanteenSettingService _canteenSettingService; private readonly IDishesMenuApi _dishesMenuApi; private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; @@ -28,6 +29,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService ICurrentUser currentUser, ILogger logger) : base(canteenSettingService, dbContext, currentUser, logger) { + _canteenSettingService = canteenSettingService; _dishesMenuApi = dishesMenuApi; _businessPlacePermissionsApi = businessPlacePermissionsApi; } @@ -41,7 +43,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService //预定时间 var now = DateTime.Now; - var bookingDeadline = await GetSettingAsync(gainfo.No, businessPlace.TenantId, CanteenSettingItemEnumType.BookingDeadline) ?? new TimeOnly(20, 30, 00); + var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, businessPlace.TenantId, gainfo.No, new TimeOnly(20, 30, 00)); var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); (now >= dealLine).ThrowIfTrue("超出订餐时间"); @@ -72,7 +74,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService No = SnowFlakeHelper.Instance.NextId().ToString(), IsOffline = false, - IsCostCrossTenant = await GetSettingAsync(gainfo.No, businessPlace.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), + IsCostCrossTenant = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowCrossTenant, businessPlace.TenantId, gainfo.No), OriginTenantId = gainfo.Factory, //人员可能跨基地消费 @@ -212,7 +214,7 @@ public class BookingOrderService : BaseOrderService, IBookingOrderService var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); order.ThrowIfNull("订单不存在"); - var bookingDeadline = await GetSettingAsync(order.No, order.TenantId, CanteenSettingItemEnumType.BookingDeadline) ?? new TimeOnly(20, 30, 00); + var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, order.TenantId, order.No, new TimeOnly(20, 30, 00)); var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); (DateTime.Now >= dealLine).ThrowIfTrue("不在订单取消允许范围内,不可操作"); diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index 59585d0b..6b45062f 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -22,6 +22,7 @@ namespace Dlhis.Canteen.Order.Services.Implements; public class OrderService : BaseOrderService, IOrderService { + private readonly ICanteenSettingService _canteenSettingService; private readonly IServiceProvider _serviceProvider; private readonly IHttpContextAccessor _httpContextAccessor; @@ -33,6 +34,7 @@ public class OrderService : BaseOrderService, IOrderService ILogger logger) : base(canteenSettingService, dbContext, currentUser, logger) { + _canteenSettingService = canteenSettingService; _serviceProvider = serviceProvider; _httpContextAccessor = httpContextAccessor; } @@ -198,7 +200,7 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task GetCostDateAsync(BusinessShiftMealRelationshipEntity shift, DateTime costDateTime) { - var switchTime = await GetSettingAsync(string.Empty, shift.TenantId, CanteenSettingItemEnumType.BusinessSwitchTime); + var switchTime = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BusinessSwitchTime, shift.TenantId, string.Empty); var costDate = costDateTime.Date; @@ -243,7 +245,7 @@ public class OrderService : BaseOrderService, IOrderService .ToListAsync(); //一天消费金额 - var dayCostMax = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.DayCostMax); + var dayCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.DayCostMax, order.TenantId, order.UserNo); if (dayCostMax != default) { //消费记录一定会有营业点代码!.Value是安全用法 @@ -255,7 +257,7 @@ public class OrderService : BaseOrderService, IOrderService } //一次消费金额 - var onceCostMax = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.OnceCostMax); + var onceCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.OnceCostMax, order.TenantId, order.UserNo); if (onceCostMax != default) { (Math.Abs(order.Amount) > onceCostMax).ThrowIfTrue($"单次消费金额不能超出{onceCostMax}元"); @@ -264,7 +266,7 @@ public class OrderService : BaseOrderService, IOrderService //超市消费限额 if (order.BusinessPlaceCode.HasValue && supermarketCodes.Contains(order.BusinessPlaceCode.Value)) { - var dayMarketCostMax = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.DayMarketCostMax); + var dayMarketCostMax = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.DayMarketCostMax, order.TenantId, order.UserNo); if (dayMarketCostMax != default) { var cost = todayAllBalanceRecords.Where(x => x.BusinessPlaceCode.HasValue) @@ -276,7 +278,7 @@ public class OrderService : BaseOrderService, IOrderService } //重复餐次消费 - var allowRepeatInOneShift = await GetSettingAsync(order.UserNo, order.TenantId, CanteenSettingItemEnumType.AllowRepeatInOneShift, true); + var allowRepeatInOneShift = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowRepeatInOneShift, order.TenantId, order.UserNo, true); if (!allowRepeatInOneShift) { var costCount = todayAllBalanceRecords.Count(x => x.ShiftType == order.ShiftType); diff --git a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs index ecd8d69d..9872d134 100644 --- a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs @@ -7,10 +7,25 @@ namespace Dlhis.Canteen.Setting.Services; public interface ICanteenSettingService : IScopeDependency { + /// + /// 获取配置分页数据 + /// + /// + /// Task> GetPageAsync(CanteenSettingPageRequest request); + /// + /// 保存配置 + /// + /// + /// Task SaveSettingAsync(CanteenSettingSaveRequest request); + /// + /// 设置配置启用开关 + /// + /// + /// Task SetEnableAsync(CanteenSettingSetEnableRequest request); /// @@ -21,7 +36,13 @@ public interface ICanteenSettingService : IScopeDependency /// Task> GetUserSettingAsync(string userNo, string tenantId); - + /// + /// 获取营业点切换时间 + /// + /// + /// Task GetBusinessSwitchTimeAsync(string tenantId); -} + + Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs index 39300afe..41f3791b 100644 --- a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs @@ -114,6 +114,31 @@ public class CanteenSettingService : ICanteenSettingService } } + /// + public async Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo, T defaultValue = default) + { + var settings = await GetUserSettingAsync(userNo, factory); + + if (settings.TryGetValue(item, out var v)) + { + try + { + if (int.TryParse(v, out var n)) + { + return n.Adapt(); + } + + return v.Adapt(); + } + catch (Exception) + { + return defaultValue; + } + } + + return defaultValue; + } + //*********************************************** help methods ***************************************************// private async Task CheckDuplicateSettingAsync(CanteenSettingSaveRequest request) From 0e0c5db6b89f0cbd374e8ad95bf9dfc861002db3 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Fri, 25 Oct 2024 17:10:18 +0800 Subject: [PATCH 08/23] =?UTF-8?q?add=20=E6=B6=88=E8=B4=B9=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Implements/SysDataService.cs | 4 +- .../Controllers/GainfoMealController.cs | 12 - .../Response/GainfoCurrentMealInfoResponse.cs | 9 - .../Response/GainfoCurrentResponse.cs | 5 - .../Response/GainfoMealResponse.cs | 2 +- .../Services/IGainfoMealService.cs | 2 - .../Services/Implements/GainfoMealService.cs | 160 +--- .../Services/Implements/GainfoService.cs | 9 +- .../Implements/MealSupplementRecordService.cs | 31 - .../Order/Controllers/BalancesController.cs | 32 - .../Order/Controllers/OrdersController.cs | 106 ++- .../Order/Dto/OrderPreCheckDto.cs | 20 +- .../Order/Repository/IBalanceRepository.cs | 40 + .../Order/Repository/IOrderRepository.cs | 11 + .../Implements/BalanceRepository.cs | 57 ++ .../Repository/Implements/OrderRepository.cs | 68 ++ .../Order/Services/IBalanceService.cs | 4 +- .../Services/Implements/BalanceService.cs | 91 +-- .../Implements/BookingOrderService.cs | 604 +++++++-------- .../Services/Implements/MiddleService.cs | 18 +- .../Order/Services/Implements/OrderService.cs | 685 ++++++++---------- .../Services/ICanteenSettingService.cs | 2 + .../Implements/CanteenSettingService.cs | 18 + .../BusinessShiftMealRelationshipEntity.cs | 12 +- .../Basic/Entity/GainfoMealEntity.cs | 86 +-- .../Canteen/Entities/WalletEntity.cs | 29 + .../Enums/CanteenApplicationTypeEnumType.cs | 3 + .../Entities/LocalGainfoMealEntity.cs | 41 ++ .../Common/Services/IJinkoJIPService.cs | 2 +- .../Common/Services/IJinkoJipService.cs | 36 + .../Services/Implements/HangfireService.cs | 4 +- .../Services/Implements/JinkoJIPService.cs | 6 +- .../Services/Implements/JinkoJipService.cs | 213 ++++++ .../Implements/SapBusinessTripService.cs | 4 +- .../Jobs/MealSupplementHandlerJob.cs | 36 +- Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs | 4 +- .../Jobs/SyncOriginCardInfoJob.cs | 4 +- Src/Dlhis.ScheduledTask/Program.cs | 2 +- 38 files changed, 1280 insertions(+), 1192 deletions(-) delete mode 100644 Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/IBalanceRepository.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs create mode 100644 Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs create mode 100644 Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs create mode 100644 Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs create mode 100644 Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index 53ec663d..3b35e692 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -39,7 +39,9 @@ public class SysDataService : ISysDataService case "GainfoMeal": { var codes = await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => x.Code).ToListAsync(); - return await _dbContext.Queryable().Where(x => codes.Contains(x.UserCode)).Select(x => (object)x).ToListAsync(); + return null; + //todo 余额 + ///return await _dbContext.Queryable().Where(x => codes.Contains(x.UserCode)).Select(x => (object)x).ToListAsync(); } default: return await _dbContext.Client.SqlQueryable(sql).ToListAsync(); diff --git a/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs b/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs index 24b50fc6..d511a2ca 100644 --- a/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs +++ b/Src/Dlhis.Basic/Organization/Controllers/GainfoMealController.cs @@ -2,7 +2,6 @@ using Dlhis.Basic.Organization.Response; using Dlhis.Basic.Organization.Services; using Kurisu.AspNetCore.DataAccess.Dto; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Aop; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -33,15 +32,4 @@ public class GainfoMealController : ControllerBase { return await _mealUserService.GetPageAsync(request); } - - /// - /// 迁移 - /// - /// - [Transactional] - [HttpPost("migration")] - public async Task MigrationAsync(GainfoBalanceMigrationRequest request) - { - await _mealUserService.MigrationAsync(request); - } } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs deleted file mode 100644 index 4ccf513f..00000000 --- a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentMealInfoResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Dlhis.Basic.Organization.Response; - -public class GainfoCurrentMealInfoResponse -{ - /// - /// 余额 - /// - public decimal Balance { get; set; } -} diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs index 06f3b3db..b2fb54b8 100644 --- a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs +++ b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs @@ -62,9 +62,4 @@ public class GainfoCurrentResponse /// 卡类型名称 /// public string CardTypeName { get; set; } - - /// - /// 就餐信息 - /// - public GainfoCurrentMealInfoResponse MealInfo { get; set; } } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs index 33c7472d..f2bc5509 100644 --- a/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs +++ b/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs @@ -41,7 +41,7 @@ public class GainfoMealResponse /// /// 餐补总额 /// - public decimal? MealSupplementAmount { get; set; } + public decimal MealSupplementAmount { get; set; } /// /// 档案状态 diff --git a/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs index f657d625..3d2505cc 100644 --- a/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/IGainfoMealService.cs @@ -7,6 +7,4 @@ namespace Dlhis.Basic.Organization.Services; public interface IGainfoMealService : IScopeDependency { Task> GetPageAsync(GainfoMealPageRequest request); - - Task MigrationAsync(GainfoBalanceMigrationRequest request); } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs index a97b3a01..f9a354e7 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs @@ -9,7 +9,6 @@ using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using Kurisu.AspNetCore.Utils; using Kurisu.AspNetCore.Utils.Extensions; using SqlSugar; @@ -28,138 +27,47 @@ public class GainfoMealService : IGainfoMealService public async Task> GetPageAsync(GainfoMealPageRequest request) { - var tenantId = _currentUser.GetStringTenantId(); + var factory = _currentUser.GetStringTenantId(); + var factories = await GetWalletFactoriesAsync(_currentUser.GetStringTenantId()); - var page = await _dbContext.Queryable() - .LeftJoin((meal, gainfo) => meal.UserCode == gainfo.Code) - .LeftJoin((meal, gainfo, gainfoLeave) => meal.UserCode == gainfoLeave.Code) - .LeftJoin((meal, gainfo, gainfoLeave, dept1) => dept1.No == gainfo.DepartmentNo) - .LeftJoin((meal, gainfo, gainfoLeave, dept1, dept2) => dept2.No == gainfoLeave.DepartmentNo) - .WhereIF(request.Search.IsPresent(), (meal, gainfo, gainfoLeave, dept1, dept2) => meal.UserCode == request.Search - || gainfo.Name == request.Search - || gainfoLeave.Name == request.Search) - .Where((meal, gainfo, gainfoLeave) => gainfo.Factory == tenantId || gainfoLeave.Factory == tenantId) - .WhereIF(request.IsLeave, (meal, gainfo, gainfoLeave) => SqlFunc.Subqueryable().Where(x => x.Code == meal.UserCode).Any()) - .WhereIF(request.IsLeave && request.SearchDateTimePeriod.HasValue(), (meal, gainfo, gainfoLeave) => gainfoLeave.ResignationDate >= request.SearchDateTimePeriod.Start && gainfoLeave.ResignationDate <= request.SearchDateTimePeriod.End) - .Select((meal, gainfo, gainfoLeave, dept1, dept2) => new GainfoMealResponse + return await _dbContext.Queryable().AS(request.IsLeave ? "GainfoLeave" : "Gainfo") + .LeftJoin((x, dept) => dept.No == x.DepartmentNo) + .WhereIF(request.Search.IsPresent(), x => x.Code == request.Search || x.Name == request.Search) + .Where(x => x.Factory == factory) + .WhereIF(request.IsLeave && request.SearchDateTimePeriod.HasValue(), x => x.ResignationDate >= request.SearchDateTimePeriod.Start && + x.ResignationDate <= request.SearchDateTimePeriod.End) + .Select((x, dept) => new GainfoMealResponse { - Balance = meal.Balance, - UserCode = meal.UserCode, - UserName = SqlFunc.IsNull(gainfo.Name, gainfoLeave.Name), - UserNo = SqlFunc.IsNull(gainfo.No, gainfoLeave.No), - DepartmentName = SqlFunc.IsNull(dept1.Name, dept2.Name), - Status = SqlFunc.IsNullOrEmpty(gainfo.No) ? GainfoStatusEnumType.Leave : GainfoStatusEnumType.Working + UserCode = x.Code, + UserName = x.Name, + UserNo = x.No, + DepartmentName = dept.Name, + Status = request.IsLeave + ? GainfoStatusEnumType.Leave + : GainfoStatusEnumType.Working, + MealSupplementAmount = SqlFunc.Subqueryable() + .Where(p => p.Code == x.Code) + .Where(p => factories.Contains(p.Factory)) + .WhereIF(request.SearchDateTimePeriod.HasValue(), p => p.Date >= request.SearchDateTimePeriod.Start && p.Date <= request.SearchDateTimePeriod.End) + .Sum(p => p.Amount), + Balance = SqlFunc.Subqueryable() + .Where(p => factories.Contains(p.TenantId)) + .Where(p => p.UserCode == x.Code && p.Status == BalanceRecordStatusEnumType.Valid && p.IsDeleted == false) + .Where(p => p.Source != BalanceRecordSourceEnumType.EMealVoucher) + .Sum(p => p.Amount) }) .ToPageListAsync(request); - - var userCodes = page.Data.Select(x => x.UserCode).ToList(); - var summary = await _dbContext.Queryable() - .Where(x => userCodes.Contains(x.Code)) - .WhereIF(request.SearchDateTimePeriod.HasValue(), x => x.Date >= request.SearchDateTimePeriod.Start && x.Date <= request.SearchDateTimePeriod.End) - .GroupBy(x => x.Code) - .Select(x => new - { - x.Code, - Amount = SqlFunc.AggregateSum(x.Amount) - }) - .ToListAsync(); - - page.Data.ForEach(x => { x.MealSupplementAmount = summary.FirstOrDefault(item => item.Code == x.UserCode)?.Amount ?? 0; }); - - return page; } - public async Task MigrationAsync(GainfoBalanceMigrationRequest request) + + //*********************************************************************************************************************************************************************** + + + public async Task> GetWalletFactoriesAsync(string factory) { - //只能针对离职人员 - var gainfoLeave = await _dbContext.Queryable().Where(x => x.Code == request.BeforeCode).SingleAsync(); - gainfoLeave.ThrowIfNull("需要对已离职人员迁移"); + var wallets = await _dbContext.Queryable().ToListAsync(); + var code = wallets.First(x => x.Factory == factory).Code; - var beforeBalance = await _dbContext.Queryable().Where(x => x.UserCode == request.BeforeCode).Select(x => x.Balance).SingleAsync(); - (beforeBalance <= 0).ThrowIfTrue("不可操作,余额不足"); - - var now = DateTime.Now; - - await _dbContext.InsertAsync(new BalanceRecordEntity - { - No = SnowFlakeHelper.Instance.NextId().ToString(), - UserNo = gainfoLeave.No, - Name = gainfoLeave.Name, - UserCode = gainfoLeave.Code, - DepartmentNo = gainfoLeave.DepartmentNo, - Source = BalanceRecordSourceEnumType.BalanceMigration, - Type = BalanceRecordTypeEnumType.In, - Amount = -Math.Abs(beforeBalance), - BusinessPlaceCode = null, - BusinessShiftCode = null, - ShiftType = null, - CostDate = now.Date, - DeviceUniqueNo = null, - IP = null, - CostId = gainfoLeave.CostId, - OriginTenantId = gainfoLeave.Factory, - IsCostCrossTenant = false, - Status = BalanceRecordStatusEnumType.Valid, - CostDateTime = DateTime.Now, - Remark = $"{gainfoLeave.Code}:余额转出", - ApplicationType = CanteenApplicationTypeEnumType.BalanceMigration, - TenantId = gainfoLeave.Factory, - Werks = gainfoLeave.Werks, - IsDeleted = false, - }); - - var beforeBalanceAmount = await _dbContext.Queryable() - .Where(x => x.UserCode == gainfoLeave.Code) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == beforeBalanceAmount) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .SetColumns(x => x.Version == x.Version + 1) - .Where(x => x.UserCode == request.BeforeCode) //不需要判断version - .ExecuteCommandAsync(); - - //转移到现有人员 - var gainfo = await _dbContext.Queryable().Where(x => x.Code == request.NowCode).SingleAsync(); - gainfo.ThrowIfNull("目标人员不存在"); - - await _dbContext.InsertAsync(new BalanceRecordEntity - { - No = SnowFlakeHelper.Instance.NextId().ToString(), - UserNo = gainfo.No, - Name = gainfo.Name, - UserCode = gainfo.Code, - DepartmentNo = gainfo.DepartmentNo, - Source = BalanceRecordSourceEnumType.BalanceMigration, - Type = BalanceRecordTypeEnumType.In, - Amount = beforeBalance, - BusinessPlaceCode = null, - BusinessShiftCode = null, - ShiftType = null, - CostDate = now.Date, - DeviceUniqueNo = null, - IP = null, - CostId = gainfo.CostId, - OriginTenantId = gainfo.Factory, - IsCostCrossTenant = false, - Status = BalanceRecordStatusEnumType.Valid, - CostDateTime = DateTime.Now, - Remark = $"{gainfoLeave.Code}:余额转入", - ApplicationType = CanteenApplicationTypeEnumType.MealSupplement, - TenantId = gainfo.Factory, - Werks = gainfo.Werks, - IsDeleted = false, - }); - - var nowGainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == request.NowCode).SingleAsync(); - var nowBalance = nowGainfoMeal.Balance + beforeBalance; - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == nowBalance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .SetColumns(x => x.Version == x.Version + 1) - .Where(x => x.UserCode == request.NowCode && x.Version == nowGainfoMeal.Version) - .ExecuteCommandThrowVersionAsync("转入失败"); + return wallets.Where(x => x.Code == code).Select(x => x.Factory).ToList(); } } \ No newline at end of file diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs index 63317086..b76ef920 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs @@ -197,10 +197,9 @@ public class GainfoService : IGainfoService var info = await _gainfoStore.Queryable(GainfoStatusEnumType.Working) .LeftJoin((ga, department) => ga.DepartmentNo == department.No) - .LeftJoin((ga, department, mealUser) => ga.Code == mealUser.UserCode) - .LeftJoin((ga, department, mealUser, type) => type.Code == ga.CardType.ToString() && type.Type == "timeperiod") + .LeftJoin((ga, department, type) => type.Code == ga.CardType.ToString() && type.Type == "timeperiod") .Where((ga, department) => ga.Id == gainfo.Id) - .Select((ga, department, mealUser, type) => new GainfoCurrentResponse + .Select((ga, department, type) => new GainfoCurrentResponse { UserNo = ga.No, Birthday = ga.Birthday, @@ -210,10 +209,6 @@ public class GainfoService : IGainfoService UserCode = ga.Code, UserName = ga.Name, Factory = ga.Factory, - MealInfo = new GainfoCurrentMealInfoResponse - { - Balance = mealUser.Balance - }, CardType = ga.CardType, CardTypeName = type.Display }).SingleAsync(); diff --git a/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs b/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs index c334c0cc..ddeb8fca 100644 --- a/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs +++ b/Src/Dlhis.Canteen/MealSupplementRecord/Services/Implements/MealSupplementRecordService.cs @@ -187,37 +187,6 @@ public class MealSupplementRecordService : IMealSupplementRecordService DepartmentNo = gainfo.DepartmentNo, DeviceUniqueNo = null }); - - await CommonHelper.RetryAsync(async () => - { - var mealUser = await _dbContext.Queryable().Where(x => x.UserCode == request.Code).FirstAsync(); - if (mealUser == null) - { - mealUser = new GainfoMealEntity - { - UserCode = gainfo.Code, - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0 - }; - - await _dbContext.InsertAsync(mealUser); - } - - //重新核算 - var balance = await _dbContext.Queryable() - .Where(x => x.UserCode == request.Code) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == mealUser.UserCode && x.Version == mealUser.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); } } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs b/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs index dcf8df4c..baa4deb1 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs @@ -167,36 +167,4 @@ public class BalancesController : ControllerBase { await _balanceService.ManuallyImportBalanceAndReCalcAsync(requests); } - - - /// - /// 重算 - /// - /// - [Authorize(Roles = "admin")] - [HttpPost("recalc")] - [IgnoreTenant] - [Transactional(IsolationLevel.ReadCommitted)] - public async Task ReCalcAsync(BalanceReCalcRequest request) - { - await _balanceService.ReCalcAsync(request.UserCode); - } - - [HttpPost("recalc1")] - [Transactional(IsolationLevel.ReadCommitted)] - [IgnoreTenant] - public async Task ReC5() - { - var sp = App.GetServiceProvider(); - var db = sp.GetService(); - var middleService = sp.GetService(); - var others = await db.Queryable().AS("LocalBalanceRecordEntity") - .Where(x => SqlFunc.Subqueryable().Where(p => p.No == x.No).NotAny()) - .ToListAsync(); - - foreach (var item in others) - { - await middleService.MiddleUploadAsync(item); - } - } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs index 59ab64b3..1949cb5b 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs @@ -19,12 +19,10 @@ namespace Dlhis.Canteen.Order.Controllers; public class OrdersController : ControllerBase { private readonly IOrderService _orderService; - private readonly IBookingOrderService _bookingOrderService; - public OrdersController(IOrderService orderService, IBookingOrderService bookingOrderService) + public OrdersController(IOrderService orderService) { _orderService = orderService; - _bookingOrderService = bookingOrderService; } /// @@ -66,56 +64,56 @@ public class OrdersController : ControllerBase return await _orderService.GetOrdersAggregationAsync(page); } - /// - /// 取消订单 - /// - /// - /// - [HttpPut("user-cancel")] - [Transactional] - [IgnoreTenant] - public async Task CancelOrderAsync(OrderCancelRequest request) - { - await _bookingOrderService.UserCancelOrderAsync(request); - } + // /// + // /// 取消订单 + // /// + // /// + // /// + // [HttpPut("user-cancel")] + // [Transactional] + // [IgnoreTenant] + // public async Task CancelOrderAsync(OrderCancelRequest request) + // { + // await _bookingOrderService.UserCancelOrderAsync(request); + // } - - /// - /// 取消订单 - /// - /// - /// - [HttpPut("manager-cancel")] - [Transactional] - [IgnoreTenant] - public async Task ManagerCancelOrderAsync(OrderCancelRequest request) - { - await _bookingOrderService.ManagerCancelOrderAsync(request); - } - - //************************************************************** 订餐功能 ********************************************************************************** - - /// - /// 取消订单 - /// - /// - /// - [HttpPost("booking")] - [Transactional] - [IgnoreTenant] - public async Task Booking(BookingMealRequest request) - { - return await _bookingOrderService.BookingAsync(request); - } - - /// - /// 订单分页 - /// - /// - /// - [HttpGet("booking/page")] - public async Task> GetBookingPageAsync([FromQuery] OrderBookingPageRequest request) - { - return await _bookingOrderService.GetBookingPageAsync(request); - } + // + // /// + // /// 取消订单 + // /// + // /// + // /// + // [HttpPut("manager-cancel")] + // [Transactional] + // [IgnoreTenant] + // public async Task ManagerCancelOrderAsync(OrderCancelRequest request) + // { + // await _bookingOrderService.ManagerCancelOrderAsync(request); + // } + // + // //************************************************************** 订餐功能 ********************************************************************************** + // + // /// + // /// 取消订单 + // /// + // /// + // /// + // [HttpPost("booking")] + // [Transactional] + // [IgnoreTenant] + // public async Task Booking(BookingMealRequest request) + // { + // return await _bookingOrderService.BookingAsync(request); + // } + // + // /// + // /// 订单分页 + // /// + // /// + // /// + // [HttpGet("booking/page")] + // public async Task> GetBookingPageAsync([FromQuery] OrderBookingPageRequest request) + // { + // return await _bookingOrderService.GetBookingPageAsync(request); + // } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs index 6667755e..c4f9c9dc 100644 --- a/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs +++ b/Src/Dlhis.Canteen/Order/Dto/OrderPreCheckDto.cs @@ -1,16 +1,16 @@ -using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Enums; -namespace Dlhis.Canteen.Order.Dto +namespace Dlhis.Canteen.Order.Dto; + +public class OrderPreCheckDto { - public class OrderPreCheckDto - { - public DateTime CostDate { get; set; } - public BusinessShiftMealRelationshipEntity Shift { get; set; } + public string Factory { get; set; } - public string BusinessPlaceTenantId { get; set; } + public DateTime CostDate { get; set; } - public Guid BusinessPlaceCode { get; set; } + public ShiftEnumType ShiftType { get; set; } - public Guid BusinessShiftCode { 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/IBalanceRepository.cs b/Src/Dlhis.Canteen/Order/Repository/IBalanceRepository.cs new file mode 100644 index 00000000..0c1e321c --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/IBalanceRepository.cs @@ -0,0 +1,40 @@ +using Dlhis.Entity.Canteen.Entities; + +namespace Dlhis.Canteen.Order.Repository; + +public interface IBalanceRepository : IScopeDependency +{ + /// + /// 获取余额 + /// + /// + /// + /// + Task GetBalanceAsync(string userCode, string factory); + + /// + /// 获取消费明细 + /// + /// + /// + /// + /// + /// + Task> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime); + + /// + /// 获取钱包厂区 + /// + /// + /// + Task> GetWalletFactoriesAsync(string factory); + + /// + /// 检测余额是否足够 + /// + /// + /// + /// + /// + Task CheckConsumableAsync(string userCode, string factory, decimal cost); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs b/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs new file mode 100644 index 00000000..d9e904c4 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/IOrderRepository.cs @@ -0,0 +1,11 @@ +using Dlhis.Canteen.Order.Dto; +using Dlhis.Canteen.Order.Request; +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; + +namespace Dlhis.Canteen.Order.Repository; + +public interface IOrderRepository : IScopeDependency +{ + Task CreateDeviceConsumeOrderAsync(OrderPreCheckDto checkData, ConsumeRequest request, GainfoEntity gainfo); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs new file mode 100644 index 00000000..5de3e2f9 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs @@ -0,0 +1,57 @@ +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Canteen.Order.Repository.Implements; + +public class BalanceRepository : IBalanceRepository +{ + private readonly IDbContext _dbContext; + + public BalanceRepository(IDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task GetBalanceAsync(string userCode, string factory) + { + var factories = await GetWalletFactoriesAsync(factory); + + return await _dbContext.Queryable() + .Where(x => x.UserCode == userCode && x.Status == BalanceRecordStatusEnumType.Valid) + .Where(x => factories.Contains(x.TenantId)) + .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) + .SumAsync(x => x.Amount); + } + + + public async Task> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime) + { + var factories = await GetWalletFactoriesAsync(factory); + + return await _dbContext.Queryable() + .Where(x => x.UserCode == userCode) + .Where(x => factories.Contains(x.TenantId)) + .Where(x => x.Type == BalanceRecordTypeEnumType.Out) + .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) + .Where(x => x.CostDateTime >= startTime && x.CostDateTime <= endTime) + .ToListAsync(); + } + + public async Task> GetWalletFactoriesAsync(string factory) + { + var wallets = await _dbContext.Queryable().ToListAsync(); + var code = wallets.First(x => x.Factory == factory).Code; + + return wallets.Where(x => x.Code == code).Select(x => x.Factory).ToList(); + } + + + public async Task CheckConsumableAsync(string userCode, string factory, decimal cost) + { + var balance = await GetBalanceAsync(userCode, factory); + var isEnough = balance - Math.Abs(cost) >= 0; + isEnough.ThrowIfFalse($"钱包余额不足({balance}元)"); + } +} \ 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 new file mode 100644 index 00000000..a9e31997 --- /dev/null +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/OrderRepository.cs @@ -0,0 +1,68 @@ +using Dlhis.Canteen.Order.Dto; +using Dlhis.Canteen.Order.Request; +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Canteen.Order.Repository.Implements; + +public class OrderRepository : IOrderRepository +{ + private readonly IDbContext _dbContext; + + public OrderRepository(IDbContext dbContext) + { + _dbContext = dbContext; + } + + /// + public async Task CreateDeviceConsumeOrderAsync(OrderPreCheckDto checkData, ConsumeRequest request, GainfoEntity gainfo) + { + var order = new CanteenOrderEntity + { + RequestJson = request.ToJson(), + IsOffline = request.IsOffline, + No = SnowFlakeHelper.Instance.NextId().ToString(), + ThirdNo = request.ThirdNo, + Type = BalanceRecordTypeEnumType.Out, + ApplicationType = CanteenApplicationTypeEnumType.Device, + Name = gainfo.Name, + Source = request.Source, + UserNo = gainfo.No, + UserCode = gainfo.Code, + Amount = request.Amount, + BusinessPlaceCode = checkData.BusinessPlaceCode, + BusinessShiftCode = checkData.BusinessShiftCode, + ShiftType = checkData.ShiftType, + CostDate = checkData.CostDate, + CostDateTime = request.CostDateTime, + CostId = gainfo.CostId, + DepartmentNo = gainfo.DepartmentNo, + DeviceUniqueNo = request.DeviceUniqueNo, + + Remark = null, + DeliveryAddress = null, + Result = null, + IP = null, + + IsCostCrossTenant = gainfo.Factory != checkData.Factory, + + Werks = gainfo.Werks, + OriginTenantId = gainfo.Factory, + //标识当前消费的基地 + TenantId = checkData.Factory, + + Status = BalanceRecordStatusEnumType.Valid, + OrderStatus = CanteenOrderStatusEnumType.Wait, + + IsDeleted = false, + }; + + var orderId = await _dbContext.InsertReturnIdentityAsync(order); + order.Id = orderId; + return order; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs b/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs index bedc7a72..4efc0902 100644 --- a/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/IBalanceService.cs @@ -14,7 +14,7 @@ public interface IBalanceService : IScopeDependency Task> GetUserBalanceGroupAsync(BalanceRecordUserGroupPageRequest request); - + Task SupplementBalanceAsync(BalanceSupplementRequest request); @@ -40,6 +40,4 @@ public interface IBalanceService : IScopeDependency Task ManuallyImportBalanceAndReCalcAsync(List requests); - - Task ReCalcAsync(string userCode); } \ 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 24e9fb11..3bedcca8 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs @@ -122,11 +122,7 @@ public class BalanceService : IBalanceService balanceRecord.Remark = "退款"; await _dbContext.UpdateAsync(balanceRecord); - if (balanceRecord.Source is BalanceRecordSourceEnumType.Face or BalanceRecordSourceEnumType.QrCode) - { - await ReCalcAsync(balanceRecord.UserCode); - } - else if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) + if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) { var originData = balanceRecord.UserNo.Split(":"); var eNo = originData[0]; @@ -323,19 +319,6 @@ public class BalanceService : IBalanceService await _dbContext.Client.Ado.RollbackTranAsync(); throw; } - - await CommonHelper.RetryAsync(async () => - { - var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == balanceInfo.UserCode).SingleAsync(); - var balance = gainfoMeal.Balance + Math.Abs(balanceInfo.Amount) + request.Amount; - - await _dbContext.Updateable() - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == balanceInfo.UserCode && x.Version == gainfoMeal.Version) - .ExecuteCommandThrowVersionAsync(); - }, 10); } public async Task GetPageByUserAsync(BalanceRecordUserPageRequest request) @@ -542,82 +525,10 @@ public class BalanceService : IBalanceService Werks = gainfo.Werks, IsDeleted = false }); - - await CommonHelper.RetryAsync(async () => - { - var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == gainfo.Code).FirstAsync(); - if (gainfoMeal == null) - { - gainfoMeal = new GainfoMealEntity - { - UserCode = gainfo.Code, - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0 - }; - - await _dbContext.InsertAsync(gainfoMeal); - } - - //重新核算 - var balance = await _dbContext.Queryable() - .Where(x => x.UserCode == gainfo.Code) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == gainfoMeal.UserCode && x.Version == gainfoMeal.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); } } - public async Task ReCalcAsync(string userCode) - { - await CommonHelper.RetryAsync(async () => - { - var gainfo = await _dbContext.Queryable().Where(x => x.Code == userCode).SingleAsync() - ?? await _dbContext.Queryable().Where(x => x.Code == userCode).SingleAsync(); - - gainfo.ThrowIfNull("档案不存在"); - - var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserCode == gainfo.Code).FirstAsync(); - if (gainfoMeal == null) - { - gainfoMeal = new GainfoMealEntity - { - UserCode = gainfo.Code, - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0 - }; - - await _dbContext.InsertAsync(gainfoMeal); - } - - //重新核算 - var balance = await _dbContext.Queryable() - .Where(x => x.UserCode == gainfo.Code) - .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .SumAsync(x => x.Amount); - - await _dbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == balance) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == gainfoMeal.UserCode && x.Version == gainfoMeal.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); - } - - /// /// 获取离线设备 /// diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs index a39482e4..e71257ed 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BookingOrderService.cs @@ -1,302 +1,302 @@ -using Dlhis.Canteen.Dishes.Request; -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.Utils.Extensions; -using Kurisu.AspNetCore.Utils; -using Dlhis.Canteen.Setting.Services; -using Dlhis.Basic.Api; -using Kurisu.AspNetCore.Authentication.Abstractions; -using Kurisu.AspNetCore.DataAccess.Dto; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using SqlSugar; -using Mapster; - -namespace Dlhis.Canteen.Order.Services.Implements; - -public class BookingOrderService : BaseOrderService, IBookingOrderService -{ - private readonly ICanteenSettingService _canteenSettingService; - private readonly IDishesMenuApi _dishesMenuApi; - private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; - - public BookingOrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, - IDishesMenuApi dishesMenuApi, - IBusinessPlacePermissionsApi businessPlacePermissionsApi, - ICurrentUser currentUser, ILogger logger) - : base(canteenSettingService, dbContext, currentUser, logger) - { - _canteenSettingService = canteenSettingService; - _dishesMenuApi = dishesMenuApi; - _businessPlacePermissionsApi = businessPlacePermissionsApi; - } - - public async Task BookingAsync(BookingMealRequest request) - { - var gainfo = await CheckExistsMyBookingsAsync(request.BookingDate, request.ShiftType); - - var businessPlace = await DbContext.Queryable().Where(x => x.Code == request.BusinessPlaceCode).FirstAsync(); - - //预定时间 - var now = DateTime.Now; - - var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, businessPlace.TenantId, gainfo.No, new TimeOnly(20, 30, 00)); - var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); - (now >= dealLine).ThrowIfTrue("超出订餐时间"); - - //菜单存在 - var menuDetailResult = await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); - var menuDetail = menuDetailResult.Data; - menuDetail.ThrowIfNull("预定失败,菜品已经下架"); - - var order = new CanteenOrderEntity - { - RequestJson = request.ToJson(), - ThirdNo = string.Empty, - Type = BalanceRecordTypeEnumType.Out, - ApplicationType = CanteenApplicationTypeEnumType.Booking, - Name = gainfo.Name, - Source = request.Source, - UserNo = gainfo.No, - UserCode = gainfo.Code, - Amount = -Math.Abs(menuDetail.Price * request.Number), - BusinessPlaceCode = request.BusinessPlaceCode, - BusinessShiftCode = null, - ShiftType = request.ShiftType, - CostDate = now.Date, - CostId = gainfo.CostId, - DepartmentNo = gainfo.DepartmentNo, - DeviceUniqueNo = string.Empty, - DeliveryAddress = request.DeliveryAddress, - - No = SnowFlakeHelper.Instance.NextId().ToString(), - IsOffline = false, - IsCostCrossTenant = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowCrossTenant, businessPlace.TenantId, gainfo.No), - - OriginTenantId = gainfo.Factory, //人员可能跨基地消费 - - Status = BalanceRecordStatusEnumType.Valid, - OrderStatus = CanteenOrderStatusEnumType.Success, - - CostDateTime = now, - - //标识当前消费的基地 - TenantId = businessPlace.TenantId, - }; - - await DbContext.InsertAsync(order); - await CommonHelper.RetryAsync(async () => - { - await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); - menuDetail = menuDetailResult.Data; - menuDetail.ThrowIfNull("预定失败,菜品已经下架"); - menuDetail.Number -= request.Number; - - (menuDetail.Number < 0).ThrowIfTrue($"{menuDetail.DishesName}剩余数量不足"); - - //餐补 - if (request.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) - { - var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); - mealInfo.ThrowIfNull("余额不足,无法消费"); - - //金额+- - mealInfo.Balance += order.Amount; - (mealInfo.Balance < 0).ThrowIfTrue("余额不足,无法消费"); - - await DbContext.Updateable() - .SetColumns(x => x.Balance == mealInfo.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == order.UserCode && x.Version == mealInfo.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - } - - await DbContext.Updateable() - .SetColumns(x => x.Number == menuDetail.Number) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == now) - .Where(x => x.StartDate <= menuDetail.Date && menuDetail.Date <= x.EndDate - && x.ShiftType == request.ShiftType - && x.BusinessPlaceCode == request.BusinessPlaceCode - && x.DishesCode == request.DishesCode - && x.Version == menuDetail.Version - ) - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); - - await DbContext.InsertAsync(new CanteenOrderDetailEntity - { - OrderNo = order.No, - BookingDate = request.BookingDate, - DishesCode = request.DishesCode, - Price = menuDetail.Price, - Unit = request.Unit, - Number = request.Number, - TenantId = order.TenantId, - }); - }, 10); - - - return new BookingResponse - { - No = order.No, - CreateTime = order.CreateTime, - Source = order.Source, - Amount = Math.Abs(order.Amount) - }; - } - - - public async Task> GetBookingPageAsync(OrderBookingPageRequest request) - { - var myCodes = (await _businessPlacePermissionsApi.GetMyAsync()).Data.Select(x => (Guid?)x.Code).ToList(); - - var page = await DbContext.Queryable() - .LeftJoin((x, ga) => x.UserNo == ga.No) - .LeftJoin((x, ga, dept) => dept.No == ga.DepartmentNo) - .Where(x => myCodes.Contains(x.BusinessPlaceCode)) - .WhereIF(request.OrderStatus.HasValue, x => x.OrderStatus == request.OrderStatus) - .WhereIF(request.Source.HasValue, x => x.Source == request.Source) - .WhereIF(request.Search.IsPresent(), (x, ga) => x.UserCode == request.Search || ga.Name.Contains(request.Search)) - .WhereIF(request.BookingPeriod?.Start.HasValue == true && request.BookingPeriod?.End.HasValue == true, x => - SqlFunc.Subqueryable() - .Where(p => p.OrderNo == x.No) - .Where(p => p.BookingDate >= request.BookingPeriod.Start) - .Where(p => p.BookingDate <= request.BookingPeriod.End) - .Any() - ) - .WhereIF(request.ShiftType.HasValue, x => x.ShiftType == request.ShiftType) - .Select((x, ga, dept) => new OrderBookingResponse - { - No = x.No, - DeliveryAddress = x.DeliveryAddress, - Remark = x.Remark, - Amount = SqlFunc.Abs(x.Amount), - CreateTime = x.CreateTime, - ShiftType = x.ShiftType, - Status = x.OrderStatus, - UserCode = x.UserCode, - UserName = ga.Name, - Source = x.Source, - DepartmentName = dept.Name, - }) - .ToPageListAsync(request); - - var nos = page.Data.Select(x => x.No).ToList(); - var details = await DbContext.Queryable() - .LeftJoin((x, dishes) => x.DishesCode == dishes.CustomCode) - .Where(x => nos.Contains(x.OrderNo)) - .Select((x, dishes) => new { x.OrderNo, x.BookingDate, x.Number, dishes.Name }).ToListAsync(); - - page.Data.ForEach(x => - { - x.BookingDate = details.First(item => item.OrderNo == x.No).BookingDate; - x.TotalNumber = details.Where(item => item.OrderNo == x.No).Sum(item => item.Number); - x.DishesInfos = details.Where(item => item.OrderNo == x.No).Select(item => new - { - item.Name - }) - .ToList(); - }); - - return page; - } - - public async Task UserCancelOrderAsync(OrderCancelRequest request) - { - var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); - order.ThrowIfNull("订单不存在"); - - var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, order.TenantId, order.No, new TimeOnly(20, 30, 00)); - var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); - (DateTime.Now >= dealLine).ThrowIfTrue("不在订单取消允许范围内,不可操作"); - - await ManagerCancelOrderAsync(new OrderCancelRequest - { - No = request.No, - Reason = request.Reason, - }); - } - - - public async Task ManagerCancelOrderAsync(OrderCancelRequest request) - { - var now = DateTime.Now; - var userId = CurrentUser.GetIntSubjectId(); - - var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); - order.ThrowIfNull("订单不存在"); - - await DbContext.Updateable() - .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Cancel) - .SetColumns(x => x.Remark == request.Reason) - .SetColumns(x => x.ModifiedTime == now) - .SetColumns(x => x.ModifiedBy == userId) - .Where(x => x.No == request.No) - .ExecuteCommandAsync(); - - await DbContext.Updateable() - .SetColumns(x => x.Status == BalanceRecordStatusEnumType.Obsolute) - .SetColumns(x => x.Remark == request.Reason) - .SetColumns(x => x.ModifiedTime == now) - .SetColumns(x => x.ModifiedBy == userId) - .Where(x => x.No == request.No) - .ExecuteCommandAsync(); - - if (order.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) - { - await CommonHelper.RetryAsync(async () => - { - var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); - var setBalance = mealInfo.Balance + Math.Abs(order.Amount); - await DbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == setBalance) - .SetColumns(x => x.ModifiedTime == now) - .SetColumns(x => x.ModifiedBy == userId) - .Where(x => x.Id == mealInfo.Id && x.Version == mealInfo.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); - } - } - - - /// - /// 检查我的订餐是否存在,存在抛出异常 - /// - /// - /// - /// - private async Task CheckExistsMyBookingsAsync(DateTime bookingDate, ShiftEnumType shiftType) - { - var subjectId = CurrentUser.GetIntSubjectId(); - var gainfo = await DbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); - var jinkoCode = CurrentUser.GetUserClaim("jinko-code") ?? string.Empty; - if (jinkoCode.IsPresent()) - { - gainfo = await DbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); - } - - var start = bookingDate.Date.AddDays(-1); - var end = start.AddDays(1).AddSeconds(-1); - - var exists = await DbContext.Queryable() - .Where(x => x.ApplicationType == CanteenApplicationTypeEnumType.Booking) - .Where(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) - .Where(x => x.ShiftType == shiftType) - .Where(x => x.UserNo == gainfo.No) - .Where(x => x.CreateTime >= start && x.CreateTime <= end) - .AnyAsync(); - - exists.ThrowIfTrue("不可重复订餐"); - - return gainfo; - } -} \ No newline at end of file +// using Dlhis.Canteen.Dishes.Request; +// 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.Utils.Extensions; +// using Kurisu.AspNetCore.Utils; +// using Dlhis.Canteen.Setting.Services; +// using Dlhis.Basic.Api; +// using Kurisu.AspNetCore.Authentication.Abstractions; +// using Kurisu.AspNetCore.DataAccess.Dto; +// using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +// using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +// using SqlSugar; +// using Mapster; +// +// namespace Dlhis.Canteen.Order.Services.Implements; +// +// public class BookingOrderService : BaseOrderService, IBookingOrderService +// { +// private readonly ICanteenSettingService _canteenSettingService; +// private readonly IDishesMenuApi _dishesMenuApi; +// private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; +// +// public BookingOrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, +// IDishesMenuApi dishesMenuApi, +// IBusinessPlacePermissionsApi businessPlacePermissionsApi, +// ICurrentUser currentUser, ILogger logger) +// : base(canteenSettingService, dbContext, currentUser, logger) +// { +// _canteenSettingService = canteenSettingService; +// _dishesMenuApi = dishesMenuApi; +// _businessPlacePermissionsApi = businessPlacePermissionsApi; +// } +// +// public async Task BookingAsync(BookingMealRequest request) +// { +// var gainfo = await CheckExistsMyBookingsAsync(request.BookingDate, request.ShiftType); +// +// var businessPlace = await DbContext.Queryable().Where(x => x.Code == request.BusinessPlaceCode).FirstAsync(); +// +// //预定时间 +// var now = DateTime.Now; +// +// var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, businessPlace.TenantId, gainfo.No, new TimeOnly(20, 30, 00)); +// var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); +// (now >= dealLine).ThrowIfTrue("超出订餐时间"); +// +// //菜单存在 +// var menuDetailResult = await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); +// var menuDetail = menuDetailResult.Data; +// menuDetail.ThrowIfNull("预定失败,菜品已经下架"); +// +// var order = new CanteenOrderEntity +// { +// RequestJson = request.ToJson(), +// ThirdNo = string.Empty, +// Type = BalanceRecordTypeEnumType.Out, +// ApplicationType = CanteenApplicationTypeEnumType.Booking, +// Name = gainfo.Name, +// Source = request.Source, +// UserNo = gainfo.No, +// UserCode = gainfo.Code, +// Amount = -Math.Abs(menuDetail.Price * request.Number), +// BusinessPlaceCode = request.BusinessPlaceCode, +// BusinessShiftCode = null, +// ShiftType = request.ShiftType, +// CostDate = now.Date, +// CostId = gainfo.CostId, +// DepartmentNo = gainfo.DepartmentNo, +// DeviceUniqueNo = string.Empty, +// DeliveryAddress = request.DeliveryAddress, +// +// No = SnowFlakeHelper.Instance.NextId().ToString(), +// IsOffline = false, +// IsCostCrossTenant = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.AllowCrossTenant, businessPlace.TenantId, gainfo.No), +// +// OriginTenantId = gainfo.Factory, //人员可能跨基地消费 +// +// Status = BalanceRecordStatusEnumType.Valid, +// OrderStatus = CanteenOrderStatusEnumType.Success, +// +// CostDateTime = now, +// +// //标识当前消费的基地 +// TenantId = businessPlace.TenantId, +// }; +// +// await DbContext.InsertAsync(order); +// await CommonHelper.RetryAsync(async () => +// { +// await _dishesMenuApi.GetBookingDishesDetailAsync(request.BookingDate, request.BusinessPlaceCode, (int)request.ShiftType, request.DishesCode); +// menuDetail = menuDetailResult.Data; +// menuDetail.ThrowIfNull("预定失败,菜品已经下架"); +// menuDetail.Number -= request.Number; +// +// (menuDetail.Number < 0).ThrowIfTrue($"{menuDetail.DishesName}剩余数量不足"); +// +// //餐补 +// if (request.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) +// { +// var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); +// mealInfo.ThrowIfNull("余额不足,无法消费"); +// +// //金额+- +// mealInfo.Balance += order.Amount; +// (mealInfo.Balance < 0).ThrowIfTrue("余额不足,无法消费"); +// +// await DbContext.Updateable() +// .SetColumns(x => x.Balance == mealInfo.Balance) +// .SetColumns(x => x.Version == x.Version + 1) +// .SetColumns(x => x.ModifiedTime == DateTime.Now) +// .Where(x => x.UserCode == order.UserCode && x.Version == mealInfo.Version) //必须与查询时的version一致 +// .ExecuteCommandThrowVersionAsync(); +// } +// +// await DbContext.Updateable() +// .SetColumns(x => x.Number == menuDetail.Number) +// .SetColumns(x => x.Version == x.Version + 1) +// .SetColumns(x => x.ModifiedTime == now) +// .Where(x => x.StartDate <= menuDetail.Date && menuDetail.Date <= x.EndDate +// && x.ShiftType == request.ShiftType +// && x.BusinessPlaceCode == request.BusinessPlaceCode +// && x.DishesCode == request.DishesCode +// && x.Version == menuDetail.Version +// ) +// .ExecuteCommandThrowVersionAsync(); +// +// //写入记录 +// var balanceRecord = order.Adapt(); +// await DbContext.InsertAsync(balanceRecord); +// +// await DbContext.InsertAsync(new CanteenOrderDetailEntity +// { +// OrderNo = order.No, +// BookingDate = request.BookingDate, +// DishesCode = request.DishesCode, +// Price = menuDetail.Price, +// Unit = request.Unit, +// Number = request.Number, +// TenantId = order.TenantId, +// }); +// }, 10); +// +// +// return new BookingResponse +// { +// No = order.No, +// CreateTime = order.CreateTime, +// Source = order.Source, +// Amount = Math.Abs(order.Amount) +// }; +// } +// +// +// public async Task> GetBookingPageAsync(OrderBookingPageRequest request) +// { +// var myCodes = (await _businessPlacePermissionsApi.GetMyAsync()).Data.Select(x => (Guid?)x.Code).ToList(); +// +// var page = await DbContext.Queryable() +// .LeftJoin((x, ga) => x.UserNo == ga.No) +// .LeftJoin((x, ga, dept) => dept.No == ga.DepartmentNo) +// .Where(x => myCodes.Contains(x.BusinessPlaceCode)) +// .WhereIF(request.OrderStatus.HasValue, x => x.OrderStatus == request.OrderStatus) +// .WhereIF(request.Source.HasValue, x => x.Source == request.Source) +// .WhereIF(request.Search.IsPresent(), (x, ga) => x.UserCode == request.Search || ga.Name.Contains(request.Search)) +// .WhereIF(request.BookingPeriod?.Start.HasValue == true && request.BookingPeriod?.End.HasValue == true, x => +// SqlFunc.Subqueryable() +// .Where(p => p.OrderNo == x.No) +// .Where(p => p.BookingDate >= request.BookingPeriod.Start) +// .Where(p => p.BookingDate <= request.BookingPeriod.End) +// .Any() +// ) +// .WhereIF(request.ShiftType.HasValue, x => x.ShiftType == request.ShiftType) +// .Select((x, ga, dept) => new OrderBookingResponse +// { +// No = x.No, +// DeliveryAddress = x.DeliveryAddress, +// Remark = x.Remark, +// Amount = SqlFunc.Abs(x.Amount), +// CreateTime = x.CreateTime, +// ShiftType = x.ShiftType, +// Status = x.OrderStatus, +// UserCode = x.UserCode, +// UserName = ga.Name, +// Source = x.Source, +// DepartmentName = dept.Name, +// }) +// .ToPageListAsync(request); +// +// var nos = page.Data.Select(x => x.No).ToList(); +// var details = await DbContext.Queryable() +// .LeftJoin((x, dishes) => x.DishesCode == dishes.CustomCode) +// .Where(x => nos.Contains(x.OrderNo)) +// .Select((x, dishes) => new { x.OrderNo, x.BookingDate, x.Number, dishes.Name }).ToListAsync(); +// +// page.Data.ForEach(x => +// { +// x.BookingDate = details.First(item => item.OrderNo == x.No).BookingDate; +// x.TotalNumber = details.Where(item => item.OrderNo == x.No).Sum(item => item.Number); +// x.DishesInfos = details.Where(item => item.OrderNo == x.No).Select(item => new +// { +// item.Name +// }) +// .ToList(); +// }); +// +// return page; +// } +// +// public async Task UserCancelOrderAsync(OrderCancelRequest request) +// { +// var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); +// order.ThrowIfNull("订单不存在"); +// +// var bookingDeadline = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BookingDeadline, order.TenantId, order.No, new TimeOnly(20, 30, 00)); +// var dealLine = DateTime.Today.Add(bookingDeadline.ToTimeSpan()); +// (DateTime.Now >= dealLine).ThrowIfTrue("不在订单取消允许范围内,不可操作"); +// +// await ManagerCancelOrderAsync(new OrderCancelRequest +// { +// No = request.No, +// Reason = request.Reason, +// }); +// } +// +// +// public async Task ManagerCancelOrderAsync(OrderCancelRequest request) +// { +// var now = DateTime.Now; +// var userId = CurrentUser.GetIntSubjectId(); +// +// var order = await DbContext.Queryable().Where(x => x.No == request.No).FirstAsync(); +// order.ThrowIfNull("订单不存在"); +// +// await DbContext.Updateable() +// .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Cancel) +// .SetColumns(x => x.Remark == request.Reason) +// .SetColumns(x => x.ModifiedTime == now) +// .SetColumns(x => x.ModifiedBy == userId) +// .Where(x => x.No == request.No) +// .ExecuteCommandAsync(); +// +// await DbContext.Updateable() +// .SetColumns(x => x.Status == BalanceRecordStatusEnumType.Obsolute) +// .SetColumns(x => x.Remark == request.Reason) +// .SetColumns(x => x.ModifiedTime == now) +// .SetColumns(x => x.ModifiedBy == userId) +// .Where(x => x.No == request.No) +// .ExecuteCommandAsync(); +// +// if (order.Source == BalanceRecordSourceEnumType.LocalTenantSubsidies) +// { +// await CommonHelper.RetryAsync(async () => +// { +// var mealInfo = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); +// var setBalance = mealInfo.Balance + Math.Abs(order.Amount); +// await DbContext.Updateable() +// .SetColumns(x => x.Version == x.Version + 1) +// .SetColumns(x => x.Balance == setBalance) +// .SetColumns(x => x.ModifiedTime == now) +// .SetColumns(x => x.ModifiedBy == userId) +// .Where(x => x.Id == mealInfo.Id && x.Version == mealInfo.Version) +// .ExecuteCommandThrowVersionAsync(); +// }, 5); +// } +// } +// +// +// /// +// /// 检查我的订餐是否存在,存在抛出异常 +// /// +// /// +// /// +// /// +// private async Task CheckExistsMyBookingsAsync(DateTime bookingDate, ShiftEnumType shiftType) +// { +// var subjectId = CurrentUser.GetIntSubjectId(); +// var gainfo = await DbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); +// var jinkoCode = CurrentUser.GetUserClaim("jinko-code") ?? string.Empty; +// if (jinkoCode.IsPresent()) +// { +// gainfo = await DbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); +// } +// +// var start = bookingDate.Date.AddDays(-1); +// var end = start.AddDays(1).AddSeconds(-1); +// +// var exists = await DbContext.Queryable() +// .Where(x => x.ApplicationType == CanteenApplicationTypeEnumType.Booking) +// .Where(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) +// .Where(x => x.ShiftType == shiftType) +// .Where(x => x.UserNo == gainfo.No) +// .Where(x => x.CreateTime >= start && x.CreateTime <= end) +// .AnyAsync(); +// +// exists.ThrowIfTrue("不可重复订餐"); +// +// return gainfo; +// } +// } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs index 213b4c83..55530c2f 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs @@ -25,21 +25,19 @@ public class MiddleService : IMiddleService if (!await _dbContext.Queryable().AnyAsync(x => x.No == entity.No)) { await _dbContext.InsertAsync(entity); - await _balanceService.ReCalcAsync(entity.UserCode); } //获取人员余额 - return await _dbContext.Queryable() - .LeftJoin((x, ga) => x.UserCode == ga.Code) - .LeftJoin((x, ga, department) => ga.DepartmentNo == department.No) - .Where(x => x.UserCode == entity.UserCode) - .Select((x, ga, department) => new OrderResultResponse + return await _dbContext.Queryable() + .LeftJoin((x, department) => x.DepartmentNo == department.No) + .Where(x => x.Code == entity.UserCode) + .Select((x, department) => new OrderResultResponse { - UserCode = ga.Code, - UserName = ga.Name, - DepartmentNo = ga.DepartmentNo, + UserCode = x.Code, + UserName = x.Name, + DepartmentNo = x.DepartmentNo, DepartmentName = department.Name, - CurrentBalance = x.Balance + //CurrentBalance = x.Balance todo 余额 }) .SingleAsync(); } diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index 6b45062f..8f3c6eb2 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -1,4 +1,5 @@ using Dlhis.Canteen.Order.Dto; +using Dlhis.Canteen.Order.Repository; using Dlhis.Canteen.Setting.Services; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; @@ -11,6 +12,7 @@ using SqlSugar; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Order.Request; using Dlhis.Entity.Device; +using Dlhis.Entity.Device.Enums; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; @@ -20,23 +22,34 @@ using Kurisu.AspNetCore.UnifyResultAndValidation; namespace Dlhis.Canteen.Order.Services.Implements; -public class OrderService : BaseOrderService, IOrderService +public class OrderService : IOrderService { private readonly ICanteenSettingService _canteenSettingService; - private readonly IServiceProvider _serviceProvider; + 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 ILogger _logger; public OrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, ICurrentUser currentUser, - IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor, + IBalanceRepository balanceRepository, + IOrderRepository orderRepository, + RedisCache redisCache, ILogger logger) - : base(canteenSettingService, dbContext, currentUser, logger) { _canteenSettingService = canteenSettingService; - _serviceProvider = serviceProvider; + _dbContext = dbContext; + _currentUser = currentUser; _httpContextAccessor = httpContextAccessor; + _balanceRepository = balanceRepository; + _orderRepository = orderRepository; + _redisCache = redisCache; + _logger = logger; } /// @@ -56,28 +69,12 @@ public class OrderService : BaseOrderService, IOrderService /// public async Task AddAsync(OrderAddRequest request) { - try - { - if (_httpContextAccessor.HttpContext!.Request.Headers.TryGetValue("X-Requested-DeviceIp", out var ip)) - { - var device = await DbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).SingleAsync(); - device.ConnectionInfo = new - { - ip = ip.ToString() - }.ToJson(); + await SetDeviceIpInfoAsync(request.DeviceUniqueNo); - await DbContext.UpdateAsync(device); - } - - Logger.LogInformation("设备IP获取:{deviceNo} {ip}", request.DeviceUniqueNo, ip); - } - catch (Exception) - { - //ignore - } + await using var handler = await _redisCache.LockAsync($"consume:device:{request.UserUniqueNo}"); + handler.Acquired.ThrowIfFalse("请勿频繁消费"); var gainfo = await GetUserAsync(request); - return new OrderResultResponseWithLogs { Response = await ToDoAsync(request, gainfo) @@ -87,15 +84,15 @@ public class OrderService : BaseOrderService, IOrderService public async Task> GetOrdersAggregationAsync(PageDto page) { - var subjectId = CurrentUser.GetIntSubjectId(); - var gainfo = await DbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); - var jinkoCode = CurrentUser.GetUserClaim("jinko-code") ?? string.Empty; + var subjectId = _currentUser.GetIntSubjectId(); + var gainfo = await _dbContext.Queryable().Where(x => x.IdentityUserId == subjectId).FirstAsync(); + var jinkoCode = _currentUser.GetUserClaim("jinko-code") ?? string.Empty; if (jinkoCode.IsPresent()) { - gainfo = await DbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); + gainfo = await _dbContext.Queryable().Where(x => x.Code == jinkoCode).FirstAsync(); } - var pageResult = await DbContext.Queryable().Where(x => x.UserNo == gainfo.No) + var pageResult = await _dbContext.Queryable().Where(x => x.UserNo == gainfo.No) .LeftJoin((x, bPlace) => x.BusinessPlaceCode == bPlace.Code) .Where(x => x.ApplicationType == CanteenApplicationTypeEnumType.Booking) .OrderBy(x => x.CreateTime, OrderByType.Desc) @@ -110,7 +107,7 @@ public class OrderService : BaseOrderService, IOrderService .ToPageListAsync(page); var nos = pageResult.Data.Select(x => x.No).ToList(); - var dtos = await DbContext.Queryable() + var dtos = await _dbContext.Queryable() .LeftJoin((x, d) => x.DishesCode == d.CustomCode) .Where(x => nos.Contains(x.OrderNo)) .Select((x, d) => new DishesDto @@ -152,10 +149,10 @@ public class OrderService : BaseOrderService, IOrderService else if (request.UserUniqueNo.StartsWith("02")) { request.Source = BalanceRecordSourceEnumType.EMealVoucher; - var eMealVoucherQrCode = await DbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); + 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(); + gainfo = await _dbContext.Queryable().Where(x => x.Code == code).SingleAsync(); } else { @@ -165,7 +162,7 @@ public class OrderService : BaseOrderService, IOrderService else { //根据档案号 - gainfo = await DbContext.Queryable() + 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) @@ -181,7 +178,7 @@ public class OrderService : BaseOrderService, IOrderService else { //兼容hpt尚未删除人员后产生的消费数据 - gainfo ??= await DbContext.Queryable().AS("GainfoLeave") + gainfo ??= await _dbContext.Queryable().AS("GainfoLeave") .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.IdentityUserId == userId) @@ -192,33 +189,6 @@ public class OrderService : BaseOrderService, IOrderService } - /// - /// 获取成本日期 - /// - /// - /// - /// - private async Task GetCostDateAsync(BusinessShiftMealRelationshipEntity shift, DateTime costDateTime) - { - var switchTime = await _canteenSettingService.GetSettingAsync(CanteenSettingItemEnumType.BusinessSwitchTime, shift.TenantId, string.Empty); - - var costDate = costDateTime.Date; - - if (!switchTime.HasValue) - { - return costDate; - } - - var beginOfDay = costDateTime.Date; - - var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); - if (costDateTime < setTime) - costDate = costDateTime.Date.AddDays(-1); - - return costDate; - } - - /// /// 验证参数 /// @@ -231,15 +201,10 @@ public class OrderService : BaseOrderService, IOrderService var endOfDay = beginOfDay.AddDays(1).AddSeconds(-1); //今天所有消费记录 - var todayAllBalanceRecords = await DbContext.Queryable() - .Where(x => x.UserNo == order.UserNo) - .Where(x => x.Type == BalanceRecordTypeEnumType.Out) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) - .Where(x => x.CreateTime >= beginOfDay && x.CreateTime <= endOfDay) - .ToListAsync(); + var todayAllBalanceRecords = await _balanceRepository.GetBalanceRecordsAsync(order.UserCode, order.TenantId, beginOfDay, endOfDay); //超市营业点 - var supermarketCodes = await DbContext.Queryable() + var supermarketCodes = await _dbContext.Queryable() .Where(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) .Select(x => x.Code) .ToListAsync(); @@ -286,40 +251,6 @@ public class OrderService : BaseOrderService, IOrderService } } - - /// - /// 生成充值记录 - /// - /// - /// - /// - /// - private async Task GenerateConsumeRecordAsync(long orderId) - { - var order = await DbContext.Queryable().Where(x => x.Id == orderId).SingleAsync(); - //校验 - await Verify(order); - - await CommonHelper.RetryAsync(async () => - { - var gainfoMeal = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).SingleAsync(); - //金额+- - gainfoMeal.Balance += order.Amount; - (gainfoMeal.Balance < 0).ThrowIfTrue("余额不足,无法消费"); - - await DbContext.Updateable() - .SetColumns(x => x.Balance == gainfoMeal.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == order.UserCode && x.Version == gainfoMeal.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); - }, 5); - } - /// /// 离线消费处理 /// @@ -327,36 +258,35 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task HandleOfflineAsync(ConsumeRequest request, GainfoEntity gainfo) { - var registry = await DbContext.Queryable() - .Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo) - .FirstAsync(); - - var businessPlaceInfo = await DbContext.Queryable() - .LeftJoin((x, sr) => x.Code == sr.PlaceCode) - .Where(x => x.Code == registry.BusinessPlaceCode) - .Select((x, sr) => new + var bindingInfo = await _dbContext.Queryable() + .LeftJoin((x, b) => x.BusinessPlaceCode == b.Code) + .LeftJoin((x, b, bs) => bs.PlaceCode == b.Code) + .Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo && x.BusinessPlaceCode.HasValue) + .Select((x, b, bs) => new { - x.Code, - sr.ShiftCode, - x.TenantId + b.Code, bs.ShiftCode, Factory = x.TenantId }) - .FirstAsync(); + .SingleAsync(); + //获取当前消费餐次 - var shifts = await DbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); + var shifts = await _dbContext.Queryable() + .Where(x => x.ShiftCode == bindingInfo.ShiftCode).ToListAsync(); - var shift = shifts.FirstOrDefault(x => new TimePeriod() + var shift = shifts.FirstOrDefault(x => new TimePeriod { Start = x.Start, End = x.End }.IsPresent(request.CostDateTime)); - var costDate = await GetCostDateAsync(shift, request.CostDateTime); + var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, request.CostDateTime); var order = new CanteenOrderEntity { RequestJson = request.ToJson(), + No = SnowFlakeHelper.Instance.NextId().ToString(), ThirdNo = request.ThirdNo, + IsOffline = request.IsOffline, Type = BalanceRecordTypeEnumType.Out, ApplicationType = CanteenApplicationTypeEnumType.Device, Name = gainfo.Name, @@ -364,7 +294,7 @@ public class OrderService : BaseOrderService, IOrderService UserNo = gainfo.No, UserCode = gainfo.Code, Amount = request.Amount, - BusinessPlaceCode = businessPlaceInfo?.Code, + BusinessPlaceCode = bindingInfo.Code, BusinessShiftCode = shift?.ShiftCode, ShiftType = shift?.ShiftType, CostDate = costDate.Date, @@ -372,9 +302,7 @@ public class OrderService : BaseOrderService, IOrderService DepartmentNo = gainfo.DepartmentNo, DeviceUniqueNo = request.DeviceUniqueNo, - No = SnowFlakeHelper.Instance.NextId().ToString(), - IsOffline = request.IsOffline, - IsCostCrossTenant = businessPlaceInfo!.TenantId != gainfo.Factory, + IsCostCrossTenant = bindingInfo.Factory != gainfo.Factory, OriginTenantId = gainfo.Factory, Werks = gainfo.Werks, @@ -386,48 +314,12 @@ public class OrderService : BaseOrderService, IOrderService CostDateTime = request.CostDateTime, //标识当前消费的基地 - TenantId = businessPlaceInfo!.TenantId, + TenantId = bindingInfo.Factory, }; - - await DbContext.InsertReturnIdentityAsync(order); - - await CommonHelper.RetryAsync(async () => - { - var redisCache = _serviceProvider.GetService(); - await using var handler = await redisCache.LockAsync($"gainfo:meal:{order.UserNo}", retryInterval: TimeSpan.FromSeconds(1)); - if (handler.Acquired) - { - var gainfoMeal = await DbContext.Queryable().Where(x => x.UserCode == order.UserCode).FirstAsync(); - if (gainfoMeal == null) - { - gainfoMeal = new GainfoMealEntity - { - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - Version = 0, - UserCode = order.UserCode, - }; - - await DbContext.InsertAsync(gainfoMeal); - } - - //金额+- - gainfoMeal.Balance += order.Amount; - - await DbContext.Updateable() - .SetColumns(x => x.Balance == gainfoMeal.Balance) - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == order.UserCode && x.Version == gainfoMeal.Version) //必须与查询时的version一致 - .ExecuteCommandThrowVersionAsync(); - - //写入记录 - var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); - } - }, 5); + await _dbContext.InsertReturnIdentityAsync(order); + var balanceRecord = order.Adapt(); + await _dbContext.InsertAsync(balanceRecord); } /// @@ -439,79 +331,83 @@ public class OrderService : BaseOrderService, IOrderService private async Task ConsumeAsync(ConsumeRequest request, GainfoEntity gainfo) { var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); + var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); - //(request.Source == BalanceRecordSourceEnumType.QrCode && checkData.BusinessPlaceTenantId != gainfo.Factory).ThrowIfTrue("禁止跨厂区消费"); - - var order = new CanteenOrderEntity + if (request.Image.IsPresent()) { - RequestJson = request.ToJson(), - ThirdNo = request.ThirdNo, - Type = BalanceRecordTypeEnumType.Out, - ApplicationType = CanteenApplicationTypeEnumType.Device, - Name = gainfo.Name, - Source = request.Source, - UserNo = gainfo.No, - UserCode = gainfo.Code, - Amount = request.Amount, - BusinessPlaceCode = checkData.BusinessPlaceCode, - BusinessShiftCode = checkData.BusinessShiftCode, - ShiftType = checkData.Shift.ShiftType, - CostDate = checkData.CostDate, - CostId = gainfo.CostId, - DepartmentNo = gainfo.DepartmentNo, - DeviceUniqueNo = request.DeviceUniqueNo, + await _dbContext.InsertAsync(new ImageRecordEntity + { + No = order.No, + TableName = "B", + Image = request.Image + }); + } - No = SnowFlakeHelper.Instance.NextId().ToString(), - IsOffline = request.IsOffline, - IsCostCrossTenant = gainfo.Factory != checkData.BusinessPlaceTenantId, + await TryConsumeAsync(order.Id, async () => + { + //校验 + await Verify(order); - OriginTenantId = gainfo.Factory, - Werks = gainfo.Werks, - Status = BalanceRecordStatusEnumType.Valid, + await _balanceRepository.CheckConsumableAsync(order.UserCode, order.TenantId, order.Amount); - OrderStatus = CanteenOrderStatusEnumType.Wait, + //写入记录 + var balanceRecord = order.Adapt(); + await _dbContext.InsertAsync(balanceRecord); + }); + } - CostDateTime = request.CostDateTime, + 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(); - //标识当前消费的基地 - TenantId = checkData.BusinessPlaceTenantId - }; + if (originData.IsEmpty()) + { + msg = "无法识别"; + return new DefaultApiResult + { + Code = ApiStateCode.Error, + Data = null, + Msg = msg + }; + } - var orderId = await DbContext.InsertReturnIdentityAsync(order); + var originDataItems = originData.Split(":"); + var no = originDataItems[0]; + var idNo = originDataItems[3]; try { - await GenerateConsumeRecordAsync(orderId); - - if (request.Image.IsPresent()) - { - await DbContext.InsertAsync(new ImageRecordEntity - { - No = order.No, - TableName = "B", - Image = request.Image - }); - } - - await DbContext.Updateable() - .SetColumns(x => x.OrderStatus == CanteenOrderStatusEnumType.Success) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.Id == orderId) - .ExecuteCommandAsync(); + await EMealVoucherConsumeAsync(request.Adapt(), gainfo, originData, no, idNo); } 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; + msg = ex.Message; } - } + //获取餐券余额 + var data = await _dbContext.Queryable() + .LeftJoin((x, d) => x.No == d.No) + .LeftJoin((x, d, u) => u.IdNo == d.IdNo) + .Where((x, d) => x.No == no && d.IdNo == idNo) + .Select((x, d, u) => new OrderResultResponse + { + UserCode = u.Code, + UserName = u.Name, + CurrentBalance = d.Balance + }) + .SingleAsync(); + + return new DefaultApiResult + { + Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = msg + }; + } /// /// 分支处理在线/离线消费 @@ -524,26 +420,35 @@ public class OrderService : BaseOrderService, IOrderService var message = string.Empty; if (request.Source == BalanceRecordSourceEnumType.EMealVoucher) + return await EMealVoucherConsumeAsync(request, gainfo); + + if (request.IsOffline) { - var eMealVoucherQrCode = await DbContext.Queryable().Where(x => x.QrCode == request.UserUniqueNo).SingleAsync(); - if (eMealVoucherQrCode == null) - { - message = "无法识别"; - return new DefaultApiResult - { - Code = ApiStateCode.Error, - Data = null, - Msg = message - }; - } - - var originData = eMealVoucherQrCode.OriginData.Split(':'); - var no = originData[0]; - var idNo = originData[3]; - try { - await EMealVoucherConsumeAsync(request.Adapt(), gainfo, eMealVoucherQrCode.OriginData, no, idNo); + if (request.ThirdNo.IsPresent() && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) + { + await using var handler = await _redisCache.LockAsync($"consume:device-offline:{request.ThirdNo}"); + if (handler.Acquired) + { + await HandleOfflineAsync(request.Adapt(), gainfo); + } + } + else + { + await HandleOfflineAsync(request.Adapt(), gainfo); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); + } + } + else + { + try + { + await ConsumeAsync(request.Adapt(), gainfo); } catch (Exception ex) { @@ -590,92 +495,33 @@ public class OrderService : BaseOrderService, IOrderService Msg = message }; } - else + + var currentFactory = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).Select(x => x.TenantId).SingleAsync(); + var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory); + + //获取人员余额 + var data = await _dbContext.Queryable() + .LeftJoin((x, department) => x.DepartmentNo == department.No) + .Where(x => x.Code == gainfo.Code) + .Select((x, department) => new OrderResultResponse + { + UserCode = x.Code, + UserName = x.Name, + DepartmentNo = x.DepartmentNo, + DepartmentName = department.Name, + CurrentBalance = balance + }) + .SingleAsync(); + + return new DefaultApiResult { - if (request.IsOffline) - { - if (request.ThirdNo.IsPresent()) - { - var redisCache = _serviceProvider.GetService(); - - await using var handler = await redisCache.LockAsync($"consume:device:{request.ThirdNo}"); - if (handler.Acquired && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) - { - 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); - } - catch (Exception ex) - { - Logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, 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; - } - } - - //获取人员余额 - var data = await DbContext.Queryable() - .LeftJoin((x, ga) => x.UserCode == ga.Code) - .LeftJoin((x, ga, department) => ga.DepartmentNo == department.No) - .Where(x => x.UserCode == gainfo.Code) - .Select((x, ga, department) => new OrderResultResponse - { - UserCode = ga.Code, - UserName = ga.Name, - DepartmentNo = ga.DepartmentNo, - DepartmentName = department.Name, - CurrentBalance = x.Balance - }) - .SingleAsync(); - - return new DefaultApiResult - { - Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, - Data = data, - Msg = 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; - } + Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = message + }; } + /// /// 根据二维码获取消费用户 /// @@ -683,74 +529,57 @@ public class OrderService : BaseOrderService, IOrderService /// private async Task GetUserByQrCodeAsync(string qrCode) { - var gainfoQrCode = await DbContext.Queryable().Where(x => x.QrCode == qrCode).SingleAsync(); + 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(); + 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).SingleAsync(); - (now >= current.Start && now <= current.End).ThrowIfFalse("餐券已过期"); + var nowDate = DateTime.Now.Date; - var eMealVoucher = await DbContext.Queryable().Where(x => x.No == no).SingleAsync(); + var current = await _dbContext.Queryable() + .Where(x => x.No == no && x.IdNo == idNo) + .Select(x => new { x.Start, x.End }) + .SingleAsync(); + + (nowDate >= current.Start && nowDate <= 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) + var isSupermarket = await _dbContext.Queryable().Where(x => x.Code == checkData.BusinessPlaceCode) .Select(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) .SingleAsync(); + isSupermarket.ThrowIfTrue("禁止在超市消费"); - eMealVoucher.Factories.Contains(checkData.BusinessPlaceTenantId).ThrowIfFalse("禁止在当前厂区消费"); + eMealVoucher.Factories.Contains(checkData.Factory).ThrowIfFalse("禁止在当前厂区消费"); - var order = new CanteenOrderEntity - { - RequestJson = request.ToJson(), - ThirdNo = request.ThirdNo, - Type = BalanceRecordTypeEnumType.Out, - ApplicationType = CanteenApplicationTypeEnumType.Device, - Name = gainfo.Name, - Source = request.Source, - UserNo = 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, + gainfo.No = originData; + var order = await _orderRepository.CreateDeviceConsumeOrderAsync(checkData, request, gainfo); - 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); - - try + await TryConsumeAsync(order.Id, async () => { await CommonHelper.RetryAsync(async () => { - var detail = await DbContext.Queryable().Where(x => x.No == no && x.IdNo == idNo).SingleAsync(); + 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() + await _dbContext.Updateable() .SetColumns(x => x.Balance == detail.Balance) .SetColumns(x => x.Version == x.Version + 1) .SetColumns(x => x.ModifiedTime == DateTime.Now) @@ -759,26 +588,9 @@ public class OrderService : BaseOrderService, IOrderService //写入记录 var balanceRecord = order.Adapt(); - await DbContext.InsertAsync(balanceRecord); + await _dbContext.InsertAsync(balanceRecord); }, 5); - - 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.Result == ex.Message) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.Id == orderId) - .ExecuteCommandAsync(); - - throw; - } + }); } /// @@ -789,28 +601,28 @@ public class OrderService : BaseOrderService, IOrderService /// 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(); + var device = await _dbContext.Queryable().Where(x => x.UniqueNo == deviceUniqueNo).SingleAsync(); device.ThrowIfNull("设备尚未登记"); device.Enable.ThrowIfFalse("设备已被停用"); + (device.Type != DeviceTypeEnumType.Meal).ThrowIfTrue("设备类型有误"); - var businessPlaceInfo = await DbContext.Queryable() - .LeftJoin((x, sr) => x.Code == sr.PlaceCode) - .Where(x => x.Code == registry.BusinessPlaceCode) - .Select((x, sr) => new + 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 { - x.Code, - sr.ShiftCode, - x.TenantId + b.Code, bs.ShiftCode, Factory = x.TenantId }) .SingleAsync(); - businessPlaceInfo.ThrowIfNull("设备尚未登记营业点"); + bindingInfo.ThrowIfNull("设备尚未登记营业点"); //获取当前消费餐次 - var shifts = await DbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); + 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 @@ -818,17 +630,88 @@ public class OrderService : BaseOrderService, IOrderService Start = x.Start, End = x.End }.IsPresent(costDateTime)); - shift.ThrowIfNull("非就餐时间,无法消费"); + shift.ThrowIfNull("非就餐时间"); - var costDate = await GetCostDateAsync(shift, costDateTime); + var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, costDateTime); return new OrderPreCheckDto { + Factory = bindingInfo.Factory, CostDate = costDate, - Shift = shift, - BusinessPlaceTenantId = businessPlaceInfo.TenantId, - BusinessPlaceCode = businessPlaceInfo.Code, - BusinessShiftCode = businessPlaceInfo.ShiftCode + 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重复提交 + /// + /// + /// + private async Task ToCheckAbleOfflineConsumeAsync(string thirdNo) + { + var order = await _dbContext.Queryable() + .Where(x => x.ThirdNo == thirdNo) + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .SingleAsync(); + + if (order == null) + return true; + + //存在在线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/Setting/Services/ICanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs index 9872d134..2376b87c 100644 --- a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs @@ -45,4 +45,6 @@ public interface ICanteenSettingService : IScopeDependency Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default); + + Task GetCostDateAsync(string factory, DateTime costDateTime); } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs index 41f3791b..c5654f0d 100644 --- a/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/CanteenSettingService.cs @@ -139,6 +139,24 @@ public class CanteenSettingService : ICanteenSettingService return defaultValue; } + /// + public async Task GetCostDateAsync(string factory, DateTime costDateTime) + { + var switchTime = await GetSettingAsync(CanteenSettingItemEnumType.BusinessSwitchTime, factory, string.Empty); + + var costDate = costDateTime.Date; + + if (!switchTime.HasValue) return costDate; + + var beginOfDay = costDateTime.Date; + + var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); + if (costDateTime < setTime) + costDate = costDateTime.Date.AddDays(-1); + + return costDate; + } + //*********************************************** help methods ***************************************************// private async Task CheckDuplicateSettingAsync(CanteenSettingSaveRequest request) diff --git a/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs b/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs index d2cc2328..0928de41 100644 --- a/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs +++ b/Src/Dlhis.Entity/Basic/Entity/BusinessShiftMealRelationshipEntity.cs @@ -10,37 +10,37 @@ namespace Dlhis.Entity.Basic.Entity; public class BusinessShiftMealRelationshipEntity : SugarBaseEntity, ITenantId { /// - /// 餐别Code + /// 餐别Code /// [SugarColumn(ColumnDescription = "餐别Code", UniqueGroupNameList = new[] { "idx_CanteenBusinessShiftRelationship_shiftcode_shifttype" })] public Guid ShiftCode { get; set; } /// - /// 餐别手工号 + /// 餐别手工号 /// [SugarColumn(ColumnDescription = "餐别编码")] public string ShiftCustomCode { get; set; } /// - /// 餐次 + /// 餐次 /// [SugarColumn(ColumnDescription = "餐次", ColumnDataType = "varchar(50)", SqlParameterDbType = typeof(EnumToStringConvert), UniqueGroupNameList = new[] { "idx_CanteenBusinessShiftRelationship_shiftcode_shifttype" })] public ShiftEnumType ShiftType { get; set; } /// - /// 经营开始时间 + /// 经营开始时间 /// [SugarColumn(ColumnDescription = "经营开始时间")] public TimeOnly Start { get; set; } /// - /// 经营结束时间 + /// 经营结束时间 /// [SugarColumn(ColumnDescription = "经营结束时间")] public TimeOnly End { get; set; } /// - /// 序号 + /// 序号 /// [SugarColumn(ColumnDescription = "序号")] public int Sequence { get; set; } diff --git a/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs b/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs index 1d311bc6..5e779cd9 100644 --- a/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs +++ b/Src/Dlhis.Entity/Basic/Entity/GainfoMealEntity.cs @@ -1,43 +1,43 @@ -using Kurisu.AspNetCore.DataAccess.Entity; -using Kurisu.AspNetCore.DataAccess.SqlSugar; -using SqlSugar; - -namespace Dlhis.Entity.Basic.Entity -{ - /// - /// 就餐人员 - /// - [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] - public class GainfoMealEntity : SugarBaseEntity, IVersion - { - /// - /// 工号 - /// - [SugarColumn(IsNullable = false, ColumnDescription = "工号", UniqueGroupNameList = new[] { "idx_CanteenUser_UserCode" })] - public string UserCode { get; set; } - - /// - /// 余额 - /// - [SugarColumn(ColumnDescription = "余额")] - public decimal Balance { get; set; } - - /// - /// 余额(充值) - /// - [SugarColumn(ColumnDescription = "余额(充值)")] - public decimal BalanceOfRecharge { get; set; } - - /// - /// 余额(餐补) - /// - [SugarColumn(ColumnDescription = "余额(餐补)")] - public decimal BalanceOfSubsidies { get; set; } - - /// - /// 版本号 - /// - [SugarColumn(ColumnDescription = "版本号")] - public int Version { get; set; } - } -} \ No newline at end of file +// using Kurisu.AspNetCore.DataAccess.Entity; +// using Kurisu.AspNetCore.DataAccess.SqlSugar; +// using SqlSugar; +// +// namespace Dlhis.Entity.Basic.Entity +// { +// /// +// /// 就餐人员 +// /// +// [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] +// public class GainfoMealEntity : SugarBaseEntity, IVersion +// { +// /// +// /// 工号 +// /// +// [SugarColumn(IsNullable = false, ColumnDescription = "工号", UniqueGroupNameList = new[] { "idx_CanteenUser_UserCode" })] +// public string UserCode { get; set; } +// +// /// +// /// 余额 +// /// +// [SugarColumn(ColumnDescription = "余额")] +// public decimal Balance { get; set; } +// +// /// +// /// 余额(充值) +// /// +// [SugarColumn(ColumnDescription = "余额(充值)")] +// public decimal BalanceOfRecharge { get; set; } +// +// /// +// /// 余额(餐补) +// /// +// [SugarColumn(ColumnDescription = "余额(餐补)")] +// public decimal BalanceOfSubsidies { get; set; } +// +// /// +// /// 版本号 +// /// +// [SugarColumn(ColumnDescription = "版本号")] +// public int Version { get; set; } +// } +// } \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs new file mode 100644 index 00000000..8f698195 --- /dev/null +++ b/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs @@ -0,0 +1,29 @@ +using Kurisu.AspNetCore.DataAccess.SqlSugar; +using SqlSugar; + +namespace Dlhis.Entity.Canteen.Entities; + +/// +/// 钱包 +/// +[SugarTable(TableName = "Wallets", TableDescription = "钱包")] +public class WalletEntity : SugarBaseEntity +{ + /// + /// 钱包编码 + /// + [SugarColumn(IsNullable = false)] + public string Code { get; set; } + + /// + /// 钱包名称 + /// + [SugarColumn(IsNullable = false)] + public string Name { get; set; } + + /// + /// 厂区 + /// + [SugarColumn(IsNullable = false, UniqueGroupNameList = new[] { "idx_wallets_facotry" })] + public string Factory { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs b/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs index 54cc684e..d3ab0bd7 100644 --- a/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs +++ b/Src/Dlhis.Entity/Canteen/Enums/CanteenApplicationTypeEnumType.cs @@ -8,6 +8,9 @@ namespace Dlhis.Entity.Canteen.Enums; [Description("餐饮应用类型")] public enum CanteenApplicationTypeEnumType { + /// + /// 设备消费 + /// [Description("设备消费")] Device = 100, diff --git a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs index e21572d2..bb9809ff 100644 --- a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs +++ b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs @@ -1,4 +1,7 @@ using Dlhis.Entity.Basic.Entity; +using Kurisu.AspNetCore.DataAccess.Entity; +using Kurisu.AspNetCore.DataAccess.SqlSugar; +using SqlSugar; namespace Dlhis.Middle.Entities { @@ -6,4 +9,42 @@ namespace Dlhis.Middle.Entities { public new int Id { get; set; } } + + + /// + /// 就餐人员 + /// + [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] + public class GainfoMealEntity : SugarBaseEntity, IVersion + { + /// + /// 工号 + /// + [SugarColumn(IsNullable = false, ColumnDescription = "工号", UniqueGroupNameList = new[] { "idx_CanteenUser_UserCode" })] + public string UserCode { get; set; } + + /// + /// 余额 + /// + [SugarColumn(ColumnDescription = "余额")] + public decimal Balance { get; set; } + + /// + /// 余额(充值) + /// + [SugarColumn(ColumnDescription = "余额(充值)")] + public decimal BalanceOfRecharge { get; set; } + + /// + /// 余额(餐补) + /// + [SugarColumn(ColumnDescription = "余额(餐补)")] + public decimal BalanceOfSubsidies { get; set; } + + /// + /// 版本号 + /// + [SugarColumn(ColumnDescription = "版本号")] + public int Version { get; set; } + } } \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs index 5533267d..6b91adbc 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJIPService.cs @@ -3,7 +3,7 @@ using Dlhis.ScheduledTask.Common.Response; namespace Dlhis.ScheduledTask.Common.Services; -public interface IJinkoJIPService : ISingletonDependency +public interface IJinkoJipService : ISingletonDependency { Task UploadAttendancesAsync(List requests); diff --git a/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs new file mode 100644 index 00000000..6b91adbc --- /dev/null +++ b/Src/Dlhis.ScheduledTask/Common/Services/IJinkoJipService.cs @@ -0,0 +1,36 @@ +using Dlhis.ScheduledTask.Common.Request; +using Dlhis.ScheduledTask.Common.Response; + +namespace Dlhis.ScheduledTask.Common.Services; + +public interface IJinkoJipService : ISingletonDependency +{ + Task UploadAttendancesAsync(List requests); + + Task SyncMealSupplementRecordsAsync(List requests); + + + /// + /// 获取出差信息 + /// + /// + /// + /// + /// + Task GetTravelInfosByFactoryAsync(DateTime begin, DateTime end, string fatory); + + /// + /// 获取出差信息 + /// + /// + /// + /// + /// + Task GetTravelInfosByUserCodeAsync(DateTime begin, DateTime end, string userCode); + + + Task SyncFaceToSapAsync(string userCode, string base64); + + + string IssueToken(); +} \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs index 6f177b4e..51d56928 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/HangfireService.cs @@ -12,9 +12,9 @@ public class HangfireService : IHangfireService { private readonly RedisCache _redisCache; private readonly IDbContext _dbContext; - private readonly IJinkoJIPService _jinkoJipService; + private readonly IJinkoJipService _jinkoJipService; - public HangfireService(RedisCache redisCache, IDbContext dbContext, IJinkoJIPService jinkoJipService) + public HangfireService(RedisCache redisCache, IDbContext dbContext, IJinkoJipService jinkoJipService) { _redisCache = redisCache; _dbContext = dbContext; diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs index 9ed40b41..88d207b2 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJIPService.cs @@ -10,13 +10,13 @@ using Newtonsoft.Json.Linq; namespace Dlhis.ScheduledTask.Common.Services.Implements; -public class JinkoJIPService : IJinkoJIPService +public class JinkoJipService : IJinkoJipService { private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfiguration _configuration; - public JinkoJIPService(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) + public JinkoJipService(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) { _httpClientFactory = httpClientFactory; _logger = logger; diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs new file mode 100644 index 00000000..88d207b2 --- /dev/null +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/JinkoJipService.cs @@ -0,0 +1,213 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using Dlhis.ScheduledTask.Common.Request; +using Dlhis.ScheduledTask.Common.Response; +using Kurisu.AspNetCore.Utils.Extensions; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json.Linq; + +namespace Dlhis.ScheduledTask.Common.Services.Implements; + +public class JinkoJipService : IJinkoJipService +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public JinkoJipService(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + _configuration = configuration; + } + + private HttpClient GetHttpClient() + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_configuration.GetSection("JinkoOptions:Jip:BaseUrl").Value); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", IssueToken()); + + return httpClient; + } + + public async Task UploadAttendancesAsync(List requests) + { + var requestUrl = "/JIP/HCM/HCM_SWIPE_DATA_SAVE"; + + var body = new + { + T_DATA = requests, + }; + + var json = body.ToJson(); + + //_logger.LogInformation("上传考勤记录请求:{json}", json); + + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(requestUrl, content); + var responseJson = await response.Content.ReadAsStringAsync(); + + _logger.LogInformation("上传考勤记录响应:{json}", responseJson); + + try + { + return responseJson.ToObject(); + } + catch (Exception) + { + return new UploadAttendanceResponse + { + OLX = "99", + OMS = "接口响应数据异常,无法反序列化" + }; + } + } + + + public async Task SyncMealSupplementRecordsAsync(List requests) + { + var requestUrl = "/JIP/DHR/DHR_DL_CALLBACK"; + + var json = requests.ToJson(Kurisu.AspNetCore.Utils.Extensions.JsonExtensions.DefaultSetting); + + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(requestUrl, content); + var responseJson = await response.Content.ReadAsStringAsync(); + + _logger.LogInformation("餐补状态同步响应:{json}", responseJson); + + var jobj = JObject.Parse(responseJson); + if (jobj.TryGetValue("exp", out var exp)) + { + true.ThrowIfTrue(exp.Value()); + } + + if (jobj.TryGetValue("message", out var message) && !jobj.ContainsKey("code")) + { + true.ThrowIfTrue(message.Value()); + } + + (jobj.GetValue("code").Value() != 200).ThrowIfTrue(jobj.GetValue("message").Value()); + + return responseJson; + } + + + public async Task GetTravelInfosByFactoryAsync(DateTime begin, DateTime end, string factory) + { + const string apiUrl = "/JIP/HCM/HCM_TRAVELINFO"; + + var beginDay = begin.ToString("yyyyMMdd"); + var endDay = end.ToString("yyyyMMdd"); + + var obj = new + { + I_PAGE = 1, + I_LINE = 500, + IV_BEGDA = beginDay, + IV_ENDDA = endDay, + IT_CCMDD = new List + { + new + { + IV_CCMDD = factory + } + } + }; + + var body = obj.ToJson(); + + var content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(apiUrl, content); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + return jsonResponse.ToObject(); + } + + public async Task GetTravelInfosByUserCodeAsync(DateTime begin, DateTime end, string userCode) + { + const string apiUrl = "/JIP/HCM/HCM_TRAVELINFO"; + + var beginDay = begin.ToString("yyyyMMdd"); + var endDay = end.ToString("yyyyMMdd"); + + //IV_GONGH IV_ORGID + //IT_2002GH + var obj = new + { + IV_BEGDA = beginDay, + IV_ENDDA = endDay, + IT_2002GH = new List + { + new + { + IV_GONGH = userCode + } + } + }; + + var body = obj.ToJson(); + + var content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(apiUrl, content); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + return jsonResponse.ToObject(); + } + + public async Task SyncFaceToSapAsync(string userCode, string base64) + { + const string apiUrl = "/JIP/HCM/HCM_I_PHOTO"; + var obj = new + { + I_WORKCODE = userCode, + I_PHOTO_STR = base64 + }; + + var body = obj.ToJson(); + + var content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await GetHttpClient().PostAsync(apiUrl, content); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + return jsonResponse.ToObject(); + } + + + public string IssueToken() + { + var jwtKey = _configuration.GetSection("JinkoOptions:Jip:Key").Value; + var jwtSecret = _configuration.GetSection("JinkoOptions:Jip:Secret").Value; + const int min = 5; + + var now = DateTime.Now.AddMinutes(-min); + var expires = DateTime.Now.AddMinutes(min); + + var claims = new List + { + new(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(now).ToUnixTimeSeconds()}"), + new(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(expires).ToUnixTimeSeconds()}"), + new(JwtRegisteredClaimNames.Iss, jwtKey), + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)); + var descriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims), + Issuer = jwtKey, + IssuedAt = now, + NotBefore = now, + Expires = expires, + SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256) + }; + + var jwtHandler = new JwtSecurityTokenHandler(); + var securityToken = jwtHandler.CreateToken(descriptor); + return jwtHandler.WriteToken(securityToken); + } +} \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs b/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs index e4282e7d..e3ec2c19 100644 --- a/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs +++ b/Src/Dlhis.ScheduledTask/Common/Services/Implements/SapBusinessTripService.cs @@ -15,12 +15,12 @@ namespace Dlhis.ScheduledTask.Common.Services.Implements; public class SapBusinessTripService : ISapBusinessTripService { - private readonly IJinkoJIPService _jinkoJipService; + private readonly IJinkoJipService _jinkoJipService; private readonly IDbContext _dbContext; private readonly IDevicePermissionApi _devicePermissionApi; private readonly ILogger _logger; - public SapBusinessTripService(IJinkoJIPService jinkoJipService, IDbContext dbContext, IDevicePermissionApi devicePermissionApi, ILogger logger) + public SapBusinessTripService(IJinkoJipService jinkoJipService, IDbContext dbContext, IDevicePermissionApi devicePermissionApi, ILogger logger) { _jinkoJipService = jinkoJipService; _dbContext = dbContext; diff --git a/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs b/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs index 8a67cd70..d81ca771 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/MealSupplementHandlerJob.cs @@ -6,8 +6,6 @@ using Dlhis.ScheduledTask.Attributes; using Dlhis.ScheduledTask.Common.Request; using Dlhis.ScheduledTask.Common.Services; using Hangfire; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; -using Kurisu.AspNetCore.Utils; namespace Dlhis.ScheduledTask.Jobs; @@ -17,10 +15,10 @@ namespace Dlhis.ScheduledTask.Jobs; [RecurringJob(nameof(MealSupplementHandlerJob), "0 0/30 * * * ?", Name = "餐补核算")] public class MealSupplementHandlerJob : BaseJob { - private readonly IJinkoJIPService _jinkoJIPService; + private readonly IJinkoJipService _jinkoJIPService; public MealSupplementHandlerJob(IServiceProvider serviceProvider, - IJinkoJIPService jinkoJIPService) : base(serviceProvider) + IJinkoJipService jinkoJIPService) : base(serviceProvider) { _jinkoJIPService = jinkoJIPService; } @@ -85,23 +83,6 @@ public class MealSupplementHandlerJob : BaseJob } } - - //创建余额数据 - var mealUser = await DbContext.Queryable().Where(x => x.UserCode == gainfo.Code).SingleAsync(); - if (mealUser == null) - { - mealUser = new GainfoMealEntity - { - Balance = 0, - BalanceOfRecharge = 0, - BalanceOfSubsidies = 0, - UserCode = gainfo.Code, - Version = 0, - }; - - await DbContext.InsertAsync(mealUser); - } - await SetMealSupplementAsync(gainfo, item); } } @@ -111,19 +92,6 @@ public class MealSupplementHandlerJob : BaseJob await DbContext.Client.Ado.BeginTranAsync(); try { - await CommonHelper.RetryAsync(async () => - { - var mealUser = await DbContext.Queryable().Where(x => x.UserCode == mealSupplement.Code).FirstAsync(); - var b = mealUser.Balance += mealSupplement.Amount; - - await DbContext.Updateable() - .SetColumns(x => x.Version == x.Version + 1) - .SetColumns(x => x.Balance == b) - .SetColumns(x => x.ModifiedTime == DateTime.Now) - .Where(x => x.UserCode == mealUser.UserCode && x.Version == mealUser.Version) - .ExecuteCommandThrowVersionAsync(); - }, 5); - await DbContext.InsertAsync(new BalanceRecordEntity { Amount = mealSupplement.Amount, diff --git a/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs b/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs index 843c2531..73af6cd2 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/SyncFaceJob.cs @@ -7,10 +7,10 @@ namespace Dlhis.ScheduledTask.Jobs; [RecurringJob(nameof(SyncFaceJob), "0 0/15 * * * ?", Name = "face数据同步sap")] public class SyncFaceJob : BaseJob { - private readonly IJinkoJIPService _jinkoJipService; + private readonly IJinkoJipService _jinkoJipService; /// - public SyncFaceJob(IServiceProvider serviceProvider, IJinkoJIPService jinkoJipService) : base(serviceProvider) + public SyncFaceJob(IServiceProvider serviceProvider, IJinkoJipService jinkoJipService) : base(serviceProvider) { _jinkoJipService = jinkoJipService; } diff --git a/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs b/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs index 35b71940..12e6e305 100644 --- a/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs +++ b/Src/Dlhis.ScheduledTask/Jobs/SyncOriginCardInfoJob.cs @@ -14,9 +14,9 @@ namespace Dlhis.ScheduledTask.Jobs; [RecurringJob(nameof(SyncOriginCardInfoJob), "0 0/1 * * * ?", Name = "上传考勤记录")] public class SyncOriginCardInfoJob : BaseJob { - private readonly IJinkoJIPService _jinkoJIPService; + private readonly IJinkoJipService _jinkoJIPService; - public SyncOriginCardInfoJob(IJinkoJIPService jinkoJIPService, IServiceProvider serviceProvider) : base(serviceProvider) + public SyncOriginCardInfoJob(IJinkoJipService jinkoJIPService, IServiceProvider serviceProvider) : base(serviceProvider) { _jinkoJIPService = jinkoJIPService; } diff --git a/Src/Dlhis.ScheduledTask/Program.cs b/Src/Dlhis.ScheduledTask/Program.cs index 999976ce..5289eb45 100644 --- a/Src/Dlhis.ScheduledTask/Program.cs +++ b/Src/Dlhis.ScheduledTask/Program.cs @@ -26,6 +26,6 @@ public class Startup : DefaultStartup { base.Configure(app, env); - var token = app.ApplicationServices.GetService().IssueToken(); + var token = app.ApplicationServices.GetService().IssueToken(); } } \ No newline at end of file From e322d7e179abc733e7b7991eb8c62e20d00a6eb5 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Mon, 28 Oct 2024 16:28:31 +0800 Subject: [PATCH 09/23] feat middleHealthz --- .../BusinessPlaceShiftTinyResponse.cs | 2 +- .../SysData/Controller/MiddlesController.cs | 29 +++++++++++++++++++ .../SysData/Request/HealthCheckRequest.cs | 9 ++++++ .../Request/MiddleHealthCheckRequest.cs | 6 ++++ .../SysData/Services/IHealthCheckService.cs | 6 ++++ .../Services/Implements/HealthCheckService.cs | 10 +++++++ .../Services/Implements/SysDataService.cs | 25 ++++++++++++++-- 7 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs create mode 100644 Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs diff --git a/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs b/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs index 359754c7..40217d10 100644 --- a/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs +++ b/Src/Dlhis.Basic/BusinessShift/Response/BusinessPlaceShiftTinyResponse.cs @@ -3,7 +3,7 @@ public class BusinessPlaceShiftTinyResponse { /// - /// 无用字段,暂时不删除 //todo + /// 无用字段,暂时不删除 /// public string CustomCode { get; set; } diff --git a/Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs b/Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs new file mode 100644 index 00000000..c3485490 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Controller/MiddlesController.cs @@ -0,0 +1,29 @@ +using Dlhis.Basic.Common.SysData.Request; +using Dlhis.Basic.Common.SysData.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Dlhis.Basic.Common.SysData.Controller; + +[Authorize] +[Route("api/[controller]")] +[ApiController] +public class MiddlesController : ControllerBase +{ + private readonly IHealthCheckService _healthCheckService; + + public MiddlesController(IHealthCheckService healthCheckService) + { + _healthCheckService = healthCheckService; + } + + /// + /// 健康检查 + /// + /// + [HttpPost("healthz")] + public async Task CheckAsync(MiddleHealthCheckRequest request) + { + await _healthCheckService.CheckAsync(request.Factories); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs b/Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs new file mode 100644 index 00000000..4e530661 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Request/HealthCheckRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace Dlhis.Basic.Common.SysData.Request; + +public class HealthCheckRequest +{ + [MinLength(1)] + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs b/Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs new file mode 100644 index 00000000..c08b33c0 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Request/MiddleHealthCheckRequest.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Basic.Common.SysData.Request; + +public class MiddleHealthCheckRequest +{ + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs b/Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs new file mode 100644 index 00000000..489f8e5b --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Services/IHealthCheckService.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Basic.Common.SysData.Services; + +public interface IHealthCheckService : IScopeDependency +{ + Task CheckAsync(List factories); +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs new file mode 100644 index 00000000..90203b63 --- /dev/null +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/HealthCheckService.cs @@ -0,0 +1,10 @@ +namespace Dlhis.Basic.Common.SysData.Services.Implements; + +public class HealthCheckService : IHealthCheckService +{ + public async Task CheckAsync(List factories) + { + //todo 状态显示 + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index 3b35e692..f413900e 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -1,5 +1,7 @@ using System.Reflection; using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; using Kurisu.AspNetCore; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; @@ -34,17 +36,34 @@ public class SysDataService : ISysDataService switch (tableName) { case "Gainfo": - //sql = $"select * from Gainfo where factory = '{factory}'"; return await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => (object)x).ToListAsync(); case "GainfoMeal": { + var factories = await GetWalletFactoriesAsync(factory); + var codes = await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => x.Code).ToListAsync(); + // return await _dbContext.Queryable() + // .Where(x => codes.Contains(x.UserCode) && factories.Contains(x.TenantId)) + // .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) + // .GroupBy(x => x.UserCode) + // .Select(x => new + // { + // x.UserCode, + // Balance = SqlFunc.AggregateSum(x.Amount) + // }) + // .ToListAsync(); return null; - //todo 余额 - ///return await _dbContext.Queryable().Where(x => codes.Contains(x.UserCode)).Select(x => (object)x).ToListAsync(); } default: return await _dbContext.Client.SqlQueryable(sql).ToListAsync(); } } + + public async Task> GetWalletFactoriesAsync(string factory) + { + var wallets = await _dbContext.Queryable().ToListAsync(); + var code = wallets.First(x => x.Factory == factory).Code; + + return wallets.Where(x => x.Code == code).Select(x => x.Factory).ToList(); + } } \ No newline at end of file From 3d8f9e19551dc6c159c7c07ba85531defc5d616e Mon Sep 17 00:00:00 2001 From: liguoyang Date: Mon, 28 Oct 2024 17:37:13 +0800 Subject: [PATCH 10/23] =?UTF-8?q?fix=20gainfomeal=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Implements/SysDataService.cs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index f413900e..1650fa30 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -24,35 +24,42 @@ public class SysDataService : ISysDataService var type = App.ActiveTypes .Where(x => x.IsDefined(typeof(SugarTable), false)) .Where(x => !x.IsDefined(typeof(SkipScanAttribute), false)) - .First(x => x.GetCustomAttribute()!.TableName == tableName); + .FirstOrDefault(x => x.GetCustomAttribute()!.TableName == tableName); - var table = type.GetCustomAttribute()!.TableName; + var table = type?.GetCustomAttribute()!.TableName; var sql = $"select * from {table}"; - if (type.IsAssignableTo(typeof(ITenantId))) + + if (type == null) { - sql += $" where tenantId = '{factory}'"; + table = tableName; + } + else + { + if (type.IsAssignableTo(typeof(ITenantId))) + { + sql += $" where tenantId = '{factory}'"; + } } - switch (tableName) + switch (table) { case "Gainfo": return await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => (object)x).ToListAsync(); case "GainfoMeal": { - var factories = await GetWalletFactoriesAsync(factory); + var r = await _dbContext.Queryable() + .LeftJoin((x, b) => x.Code == b.UserCode) + .Where(x => x.Factory == factory) + .Where((x, b) => b.Source != BalanceRecordSourceEnumType.EMealVoucher && b.Status == BalanceRecordStatusEnumType.Valid && b.IsDeleted == false) + .GroupBy(x => x.Code) + .Select((x, b) => new + { + UserCode = x.Code, + Balance = SqlFunc.AggregateSum(b.Amount) + }) + .ToListAsync(); - var codes = await _dbContext.Queryable().Where(x => x.Factory == factory).Select(x => x.Code).ToListAsync(); - // return await _dbContext.Queryable() - // .Where(x => codes.Contains(x.UserCode) && factories.Contains(x.TenantId)) - // .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - // .GroupBy(x => x.UserCode) - // .Select(x => new - // { - // x.UserCode, - // Balance = SqlFunc.AggregateSum(x.Amount) - // }) - // .ToListAsync(); - return null; + return r.Select(x => (object)x).ToList(); } default: return await _dbContext.Client.SqlQueryable(sql).ToListAsync(); From 743ab5b31361873048bc8224a5af30c5b709086b Mon Sep 17 00:00:00 2001 From: liguoyang Date: Tue, 29 Oct 2024 12:03:19 +0800 Subject: [PATCH 11/23] =?UTF-8?q?fix=20=E6=9F=A5=E8=AF=A2=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/SysData/Services/Implements/SysDataService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs index 1650fa30..976e0244 100644 --- a/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs +++ b/Src/Dlhis.Basic/Common/SysData/Services/Implements/SysDataService.cs @@ -48,14 +48,14 @@ public class SysDataService : ISysDataService case "GainfoMeal": { var r = await _dbContext.Queryable() - .LeftJoin((x, b) => x.Code == b.UserCode) .Where(x => x.Factory == factory) - .Where((x, b) => b.Source != BalanceRecordSourceEnumType.EMealVoucher && b.Status == BalanceRecordStatusEnumType.Valid && b.IsDeleted == false) .GroupBy(x => x.Code) - .Select((x, b) => new + .Select(x => new { UserCode = x.Code, - Balance = SqlFunc.AggregateSum(b.Amount) + Balance = SqlFunc.Subqueryable().Where(p => p.UserCode == x.Code) + .Where(p => p.Source != BalanceRecordSourceEnumType.EMealVoucher && p.Status == BalanceRecordStatusEnumType.Valid && p.IsDeleted == false) + .Sum(p => p.Amount) }) .ToListAsync(); From 1a0ba2824919ef1d5e1c5780f9156c61ba7d0cac Mon Sep 17 00:00:00 2001 From: liguoyang Date: Fri, 8 Nov 2024 16:36:10 +0800 Subject: [PATCH 12/23] =?UTF-8?q?fix=20=E5=A4=9A=E9=92=B1=E5=8C=85?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Order/Controllers/OrdersController.cs | 9 +- .../Implements/BalanceRepository.cs | 17 +- .../Order/Response/OrderResultResponse.cs | 11 +- .../Order/Services/IOrderService.cs | 5 +- .../Order/Services/Implements/OrderService.cs | 155 +++++++++--------- .../Services/ICanteenSettingService.cs | 8 +- .../Api/FaceBearerTokenHandler.cs | 37 ----- .../Apis/Handlers/ClientHandler.cs | 23 +++ .../Apis/Handlers/FaceBearerTokenHandler.cs | 14 ++ .../{Api => Apis}/IAssignmentDeviceTaskApi.cs | 6 +- Src/Dlhis.Middle/{Api => Apis}/ICanteenApi.cs | 19 ++- Src/Dlhis.Middle/Apis/ICommonApi.cs | 22 +++ .../BalanceRecordUploadBackgroundService.cs} | 91 +++------- .../HealthCheckBackgroundService.cs | 4 +- .../Controllers/AssignmentsController.cs | 2 +- .../Controllers/BalancesController.cs | 2 +- .../Controllers/GainfosController.cs | 17 +- .../Controllers/OAuth2Controller.cs | 25 +-- Src/Dlhis.Middle/Dlhis.Middle.csproj | 15 +- .../Entities/LocalGainfoMealEntity.cs | 21 +-- Src/Dlhis.Middle/Program.cs | 22 +-- .../Request/HealthCheckRequest.cs | 6 + .../Response/OrderResultResponse.cs | 3 + .../Services/IDataDownloadService.cs | 4 +- .../Services/IRemoteOrderService.cs | 6 +- Src/Dlhis.Middle/Services/ISettingService.cs | 8 + Src/Dlhis.Middle/Services/ITokenService.cs | 6 + .../Implements/DataDownloadService.cs | 74 ++------- .../Services/Implements/HealthcheckService.cs | 61 ++++--- .../Services/Implements/OrderService.cs | 12 +- .../Services/Implements/RemoteOrderService.cs | 73 ++------- .../Services/Implements/SettingService.cs | 23 +++ .../Services/Implements/TokenService.cs | 43 +++++ Src/Dlhis.Middle/appsettings.Development.json | 5 +- Src/Dlhis.Middle/appsettings.json | 3 - Src/Dlhis.Middle/local.db-journal | Bin 0 -> 127736 bytes 36 files changed, 418 insertions(+), 434 deletions(-) delete mode 100644 Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs create mode 100644 Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs create mode 100644 Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs rename Src/Dlhis.Middle/{Api => Apis}/IAssignmentDeviceTaskApi.cs (69%) rename Src/Dlhis.Middle/{Api => Apis}/ICanteenApi.cs (67%) create mode 100644 Src/Dlhis.Middle/Apis/ICommonApi.cs rename Src/Dlhis.Middle/{Services/Implements/BalanceRecordCheckBackgroundService.cs => BackgroundServices/BalanceRecordUploadBackgroundService.cs} (60%) rename Src/Dlhis.Middle/{Services/Implements => BackgroundServices}/HealthCheckBackgroundService.cs (87%) create mode 100644 Src/Dlhis.Middle/Request/HealthCheckRequest.cs create mode 100644 Src/Dlhis.Middle/Services/ISettingService.cs create mode 100644 Src/Dlhis.Middle/Services/ITokenService.cs create mode 100644 Src/Dlhis.Middle/Services/Implements/SettingService.cs create mode 100644 Src/Dlhis.Middle/Services/Implements/TokenService.cs create mode 100644 Src/Dlhis.Middle/local.db-journal diff --git a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs index 1949cb5b..ba3482cd 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs @@ -1,5 +1,4 @@ -using Dlhis.Canteen.Dishes.Request; -using Dlhis.Canteen.Order.Request; +using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Order.Services; using Kurisu.AspNetCore.DataAccess.Dto; @@ -35,8 +34,7 @@ public class OrdersController : ControllerBase [IgnoreTenant] public async Task> AddAsync(OrderAddRequest request) { - var result = await _orderService.AddAsync(request); - return result.Response; + return await _orderService.AddAsync(request); } /// @@ -49,8 +47,7 @@ public class OrdersController : ControllerBase [IgnoreTenant] public async Task> DeviceConsume(OrderAddRequest request) { - var result = await _orderService.AddAsync(request); - return result.Response; + return await _orderService.AddAsync(request); } /// diff --git a/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs index 5de3e2f9..f1d60d9e 100644 --- a/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs @@ -16,15 +16,18 @@ public class BalanceRepository : IBalanceRepository public async Task GetBalanceAsync(string userCode, string factory) { - var factories = await GetWalletFactoriesAsync(factory); - - return await _dbContext.Queryable() + var queryable = _dbContext.Queryable() .Where(x => x.UserCode == userCode && x.Status == BalanceRecordStatusEnumType.Valid) - .Where(x => factories.Contains(x.TenantId)) - .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - .SumAsync(x => x.Amount); - } + .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher); + if (!factory.IsPresent()) + { + return await queryable.SumAsync(x => x.Amount); + } + + var factories = await GetWalletFactoriesAsync(factory); + return await queryable.Where(x => factories.Contains(x.TenantId)).SumAsync(x => x.Amount); + } public async Task> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime) { diff --git a/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs b/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs index d564e96b..a1472e19 100644 --- a/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs +++ b/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs @@ -32,4 +32,13 @@ public class OrderResultResponse /// 余额 /// public string Balance => CurrentBalance.ToString("f2"); -} + + + [JsonIgnore] + public decimal CurrentTotalBalance { get; set; } + + /// + /// 总余额 + /// + public string TotalBalance => CurrentTotalBalance.ToString("f2"); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/IOrderService.cs b/Src/Dlhis.Canteen/Order/Services/IOrderService.cs index a956a361..a738db3f 100644 --- a/Src/Dlhis.Canteen/Order/Services/IOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/IOrderService.cs @@ -1,19 +1,20 @@ using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Kurisu.AspNetCore.DataAccess.Dto; +using Kurisu.AspNetCore.UnifyResultAndValidation; namespace Dlhis.Canteen.Order.Services; public interface IOrderService : IScopeDependency { - Task AddAsync(OrderAddRequest request); + Task> AddAsync(OrderAddRequest request); /// /// 设备消费 /// /// /// - Task DeviceConsume(OrderAddRequest request); + Task> DeviceConsume(OrderAddRequest request); Task> GetOrdersAggregationAsync(PageDto page); } \ 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 22c6c78d..ff0362d8 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -57,7 +57,7 @@ public class OrderService : IOrderService /// /// /// - public async Task DeviceConsume(OrderAddRequest request) + public async Task> DeviceConsume(OrderAddRequest request) { return await AddAsync(request); } @@ -67,7 +67,7 @@ public class OrderService : IOrderService /// /// /// - public async Task AddAsync(OrderAddRequest request) + public async Task> AddAsync(OrderAddRequest request) { await SetDeviceIpInfoAsync(request.DeviceUniqueNo); @@ -75,10 +75,7 @@ public class OrderService : IOrderService handler.Acquired.ThrowIfFalse("请勿频繁消费"); var gainfo = await GetUserAsync(request); - return new OrderResultResponseWithLogs - { - Response = await ToDoAsync(request, gainfo) - }; + return await ToDoAsync(request, gainfo); } @@ -256,7 +253,7 @@ public class OrderService : IOrderService /// /// /// - private async Task HandleOfflineAsync(ConsumeRequest request, GainfoEntity gainfo) + private async Task OfflineHandleAsync(ConsumeRequest request, GainfoEntity gainfo) { var bindingInfo = await _dbContext.Queryable() .LeftJoin((x, b) => x.BusinessPlaceCode == b.Code) @@ -273,11 +270,13 @@ public class OrderService : IOrderService var shifts = await _dbContext.Queryable() .Where(x => x.ShiftCode == bindingInfo.ShiftCode).ToListAsync(); - var shift = shifts.FirstOrDefault(x => new TimePeriod - { - Start = x.Start, - End = x.End - }.IsPresent(request.CostDateTime)); + var shift = shifts.FirstOrDefault(x => + new TimePeriod + { + Start = x.Start, + End = x.End + }.IsPresent(request.CostDateTime) + ); var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, request.CostDateTime); @@ -317,7 +316,7 @@ public class OrderService : IOrderService TenantId = bindingInfo.Factory, }; - await _dbContext.InsertReturnIdentityAsync(order); + await _dbContext.InsertAsync(order); var balanceRecord = order.Adapt(); await _dbContext.InsertAsync(balanceRecord); } @@ -391,16 +390,36 @@ public class OrderService : IOrderService //获取餐券余额 var data = await _dbContext.Queryable() .LeftJoin((x, d) => x.No == d.No) - .LeftJoin((x, d, u) => u.IdNo == d.IdNo) .Where((x, d) => x.No == no && d.IdNo == idNo) - .Select((x, d, u) => new OrderResultResponse + .Select((x, d) => new OrderResultResponse { - UserCode = u.Code, - UserName = u.Name, + 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 e) + { + //ignore + } + return new DefaultApiResult { Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, @@ -409,6 +428,30 @@ public class OrderService : IOrderService }; } + + private async Task OfflineConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) + { + try + { + if (request.ThirdNo.IsPresent() && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) + { + await using var handler = await _redisCache.LockAsync($"consume:device-offline:{request.ThirdNo}"); + if (handler.Acquired) + { + await OfflineHandleAsync(request.Adapt(), gainfo); + } + } + else + { + await OfflineHandleAsync(request.Adapt(), gainfo); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); + } + } + /// /// 分支处理在线/离线消费 /// @@ -423,27 +466,7 @@ public class OrderService : IOrderService return await EMealVoucherConsumeAsync(request, gainfo); if (request.IsOffline) - { - try - { - if (request.ThirdNo.IsPresent() && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) - { - await using var handler = await _redisCache.LockAsync($"consume:device-offline:{request.ThirdNo}"); - if (handler.Acquired) - { - await HandleOfflineAsync(request.Adapt(), gainfo); - } - } - else - { - await HandleOfflineAsync(request.Adapt(), gainfo); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); - } - } + await OfflineConsumeAsync(request, gainfo); else { try @@ -454,52 +477,17 @@ public class OrderService : IOrderService { message = 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 e) - { - - } - - return new DefaultApiResult - { - Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, - Data = data, - Msg = message - }; } - var currentFactory = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).Select(x => x.TenantId).SingleAsync(); - var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory); + //获取人员信息 + var currentFactory = await _dbContext.Queryable() + .Where(x => x.UniqueNo == request.DeviceUniqueNo) + .Select(x => x.TenantId) + .SingleAsync(); + + var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory); + var totalBalance = await _balanceRepository.GetBalanceAsync(gainfo.Code, string.Empty); - //获取人员余额 var data = await _dbContext.Queryable() .LeftJoin((x, department) => x.DepartmentNo == department.No) .Where(x => x.Code == gainfo.Code) @@ -509,7 +497,9 @@ public class OrderService : IOrderService UserName = x.Name, DepartmentNo = x.DepartmentNo, DepartmentName = department.Name, - CurrentBalance = balance + + CurrentBalance = balance, + CurrentTotalBalance = totalBalance }) .SingleAsync(); @@ -555,6 +545,7 @@ public class OrderService : IOrderService (nowDate >= current.Start && nowDate <= current.End).ThrowIfFalse("餐券已过期"); + var eMealVoucher = await _dbContext.Queryable().Where(x => x.No == no).SingleAsync(); var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); diff --git a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs index 2376b87c..9c4c6b36 100644 --- a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs @@ -44,7 +44,13 @@ public interface ICanteenSettingService : IScopeDependency Task GetBusinessSwitchTimeAsync(string tenantId); - Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default); + Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo, T defaultValue = default); + /// + /// 获取消费日期 + /// + /// + /// + /// Task GetCostDateAsync(string factory, DateTime costDateTime); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs b/Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs deleted file mode 100644 index a0e806ff..00000000 --- a/Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text; -using Dlhis.Middle.Response; -using Kurisu.AspNetCore.UnifyResultAndValidation; -using Kurisu.RemoteCall.Abstractions; -using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json; - -namespace Dlhis.Middle.Api -{ - public class FaceBearerTokenHandler : IAuthTokenHandler - { - /// - public string GetToken(IServiceProvider serviceProvider) - { - var cache = serviceProvider.GetService(); - if (cache.TryGetValue("token", out string token)) return "Bearer " + token; - - var httpClient = serviceProvider.GetService().CreateClient("face"); - var hostName = serviceProvider.GetService().GetSection("HostName").Value; - - var tokenResponse = httpClient.PostAsync($"https://{hostName}/api/user/oauth2/clientgrant", new StringContent(JsonConvert.SerializeObject(new - { - ClientId = "e509c2aaa98e300c", - ClientSecret = "6184d8979fc48105" - }), Encoding.UTF8, "application/json")).GetAwaiter().GetResult(); - - var tokenJson = tokenResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - var tokenInfo = JsonConvert.DeserializeObject>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - cache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } - } -} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs b/Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs new file mode 100644 index 00000000..fa3b1694 --- /dev/null +++ b/Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs @@ -0,0 +1,23 @@ +using Kurisu.RemoteCall.Abstractions; + +namespace Dlhis.Middle.Apis.Handlers; + +public class ClientHandler : IHttpRemoteCallPolicyHandler +{ + /// + public IHttpClientBuilder ConfigureHttpClient(IHttpClientBuilder builder) + { + builder.ConfigureHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(10); }).ConfigurePrimaryHttpMessageHandler(() => + { + var handler = new HttpClientHandler + { + ClientCertificateOptions = ClientCertificateOption.Manual, + ServerCertificateCustomValidationCallback = (_, _, _, _) => true + }; + + return handler; + }); + + return builder; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs b/Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs new file mode 100644 index 00000000..37c409a2 --- /dev/null +++ b/Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs @@ -0,0 +1,14 @@ +using Dlhis.Middle.Services; +using Kurisu.RemoteCall.Abstractions; + +namespace Dlhis.Middle.Apis.Handlers; + +public class FaceBearerTokenHandler : IAsyncAuthTokenHandler +{ + /// + public async Task GetTokenAsync(IServiceProvider serviceProvider) + { + var tokenService = serviceProvider.GetService(); + return await tokenService.AuthAsync(); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Api/IAssignmentDeviceTaskApi.cs b/Src/Dlhis.Middle/Apis/IAssignmentDeviceTaskApi.cs similarity index 69% rename from Src/Dlhis.Middle/Api/IAssignmentDeviceTaskApi.cs rename to Src/Dlhis.Middle/Apis/IAssignmentDeviceTaskApi.cs index 8c856a82..d15f6f0f 100644 --- a/Src/Dlhis.Middle/Api/IAssignmentDeviceTaskApi.cs +++ b/Src/Dlhis.Middle/Apis/IAssignmentDeviceTaskApi.cs @@ -1,11 +1,13 @@ +using Dlhis.Middle.Apis.Handlers; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.RemoteCall.Attributes; -namespace Dlhis.Middle.Api; +namespace Dlhis.Middle.Apis; -[EnableRemoteClient(Name = "face-service", BaseUrl = "${Authority}")] +[RequestLog] +[EnableRemoteClient(Name = "face-service", BaseUrl = "${BaseUrl}", PolicyHandler = typeof(ClientHandler))] public interface IAssignmentDeviceTaskApi { [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] diff --git a/Src/Dlhis.Middle/Api/ICanteenApi.cs b/Src/Dlhis.Middle/Apis/ICanteenApi.cs similarity index 67% rename from Src/Dlhis.Middle/Api/ICanteenApi.cs rename to Src/Dlhis.Middle/Apis/ICanteenApi.cs index 84a4d7e9..bbc2c1eb 100644 --- a/Src/Dlhis.Middle/Api/ICanteenApi.cs +++ b/Src/Dlhis.Middle/Apis/ICanteenApi.cs @@ -1,26 +1,29 @@ +using Dlhis.Middle.Apis.Handlers; +using Dlhis.Middle.Entities; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.RemoteCall.Attributes; -namespace Dlhis.Middle.Api; +namespace Dlhis.Middle.Apis; -[EnableRemoteClient(Name = "face-service", BaseUrl = "${Authority}")] +[RequestLog] +[EnableRemoteClient(Name = "face-service", BaseUrl = "${BaseUrl}", PolicyHandler = typeof(ClientHandler))] public interface ICanteenApi { - [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/orders")] Task> AddAsync(OrderAddRequest request); - - - + + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] + [Post("/api/canteen/middle/upload")] + Task> UploadAsync(LocalBalanceRecordEntity entity); + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/balances/device/page")] Task>> GetPageByDeviceUniqueNoAsync(BalanceRecordPageByDeviceUniqueNoRequest request); - - + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/balances/refund")] Task> RefundAsync(BalanceRecordRefundRequest request); diff --git a/Src/Dlhis.Middle/Apis/ICommonApi.cs b/Src/Dlhis.Middle/Apis/ICommonApi.cs new file mode 100644 index 00000000..b44214fc --- /dev/null +++ b/Src/Dlhis.Middle/Apis/ICommonApi.cs @@ -0,0 +1,22 @@ +using Dlhis.Middle.Apis.Handlers; +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Kurisu.RemoteCall.Attributes; + +namespace Dlhis.Middle.Apis; + +[EnableRemoteClient(Name = "face-service", BaseUrl = "${BaseUrl}", PolicyHandler = typeof(ClientHandler))] +public interface ICommonApi +{ + [Post("/api/user/oauth2/clientgrant")] + public Task> AuthAsync(object request); + + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] + [Post("/api/common/middles/healthz")] + public Task CheckHealthAsync(HealthCheckRequest request); + + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] + [Get("/api/common/SysData/download")] + public Task>> DownloadAsync(string factory, string tableName); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/BalanceRecordCheckBackgroundService.cs b/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs similarity index 60% rename from Src/Dlhis.Middle/Services/Implements/BalanceRecordCheckBackgroundService.cs rename to Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs index a117b24b..f0f441a0 100644 --- a/Src/Dlhis.Middle/Services/Implements/BalanceRecordCheckBackgroundService.cs +++ b/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs @@ -1,46 +1,43 @@ -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Text; +using Dlhis.Middle.Apis; using Dlhis.Middle.Entities; using Dlhis.Middle.Response; +using Dlhis.Middle.Services; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.AspNetCore.Utils.Extensions; -using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json; -namespace Dlhis.Middle.Services.Implements; +namespace Dlhis.Middle.BackgroundServices; -public class BalanceRecordCheckBackgroundService : BackgroundService +public class BalanceRecordUploadBackgroundService : BackgroundService { private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; - private readonly IMemoryCache _memoryCache; - private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IWebHostEnvironment _environment; + private readonly ISettingService _settingService; + private readonly ICanteenApi _canteenApi; private readonly IHealthCheckService _healthCheckService; - private readonly string _factory; - private readonly string _hostName; - - public BalanceRecordCheckBackgroundService(IServiceProvider serviceProvider, - ILogger logger, - IMemoryCache memoryCache, - IHttpClientFactory httpClientFactory, - IHealthCheckService healthCheckService, - IConfiguration configuration) + public BalanceRecordUploadBackgroundService(IServiceProvider serviceProvider, + ILogger logger, + IWebHostEnvironment environment, + ISettingService settingService, + ICanteenApi canteenApi, + IHealthCheckService healthCheckService) { _serviceProvider = serviceProvider; _logger = logger; - _memoryCache = memoryCache; - _httpClientFactory = httpClientFactory; + _environment = environment; + _settingService = settingService; + _canteenApi = canteenApi; _healthCheckService = healthCheckService; - _factory = configuration.GetSection("Factory").Value; - _hostName = configuration.GetSection("HostName").Value; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested && _environment.IsProduction()) { if (!_healthCheckService.GetIsOffline()) { @@ -50,7 +47,8 @@ public class BalanceRecordCheckBackgroundService : BackgroundService var db = scope.ServiceProvider.GetService(); var ds = scope.ServiceProvider.GetService(); - foreach (var factory in _factory.Split(',')) + var factories = _settingService.GetFactories(); + foreach (var factory in factories) { try { @@ -64,7 +62,7 @@ public class BalanceRecordCheckBackgroundService : BackgroundService await db.Client.Ado.BeginTranAsync(); try { - var r = await UploadAsync(balanceRecord); + var r = await _canteenApi.UploadAsync(balanceRecord); if (r.Code != ApiStateCode.Success) { throw new Exception(r.Msg); @@ -108,62 +106,23 @@ public class BalanceRecordCheckBackgroundService : BackgroundService && !_healthCheckService.GetIsOffline() && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) { - await ds.InitAsync(_factory); + await ds.InitAsync(factories); } else if (now is { Hour: 15, Minute: 1 } && !_healthCheckService.GetIsOffline() && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) { - await ds.InitAsync(_factory); + await ds.InitAsync(factories); } else if (now is { Hour: 20, Minute: 1 } && !_healthCheckService.GetIsOffline() && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) { - await ds.InitAsync(_factory); + await ds.InitAsync(factories); } } await Task.Delay(60 * 1000, stoppingToken); } } - - public async Task> UploadAsync(LocalBalanceRecordEntity entity) - { - var token = await AuthAsync(); - - var httpClient = _httpClientFactory.CreateClient("face"); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - var url = $"https://{_hostName}/api/canteen/middle/upload"; - - var json = entity.ToJson(); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - - var response = await httpClient.PostAsync(url, content); - var responseJson = await response.Content.ReadAsStringAsync(); - return responseJson.ToObject>(); - } - - private async Task AuthAsync() - { - var httpClient = _httpClientFactory.CreateClient("face"); - - if (_memoryCache.TryGetValue("token", out string token)) return token; - - var tokenResponse = await httpClient.PostAsync($"https://{_hostName}/api/User/OAuth2/clientgrant", new StringContent(JsonConvert.SerializeObject(new - { - ClientId = "e509c2aaa98e300c", - ClientSecret = "6184d8979fc48105" - }), Encoding.UTF8, "application/json")); - - var tokenJson = await tokenResponse.Content.ReadAsStringAsync(); - var tokenInfo = JsonConvert.DeserializeObject>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs b/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs similarity index 87% rename from Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs rename to Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs index bc658f64..a765b742 100644 --- a/Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs +++ b/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs @@ -1,4 +1,6 @@ -namespace Dlhis.Middle.Services.Implements; +using Dlhis.Middle.Services; + +namespace Dlhis.Middle.BackgroundServices; public class HealthCheckBackgroundService : BackgroundService { diff --git a/Src/Dlhis.Middle/Controllers/AssignmentsController.cs b/Src/Dlhis.Middle/Controllers/AssignmentsController.cs index 5dfaa784..8238041f 100644 --- a/Src/Dlhis.Middle/Controllers/AssignmentsController.cs +++ b/Src/Dlhis.Middle/Controllers/AssignmentsController.cs @@ -1,5 +1,5 @@ using Dlhis.Entity.Device.Enums; -using Dlhis.Middle.Api; +using Dlhis.Middle.Apis; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Dlhis.Middle.Services; diff --git a/Src/Dlhis.Middle/Controllers/BalancesController.cs b/Src/Dlhis.Middle/Controllers/BalancesController.cs index 04749614..31444388 100644 --- a/Src/Dlhis.Middle/Controllers/BalancesController.cs +++ b/Src/Dlhis.Middle/Controllers/BalancesController.cs @@ -1,4 +1,4 @@ -using Dlhis.Middle.Api; +using Dlhis.Middle.Apis; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Dlhis.Middle.Services; diff --git a/Src/Dlhis.Middle/Controllers/GainfosController.cs b/Src/Dlhis.Middle/Controllers/GainfosController.cs index a8f72fa6..6fc33430 100644 --- a/Src/Dlhis.Middle/Controllers/GainfosController.cs +++ b/Src/Dlhis.Middle/Controllers/GainfosController.cs @@ -1,4 +1,5 @@ using Dlhis.Middle.Filters; +using Dlhis.Middle.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,14 +13,13 @@ namespace Dlhis.Middle.Controllers; public class GainfosController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; + private readonly ISettingService _settingService; - public GainfosController(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger logger) + public GainfosController(IHttpClientFactory httpClientFactory, + ISettingService settingService) { _httpClientFactory = httpClientFactory; - _configuration = configuration; - _logger = logger; + _settingService = settingService; } /// @@ -32,14 +32,11 @@ public class GainfosController : ControllerBase [HttpGet("face")] public async Task GetFaceAsync(string token) { - var hostName = _configuration.GetSection("HostName").Value; - var httpClient = _httpClientFactory.CreateClient("face"); - - _logger.LogInformation("token:{token}", token); + var httpClient = _httpClientFactory.CreateClient(); try { - var faceBytes = await httpClient.GetByteArrayAsync($"https://{hostName}/api/basic/gainfos/face?token={token}"); + var faceBytes = await httpClient.GetByteArrayAsync($"{_settingService.GetBaseUrl()}/api/basic/gainfos/face?token={token}"); if (faceBytes.Length == 0) { diff --git a/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs b/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs index 5b448c0d..f6b8624e 100644 --- a/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs +++ b/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs @@ -1,4 +1,5 @@ using System.Text; +using Dlhis.Middle.Apis; using Dlhis.Middle.Request; using Dlhis.Middle.Services; using Microsoft.AspNetCore.Mvc; @@ -13,23 +14,20 @@ namespace Dlhis.Middle.Controllers; [ApiController] public class OAuth2Controller : ControllerBase { - private readonly IHttpClientFactory _httpClientFactory; - private readonly IConfiguration _configuration; + private readonly ICommonApi _commonApi; private readonly IHealthCheckService _healthCheckService; - public OAuth2Controller(IHttpClientFactory httpClientFactory, IConfiguration configuration, IHealthCheckService healthCheckService) + public OAuth2Controller( + ICommonApi commonApi, + IHealthCheckService healthCheckService) { - _httpClientFactory = httpClientFactory; - _configuration = configuration; + _commonApi = commonApi; _healthCheckService = healthCheckService; } [HttpPost("clientgrant")] public async Task ClientGrant(ClientGrantRequest request) { - var authority = _configuration.GetSection("Authority").Value; - var httpClient = _httpClientFactory.CreateClient("face"); - if (_healthCheckService.GetIsOffline()) { return new @@ -41,9 +39,12 @@ public class OAuth2Controller : ControllerBase }; } - var content = new StringContent($"grant_type=client_credentials&client_id={request.ClientId}&client_secret={request.ClientSecret}&scope={request.Scope}", Encoding.UTF8, "application/x-www-form-urlencoded"); - var response = await httpClient.PostAsync($"{authority}/oauth2/connect/token", content); - var result = await response.Content.ReadAsStringAsync(); - return JObject.Parse(result); + var r = await _commonApi.AuthAsync(new + { + request.ClientId, + request.ClientSecret + }); + + return r.Data; } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Dlhis.Middle.csproj b/Src/Dlhis.Middle/Dlhis.Middle.csproj index accf19a9..07f7413e 100644 --- a/Src/Dlhis.Middle/Dlhis.Middle.csproj +++ b/Src/Dlhis.Middle/Dlhis.Middle.csproj @@ -17,10 +17,23 @@ - + + + + appsettings.json + + + + + + + + + + diff --git a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs index bb9809ff..322272aa 100644 --- a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs +++ b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs @@ -1,4 +1,3 @@ -using Dlhis.Entity.Basic.Entity; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar; using SqlSugar; @@ -12,7 +11,7 @@ namespace Dlhis.Middle.Entities /// - /// 就餐人员 + /// 就餐人员 /// [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] public class GainfoMealEntity : SugarBaseEntity, IVersion @@ -24,25 +23,13 @@ namespace Dlhis.Middle.Entities public string UserCode { get; set; } /// - /// 余额 + /// 余额 /// [SugarColumn(ColumnDescription = "余额")] public decimal Balance { get; set; } - + /// - /// 余额(充值) - /// - [SugarColumn(ColumnDescription = "余额(充值)")] - public decimal BalanceOfRecharge { get; set; } - - /// - /// 余额(餐补) - /// - [SugarColumn(ColumnDescription = "余额(餐补)")] - public decimal BalanceOfSubsidies { get; set; } - - /// - /// 版本号 + /// 版本号 /// [SugarColumn(ColumnDescription = "版本号")] public int Version { get; set; } diff --git a/Src/Dlhis.Middle/Program.cs b/Src/Dlhis.Middle/Program.cs index 99f6c4a6..7558c266 100644 --- a/Src/Dlhis.Middle/Program.cs +++ b/Src/Dlhis.Middle/Program.cs @@ -1,5 +1,5 @@ +using Dlhis.Middle.BackgroundServices; using Dlhis.Middle.Services; -using Dlhis.Middle.Services.Implements; using Kurisu.AspNetCore; using Kurisu.AspNetCore.Startup; using Microsoft.IdentityModel.Logging; @@ -24,20 +24,11 @@ public class Startup : DefaultStartup public override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); + + services.AddHostedService(); + services.AddHostedService(); services.AddRemoteCall(App.ActiveTypes); - services.AddHttpClient("face", client => { client.Timeout = TimeSpan.FromSeconds(30); }).ConfigurePrimaryHttpMessageHandler(() => - { - var handler = new HttpClientHandler - { - ClientCertificateOptions = ClientCertificateOption.Manual, - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - return handler; - }); - services.AddHostedService(); - services.AddHostedService(); services.AddMemoryCache(); } @@ -49,10 +40,9 @@ public class Startup : DefaultStartup if (Configuration.GetSection("Download").Value == "1") { using var scope = app.ApplicationServices.CreateScope(); - var factory = Configuration.GetSection("Factory").Value; - + var factories = scope.ServiceProvider.GetService().GetFactories(); var ds = scope.ServiceProvider.GetService(); - ds.InitAsync(factory).Wait(); + ds.InitAsync(factories).Wait(); } } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Request/HealthCheckRequest.cs b/Src/Dlhis.Middle/Request/HealthCheckRequest.cs new file mode 100644 index 00000000..e783dc81 --- /dev/null +++ b/Src/Dlhis.Middle/Request/HealthCheckRequest.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Middle.Request; + +public class HealthCheckRequest +{ + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Response/OrderResultResponse.cs b/Src/Dlhis.Middle/Response/OrderResultResponse.cs index 65b07959..eaf00498 100644 --- a/Src/Dlhis.Middle/Response/OrderResultResponse.cs +++ b/Src/Dlhis.Middle/Response/OrderResultResponse.cs @@ -28,4 +28,7 @@ public class OrderResultResponse /// 余额 /// public decimal Balance { get; set; } + + + public decimal TotalBalance { get; set; } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IDataDownloadService.cs b/Src/Dlhis.Middle/Services/IDataDownloadService.cs index 0efb64d4..eccedb1c 100644 --- a/Src/Dlhis.Middle/Services/IDataDownloadService.cs +++ b/Src/Dlhis.Middle/Services/IDataDownloadService.cs @@ -2,9 +2,7 @@ namespace Dlhis.Middle.Services; public interface IDataDownloadService : IScopeDependency { - Task InitAsync(string factory); + Task InitAsync(string[] factories); Task DownloadAsync(string factory) where T : class, new(); - - Task TruncateAsync(); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IRemoteOrderService.cs b/Src/Dlhis.Middle/Services/IRemoteOrderService.cs index e161d9ad..e5f6dfc0 100644 --- a/Src/Dlhis.Middle/Services/IRemoteOrderService.cs +++ b/Src/Dlhis.Middle/Services/IRemoteOrderService.cs @@ -1,12 +1,8 @@ -using Dlhis.Middle.Request; -using Dlhis.Middle.Response; -using Kurisu.AspNetCore.UnifyResultAndValidation; - namespace Dlhis.Middle.Services; public interface IRemoteOrderService : IScopeDependency { - Task> AddAsync(OrderAddRequest request); + //Task> AddAsync(OrderAddRequest request); Task AbleRemoteCallAsync(); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/ISettingService.cs b/Src/Dlhis.Middle/Services/ISettingService.cs new file mode 100644 index 00000000..b7946df7 --- /dev/null +++ b/Src/Dlhis.Middle/Services/ISettingService.cs @@ -0,0 +1,8 @@ +namespace Dlhis.Middle.Services; + +public interface ISettingService : ISingletonDependency +{ + string[] GetFactories(); + + string GetBaseUrl(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/ITokenService.cs b/Src/Dlhis.Middle/Services/ITokenService.cs new file mode 100644 index 00000000..d4dffabe --- /dev/null +++ b/Src/Dlhis.Middle/Services/ITokenService.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Middle.Services; + +public interface ITokenService : ISingletonDependency +{ + Task AuthAsync(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs b/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs index c9498000..9f1e83bd 100644 --- a/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs +++ b/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs @@ -1,43 +1,32 @@ -using System.Net.Http.Headers; using System.Reflection; -using System.Text; +using Dlhis.Middle.Apis; using Dlhis.Middle.Entities; -using Dlhis.Middle.Response; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using Kurisu.AspNetCore.UnifyResultAndValidation; -using Kurisu.AspNetCore.Utils.Extensions; -using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json; using SqlSugar; namespace Dlhis.Middle.Services.Implements; public class DataDownloadService : IDataDownloadService { - private readonly IHttpClientFactory _httpClientFactory; - private readonly IMemoryCache _memoryCache; private readonly IDbContext _dbContext; + private readonly ICommonApi _commonApi; private readonly ILogger _logger; - private readonly string _hostName; - public DataDownloadService(IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, IConfiguration configuration, IDbContext dbContext, ILogger logger) + public DataDownloadService( + IDbContext dbContext, + ICommonApi commonApi, + ILogger logger) { - _httpClientFactory = httpClientFactory; - _memoryCache = memoryCache; _dbContext = dbContext; + _commonApi = commonApi; _logger = logger; - _hostName = configuration.GetSection("HostName").Value; } - public async Task InitAsync(string factory) + public async Task InitAsync(string[] factories) { + //需要上传完记录才可下载 if (await _dbContext.Queryable().Where(x => x.IsSync == false).AnyAsync()) - { return; - } - - var factories = factory.Split(','); await _dbContext.Client.Ado.BeginTranAsync(); try @@ -84,9 +73,10 @@ public class DataDownloadService : IDataDownloadService _logger.LogInformation("档案{count}", await _dbContext.Queryable().CountAsync()); _logger.LogInformation("余额{count}", await _dbContext.Queryable().CountAsync()); _logger.LogInformation("下载完成"); + await _dbContext.Client.Ado.CommitTranAsync(); } - catch (Exception e) + catch (Exception) { await _dbContext.Client.Ado.RollbackTranAsync(); throw; @@ -95,27 +85,11 @@ public class DataDownloadService : IDataDownloadService public async Task DownloadAsync(string factory) where T : class, new() { - var token = await AuthAsync(); - - var httpClient = _httpClientFactory.CreateClient("face"); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - var url = $"https://{_hostName}/api/common/SysData/download"; - - var requestUrl = QueryHelpers.AddQueryString(url, new Dictionary - { - ["factory"] = factory, - ["tableName"] = typeof(T).GetCustomAttribute()!.TableName - }); - - var response = await httpClient.GetStringAsync(requestUrl); - var pack = response.ToObject>>(); - + var pack = await _commonApi.DownloadAsync(factory, typeof(T).GetCustomAttribute()!.TableName); await _dbContext.InsertAsync(pack.Data); } - /// - public async Task TruncateAsync() + private async Task TruncateAsync() { await _dbContext.Client.Deleteable().ExecuteCommandAsync(); await _dbContext.Client.Deleteable().ExecuteCommandAsync(); @@ -128,26 +102,4 @@ public class DataDownloadService : IDataDownloadService await _dbContext.Client.Deleteable().ExecuteCommandAsync(); await _dbContext.Client.Deleteable().ExecuteCommandAsync(); } - - private async Task AuthAsync() - { - var httpClient = _httpClientFactory.CreateClient("face"); - - if (_memoryCache.TryGetValue("token", out string token)) return token; - - var tokenResponse = await httpClient.PostAsync($"https://{_hostName}/api/User/OAuth2/clientgrant", new StringContent(JsonConvert.SerializeObject(new - { - ClientId = "e509c2aaa98e300c", - ClientSecret = "6184d8979fc48105" - }), Encoding.UTF8, "application/json")); - - var tokenJson = await tokenResponse.Content.ReadAsStringAsync(); - var tokenInfo = JsonConvert.DeserializeObject>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs index 2c01192a..a254668d 100644 --- a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs +++ b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs @@ -1,19 +1,26 @@ -using System.Net.Sockets; +using Dlhis.Middle.Apis; +using Dlhis.Middle.Request; +using Kurisu.AspNetCore.UnifyResultAndValidation; namespace Dlhis.Middle.Services.Implements; public class HealthCheckService : IHealthCheckService { private bool _isOffline; - private readonly string _hostName; private readonly ILogger _logger; - private int _successRetry; - private int _retry; + private readonly ISettingService _settingService; + private readonly ICommonApi _healthCheckApi; + private int _attempted; - public HealthCheckService(ILogger logger, IConfiguration configuration) + private bool _isBegin; + + public HealthCheckService(ILogger logger, + ISettingService settingService, + ICommonApi healthCheckApi) { _logger = logger; - _hostName = configuration.GetSection("HostName").Value; + _settingService = settingService; + _healthCheckApi = healthCheckApi; } public bool GetIsOffline() @@ -23,44 +30,44 @@ public class HealthCheckService : IHealthCheckService public async Task DoHealthCheckAsync() { - var cancellationTokenSource = new CancellationTokenSource(5000); - var tcpClient = new TcpClient(); - try { - _logger.LogDebug("tcp connecting {host} {port}", _hostName, 443); - await tcpClient.ConnectAsync(_hostName, 443, cancellationTokenSource.Token); - _retry++; - _logger.LogDebug("retry {retry}", _retry); - if (tcpClient.Connected) + var factories = _settingService.GetFactories(); + + var response = await _healthCheckApi.CheckHealthAsync(new HealthCheckRequest { Factories = factories.ToList() }); + if (response.Code == ApiStateCode.Success) { - _successRetry++; - _logger.LogDebug("success connected {count}", _successRetry); - if (_successRetry == 4) + _attempted++; + if (_attempted == 5) { _isOffline = false; - _successRetry = 0; - _retry = 0; - _logger.LogInformation("current is {status}", "healthy"); + _attempted = 0; } - else + + if (!_isBegin) { - _logger.LogDebug("wait next check in {waitTime}", _successRetry * 1000); - await Task.Delay(_successRetry * 1000, cancellationTokenSource.Token); + _isOffline = false; + _isBegin = true; } + + if (_isOffline == false) + { + _logger.LogInformation("Health"); + } + + await Task.Delay(_attempted * 1000); } else { - _successRetry = 0; _isOffline = true; - _logger.LogDebug("current is {status}", "unavailable"); - await Task.Delay(500); + _logger.LogInformation("Error {message}", response.Msg); } } catch (Exception ex) { - _logger.LogError("fail cause :{reason}", ex.Message); + _logger.LogError("Failed cause :{reason}", ex.Message); _isOffline = true; + await Task.Delay(5000); } } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/OrderService.cs b/Src/Dlhis.Middle/Services/Implements/OrderService.cs index 919663c6..fadb172f 100644 --- a/Src/Dlhis.Middle/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Middle/Services/Implements/OrderService.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Dlhis.Entity.Canteen.Enums; +using Dlhis.Middle.Apis; using Dlhis.Middle.Entities; using Kurisu.AspNetCore.Utils; using Kurisu.AspNetCore.Utils.Extensions; @@ -17,6 +18,7 @@ public class OrderService : IOrderService { private readonly ICanteenSettingService _canteenSettingService; private readonly IRemoteOrderService _remoteOrderService; + private readonly ICanteenApi _canteenApi; private readonly ILogger _logger; private readonly string _factory; private readonly IDbContext _dbContext; @@ -24,14 +26,16 @@ public class OrderService : IOrderService public OrderService(ICanteenSettingService canteenSettingService, IRemoteOrderService remoteOrderService, - IConfiguration configuration, + ISettingService settingService, + ICanteenApi canteenApi, ILogger logger, IDbContext dbContext) { _canteenSettingService = canteenSettingService; _remoteOrderService = remoteOrderService; + _canteenApi = canteenApi; _logger = logger; - _factory = configuration.GetSection("Factory").Value; + _factory = settingService.GetFactories().Join(","); _dbContext = dbContext; } @@ -43,12 +47,12 @@ public class OrderService : IOrderService { if (await _remoteOrderService.AbleRemoteCallAsync()) { - var remoteResult = await _remoteOrderService.AddAsync(request); + var remoteResult = await _canteenApi.AddAsync(request); if (remoteResult.Code == ApiStateCode.Success) { var d = remoteResult.Data; await _dbContext.Updateable() - .SetColumns(x => x.Balance == d.Balance) + .SetColumns(x => x.Balance == d.TotalBalance) .Where(x => x.UserCode == d.UserCode) .ExecuteCommandAsync(); } diff --git a/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs b/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs index 05d84b14..707e7338 100644 --- a/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs +++ b/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs @@ -1,54 +1,37 @@ -using System.Net.Http.Headers; -using System.Text; using Dlhis.Middle.Entities; -using Dlhis.Middle.Request; -using Dlhis.Middle.Response; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; -using Kurisu.AspNetCore.UnifyResultAndValidation; -using Kurisu.AspNetCore.Utils.Extensions; -using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json; namespace Dlhis.Middle.Services.Implements; public class RemoteOrderService : IRemoteOrderService { - private readonly string _hostName; - private readonly IHttpClientFactory _httpClientFactory; - private readonly IMemoryCache _memoryCache; private readonly IHealthCheckService _healthCheckService; private readonly IDbContext _dbContext; - public RemoteOrderService(IHttpClientFactory httpClientFactory, - IMemoryCache memoryCache, - IConfiguration configuration, + public RemoteOrderService( IHealthCheckService healthCheckService, IDbContext dbContext) { - _httpClientFactory = httpClientFactory; - _memoryCache = memoryCache; - _hostName = configuration.GetSection("hostName").Value; _healthCheckService = healthCheckService; _dbContext = dbContext; } - - /// - public async Task> AddAsync(OrderAddRequest request) - { - var token = await AuthAsync(); - - var httpClient = _httpClientFactory.CreateClient("face"); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - var content = new StringContent(request.ToJson(), Encoding.UTF8, "application/json"); - - var response = await httpClient.PostAsync($"https://{_hostName}/api/canteen/orders", content); - - var responseJson = await response.Content.ReadAsStringAsync(); - return responseJson.ToObject>(); - } + ///// + // public async Task> AddAsync(OrderAddRequest request) + // { + // var token = await _tokenService.AuthAsync(); + // + // var httpClient = _httpClientFactory.CreateClient("face"); + // httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + // + // var content = new StringContent(request.ToJson(), Encoding.UTF8, "application/json"); + // + // var response = await httpClient.PostAsync($"{_settingService.GetBaseUrl()}/api/canteen/orders", content); + // + // var responseJson = await response.Content.ReadAsStringAsync(); + // return responseJson.ToObject>(); + // } /// public async Task AbleRemoteCallAsync() @@ -60,28 +43,4 @@ public class RemoteOrderService : IRemoteOrderService return !_healthCheckService.GetIsOffline(); } - - - - private async Task AuthAsync() - { - var httpClient = _httpClientFactory.CreateClient("face"); - - if (_memoryCache.TryGetValue("token", out string token)) return token; - - var tokenResponse = await httpClient.PostAsync($"https://{_hostName}/api/User/OAuth2/clientgrant", new StringContent(JsonConvert.SerializeObject(new - { - ClientId = "e509c2aaa98e300c", - ClientSecret = "6184d8979fc48105" - }), Encoding.UTF8, "application/json")); - - var tokenJson = await tokenResponse.Content.ReadAsStringAsync(); - var tokenInfo = JsonConvert.DeserializeObject>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/SettingService.cs b/Src/Dlhis.Middle/Services/Implements/SettingService.cs new file mode 100644 index 00000000..b583f924 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/SettingService.cs @@ -0,0 +1,23 @@ +namespace Dlhis.Middle.Services.Implements; + +public class SettingService : ISettingService +{ + private readonly IConfiguration _configuration; + + public SettingService(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public string[] GetFactories() + { + return _configuration.GetSection("Factory").Value.Split(","); + } + + /// + public string GetBaseUrl() + { + return _configuration.GetSection("BaseUrl").Value; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/TokenService.cs b/Src/Dlhis.Middle/Services/Implements/TokenService.cs new file mode 100644 index 00000000..226a9de0 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/TokenService.cs @@ -0,0 +1,43 @@ +using Dlhis.Middle.Apis; +using Microsoft.Extensions.Caching.Memory; + +namespace Dlhis.Middle.Services.Implements; + +public class TokenService : ITokenService +{ + private readonly IMemoryCache _memoryCache; + private readonly ICommonApi _commonApi; + + public TokenService(IMemoryCache memoryCache, ICommonApi commonApi) + { + _memoryCache = memoryCache; + _commonApi = commonApi; + } + + /// + public async Task AuthAsync() + { + var exists = _memoryCache.TryGetValue("token", out string token); + try + { + if (!exists) + { + var tokenInfo = await _commonApi.AuthAsync(new + { + ClientId = "e509c2aaa98e300c", + ClientSecret = "6184d8979fc48105" + }); + + token = tokenInfo.Data.AccessToken; + + _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); + } + } + finally + { + token = "Bearer " + token; + } + + return token; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/appsettings.Development.json b/Src/Dlhis.Middle/appsettings.Development.json index efb527ae..1339edac 100644 --- a/Src/Dlhis.Middle/appsettings.Development.json +++ b/Src/Dlhis.Middle/appsettings.Development.json @@ -5,8 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "Authority": "https://face.jinkosolar.com", - "Factory": "1201", - "HostName": "face.jinkosolar.com", + "BaseUrl": "https://yikatong.isawesome.cn", + "Factory": "1234", "Download":1 } diff --git a/Src/Dlhis.Middle/appsettings.json b/Src/Dlhis.Middle/appsettings.json index f85ae1ef..b6e736e0 100644 --- a/Src/Dlhis.Middle/appsettings.json +++ b/Src/Dlhis.Middle/appsettings.json @@ -12,9 +12,6 @@ "SlowSqlTime": 1, "EnableSqlLog": false }, - "Authority": "https://face.jinkosolar.com", - "Factory": "", - "HostName": "face.jinkosolar.com", "AllowedHosts": "*", "SwaggerOptions": { "Enable": true, diff --git a/Src/Dlhis.Middle/local.db-journal b/Src/Dlhis.Middle/local.db-journal new file mode 100644 index 0000000000000000000000000000000000000000..750c5ab6103838ae055a5ee18909d19742871fc0 GIT binary patch literal 127736 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 YzWx7cZUCoa82sA28CfTSCve^W0U__dL;wH) literal 0 HcmV?d00001 From 74912c2efc0a68663e5b9c54d76ec7538b2177a8 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Tue, 12 Nov 2024 16:08:49 +0800 Subject: [PATCH 13/23] =?UTF-8?q?feat=20=E9=92=B1=E5=8C=85=E5=92=8C?= =?UTF-8?q?=E9=92=B1=E5=8C=85=E4=BD=99=E9=A2=9D=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Order/Controllers/BalancesController.cs | 7 +-- .../Setting/Controllers/WalletsController.cs | 50 +++++++++++++++++++ .../Setting/Response/WalletResponse.cs | 19 +++++++ .../Setting/Services/IWalletService.cs | 8 +++ .../Services/Implements/WalletService.cs | 30 +++++++++++ .../Canteen/Entities/WalletEntity.cs | 2 +- 6 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs create mode 100644 Src/Dlhis.Canteen/Setting/Response/WalletResponse.cs create mode 100644 Src/Dlhis.Canteen/Setting/Services/IWalletService.cs create mode 100644 Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs diff --git a/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs b/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs index baa4deb1..00b9eae8 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/BalancesController.cs @@ -1,16 +1,11 @@ -using System.ComponentModel.DataAnnotations; -using System.Data; +using System.Data; using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Order.Services; -using Dlhis.Entity.Canteen.Entities; -using Kurisu.AspNetCore; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.SqlSugar.Aop; -using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using SqlSugar; namespace Dlhis.Canteen.Order.Controllers; diff --git a/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs b/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs new file mode 100644 index 00000000..2bcbf0dc --- /dev/null +++ b/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs @@ -0,0 +1,50 @@ +using Dlhis.Canteen.Order.Repository; +using Dlhis.Canteen.Setting.Response; +using Dlhis.Canteen.Setting.Services; +using Kurisu.AspNetCore.Authentication.Abstractions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Dlhis.Canteen.Setting.Controllers; + +///

+/// 钱包 +/// +[Authorize] +[Route("api/[controller]")] +[ApiController] +public class WalletsController : ControllerBase +{ + private readonly IWalletService _walletService; + private readonly IBalanceRepository _balanceRepository; + private readonly ICurrentUser _currentUser; + + public WalletsController(IWalletService walletService, IBalanceRepository balanceRepository, ICurrentUser currentUser) + { + _walletService = walletService; + _balanceRepository = balanceRepository; + _currentUser = currentUser; + } + + /// + /// 钱包列表 + /// + /// + [HttpGet("list")] + public async Task> GetListAsync() + { + return await _walletService.GetListAsync(); + } + + /// + /// 余额 + /// + /// + /// + [HttpGet("balance")] + public async Task GetBalanceAsync(string factory) + { + var code = _currentUser.GetUserClaim("jinko-code"); + return await _balanceRepository.GetBalanceAsync(code, factory); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Response/WalletResponse.cs b/Src/Dlhis.Canteen/Setting/Response/WalletResponse.cs new file mode 100644 index 00000000..6e4879a3 --- /dev/null +++ b/Src/Dlhis.Canteen/Setting/Response/WalletResponse.cs @@ -0,0 +1,19 @@ +namespace Dlhis.Canteen.Setting.Response; + +public class WalletResponse +{ + /// + /// 钱包编码 + /// + public string Code { get; set; } + + /// + /// 钱包名称 + /// + public string Name { get; set; } + + /// + /// 厂区 + /// + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs b/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs new file mode 100644 index 00000000..614c7c1f --- /dev/null +++ b/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs @@ -0,0 +1,8 @@ +using Dlhis.Canteen.Setting.Response; + +namespace Dlhis.Canteen.Setting.Services; + +public interface IWalletService : IScopeDependency +{ + Task> GetListAsync(); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs new file mode 100644 index 00000000..819cfabe --- /dev/null +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs @@ -0,0 +1,30 @@ +using Dlhis.Canteen.Setting.Response; +using Dlhis.Entity.Canteen.Entities; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using SqlSugar; + +namespace Dlhis.Canteen.Setting.Services.Implements; + +public class WalletService : IWalletService +{ + private readonly IDbContext _dbContext; + + public WalletService(IDbContext dbContext) + { + _dbContext = dbContext; + } + + /// + public async Task> GetListAsync() + { + return await _dbContext.Queryable() + .GroupBy(x => new { x.Code, x.Name }) + .Select(x => new WalletResponse + { + Code = x.Code, + Name = x.Name, + Factories = SqlFunc.Subqueryable().Where(p => p.Code == x.Code).ToList(p => p.Factory) + }) + .ToListAsync(); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs b/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs index 8f698195..f396b88a 100644 --- a/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs +++ b/Src/Dlhis.Entity/Canteen/Entities/WalletEntity.cs @@ -24,6 +24,6 @@ public class WalletEntity : SugarBaseEntity /// /// 厂区 /// - [SugarColumn(IsNullable = false, UniqueGroupNameList = new[] { "idx_wallets_facotry" })] + [SugarColumn(IsNullable = false, UniqueGroupNameList = new[] { "idx_wallets_factry" })] public string Factory { get; set; } } \ No newline at end of file From e2bb6d5d0ec728f9cb64f24ca5ad327afe1f1e77 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Tue, 12 Nov 2024 16:19:31 +0800 Subject: [PATCH 14/23] =?UTF-8?q?fix=20=E6=9F=A5=E8=AF=A2=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Setting/Controllers/WalletsController.cs | 16 +++++----------- .../Setting/Services/IWalletService.cs | 2 ++ .../Services/Implements/WalletService.cs | 17 ++++++++++++++++- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs b/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs index 2bcbf0dc..53a38aaa 100644 --- a/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs +++ b/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs @@ -1,7 +1,6 @@ -using Dlhis.Canteen.Order.Repository; +using System.ComponentModel.DataAnnotations; using Dlhis.Canteen.Setting.Response; using Dlhis.Canteen.Setting.Services; -using Kurisu.AspNetCore.Authentication.Abstractions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,14 +15,10 @@ namespace Dlhis.Canteen.Setting.Controllers; public class WalletsController : ControllerBase { private readonly IWalletService _walletService; - private readonly IBalanceRepository _balanceRepository; - private readonly ICurrentUser _currentUser; - public WalletsController(IWalletService walletService, IBalanceRepository balanceRepository, ICurrentUser currentUser) + public WalletsController(IWalletService walletService) { _walletService = walletService; - _balanceRepository = balanceRepository; - _currentUser = currentUser; } /// @@ -39,12 +34,11 @@ public class WalletsController : ControllerBase /// /// 余额 /// - /// + /// 钱包code /// [HttpGet("balance")] - public async Task GetBalanceAsync(string factory) + public async Task GetBalanceAsync([Required] string walletCode) { - var code = _currentUser.GetUserClaim("jinko-code"); - return await _balanceRepository.GetBalanceAsync(code, factory); + return await _walletService.GetBalanceAsync(walletCode); } } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs b/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs index 614c7c1f..38aa63f8 100644 --- a/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/IWalletService.cs @@ -5,4 +5,6 @@ namespace Dlhis.Canteen.Setting.Services; public interface IWalletService : IScopeDependency { Task> GetListAsync(); + + Task GetBalanceAsync(string walletCode); } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs index 819cfabe..20ac1476 100644 --- a/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs @@ -1,5 +1,7 @@ +using Dlhis.Canteen.Order.Repository; using Dlhis.Canteen.Setting.Response; using Dlhis.Entity.Canteen.Entities; +using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using SqlSugar; @@ -8,10 +10,14 @@ namespace Dlhis.Canteen.Setting.Services.Implements; public class WalletService : IWalletService { private readonly IDbContext _dbContext; + private readonly ICurrentUser _currentUser; + private readonly IBalanceRepository _balanceRepository; - public WalletService(IDbContext dbContext) + public WalletService(IDbContext dbContext, ICurrentUser currentUser, IBalanceRepository balanceRepository) { _dbContext = dbContext; + _currentUser = currentUser; + _balanceRepository = balanceRepository; } /// @@ -27,4 +33,13 @@ public class WalletService : IWalletService }) .ToListAsync(); } + + /// + public async Task GetBalanceAsync(string walletCode) + { + var userCode = _currentUser.GetUserClaim("jinko-code"); + var factory = await _dbContext.Queryable().Where(x => x.Code == walletCode).Select(x => x.Factory).FirstAsync(); + + return await _balanceRepository.GetBalanceAsync(userCode, factory); + } } \ No newline at end of file From 3a47580441460254f9177932a67fd109e67028af Mon Sep 17 00:00:00 2001 From: liguoyang Date: Wed, 13 Nov 2024 16:11:23 +0800 Subject: [PATCH 15/23] =?UTF-8?q?fix=20=E5=B0=B1=E9=A4=90=E4=BA=BA?= =?UTF-8?q?=E5=91=98=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E6=B6=88=E8=B4=B9=E8=AE=B0=E5=BD=95=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Implements/GainfoMealService.cs | 4 ++-- .../Order/Services/Implements/MiddleService.cs | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs index f9a354e7..a121c671 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs @@ -28,12 +28,12 @@ public class GainfoMealService : IGainfoMealService public async Task> GetPageAsync(GainfoMealPageRequest request) { var factory = _currentUser.GetStringTenantId(); - var factories = await GetWalletFactoriesAsync(_currentUser.GetStringTenantId()); + var factories = await GetWalletFactoriesAsync(factory); return await _dbContext.Queryable().AS(request.IsLeave ? "GainfoLeave" : "Gainfo") .LeftJoin((x, dept) => dept.No == x.DepartmentNo) .WhereIF(request.Search.IsPresent(), x => x.Code == request.Search || x.Name == request.Search) - .Where(x => x.Factory == factory) + .Where(x => SqlFunc.Subqueryable().Where(p => p.UserCode == x.Code && p.TenantId == factory).Any()) .WhereIF(request.IsLeave && request.SearchDateTimePeriod.HasValue(), x => x.ResignationDate >= request.SearchDateTimePeriod.Start && x.ResignationDate <= request.SearchDateTimePeriod.End) .Select((x, dept) => new GainfoMealResponse diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs index 55530c2f..f0a1bb2f 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/MiddleService.cs @@ -1,3 +1,4 @@ +using Dlhis.Canteen.Order.Repository; using Dlhis.Canteen.Order.Response; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; @@ -8,12 +9,12 @@ namespace Dlhis.Canteen.Order.Services.Implements; public class MiddleService : IMiddleService { private readonly IDbContext _dbContext; - private readonly IBalanceService _balanceService; + private readonly IBalanceRepository _balanceRepository; - public MiddleService(IDbContext dbContext, IBalanceService balanceService) + public MiddleService(IDbContext dbContext, IBalanceRepository balanceRepository) { _dbContext = dbContext; - _balanceService = balanceService; + _balanceRepository = balanceRepository; } /// @@ -27,6 +28,8 @@ public class MiddleService : IMiddleService await _dbContext.InsertAsync(entity); } + var balance = await _balanceRepository.GetBalanceAsync(entity.UserCode, string.Empty); + //获取人员余额 return await _dbContext.Queryable() .LeftJoin((x, department) => x.DepartmentNo == department.No) @@ -37,7 +40,7 @@ public class MiddleService : IMiddleService UserName = x.Name, DepartmentNo = x.DepartmentNo, DepartmentName = department.Name, - //CurrentBalance = x.Balance todo 余额 + CurrentBalance = balance }) .SingleAsync(); } From b627a66254fdc702693cddf958300586cd0d550e Mon Sep 17 00:00:00 2001 From: liguoyang Date: Wed, 13 Nov 2024 16:14:40 +0800 Subject: [PATCH 16/23] =?UTF-8?q?fix=20=E5=8C=85=E5=90=AB=E5=8E=82?= =?UTF-8?q?=E5=8C=BA=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Organization/Services/Implements/GainfoMealService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs index a121c671..a52cd610 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs @@ -33,7 +33,7 @@ public class GainfoMealService : IGainfoMealService return await _dbContext.Queryable().AS(request.IsLeave ? "GainfoLeave" : "Gainfo") .LeftJoin((x, dept) => dept.No == x.DepartmentNo) .WhereIF(request.Search.IsPresent(), x => x.Code == request.Search || x.Name == request.Search) - .Where(x => SqlFunc.Subqueryable().Where(p => p.UserCode == x.Code && p.TenantId == factory).Any()) + .Where(x => SqlFunc.Subqueryable().Where(p => p.UserCode == x.Code && factories.Contains(p.TenantId)).Any()) .WhereIF(request.IsLeave && request.SearchDateTimePeriod.HasValue(), x => x.ResignationDate >= request.SearchDateTimePeriod.Start && x.ResignationDate <= request.SearchDateTimePeriod.End) .Select((x, dept) => new GainfoMealResponse From 51b0d4a7bb23fec9fe69c73827c35a31e178b0c4 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 14:14:47 +0800 Subject: [PATCH 17/23] =?UTF-8?q?fix=20=E4=BD=99=E9=A2=9D=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E8=B7=A8=E5=8E=82=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs | 2 ++ Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs b/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs index 53a38aaa..36fd7142 100644 --- a/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs +++ b/Src/Dlhis.Canteen/Setting/Controllers/WalletsController.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using Dlhis.Canteen.Setting.Response; using Dlhis.Canteen.Setting.Services; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Aop; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -37,6 +38,7 @@ public class WalletsController : ControllerBase /// 钱包code /// [HttpGet("balance")] + [IgnoreTenant] public async Task GetBalanceAsync([Required] string walletCode) { return await _walletService.GetBalanceAsync(walletCode); diff --git a/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs b/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs index 20ac1476..1ef8e9c5 100644 --- a/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/Implements/WalletService.cs @@ -28,7 +28,7 @@ public class WalletService : IWalletService .Select(x => new WalletResponse { Code = x.Code, - Name = x.Name, + Name = x.Name + "钱包", Factories = SqlFunc.Subqueryable().Where(p => p.Code == x.Code).ToList(p => p.Factory) }) .ToListAsync(); From 5fd97fbd73e9a44870bc330ee4c3ee18bc2a2d7c Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 14:30:13 +0800 Subject: [PATCH 18/23] =?UTF-8?q?fix=20=E7=94=A8=E6=88=B7=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=8C=BA=E5=88=86=E5=8E=82=E5=8C=BA=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E5=92=8C=E7=BC=96=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Organization/Response/GainfoCurrentResponse.cs | 7 ++++++- .../Organization/Services/Implements/GainfoService.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs index b2fb54b8..04cd105e 100644 --- a/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs +++ b/Src/Dlhis.Basic/Organization/Response/GainfoCurrentResponse.cs @@ -49,10 +49,15 @@ public class GainfoCurrentResponse public DateTime Birthday { get; set; } /// - /// 厂区 + /// 厂区编码 /// public string Factory { get; set; } + /// + /// 厂区名称 + /// + public string FactoryName { get; set; } + /// /// 卡类型 /// diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs index b76ef920..ad8f6cfa 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoService.cs @@ -220,7 +220,7 @@ public class GainfoService : IGainfoService var tenantResult = await _tenantApi.GetTenantByCodeAsync(info.Factory); if (tenantResult is { Data: not null }) { - info.Factory = tenantResult.Data.Name; + info.FactoryName = tenantResult.Data.Name; } } catch (Exception) From dc9a160e053bb9e4a58d8b2e6eb4b4540ef47446 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 14:46:34 +0800 Subject: [PATCH 19/23] =?UTF-8?q?fix=20=E7=94=A8=E6=88=B7=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=B6=88=E8=B4=B9=E6=A0=B9=E6=8D=AE=E9=92=B1=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Request/BalanceRecordUserPageRequest.cs | 7 ++++++- .../Order/Services/Implements/BalanceService.cs | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Src/Dlhis.Canteen/Order/Request/BalanceRecordUserPageRequest.cs b/Src/Dlhis.Canteen/Order/Request/BalanceRecordUserPageRequest.cs index 32f30543..44f78456 100644 --- a/Src/Dlhis.Canteen/Order/Request/BalanceRecordUserPageRequest.cs +++ b/Src/Dlhis.Canteen/Order/Request/BalanceRecordUserPageRequest.cs @@ -17,6 +17,11 @@ public class BalanceRecordUserPageRequest : PageDto, IValidatableObject /// public DateTimePeriod SearchDateTimePeriod { get; set; } + /// + /// 钱包code + /// + [Required(ErrorMessage = "请选择钱包")] + public string WalletCode { get; set; } public IEnumerable Validate(ValidationContext validationContext) { @@ -27,4 +32,4 @@ public class BalanceRecordUserPageRequest : PageDto, IValidatableObject yield break; } -} +} \ 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 3bedcca8..3f0957d4 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs @@ -1,4 +1,5 @@ using Dlhis.Basic.Api; +using Dlhis.Canteen.Order.Repository; using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Setting.Services; @@ -29,6 +30,7 @@ public class BalanceService : IBalanceService private readonly ITenantApi _tenantApi; private readonly ICanteenSettingService _canteenSettingService; private readonly RedisCache _redisCache; + private readonly IBalanceRepository _balanceRepository; private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; public BalanceService(IDbContext dbContext, @@ -36,6 +38,7 @@ public class BalanceService : IBalanceService ITenantApi tenantApi, ICanteenSettingService canteenSettingService, RedisCache redisCache, + IBalanceRepository balanceRepository, IBusinessPlacePermissionsApi businessPlacePermissionsApi) { _dbContext = dbContext; @@ -43,6 +46,7 @@ public class BalanceService : IBalanceService _tenantApi = tenantApi; _canteenSettingService = canteenSettingService; _redisCache = redisCache; + _balanceRepository = balanceRepository; _businessPlacePermissionsApi = businessPlacePermissionsApi; } @@ -122,7 +126,7 @@ public class BalanceService : IBalanceService balanceRecord.Remark = "退款"; await _dbContext.UpdateAsync(balanceRecord); - if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) + if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) { var originData = balanceRecord.UserNo.Split(":"); var eNo = originData[0]; @@ -323,13 +327,16 @@ public class BalanceService : IBalanceService public async Task GetPageByUserAsync(BalanceRecordUserPageRequest request) { - var jinkoCode = _currentUser.GetUserClaim("jinko-code") ?? string.Empty; + var userCode = _currentUser.GetUserClaim("jinko-code") ?? string.Empty; + + var factory = await _dbContext.Queryable().Where(x => x.Code == request.WalletCode).Select(x => x.Factory).FirstAsync(); + var factories = await _balanceRepository.GetWalletFactoriesAsync(factory); var page = await _dbContext.Queryable() .LeftJoin((record, ga) => record.UserNo == ga.No) .LeftJoin((record, ga, department) => ga.DepartmentNo == department.No) .LeftJoin((record, ga, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) - .Where((record, ga) => record.UserCode == jinkoCode) + .Where((record, ga) => record.UserCode == userCode && factories.Contains(record.TenantId)) .Where(record => record.Status == BalanceRecordStatusEnumType.Valid && record.Type == BalanceRecordTypeEnumType.Out) .Where(record => record.Source != BalanceRecordSourceEnumType.EMealVoucher) //不展示电子餐券信息 .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) @@ -353,10 +360,10 @@ public class BalanceService : IBalanceService var monthEnd = monthStart.AddMonths(1).AddSeconds(-1); var monthSum = await _dbContext.Queryable() - .Where(x => x.CostDateTime >= monthStart && x.CostDateTime <= monthEnd) + .Where(x => x.UserCode == userCode && factories.Contains(x.TenantId)) + .Where(x => x.CostDate >= monthStart && x.CostDate <= monthEnd) .Where(x => x.Status == BalanceRecordStatusEnumType.Valid && x.Type == BalanceRecordTypeEnumType.Out) .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - .Where(x => x.UserCode == jinkoCode) .SumAsync(x => SqlFunc.Abs(x.Amount)); return new BalanceRecordUserResultResponse From cdd3e73636ec8dbc0c2d5def2d9243c979c20aa6 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 14:59:12 +0800 Subject: [PATCH 20/23] =?UTF-8?q?fix=20=E4=BC=98=E5=8C=96=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E4=BD=99=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Organization/Response/GainfoMealResponse.cs | 10 ++++++++-- .../Services/Implements/GainfoMealService.cs | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs b/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs index f2bc5509..6ef96e89 100644 --- a/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs +++ b/Src/Dlhis.Basic/Organization/Response/GainfoMealResponse.cs @@ -1,5 +1,6 @@ using Dlhis.Entity.Basic.Enums; using Kurisu.AspNetCore.Utils.Extensions; +using Newtonsoft.Json; namespace Dlhis.Basic.Organization.Response; @@ -20,7 +21,10 @@ public class GainfoMealResponse /// /// 余额 /// - public decimal Balance { get; set; } + public string Balance => CurrentBalance.ToString("F2"); + + [JsonIgnore] + public decimal CurrentBalance { get; set; } // /// // /// 余额(充值) @@ -41,7 +45,9 @@ public class GainfoMealResponse /// /// 餐补总额 /// - public decimal MealSupplementAmount { get; set; } + public string MealSupplementAmount => CurrentMealSupplementAmount.ToString("F2"); + + public decimal CurrentMealSupplementAmount { get; set; } /// /// 档案状态 diff --git a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs index a52cd610..743abed2 100644 --- a/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs +++ b/Src/Dlhis.Basic/Organization/Services/Implements/GainfoMealService.cs @@ -45,12 +45,12 @@ public class GainfoMealService : IGainfoMealService Status = request.IsLeave ? GainfoStatusEnumType.Leave : GainfoStatusEnumType.Working, - MealSupplementAmount = SqlFunc.Subqueryable() + CurrentMealSupplementAmount = SqlFunc.Subqueryable() .Where(p => p.Code == x.Code) .Where(p => factories.Contains(p.Factory)) .WhereIF(request.SearchDateTimePeriod.HasValue(), p => p.Date >= request.SearchDateTimePeriod.Start && p.Date <= request.SearchDateTimePeriod.End) .Sum(p => p.Amount), - Balance = SqlFunc.Subqueryable() + CurrentBalance = SqlFunc.Subqueryable() .Where(p => factories.Contains(p.TenantId)) .Where(p => p.UserCode == x.Code && p.Status == BalanceRecordStatusEnumType.Valid && p.IsDeleted == false) .Where(p => p.Source != BalanceRecordSourceEnumType.EMealVoucher) From 5ddfbb680f8b4e57696ec449af457328efe41c64 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 15:10:45 +0800 Subject: [PATCH 21/23] =?UTF-8?q?fix=20=E5=B1=95=E7=A4=BA=E5=85=A5?= =?UTF-8?q?=E8=B4=A6=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Order/Services/Implements/BalanceService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs index 3f0957d4..5b704377 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs @@ -337,7 +337,7 @@ public class BalanceService : IBalanceService .LeftJoin((record, ga, department) => ga.DepartmentNo == department.No) .LeftJoin((record, ga, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) .Where((record, ga) => record.UserCode == userCode && factories.Contains(record.TenantId)) - .Where(record => record.Status == BalanceRecordStatusEnumType.Valid && record.Type == BalanceRecordTypeEnumType.Out) + .Where(record => record.Status == BalanceRecordStatusEnumType.Valid || record.Status == BalanceRecordStatusEnumType.Obsolute) .Where(record => record.Source != BalanceRecordSourceEnumType.EMealVoucher) //不展示电子餐券信息 .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .WhereIF(request.ApplicationType.HasValue, record => record.ApplicationType == request.ApplicationType) @@ -345,7 +345,7 @@ public class BalanceService : IBalanceService .Select((record, ga, department, bPlace) => new BalanceRecordUserPageResponse { Id = record.Id, - CurrentAmount = SqlFunc.Abs(record.Amount), + CurrentAmount = record.Amount, Source = record.Source, No = record.No, BusinessPlaceName = bPlace.Name, @@ -362,7 +362,7 @@ public class BalanceService : IBalanceService var monthSum = await _dbContext.Queryable() .Where(x => x.UserCode == userCode && factories.Contains(x.TenantId)) .Where(x => x.CostDate >= monthStart && x.CostDate <= monthEnd) - .Where(x => x.Status == BalanceRecordStatusEnumType.Valid && x.Type == BalanceRecordTypeEnumType.Out) + .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) .SumAsync(x => SqlFunc.Abs(x.Amount)); From ecf98fc1d328ce4b5247e76251f1313656b4a29f Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 15:15:23 +0800 Subject: [PATCH 22/23] =?UTF-8?q?fix=20=E6=9C=88=E4=BB=BD=E6=B1=87?= =?UTF-8?q?=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs index 5b704377..3b7bdd0f 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/BalanceService.cs @@ -364,7 +364,7 @@ public class BalanceService : IBalanceService .Where(x => x.CostDate >= monthStart && x.CostDate <= monthEnd) .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - .SumAsync(x => SqlFunc.Abs(x.Amount)); + .SumAsync(x => x.Amount); return new BalanceRecordUserResultResponse { From c6593f93bc4208fceabc450bfecd3a8e2d333439 Mon Sep 17 00:00:00 2001 From: liguoyang Date: Thu, 14 Nov 2024 15:32:52 +0800 Subject: [PATCH 23/23] =?UTF-8?q?fix=20=E4=BC=98=E5=8C=96=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E6=A3=80=E6=9F=A5,=E6=95=B0=E6=8D=AE=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BalanceRecordUploadBackgroundService.cs | 39 ++++++++++-------- .../HealthCheckBackgroundService.cs | 18 +++++++- Src/Dlhis.Middle/Packs/SqlSugarPack.cs | 7 +--- Src/Dlhis.Middle/Program.cs | 15 ------- .../Services/IHealthCheckService.cs | 2 +- .../Services/Implements/HealthcheckService.cs | 9 ++-- Src/Dlhis.Middle/appsettings.Development.json | 2 +- Src/Dlhis.Middle/local.db-journal | Bin 127736 -> 0 bytes 8 files changed, 47 insertions(+), 45 deletions(-) delete mode 100644 Src/Dlhis.Middle/local.db-journal diff --git a/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs b/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs index f0f441a0..9984ac59 100644 --- a/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs +++ b/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs @@ -1,12 +1,8 @@ -using System.Net.Http.Headers; -using System.Text; using Dlhis.Middle.Apis; using Dlhis.Middle.Entities; -using Dlhis.Middle.Response; using Dlhis.Middle.Services; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.UnifyResultAndValidation; -using Kurisu.AspNetCore.Utils.Extensions; namespace Dlhis.Middle.BackgroundServices; @@ -102,23 +98,30 @@ public class BalanceRecordUploadBackgroundService : BackgroundService } - if (now is { Hour: 4, Minute: 1 } - && !_healthCheckService.GetIsOffline() - && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) + try { - await ds.InitAsync(factories); + if (now is { Hour: 4, Minute: 1 } + && !_healthCheckService.GetIsOffline() + && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) + { + await ds.InitAsync(factories); + } + else if (now is { Hour: 15, Minute: 1 } + && !_healthCheckService.GetIsOffline() + && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) + { + await ds.InitAsync(factories); + } + else if (now is { Hour: 20, Minute: 1 } + && !_healthCheckService.GetIsOffline() + && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) + { + await ds.InitAsync(factories); + } } - else if (now is { Hour: 15, Minute: 1 } - && !_healthCheckService.GetIsOffline() - && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) + catch (Exception e) { - await ds.InitAsync(factories); - } - else if (now is { Hour: 20, Minute: 1 } - && !_healthCheckService.GetIsOffline() - && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) - { - await ds.InitAsync(factories); + _logger.LogError(e, "定时同步离线数据失败"); } } diff --git a/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs b/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs index a765b742..44320032 100644 --- a/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs +++ b/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs @@ -5,17 +5,31 @@ namespace Dlhis.Middle.BackgroundServices; public class HealthCheckBackgroundService : BackgroundService { private readonly IHealthCheckService _healthCheckService; + private readonly IServiceProvider _serviceProvider; + private bool _init; - public HealthCheckBackgroundService(IHealthCheckService healthCheckService) + public HealthCheckBackgroundService(IHealthCheckService healthCheckService, IServiceProvider serviceProvider) { _healthCheckService = healthCheckService; + _serviceProvider = serviceProvider; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { - await _healthCheckService.DoHealthCheckAsync(); + if (await _healthCheckService.DoHealthCheckAsync()) + { + if (!_init) + { + using var scope = _serviceProvider.CreateScope(); + var factories = scope.ServiceProvider.GetService().GetFactories(); + var ds = scope.ServiceProvider.GetService(); + await ds.InitAsync(factories); + } + } + + _init = true; } } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Packs/SqlSugarPack.cs b/Src/Dlhis.Middle/Packs/SqlSugarPack.cs index 89e24c61..b47ad750 100644 --- a/Src/Dlhis.Middle/Packs/SqlSugarPack.cs +++ b/Src/Dlhis.Middle/Packs/SqlSugarPack.cs @@ -1,7 +1,4 @@ -using Dlhis.Entity.Basic.Entity; -using Dlhis.Entity.Canteen.Entities; -using Dlhis.Entity.Device.Entities; -using Dlhis.Middle.Entities; +using Dlhis.Middle.Entities; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar.Attributes; @@ -141,7 +138,7 @@ public class SqlSugarPack : BaseAppPack { using var scope = app.ApplicationServices.CreateScope(); var db = scope.ServiceProvider.GetService(); - + db.Client.DbMaintenance.CreateDatabase(); db.Client.CodeFirst.InitTables(); db.Client.CodeFirst.InitTables(); diff --git a/Src/Dlhis.Middle/Program.cs b/Src/Dlhis.Middle/Program.cs index 7558c266..f139b0fd 100644 --- a/Src/Dlhis.Middle/Program.cs +++ b/Src/Dlhis.Middle/Program.cs @@ -1,5 +1,4 @@ using Dlhis.Middle.BackgroundServices; -using Dlhis.Middle.Services; using Kurisu.AspNetCore; using Kurisu.AspNetCore.Startup; using Microsoft.IdentityModel.Logging; @@ -31,18 +30,4 @@ public class Startup : DefaultStartup services.AddRemoteCall(App.ActiveTypes); services.AddMemoryCache(); } - - /// - public override void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - base.Configure(app, env); - - if (Configuration.GetSection("Download").Value == "1") - { - using var scope = app.ApplicationServices.CreateScope(); - var factories = scope.ServiceProvider.GetService().GetFactories(); - var ds = scope.ServiceProvider.GetService(); - ds.InitAsync(factories).Wait(); - } - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IHealthCheckService.cs b/Src/Dlhis.Middle/Services/IHealthCheckService.cs index e8c05049..b3f8fd9a 100644 --- a/Src/Dlhis.Middle/Services/IHealthCheckService.cs +++ b/Src/Dlhis.Middle/Services/IHealthCheckService.cs @@ -4,5 +4,5 @@ public interface IHealthCheckService : ISingletonDependency { bool GetIsOffline(); - Task DoHealthCheckAsync(); + Task DoHealthCheckAsync(); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs index a254668d..3b44aa94 100644 --- a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs +++ b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs @@ -28,7 +28,7 @@ public class HealthCheckService : IHealthCheckService return _isOffline; } - public async Task DoHealthCheckAsync() + public async Task DoHealthCheckAsync() { try { @@ -50,12 +50,13 @@ public class HealthCheckService : IHealthCheckService _isBegin = true; } + await Task.Delay(_attempted * 1000); + if (_isOffline == false) { _logger.LogInformation("Health"); + return true; } - - await Task.Delay(_attempted * 1000); } else { @@ -69,5 +70,7 @@ public class HealthCheckService : IHealthCheckService _isOffline = true; await Task.Delay(5000); } + + return false; } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/appsettings.Development.json b/Src/Dlhis.Middle/appsettings.Development.json index 1339edac..4a755b60 100644 --- a/Src/Dlhis.Middle/appsettings.Development.json +++ b/Src/Dlhis.Middle/appsettings.Development.json @@ -7,5 +7,5 @@ }, "BaseUrl": "https://yikatong.isawesome.cn", "Factory": "1234", - "Download":1 + "InternalIP":"" } diff --git a/Src/Dlhis.Middle/local.db-journal b/Src/Dlhis.Middle/local.db-journal deleted file mode 100644 index 750c5ab6103838ae055a5ee18909d19742871fc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127736 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 YzWx7cZUCoa82sA28CfTSCve^W0U__dL;wH)