275 lines
11 KiB
C#
275 lines
11 KiB
C#
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 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 = 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);
|
||
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<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())
|
||
{
|
||
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
|
||
});
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
private (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>> 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 limit = end.Date.AddHours(9);//9点入职
|
||
return await DbContext.Queryable<GainfoEntity>()
|
||
.WhereIF(userCodes.IsPresent(), x => userCodes.Contains(x.Code))
|
||
.Where(x => !SqlFunc.IsNullOrEmpty(x.DepartmentNo))
|
||
.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);
|
||
}
|
||
|
||
/// <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);
|
||
}
|
||
} |