diff --git a/Src/Dlhis.Middle/Api/ICanteenApi.cs b/Src/Dlhis.Middle/Api/ICanteenApi.cs index a48aa144..84a4d7e9 100644 --- a/Src/Dlhis.Middle/Api/ICanteenApi.cs +++ b/Src/Dlhis.Middle/Api/ICanteenApi.cs @@ -1,5 +1,6 @@ using Dlhis.Middle.Request; using Dlhis.Middle.Response; +using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.RemoteCall.Attributes; @@ -12,4 +13,15 @@ public interface ICanteenApi [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/orders")] Task> AddAsync(OrderAddRequest request); + + + + [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); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Controllers/BalancesController.cs b/Src/Dlhis.Middle/Controllers/BalancesController.cs new file mode 100644 index 00000000..b21186d8 --- /dev/null +++ b/Src/Dlhis.Middle/Controllers/BalancesController.cs @@ -0,0 +1,89 @@ +using Dlhis.Middle.Api; +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Dlhis.Middle.Services; +using Kurisu.AspNetCore.DataAccess.Dto; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Aop; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Microsoft.AspNetCore.Mvc; + +namespace Dlhis.Middle.Controllers; + +[Route("api/canteen/[controller]")] +[ApiController] +public class BalancesController : ControllerBase +{ + private readonly IBalanceService _balanceService; + private readonly IHealthCheckService _healthCheckService; + private readonly ICanteenApi _canteenApi; + + public BalancesController(IBalanceService balanceService, IHealthCheckService healthCheckService, ICanteenApi canteenApi) + { + _balanceService = balanceService; + _healthCheckService = healthCheckService; + _canteenApi = canteenApi; + } + + /// + /// 设备消费记录 + /// + /// + /// + [HttpGet("device/page")] + [IgnoreTenant] + public async Task>> GetPageByDeviceUniqueNoGetAsync([FromQuery] BalanceRecordPageByDeviceUniqueNoRequest request) + { + if (_healthCheckService.GetIsOffline()) + { + return new DefaultApiResult>() + { + Data = await _balanceService.GetPageByDeviceUniqueNoAsync(request), + Code = ApiStateCode.Success + }; + } + + return await _canteenApi.GetPageByDeviceUniqueNoAsync(request); + } + + /// + /// 设备消费记录 + /// + /// + /// + [HttpPost("device/page")] + [IgnoreTenant] + public async Task>> GetPageByDeviceUniqueNoPostAsync(BalanceRecordPageByDeviceUniqueNoRequest request) + { + if (_healthCheckService.GetIsOffline()) + { + return new DefaultApiResult>() + { + Data = await _balanceService.GetPageByDeviceUniqueNoAsync(request), + Code = ApiStateCode.Success + }; + } + + return await _canteenApi.GetPageByDeviceUniqueNoAsync(request); + } + + /// + /// 退款 + /// + /// + [HttpPost("refund")] + [IgnoreTenant] + public async Task> RefundAsync(BalanceRecordRefundRequest request) + { + if (_healthCheckService.GetIsOffline()) + { + return new DefaultApiResult() + { + Data = null, + Code = ApiStateCode.Error, + Msg = "不支持离线退款" + }; + } + + return await _canteenApi.RefundAsync(request); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Request/BalanceRecordPageByDeviceUniqueNoRequest.cs b/Src/Dlhis.Middle/Request/BalanceRecordPageByDeviceUniqueNoRequest.cs new file mode 100644 index 00000000..d476c1f1 --- /dev/null +++ b/Src/Dlhis.Middle/Request/BalanceRecordPageByDeviceUniqueNoRequest.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using Kurisu.AspNetCore.DataAccess.Dto; + +namespace Dlhis.Middle.Request; + +public class BalanceRecordPageByDeviceUniqueNoRequest : PageDto +{ + /// + /// 设备唯一号 + /// + [Required(ErrorMessage = "设备唯一号不能为空")] + public string DeviceUniqueNo { get; set; } + + /// + /// 查询日期(优先使用) + /// + public DateTime? SearchDate { get; set; } + + /// + /// 查询时间段(开始) + /// + public DateTime? Start { get; set; } + + /// + /// 查询时间段(结束) + /// + public DateTime? End { get; set; } + + /// + /// 工号 + /// + public string UserCode { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Request/BalanceRecordRefundRequest.cs b/Src/Dlhis.Middle/Request/BalanceRecordRefundRequest.cs new file mode 100644 index 00000000..465ce468 --- /dev/null +++ b/Src/Dlhis.Middle/Request/BalanceRecordRefundRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Dlhis.Middle.Request; + +public class BalanceRecordRefundRequest +{ + /// + /// no + /// + [Required(ErrorMessage = "消费流水号不能为空")] + public string No { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Response/BalanceRecordDeviceResponse.cs b/Src/Dlhis.Middle/Response/BalanceRecordDeviceResponse.cs new file mode 100644 index 00000000..aa9e563a --- /dev/null +++ b/Src/Dlhis.Middle/Response/BalanceRecordDeviceResponse.cs @@ -0,0 +1,109 @@ +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.Utils.Converters; +using Kurisu.AspNetCore.Utils.Extensions; +using Newtonsoft.Json; + +namespace Dlhis.Middle.Response; + +public class BalanceRecordDeviceResponse +{ + public long Id { get; set; } + + /// + /// 流水号 + /// + public string No { get; set; } + + /// + /// 档案号/QRCode + /// + public string UserNo { get; set; } + + /// + /// 姓名 + /// + public string Name { get; set; } + + /// + /// 学工号 + /// + public string UserCode { get; set; } + + /// + /// 部门号 + /// + public string DepartmentNo { get; set; } + + /// + /// 部门 + /// + public string DepartmentName { get; set; } + + /// + /// 所属成本日期 + /// + [JsonConverter(typeof(ToDateJsonConverter))] + public DateTime CostDate { get; set; } + + /// + /// 来源 + /// + public BalanceRecordSourceEnumType Source { get; set; } + public string SourceDesc => Source.GetDescription(); + + /// + /// 收入/支出 + /// + public BalanceRecordTypeEnumType Type { get; set; } + public string TypeDesc => Type.GetDescription(); + + /// + /// 金额 + /// + public decimal Amount { get; set; } + + /// + /// 营业点 + /// + public string BusinessPlaceName { get; set; } + + /// + /// 餐次 + /// + public ShiftEnumType? ShiftType { get; set; } + + /// + /// 数据状态 + /// + public BalanceRecordStatusEnumType Status { get; set; } + + /// + /// 数据状态描述 + /// + public string StatusDesc => Status.GetDescription(); + + /// + /// 记录产生时间 + /// + public DateTime CostDateTime { get; set; } + + /// + /// 汇总金额 + /// + public decimal Sum { get; set; } + + /// + /// 厂区id + /// + public string TenantId { get; set; } + + /// + /// 厂区名称 + /// + public string TenantName { get; set; } + + /// + /// image + /// + public string Image { get; set; } +} diff --git a/Src/Dlhis.Middle/Services/IBalanceService.cs b/Src/Dlhis.Middle/Services/IBalanceService.cs new file mode 100644 index 00000000..75afc3c8 --- /dev/null +++ b/Src/Dlhis.Middle/Services/IBalanceService.cs @@ -0,0 +1,10 @@ +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Kurisu.AspNetCore.DataAccess.Dto; + +namespace Dlhis.Middle.Services; + +public interface IBalanceService : IScopeDependency +{ + Task> GetPageByDeviceUniqueNoAsync(BalanceRecordPageByDeviceUniqueNoRequest request); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/ICanteenSettingService.cs b/Src/Dlhis.Middle/Services/ICanteenSettingService.cs index 144efc61..ed50ad05 100644 --- a/Src/Dlhis.Middle/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Middle/Services/ICanteenSettingService.cs @@ -11,5 +11,8 @@ public interface ICanteenSettingService : IScopeDependency /// /// Task> GetUserSettingAsync(string userNo, string tenantId); + + + Task GetBusinessSwitchTimeAsync(string tenantId); } diff --git a/Src/Dlhis.Middle/Services/Implements/BalanceService.cs b/Src/Dlhis.Middle/Services/Implements/BalanceService.cs new file mode 100644 index 00000000..9f6acfa7 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/BalanceService.cs @@ -0,0 +1,102 @@ +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Enums; +using Dlhis.Middle.Entities; +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Kurisu.AspNetCore.DataAccess.Dto; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Utils.Extensions; +using SqlSugar; + +namespace Dlhis.Middle.Services.Implements; + +public class BalanceService : IBalanceService +{ + private readonly IDbContext _dbContext; + private readonly ICanteenSettingService _canteenSettingService; + + public BalanceService(IDbContext dbContext, ICanteenSettingService canteenSettingService) + { + _dbContext = dbContext; + _canteenSettingService = canteenSettingService; + } + + /// + public async Task> GetPageByDeviceUniqueNoAsync(BalanceRecordPageByDeviceUniqueNoRequest request) + { + var device = await _dbContext.Queryable() + .Where(x => x.UniqueNo == request.DeviceUniqueNo) + .SingleAsync(); + + if (device == null) + { + return new Pagination + { + Data = new List() + }; + } + + if (request.SearchDate.HasValue) + { + request.Start = request.SearchDate; + request.End = request.SearchDate; + } + + + var switchTime = await _canteenSettingService.GetBusinessSwitchTimeAsync(device.TenantId); + if (switchTime.HasValue) + { + var setTime = request.Start!.Value.Add(switchTime.Value.ToTimeSpan()); + if (DateTime.Now < setTime) + { + request.Start = request.Start.Value.AddDays(-1); + request.End = request.End!.Value.AddDays(-1); + } + } + + 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) + //.LeftJoin((record, ga, department, bPlace, imageRecord) => imageRecord.No == record.No) + .Where(record => record.DeviceUniqueNo == device.UniqueNo) + .Where(record => record.CostDate >= request.Start && record.CostDate <= request.End) + .Where(record => record.Type == BalanceRecordTypeEnumType.Out + && (record.Status == BalanceRecordStatusEnumType.Valid || (record.Status == BalanceRecordStatusEnumType.Obsolute && record.Remark == "退款"))) + .WhereIF(request.UserCode.IsPresent(), record => record.UserCode == request.UserCode) + .OrderBy(record => record.CostDateTime, OrderByType.Desc) + .Select((record, ga, department, bPlace) => new BalanceRecordDeviceResponse + { + Id = record.Id, + Amount = SqlFunc.Abs(record.Amount), + CostDate = record.CostDate, + DepartmentNo = record.DepartmentNo, + UserNo = record.UserNo, + UserCode = record.UserCode, + Type = record.Type, + Status = record.Status, + Source = record.Source, + No = SqlFunc.IIF(record.Status == BalanceRecordStatusEnumType.Obsolute && record.Remark == "退款", "(已退)" + record.No, record.No), + Name = record.Name, + DepartmentName = department.Name, + BusinessPlaceName = bPlace.Name, + ShiftType = record.ShiftType, + CostDateTime = record.CostDateTime, + TenantId = record.TenantId, + Image = string.Empty + }) + .ToPageListAsync(request); + + //消费总金额 + var sum = await _dbContext.Queryable() + .Where(record => record.CostDate >= request.Start && record.CostDate <= request.End) + .Where(record => record.DeviceUniqueNo == device.UniqueNo) + .Where(record => record.Type == BalanceRecordTypeEnumType.Out && record.Status == BalanceRecordStatusEnumType.Valid) + .SumAsync(record => SqlFunc.Abs(SqlFunc.IsNull(record.Amount, 0))); + + page.Data.ForEach(x => x.Sum = sum); + + return page; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs b/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs index 0ee0162f..79239dda 100644 --- a/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs +++ b/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs @@ -34,4 +34,23 @@ public class CanteenSettingService : ICanteenSettingService return result; } + + /// + public async Task GetBusinessSwitchTimeAsync(string tenantId) + { + var setting = await _context.Queryable() + .Where(x => x.Enable) + .Where(x => x.TenantId == tenantId) + .Where(x => x.Item == CanteenSettingItemEnumType.BusinessSwitchTime) + .FirstAsync(); + + try + { + return TimeOnly.Parse(setting.Value); + } + catch (Exception) + { + return null; + } + } } \ No newline at end of file