using System.Collections.Generic; using System.Text; using Dlhis.Basic.Api; using Dlhis.Canteen.Tickets.Services; using Dlhis.Canteen.Tickets.Services.Implements; using Dlhis.Canteen.Voucher.Requests; using Dlhis.Canteen.Voucher.Response; using Dlhis.Constants; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Basic.Enums; using Dlhis.Entity.Canteen.Entities; using Dlhis.Entity.Canteen.Entities.Voucher; using Dlhis.Entity.Canteen.Enums; using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.Utils.Extensions; using SqlSugar; namespace Dlhis.Canteen.Voucher.Services.Implements; [Service("BirthdayVoucherService")] public class BirthdayVoucherService : BaseVoucherService, IVoucherService { private readonly IFactorySettingApi _factorySettingApi; /// public BirthdayVoucherService(IDbContext dbContext, IFactorySettingApi factorySettingApi) : base(dbContext) { _factorySettingApi = factorySettingApi; } /// public override async Task CreateAsync(Dictionary pairs) { var p = GetDateRange(pairs); var remark = pairs != null && pairs.TryGetValue("remark", out var r) ? r?.ToString() : string.Empty; var userCodes = pairs != null && pairs.TryGetValue("userCodes", out var c) ? c as List : new List(); //忽略tenant操作 await DbContext.IgnoreAsync(async () => { var gainfos = await GetGainfosAsync(userCodes, p.begin, p.end); var departmentNos = gainfos.Select(g => g.DepartmentNo).Distinct().ToList(); var departmentInfos = await DbContext.Queryable() .Where(x => departmentNos.Contains(x.No)) .ToListAsync(); foreach (var gainfo in gainfos.Where(x => x.Birthday.HasValue)) { await DbContext.Client.Ado.BeginTranAsync(); try { var years = GetBirthdayYears(gainfo.Birthday.Value, p.begin, p.end); foreach (var year in years) { await GenerateVoucherAsync(gainfo, departmentInfos, year, remark); } await DbContext.Client.Ado.CommitTranAsync(); } catch (Exception) { await DbContext.Client.Ado.RollbackTranAsync(); } } }); } private async Task GenerateVoucherAsync(GainfoEntity gainfo, List departments, int year, string remark) { //生日券金额固定100元 const decimal amount = 100; var dayStr = year + "-" + gainfo.Birthday.Value.ToString("MM-dd"); //由数据类型+工号+生日组成唯一标识 var no = GenerateUniqueCode(VoucherEnumType.Birthday.ToString() + gainfo.Code + dayStr); if (!await DbContext.Queryable() .Where(x => x.No == no) .Where(x => x.Type == VoucherEnumType.Birthday) .AnyAsync()) { await DbContext.InsertAsync(new VoucherEntity { No = no, Type = VoucherEnumType.Birthday, ClaimDateTime = new DateTime(year, gainfo.Birthday.Value.Month, gainfo.Birthday.Value.Day), OriginalBalance = amount, Balance = amount, Description = dayStr + "生日券", Remark = remark, CreatedBy = 1, CreateTime = DateTime.Now, ModifiedBy = 1, ModifiedTime = DateTime.Now, Version = 0 }); var department = departments.FirstOrDefault(x => x.No == gainfo.DepartmentNo); await DbContext.InsertAsync(new VoucherUserInfoEntity { VoucherNo = no, UserCode = gainfo.Code, CostId = gainfo.CostId, Werks = gainfo.Werks, Factory = gainfo.Factory, DepartmentNo = gainfo.DepartmentNo, DepartmentNoFirst = department?.DepartmentNoFirst, DepartmentNoSecond = department?.DepartmentNoSecond, CreatedBy = 1, CreateTime = DateTime.Now, ModifiedBy = 1, ModifiedTime = DateTime.Now }); } } private (DateTime begin, DateTime end) GetDateRange(Dictionary pairs) { var begin = DateTime.Today; var end = begin; if (pairs != null) { if (pairs.TryGetValue("begin", out var b)) { if (b is DateTime bb) { begin = bb; } } if (pairs.TryGetValue("end", out var e)) { if (e is DateTime ee) { end = ee; } } } return (begin, end); } private async Task> GetGainfosAsync(List userCodes, DateTime begin, DateTime end) { var factries = await _factorySettingApi.GetEnableBirthdayVoucherFactoriesAsync(); var isToday = begin.Date == end.Date; var beginMonth = begin.Month; var endMonth = end.Month; var overYear = endMonth < beginMonth; //var limit = end.Date.AddHours(9);//9点入职 return await DbContext.Queryable() .WhereIF(userCodes.IsPresent(), x => userCodes.Contains(x.Code)) .Where(x => x.Type == GainfoUserTypeEnumType.Normal || x.Type == GainfoUserTypeEnumType.SecurityWhite) .Where(x => x.Birthday.HasValue && !string.IsNullOrEmpty(x.CostId)) .WhereIF(isToday, x => SqlFunc.DateValue(x.Birthday.Value, DateType.Month) == begin.Month && SqlFunc.DateValue(x.Birthday.Value, DateType.Day) == begin.Day) .WhereIF(!isToday && !overYear, x => SqlFunc.DateValue(x.Birthday.Value, DateType.Month) >= beginMonth && SqlFunc.DateValue(x.Birthday.Value, DateType.Month) <= endMonth) .WhereIF(!isToday && overYear, x => SqlFunc.DateValue(x.Birthday.Value, DateType.Month) >= beginMonth || SqlFunc.DateValue(x.Birthday.Value, DateType.Month) <= endMonth) .Where(x => factries.Contains(x.Factory)) .ToListAsync(); } private static string GenerateUniqueCode(string data) { return QrCodeConstants.Birthday + data.ToArray().Select(x => { var b = Encoding.ASCII.GetBytes(x.ToString()); int i = b[0]; return Convert.ToString(i); }).Join(string.Empty); } /// /// 计算给定生日在区间 [begin, end] 内出现的所有年份(可能为0、1或多个)。 /// - 对于每个候选年份构造安全的日期(处理 2/29 在非闰年的情况,退到当年最大天数)。 /// - 若区间内找不到匹配年份,则返回包含 begin.Year 的单元素列表作为兜底行为,保持向后兼容。 /// private static List GetBirthdayYears(DateTime birthday, DateTime begin, DateTime end) { var years = new List(); // 扩展范围到 begin.Year-1 .. end.Year+1 以覆盖跨年边界的极端情况 var startYear = begin.Year - 1; var endYear = end.Year + 1; for (var y = startYear; y <= endYear; y++) { var candidate = SafeDate(y, birthday); if (candidate >= begin && candidate <= end) { years.Add(candidate.Year); } } // 去重并排序 years = years.Distinct().OrderBy(x => x).ToList(); return years; static DateTime SafeDate(int year, DateTime bday) { var day = Math.Min(bday.Day, DateTime.DaysInMonth(year, bday.Month)); return new DateTime(year, bday.Month, day); } } public override async Task> GetVoucherBalancePageAsync(VoucherBalancePageRequest request) { return await DbContext.Queryable() .LeftJoin((x, bPlace) => x.BusinessPlaceCode == bPlace.Code) .LeftJoin((x, bPlace, department) => x.DepartmentNo == department.No) .Where(x => x.UserCode == request.UserCode) .WhereIF(request.SearchDateTimePeriod.HasValue(), x => x.CostDate >= request.SearchDateTimePeriod.Start && x.CostDate <= request.SearchDateTimePeriod.End) //成本日期 .Where(x => x.Status == BalanceRecordStatusEnumType.Valid && x.Type == BalanceRecordTypeEnumType.Out) //有效,消费数据 .Where(x => x.Source == BalanceRecordSourceEnumType.Birthday) .Select((x, bPlace, department) => new VoucherBalancePageResponse { No = x.No, UserNo = x.UserNo, Name = x.Name, UserCode = x.UserCode, DepartmentNo = x.DepartmentNo, DepartmentName = department.Name, CostDate = x.CostDate, Remark = x.Remark, Source = x.Source, Type = x.Type, Amount = SqlFunc.Abs(x.Amount), BusinessPlaceName = bPlace.Name, ShiftType = x.ShiftType, DeviceInfo = x.DeviceUniqueNo, CostId = x.CostId, OriginTenantId = x.OriginTenantId, Status = x.Status, CostDateTime = x.CostDateTime, Factory = x.TenantId, Werks = x.Werks }) .ToPageListAsync(request); } }