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 Kurisu.Core.DataAccess.Dto; using Kurisu.SqlSugar.Services; using Kurisu.Core.User.Abstractions; using Dlhis.Canteen.Setting.Services; using Dlhis.Basic.Api; using Kurisu.SqlSugar.Extensions; using SqlSugar; using Mapster; namespace Dlhis.Canteen.Order.Services.Implements; public class BookingOrderService : BaseOrderService, IBookingOrderService { private readonly IDishesMenuApi _dishesMenuApi; private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; public BookingOrderService(ICanteenSettingService canteenSettingService, IDbContext dbContext, IDishesMenuApi dishesMenuApi, IBusinessPlacePermissionsApi businessPlacePermissionsApi, ICurrentUser currentUser, IDevicePermissionApi devicePermissionApi, ILogger logger) : base(canteenSettingService, dbContext, currentUser, devicePermissionApi, logger) { _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 GetSettingAsync(businessPlace.TenantId, CanteenSettingItemEnumType.BookingDeadline) ?? 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 = CanteenApplicaitonTypeEnumType.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(), SubsidiesFromMonth = now.ToString("yyyyMM"), IsOffline = false, IsCostCrossTenant = await GetSettingAsync(businessPlace.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), OriginTenantId = gainfo.Factory, //人员可能跨基地消费 Status = BalanceRecordStatusEnumType.Valid, OrderStatus = CanteenOrderStatusEnumType.Success, CostDateTime = now, //标识当前消费的基地 TenantId = businessPlace.TenantId, }; var orderId = await DbContext.InsertReturnIdentityAsync(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.UserNo == order.UserNo).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.UserNo == order.UserNo && 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.ShiftTpe.HasValue, x => x.ShiftType == request.ShiftTpe) .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 GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.BookingDeadline) ?? 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.UserNo == order.UserNo).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 == CanteenApplicaitonTypeEnumType.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; } }