using Dlhis.Basic.Api; using Dlhis.Canteen.Order.Repository; using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Setting.Services; using Dlhis.Constants; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; using Dlhis.Entity.Canteen.Entities.EMealVoucher; using Dlhis.Entity.Canteen.Entities.Voucher; using Dlhis.Entity.Canteen.Enums; using Dlhis.Entity.Device; using Dlhis.Entity.Device.Entities; using Dlhis.Entity.Device.Enums; using Dlhis.Sys.Api; using Kurisu.AspNetCore; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.Cache; 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 Mapster; using SqlSugar; namespace Dlhis.Canteen.Order.Services.Implements; public class BalanceService : IBalanceService { private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; private readonly ITenantApi _tenantApi; private readonly ICanteenSettingService _canteenSettingService; private readonly RedisCache _redisCache; private readonly IBalanceRepository _balanceRepository; private readonly IBusinessPlacePermissionsApi _businessPlacePermissionsApi; public BalanceService(IDbContext dbContext, ICurrentUser currentUser, ITenantApi tenantApi, ICanteenSettingService canteenSettingService, RedisCache redisCache, IBalanceRepository balanceRepository, IBusinessPlacePermissionsApi businessPlacePermissionsApi) { _dbContext = dbContext; _currentUser = currentUser; _tenantApi = tenantApi; _canteenSettingService = canteenSettingService; _redisCache = redisCache; _balanceRepository = balanceRepository; _businessPlacePermissionsApi = businessPlacePermissionsApi; } public async Task> GetPageAsync(BalanceRecordPageRequest request) { var page = await _dbContext.Queryable() .LeftJoin((record, department) => record.DepartmentNo == department.No) .LeftJoin((record, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .WhereIF(request.Factory.IsPresent(), record => record.TenantId == request.Factory) .WhereIF(request.Type.HasValue, record => record.Type == request.Type) .WhereIF(request.ShiftTypes.IsPresent(), record => request.ShiftTypes.Contains(record.ShiftType)) .WhereIF(request.UserCode.IsPresent(), record => record.UserCode == request.UserCode) .WhereIF(request.UserName.IsPresent(), record => record.Name == request.UserName) .WhereIF(request.FromFactory.IsPresent(), record => record.OriginTenantId == request.FromFactory) .OrderBy(record => record.CreateTime, OrderByType.Desc) .Select((record, department, bPlace) => new BalanceRecordResponse { Id = record.Id, No = record.No, Amount = record.Amount, CostDate = record.CostDate, CostId = record.CostId, DepartmentNo = record.DepartmentNo, DeviceInfo = record.DeviceUniqueNo, UserNo = record.UserNo, UserCode = record.UserCode, Type = record.Type, Status = record.Status, Source = record.Source, OriginTenantId = record.OriginTenantId, Name = record.Name, DepartmentName = department.Name, BusinessPlaceName = bPlace.Name, CostDateTime = record.CostDateTime, Factory = record.TenantId, ShiftType = record.ShiftType, Remark = record.Remark, Werks = record.Werks }) .ToPageListAsync(request); try { var tenants = await _tenantApi.GetTenantsInternalAsync(); page.Data.ForEach(item => { item.OriginTenantId = tenants.FirstOrDefault(x => x.Code == item.OriginTenantId)?.Name; item.Factory = tenants.FirstOrDefault(x => x.Code == item.Factory)?.Name; }); } catch (Exception) { //ignore } return page; } /// public async Task> GetPageToExportAsync(BalanceRecordPageRequest request) { var rows = await _dbContext.Queryable() .LeftJoin((record, department) => record.DepartmentNo == department.No) .LeftJoin((record, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .WhereIF(request.Factory.IsPresent(), record => record.TenantId == request.Factory) .WhereIF(request.Type.HasValue, record => record.Type == request.Type) .WhereIF(request.ShiftTypes.IsPresent(), record => request.ShiftTypes.Contains(record.ShiftType)) .WhereIF(request.UserCode.IsPresent(), record => record.UserCode == request.UserCode) .WhereIF(request.UserName.IsPresent(), record => record.Name == request.UserName) .WhereIF(request.FromFactory.IsPresent(), record => record.OriginTenantId == request.FromFactory) .OrderBy(record => record.CreateTime, OrderByType.Desc) .Select((record, department, bPlace) => new BalanceRecordResponse { Id = record.Id, No = record.No, Amount = record.Amount, CostDate = record.CostDate, CostId = record.CostId, DepartmentNo = record.DepartmentNo, DeviceInfo = record.DeviceUniqueNo, UserNo = record.UserNo, UserCode = record.UserCode, Type = record.Type, Status = record.Status, Source = record.Source, OriginTenantId = record.OriginTenantId, Name = record.Name, DepartmentName = department.Name, BusinessPlaceName = bPlace.Name, CostDateTime = record.CostDateTime, Factory = record.TenantId, ShiftType = record.ShiftType, Remark = record.Remark, Werks = record.Werks }) .ToListAsync(); try { var tenants = await _tenantApi.GetTenantsInternalAsync(); rows.ForEach(item => { item.OriginTenantId = tenants.FirstOrDefault(x => x.Code == item.OriginTenantId)?.Name; item.Factory = tenants.FirstOrDefault(x => x.Code == item.Factory)?.Name; }); } catch (Exception) { //ignore } return rows; } public async Task RefundAsync(string no) { var cache = App.GetServiceProvider().GetService(); var balanceRecord = await _dbContext.Queryable().Where(x => x.No == no).SingleAsync(); balanceRecord.ThrowIfNull("数据不存在"); (balanceRecord.Status != BalanceRecordStatusEnumType.Valid).ThrowIfTrue("无法退款"); await using var handler = await cache.LockAsync($"balance:refund:{no}", TimeSpan.FromSeconds(9)); if (handler.Acquired) { if (balanceRecord is { Status: BalanceRecordStatusEnumType.Valid, Type: BalanceRecordTypeEnumType.Out }) { balanceRecord.Status = BalanceRecordStatusEnumType.Obsolute; balanceRecord.Remark = "退款"; await _dbContext.UpdateAsync(balanceRecord); if (balanceRecord.Source == BalanceRecordSourceEnumType.EMealVoucher) { var originData = balanceRecord.UserNo.Split(":"); var eNo = originData[0]; var idNo = originData.Last(); await CommonHelper.RetryAsync(async () => { var eDetail = await _dbContext.Queryable().Where(x => x.No == eNo && x.IdNo == idNo).SingleAsync(); var amount = Math.Abs(balanceRecord.Amount); await _dbContext.Updateable() .SetColumns(x => x.Version == x.Version + 1) .SetColumns(x => x.Balance == x.Balance + amount) .SetColumns(x => x.ModifiedTime == DateTime.Now) .Where(x => x.No == eNo && x.IdNo == idNo && x.Version == eDetail.Version) .ExecuteCommandThrowVersionAsync(); }, 5); } else if (balanceRecord.Source == BalanceRecordSourceEnumType.Birthday) { //生日券消费退款 throw new UserFriendlyException("不支持生日券消费退款"); //var voucherNo = balanceRecord.Remark.Replace("(生日券消费)", ""); //await CommonHelper.RetryAsync(async () => //{ // var voucher = await _dbContext.Queryable().Where(x => x.No == voucherNo).SingleAsync(); // var amount = Math.Abs(balanceRecord.Amount); // await _dbContext.Updateable() // .SetColumns(x => x.Version == x.Version + 1) // .SetColumns(x => x.Balance == x.Balance + amount) // .SetColumns(x => x.ModifiedTime == DateTime.Now) // .Where(x => x.No == voucherNo && x.Version == voucher.Version) // .ExecuteCommandThrowVersionAsync(); //},5); } else { await _balanceRepository.CalculateBalanceAsync(balanceRecord.UserCode); } } } } 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, department) => record.DepartmentNo == department.No) .LeftJoin((record, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) .LeftJoin((record, 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, department, bPlace, imageRecord) => 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 = imageRecord.Image }) .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; } public async Task RecordPageDetailsAsync(BalanceRecordPageDetailRequest request) { var myCodes = (await _businessPlacePermissionsApi.GetMyAsync()).Select(x => (Guid?)x.Code).ToList(); if (request.BusinessplaceCodes.IsPresent()) { myCodes = myCodes.Where(x => request.BusinessplaceCodes.Contains(x)).ToList(); } //var myCodes = request.BusinessplaceCodes; var page = await GetBaseQuery() .OrderBy(record => new { CostDate = SqlFunc.Desc(record.CostDate), CostDateTime = SqlFunc.Desc(record.CostDateTime) }) .Select((record, department, bPlace) => new BalanceRecordDetailPageResponse { Id = record.Id, No = record.No, CurrentAmount = SqlFunc.Abs(record.Amount), CostDate = record.CostDate, UserNo = record.UserNo, UserCode = record.UserCode, Source = record.Source, UserName = record.Name, DepartmentName = department.Name, BusinessPlaceName = bPlace.Name, CostDateTime = record.CostDateTime, ShiftType = record.ShiftType, Werks = record.Werks, Factory = record.TenantId, Remark = record.Remark, }) .ToPageListAsync(request); try { var tenants = await _tenantApi.GetTenantsInternalAsync(); page.Data.ForEach(item => { item.Factory = tenants.FirstOrDefault(x => x.Code == item.Factory)?.Name; }); } catch (Exception) { //ignore } var amount = await GetBaseQuery().SumAsync(record => SqlFunc.Abs(record.Amount)); var userCount = await GetBaseQuery().Select(record => record.UserCode).Distinct().CountAsync(); return new BalanceRecordByBusinessPlaceResponse { Amount = amount, Total = userCount, Pagination = page, }; ISugarQueryable GetBaseQuery() { return _dbContext.Queryable() .LeftJoin((record, department) => record.DepartmentNo == department.No) .LeftJoin((record, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .Where(record => record.Type == BalanceRecordTypeEnumType.Out && record.Status == BalanceRecordStatusEnumType.Valid) .Where(record => myCodes.Contains(record.BusinessPlaceCode)) .WhereIF(request.ShiftTypes.IsPresent(), record => request.ShiftTypes.Contains(record.ShiftType)) .WhereIF(request.Search.IsPresent(), record => record.UserCode == request.Search || record.Name == request.Search); } } public async Task> RecordPageDetailsToExportAsync(BalanceRecordPageDetailRequest request) { var myCodes = (await _businessPlacePermissionsApi.GetMyAsync()).Select(x => (Guid?)x.Code).ToList(); if (request.BusinessplaceCodes.IsPresent()) { myCodes = myCodes.Where(x => request.BusinessplaceCodes.Contains(x)).ToList(); } //var myCodes = request.BusinessplaceCodes; var rows = await _dbContext.Queryable() .LeftJoin((record, department) => record.DepartmentNo == department.No) .LeftJoin((record, department, bPlace) => record.BusinessPlaceCode == bPlace.Code) .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .Where(record => record.Type == BalanceRecordTypeEnumType.Out && record.Status == BalanceRecordStatusEnumType.Valid) .Where(record => myCodes.Contains(record.BusinessPlaceCode)) .WhereIF(request.ShiftTypes.IsPresent(), record => request.ShiftTypes.Contains(record.ShiftType)) .WhereIF(request.Search.IsPresent(), record => record.UserCode == request.Search || record.Name == request.Search) .OrderBy(record => new { CostDate = SqlFunc.Desc(record.CostDate), CostDateTime = SqlFunc.Desc(record.CostDateTime) }) .Select((record, department, bPlace) => new BalanceRecordDetailPageResponse { Id = record.Id, No = record.No, CurrentAmount = SqlFunc.Abs(record.Amount), CostDate = record.CostDate, UserNo = record.UserNo, UserCode = record.UserCode, Source = record.Source, UserName = record.Name, DepartmentName = department.Name, BusinessPlaceName = bPlace.Name, CostDateTime = record.CostDateTime, ShiftType = record.ShiftType, Werks = record.Werks, Factory = record.TenantId, Remark = record.Remark, }) .ToListAsync(); try { var tenants = await _tenantApi.GetTenantsInternalAsync(); rows.ForEach(item => { item.Factory = tenants.FirstOrDefault(x => x.Code == item.Factory)?.Name; }); } catch (Exception) { //ignore } return rows; } public async Task SupplementBalanceAsync(BalanceSupplementRequest request) { var balanceInfo = await _dbContext.Queryable().Where(x => x.No == request.No).SingleAsync(); balanceInfo.ThrowIfNull("明细数据不存在"); BalanceRecordSourceConstants.IgnoreSources.Contains(balanceInfo.Source).ThrowIfTrue("类型不支持"); (balanceInfo.Status != BalanceRecordStatusEnumType.Valid || balanceInfo.Type != BalanceRecordTypeEnumType.Out).ThrowIfTrue("消费明细状态有误"); balanceInfo.Status = BalanceRecordStatusEnumType.Obsolute; balanceInfo.Remark += request.Reason; await _dbContext.Client.Ado.BeginTranAsync(); try { await _dbContext.UpdateAsync(balanceInfo); //不为空则插入记录 if (request.Amount != 0) { var newBalanceRecord = balanceInfo.Adapt(); newBalanceRecord.Id = 0; newBalanceRecord.No = SnowFlakeHelper.Instance.NextId().ToString(); newBalanceRecord.Amount = request.Amount; newBalanceRecord.Remark = balanceInfo.No + ":" + request.Reason; newBalanceRecord.Status = BalanceRecordStatusEnumType.Valid; await _dbContext.InsertAsync(newBalanceRecord); } await _balanceRepository.CalculateBalanceAsync(balanceInfo.UserCode); await _dbContext.Client.Ado.CommitTranAsync(); } catch (Exception) { await _dbContext.Client.Ado.RollbackTranAsync(); throw; } } public async Task GetPageByUserAsync(BalanceRecordUserPageRequest request) { var userCode = _currentUser.GetUserClaim(UserClaimConstants.JinkoCode) ?? 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 == userCode && factories.Contains(record.TenantId)) .Where(record => record.Status == BalanceRecordStatusEnumType.Valid || record.Status == BalanceRecordStatusEnumType.Obsolute) .Where(record => !BalanceRecordSourceConstants.IgnoreSources.Contains(record.Source)) .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .WhereIF(request.ApplicationType.HasValue, record => record.ApplicationType == request.ApplicationType) .OrderBy(record => record.CostDateTime) .Select((record, ga, department, bPlace) => new BalanceRecordUserPageResponse { Id = record.Id, CurrentAmount = record.Amount, Source = record.Source, Status = record.Status, No = SqlFunc.IIF(record.Status == BalanceRecordStatusEnumType.Obsolute, "(已退)" + record.No, record.No), BusinessPlaceName = bPlace.Name, ShiftType = record.ShiftType, CostDateTime = record.CostDateTime, ApplicationType = record.ApplicationType, }) .ToPageListAsync(request); var now = DateTime.Now; var monthStart = new DateTime(now.Year, now.Month, 1); var monthEnd = monthStart.AddMonths(1).AddSeconds(-1); 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) .Where(x => !BalanceRecordSourceConstants.IgnoreSources.Contains(x.Source)) .SumAsync(x => x.Amount); return new BalanceRecordUserResultResponse { CurrentMonthSum = monthSum, Items = page }; } public async Task> GetUserBalanceGroupAsync(BalanceRecordUserGroupPageRequest request) { return await _dbContext.Queryable() .LeftJoin((record, ga) => record.UserNo == ga.No) //.LeftJoinIF(request.GainfoStatus == GainfoStatusEnumType.Working, (record, ga) => record.UserNo == ga.No) //.LeftJoinIF(request.GainfoStatus == GainfoStatusEnumType.Leave, (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, order) => record.No == order.No) .Where(record => record.UserCode == request.UserCode) .WhereIF(request.SearchDateTimePeriod.HasValue(), record => record.CostDate >= request.SearchDateTimePeriod.Start && record.CostDate <= request.SearchDateTimePeriod.End) .WhereIF(request.Type.HasValue, record => record.Type == request.Type) .OrderBy(record => record.CreateTime, OrderByType.Desc) .Select((record, ga, department, bPlace, order) => new BalanceRecordResponse { Id = record.Id, No = record.No, IsOffline = order.IsOffline, Amount = record.Amount, CostDate = record.CostDate, CostId = record.CostId, DepartmentNo = record.DepartmentNo, DeviceInfo = record.DeviceUniqueNo, UserNo = record.UserNo, UserCode = record.UserCode, Type = record.Type, Status = record.Status, Source = record.Source, OriginTenantId = record.OriginTenantId, Name = record.Name, DepartmentName = department.Name, BusinessPlaceName = bPlace.Name, CostDateTime = record.CostDateTime, Factory = record.TenantId, ShiftType = record.ShiftType, Remark = record.Remark, Werks = record.Werks }) .ToPageListAsync(request); } public async Task GetConsumeNumberAsync() { var cacheKey = $"GetConsumeNumberAsync:{_currentUser.GetStringTenantId()}"; var cache = await _redisCache.StringGetAsync(cacheKey); if (cache.IsPresent()) { return cache.ToObject(); } var devices = await _dbContext.Queryable() .Where(x => x.Type == DeviceTypeEnumType.Meal) .ToListAsync(); var offCount = await GetOfflineDevicesCount(devices); var places = await _dbContext.Queryable() .Where(x => x.Status == CanteenBusinessPlaceStatusEnumType.Opened) .ToListAsync(); //消费走势 var months = await GetConsumptionTrendsAsync(1); var days = await GetConsumptionTrendsAsync(2); var result = new ConsumeNumberResponse { Total = devices.Count, OffNumber = offCount.Count, PlaceNumber = places.Count, DailyNumber = 20 + Random.Shared.Next(1, 5), Days = days, Months = months }; await _redisCache.StringSetAsync(cacheKey, result.ToJson(), TimeSpan.FromMinutes(30)); return result; } /// public async Task ManuallyImportBalanceAndReCalcAsync(List requests) { foreach (var request in requests) { var gainfo = await _dbContext.Queryable() .Where(x => x.Code == request.UserCode) .SingleAsync() ?? await _dbContext.Queryable().AS("GainfoLeave") .Where(x => x.Code == request.UserCode) .SingleAsync(); gainfo.ThrowIfNull($"工号{request.UserCode}不存在"); await _dbContext.InsertAsync(new BalanceRecordEntity { No = "M" + SnowFlakeHelper.Instance.NextId(), UserNo = gainfo.No, Name = gainfo.Name, UserCode = gainfo.Code, DepartmentNo = gainfo.DepartmentNo, Source = BalanceRecordSourceEnumType.OtherTenantSubsidies, Type = BalanceRecordTypeEnumType.In, Amount = request.Balance, BusinessPlaceCode = null, BusinessShiftCode = null, ShiftType = null, CostDate = DateTime.Now.Date, DeviceUniqueNo = null, IP = null, CostId = gainfo.CostId, OriginTenantId = gainfo.Factory, IsCostCrossTenant = false, Status = BalanceRecordStatusEnumType.Valid, CostDateTime = DateTime.Now, Remark = $"手动写入余额({request.Reason})", ApplicationType = CanteenApplicationTypeEnumType.Manually, TenantId = gainfo.Factory, Werks = gainfo.Werks, IsDeleted = false }); await _balanceRepository.CalculateBalanceAsync(gainfo.Code); } } /// /// 获取离线设备 /// /// /// private async Task> GetOfflineDevicesCount(List devices) { var now = DateTime.Now; var deviceUniqueNos = devices.Select(x => x.UniqueNo).ToList(); var deviceRegistries = await _dbContext.Queryable() .LeftJoin((x, d) => x.DeviceUniqueNo == d.UniqueNo) .Where(x => deviceUniqueNos.Contains(x.DeviceUniqueNo)) .Select((x, d) => new { d.UniqueNo, d.HeartbeatInterval, x.LastHeartbeatTime }) .ToListAsync(); var onlineDevices = deviceRegistries .Where(x => { if (x.HeartbeatInterval <= 0) return true; return x.LastHeartbeatTime >= now; }) .Select(x => x.UniqueNo) .ToList(); var offUniqueNos = deviceUniqueNos.Except(onlineDevices).ToList(); var list = devices.Where(x => offUniqueNos.Contains(x.UniqueNo)).ToList(); return list; } private async Task> GetConsumptionTrendsAsync(int type) { if (type == 1) { //月与总额对比 var months = GetMonthsOfYear(); var last = months.Last().AddMonths(1).AddDays(-1); var data = await _dbContext.Queryable() .Where(x => x.CostDate >= months.First() && x.CostDate <= last) .Where(x => x.Type == BalanceRecordTypeEnumType.Out && x.Status == BalanceRecordStatusEnumType.Valid) .GroupBy(x => SqlFunc.DateValue(x.CostDate, DateType.Month)) .Select(x => new { CostDate = SqlFunc.DateValue(x.CostDate, DateType.Month), Amount = SqlFunc.AggregateSum(SqlFunc.Abs(x.Amount)) }) .ToListAsync(); var total = data.Sum(x => x.Amount); return months.Select(m => { var current = data.Where(x => x.CostDate == m.Month).ToList(); return new ConsumptionTrendResponse { DateTime = m.ToString("yyyy-MM"), CurrentAmount = current.Sum(x => x.Amount), TotalAmount = total }; }).ToList(); } else { //日与总额对比 var days = GetDaysOfCurrentMonth(); var data = await _dbContext.Queryable() .Where(x => x.CostDate >= days.First() && x.CostDate <= days.Last()) .Where(x => x.Type == BalanceRecordTypeEnumType.Out && x.Status == BalanceRecordStatusEnumType.Valid) .GroupBy(x => x.CostDate) .Select(x => new { x.CostDate, Amount = SqlFunc.AggregateSum(SqlFunc.Abs(x.Amount)) }) .ToListAsync(); var total = data.Sum(x => x.Amount); return days.Select(d => { var current = data.FirstOrDefault(x => x.CostDate == d); return new ConsumptionTrendResponse { DateTime = d.ToString("yyyy-MM-dd"), CurrentAmount = current?.Amount ?? 0, TotalAmount = total }; }).ToList(); } } /// /// 获取当前年所有月 /// /// private List GetMonthsOfYear() { var months = new List(); // 获取当前年份 int year = DateTime.Now.Year; // 遍历12个月份并添加到列表中 for (var month = 1; month <= 12; month++) { var monthDate = new DateTime(year, month, 1); months.Add(monthDate); } return months; } /// /// 获取当前月所有天 /// /// private List GetDaysOfCurrentMonth() { var days = new List(); // 获取当前月份的第一天和最后一天 var firstDayOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); var lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1); // 遍历当前月的所有日期并添加到列表中 for (DateTime date = firstDayOfMonth; date <= lastDayOfMonth; date = date.AddDays(1)) { days.Add(date); } return days; } }