Files
jinko.yikatong/Src/Dlhis.Canteen/Voucher/Services/Implements/BirthdayVoucherService.cs

360 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text;
using Dlhis.Basic.Api;
using Dlhis.Basic.Api.Requests;
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 Dlhis.Entity.Common.Entities;
using Kurisu.AspNetCore.CustomClass;
using Kurisu.AspNetCore.DataAccess.Dto;
using Kurisu.AspNetCore.DataAccess.Entity;
using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions;
using Kurisu.AspNetCore.Utils.Extensions;
using SqlSugar;
namespace Dlhis.Canteen.Voucher.Services.Implements;
[Service("BirthdayVoucherService")]
public class BirthdayVoucherService : BaseVoucherService, IVoucherService
{
private readonly IFactorySettingApi _factorySettingApi;
private readonly IDingTalkApi _dingTalkApi;
private readonly ILogger<BirthdayVoucherService> _logger;
/// <inheritdoc />
public BirthdayVoucherService(IServiceProvider serviceProvider) : base(serviceProvider)
{
_factorySettingApi = serviceProvider.GetService<IFactorySettingApi>();
_dingTalkApi = serviceProvider.GetService<IDingTalkApi>();
_logger = serviceProvider.GetService<ILogger<BirthdayVoucherService>>();
}
/// <inheritdoc />
public override async Task CreateAsync(Dictionary<string, object> 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<string> : new List<string>();
//忽略tenant操作
await DbContext.IgnoreAsync<ITenantId>(async () =>
{
var (gainfos, events) = await GetGainfosAsync(userCodes, p.begin, p.end);
var departmentNos = gainfos.Select(g => g.DepartmentNo).Distinct().ToList();
var departmentInfos = await DbContext.Queryable<DepartmentEntity>()
.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);
if (gainfo.EntryDate.HasValue)
{
// 仅保留在候选发放年份中,入职日期严格早于该年生日的年份
years = years.Where(y =>
{
var r = gainfo.EntryDate.Value.Date < SafeDateForYear(y, gainfo.Birthday.Value);
if (!r)
{
events.Add(new UserBusinessEventEntity
{
UserCode = gainfo.Code,
EventType = "GenerateBirthdayVoucher",
Content = $"入职时间为{gainfo.EntryDate.Value.ToString("yyyy-MM-dd")},不发放日期{SafeDateForYear(y, gainfo.Birthday.Value).ToString("yyyy-MM-dd")}的生日券"
});
}
return r;
}).ToList();
}
foreach (var year in years)
{
await GenerateVoucherAsync(gainfo, departmentInfos, year, remark);
}
await DbContext.Client.Ado.CommitTranAsync();
}
catch (Exception ex)
{
await DbContext.Client.Ado.RollbackTranAsync();
events.Add(new UserBusinessEventEntity { UserCode = gainfo.Code, EventType = "GenerateBirthdayVoucher", Content = $"出现异常:{ex.Message}" });
}
}
if (events.Any())
{
await DbContext.InsertAsync(events);
}
});
}
private async Task GenerateVoucherAsync(GainfoEntity gainfo, List<DepartmentEntity> departments, int year, string remark)
{
//生日券金额固定100元
const decimal amount = 100;
var dayStr = year.ToString();//按年区分,避免生日日期变更重复发放
//由数据类型+工号+生日组成唯一标识
var no = GenerateUniqueCode(VoucherEnumType.Birthday.ToString() + gainfo.Code + dayStr);
if (!await DbContext.Queryable<VoucherEntity>()
.Where(x => x.No == no)
.Where(x => x.Type == VoucherEnumType.Birthday)
.AnyAsync())
{
// 使用安全日期来处理 2/29 情况:非闰年会退到当年该月最大天数(即 2/28)
var claimDate = SafeDateForYear(year, gainfo.Birthday.Value);
await DbContext.InsertAsync(new VoucherEntity
{
No = no,
Type = VoucherEnumType.Birthday,
ClaimDateTime = claimDate,
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,
PositionNo = gainfo.PositionNo,
DepartmentNo = gainfo.DepartmentNo,
DepartmentNoFirst = department?.DepartmentNoFirst,
DepartmentNoSecond = department?.DepartmentNoSecond,
CreatedBy = 1,
CreateTime = DateTime.Now,
ModifiedBy = 1,
ModifiedTime = DateTime.Now
});
try
{
await _dingTalkApi.SendMessageUrlAsync(new DingTalkMessageUrlRequest
{
MessageUrl = "dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fiam.jinkosolar.com%2Fsso%2Foidc1%2FdAuthorize%3Fclient_id%3DAPP104%26response_type%3Dcode%26redirect_uri%3Dhttps%3a%2f%2fface.jinkosolar.com%2fh5%2f%3fto%3dmyBirthdayCoupon",
PicUrl = "@lALOACZwe2Rk",
Title = "请查收你的生日消费券",
Text = gainfo.Name + "生日快乐!特意为你准备了一张生日消费券,点击下方链接去查看,快去超市选购心仪好物吧!",
UserCode = gainfo.Code
});
}
catch (Exception ex)
{
_logger.LogError(ex, "发送生日券消息失败,工号:{UserCode},错误原因:{Error}", gainfo.Code, ex.Message);
}
}
}
/// <summary>
/// 返回在指定年份内与原生日相匹配的安全日期。
/// - 对 2/29 在非闰年情况下,会退到当年该月最大天数(即 2/28)。
/// </summary>
private static DateTime SafeDateForYear(int year, DateTime birthday)
{
var day = Math.Min(birthday.Day, DateTime.DaysInMonth(year, birthday.Month));
return new DateTime(year, birthday.Month, day);
}
private static (DateTime begin, DateTime end) GetDateRange(Dictionary<string, object> 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<(List<GainfoEntity>, List<UserBusinessEventEntity>)> GetGainfosAsync(List<string> 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 excludePositionNos = new List<string>
{
"41001471",
"43000181",
"43000452",
"43000459",
"49200047",
"49400001",
"49400002",
"49990004"
};
var gainfos = await DbContext.Queryable<GainfoEntity>()
.WhereIF(userCodes.IsPresent(), x => userCodes.Contains(x.Code))
//.Where(x => !SqlFunc.IsNullOrEmpty(x.DepartmentNo))
//.Where(x => !excludePositionNos.Contains(x.PositionNo))//排除特殊岗位
.Where(x => x.Type == GainfoUserTypeEnumType.Normal || x.Type == GainfoUserTypeEnumType.SecurityWhite)
.Where(x => x.Birthday.HasValue)
//.Where(x => !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();
//UserBusinessEventEntity
var events = new List<UserBusinessEventEntity>();
foreach (var gainfo in gainfos)
{
if (gainfo.DepartmentNo.IsEmpty())
{
events.Add(new UserBusinessEventEntity { UserCode = gainfo.Code, EventType = "GenerateBirthdayVoucher", Content = "部门编号为空" });
}
else if (excludePositionNos.Contains(gainfo.PositionNo))
{
events.Add(new UserBusinessEventEntity { UserCode = gainfo.Code, EventType = "GenerateBirthdayVoucher", Content = $"排除岗位编码{gainfo.PositionNo}" });
}
else if (gainfo.CostId.IsEmpty())
{
events.Add(new UserBusinessEventEntity { UserCode = gainfo.Code, EventType = "GenerateBirthdayVoucher", Content = "成本中心为空" });
}
}
return (gainfos
.Where(x => x.DepartmentNo.IsPresent())
.Where(x => !excludePositionNos.Contains(x.PositionNo))
.Where(x => x.CostId.IsPresent())
.ToList(), events);
}
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);
}
/// <summary>
/// 计算给定生日在区间 [begin, end] 内出现的所有年份(可能为0、1或多个)。
/// - 对于每个候选年份构造安全的日期(处理 2/29 在非闰年的情况,退到当年最大天数)。
/// - 若区间内找不到匹配年份,则返回包含 begin.Year 的单元素列表作为兜底行为,保持向后兼容。
/// </summary>
private static List<int> GetBirthdayYears(DateTime birthday, DateTime begin, DateTime end)
{
var years = new List<int>();
// 扩展范围到 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<Pagination<VoucherBalancePageResponse>> GetVoucherBalancePageAsync(VoucherBalancePageRequest request)
{
return await DbContext.Queryable<BalanceRecordEntity>()
.LeftJoin<BusinessPlaceEntity>((x, bPlace) => x.BusinessPlaceCode == bPlace.Code)
.LeftJoin<DepartmentEntity>((x, bPlace, department) => x.DepartmentNo == department.No)
.WhereIF(request.Factories.IsPresent(), x => request.Factories.Contains(x.TenantId))
.WhereIF(request.UserCode.IsPresent(), x => x.UserCode == request.UserCode)
.WhereIF(request.DepartmentNo.IsPresent(), x => x.DepartmentNo == request.DepartmentNo)
.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);
}
}