using Dlhis.Basic.Common.Message.Requests; using Dlhis.Basic.Common.Message.Services; using Dlhis.Basic.Organization.Comparers; using Dlhis.Basic.Organization.Repositories; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Basic.Enums; using Dlhis.Entity.Device.Enums; using Dlhis.Entity.Sys.Entities; using Dlhis.Entity.Sys.Enums; using Dlhis.Jip.Api.Dto; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.Utils.Extensions; namespace Dlhis.Basic.Organization.Services.Implements; public class GainfoUpdateService : IGainfoUpdateService { private readonly IDbContext _dbContext; private readonly ILogger _logger; private readonly IJinkoSapService _jinkoSapService; private readonly IGainfoPermissionService _gainfoPermissionService; private readonly IJinkoApiService _dingTalkApi; private readonly IIdentityUserSyncService _identityUserSyncService; private readonly IGainfoSapRepository _gainfoSapRepository; public GainfoUpdateService(IDbContext dbContext, ILogger logger, IJinkoSapService jinkoSapService, IGainfoPermissionService gainfoPermissionService, IJinkoApiService dingTalkApi, IIdentityUserSyncService identityUserSyncService, IGainfoSapRepository gainfoSapRepository) { _dbContext = dbContext; _logger = logger; _jinkoSapService = jinkoSapService; _gainfoPermissionService = gainfoPermissionService; _dingTalkApi = dingTalkApi; _identityUserSyncService = identityUserSyncService; _gainfoSapRepository = gainfoSapRepository; } public async Task TodoByWerkAsync(string werk) { var werkInfo = await _dbContext.Queryable().Where(x => x.Werk == werk).SingleAsync(); werkInfo.LastSyncStartTime = DateTime.Now; werkInfo.HandleMessage = "任务执行中"; await _dbContext.UpdateAsync(werkInfo); try { var personResp = await _jinkoSapService.GetPersonsByWerkAsync(werk); var persons = personResp.Persons; werkInfo.HandleMessage = "SAP人员数量" + persons.Count; werkInfo.DiffNumber = 0; werkInfo.Total = persons.Count; await _dbContext.UpdateAsync(werkInfo); if (persons.Count > 0) { var modifiedPersons = await GetModifiedPersonAsync(persons.OrderByDescending(x => x.ZSTATUS).ThenBy(x => x.ZRZRQ).ToList()); werkInfo.HandleMessage = "SAP人员差异数量" + modifiedPersons.Count; werkInfo.DiffNumber = modifiedPersons.Count; await _dbContext.UpdateAsync(werkInfo); if (modifiedPersons.Count > 0) { await SavePersonAsync(modifiedPersons); } } werkInfo.HandleMessage = "同步结束"; werkInfo.LastSyncEndTime = DateTime.Now; werkInfo.BackgroundJobKey = string.Empty; await _dbContext.UpdateAsync(werkInfo); } catch (Exception ex) { _logger.LogError(ex, "{werk}同步处理异常:{message}", werk, ex.Message); werkInfo.HandleMessage = ex.Message; werkInfo.LastSyncEndTime = DateTime.Now; werkInfo.BackgroundJobKey = string.Empty; await _dbContext.UpdateAsync(werkInfo); } } public async Task TodoByCodeAsync(string code) { _logger.LogInformation("SAP根据学工号同步开始:{code}", code); try { var personResp = await _jinkoSapService.GetPersonsByCodeAsync(code); var persons = personResp.Persons; _logger.LogInformation("获取SAP人员数量为:{count}", persons.Count); if (persons.Count > 0) { var modifiedPersons = await GetModifiedPersonAsync(persons.OrderByDescending(x => x.ZSTATUS).ThenBy(x => x.ZRZRQ).ToList()); _logger.LogInformation("SAP人员差异数量为:{count}", modifiedPersons.Count); if (modifiedPersons.Count > 0) { await SavePersonAsync(modifiedPersons); } } _logger.LogInformation("SAP根据学工号同步结束:{code}", code); } catch (Exception e) { _logger.LogError(e, "同步人员异常:{msg}", e.Message); } } private async Task SavePersonAsync(List persons) { var thirdPartPersons = persons.Where(x => !string.IsNullOrWhiteSpace(x.ZWORKCODE)).ToList(); if (thirdPartPersons.Count <= 0) { return; } var workCodes = thirdPartPersons.Select(x => x.ZWORKCODE).Distinct().ToList(); var chunks = workCodes.Chunk(4000).ToList(); var comparer = new SapPersonComparer(); foreach (var searchCodes in chunks) { _logger.LogWarning("SAP分批处理Chunk数量:{count}", searchCodes.Length); var chunkThirdPartPersons = thirdPartPersons.Where(x => searchCodes.Contains(x.ZWORKCODE)).ToList(); var latestHistoryPersons = (await _gainfoSapRepository.GetLatestPersonsAsync(searchCodes.ToList())) .ToDictionary(x => x.ZWORKCODE, x => x); foreach (var person in chunkThirdPartPersons) { try { await _dbContext.UseTransactionAsync(async () => { var item = ToGainfoSap(person); if (!latestHistoryPersons.TryGetValue(person.ZWORKCODE, out var latestHistory) || !comparer.Equals(person, latestHistory)) { await _dbContext.InsertAsync(item); } var no = await _dbContext.Queryable() .Where(x => x.Code == item.ename) .Select(x => x.No) .SingleAsync(); if (no.IsEmpty()) { await InsertCurrentPersonCoreAsync(item); } else { await UpdatePersonAsync(item, false); } }); } catch (Exception ex) { _logger.LogError(ex, "执行人员同步失败,工号:{workCode},错误:{message}", person.ZWORKCODE, ex.Message); } } } } private async Task InsertCurrentPersonCoreAsync(GainfoSapEntity item) { var info = GainfoSapToGainfo(item); //只有身份证号存在才进行新增 if (info.IdNo.IsPresent()) { if (item.isowner == "T") { if (!await _dbContext.Queryable().AnyAsync(x => x.Code == item.ename)) { await _dbContext.InsertAsync(info); } await _identityUserSyncService.SyncAccountAsync(info.Code, info.Name, info.IdNo, info.Factory); } else { if (!await _dbContext.Queryable().AnyAsync(x => x.Code == item.ename)) { await _dbContext.Client.Insertable(info).AS("GainfoLeave").ExecuteCommandAsync(); } } } else { _logger.LogWarning("SAP新增人员失败,身份证号不存在:{userCode},{userName}", info.Code, info.Name); } } private static GainfoEntity GainfoSapToGainfo(GainfoSapEntity item) { var info = new GainfoEntity { No = Guid.NewGuid().ToString(), InternalNo = item.PERNR, Type = GainfoUserTypeEnumType.Normal, Name = item.gname, NameEn = item.gname2, Code = item.ename, CountryNo = item.country, IdClass = item.idcls, IdNo = item.idno.ToUpperInvariant(), Native = item.native, Gender = item.sex == "F" ? GenderEnumType.Female : GenderEnumType.Male, Birthday = item.birthday, Address = item.address, DepartmentNo = item.appellation, Nation = item.nation, Phone = item.phone, Degree = item.email, Grade = item.ZZ_ZD, Title = item.Z_ZJ, PositionNo = item.STELL, Remark = item.remark, Werks = item.WERKS, CostId = item.KOSTL, EntryDate = item.createdate, ResignationDate = item.ZLZRQ, Workplace = item.ZZ_GZDJD, Factory = item.ZZ_GZDCQ }; return info; } public async Task UpdatePersonAsync(GainfoSapEntity item, bool useTransaction = true) { if (useTransaction) { await _dbContext.Client.Ado.BeginTranAsync(); } try { //获取当前在职人员 var gainfo = await _dbContext.Queryable().Where(x => x.Code == item.ename).SingleAsync(); //默认人员存在 if (gainfo != null) { var workplace = item.ZZ_GZDJD; var factory = item.ZZ_GZDCQ; if (item.isowner == "T") { var change = string.Empty; var hasFactoryChange = gainfo.Factory != factory || gainfo.Workplace != workplace; var hasDepartmentChange = gainfo.DepartmentNo != item.appellation; if (hasFactoryChange) { await _identityUserSyncService.SyncAccountAsync(gainfo.Code, gainfo.Name, gainfo.IdNo, factory); change = $"{gainfo.Factory}->{factory}"; } if (hasDepartmentChange) { change += $"{gainfo.DepartmentNo}->{item.appellation}"; } if (change.IsPresent()) { await _gainfoPermissionService.ResetPermissionsAsync(gainfo.No, DeviceTaskEnumType.Delete, null, null, $"异动生成({change})"); } await _dbContext.Updateable().SetColumns(g => new GainfoEntity { InternalNo = item.PERNR, Name = item.gname, NameEn = item.gname2, IdNo = item.idno.ToUpperInvariant(), Birthday = item.birthday, PositionNo = item.STELL, Grade = item.ZZ_ZD, Title = item.Z_ZJ, Phone = item.phone, DepartmentNo = item.appellation, Remark = item.remark, Degree = item.email, Werks = item.WERKS, CostId = item.KOSTL, EntryDate = item.createdate, ResignationDate = item.ZLZRQ, Workplace = workplace, Factory = factory, ModifiedTime = DateTime.Now }).Where(x => x.Code == item.ename).ExecuteCommandAsync(); //有变更就重新下发 if (change.IsPresent()) { await _gainfoPermissionService.ResetPermissionsAsync(gainfo.No, DeviceTaskEnumType.Add, null, null, $"异动生成({change})"); //只有厂区变更才需要处理工作网格 if (hasFactoryChange) { await HandleWorkGridChangeAsync(gainfo, DateOnly.FromDateTime(DateTime.Now)); } } } else { await _gainfoPermissionService.ResetPermissionsAsync(gainfo.No, DeviceTaskEnumType.Delete, null, null, "人员离职生成"); //先写入离职表 gainfo.IdentityUserId = (int)gainfo.Id; //记录原始id gainfo.Id = 0; gainfo.ResignationDate = item.ZLZRQ; if (!await _dbContext.Queryable().AnyAsync(x => x.Code == item.ename)) { await _dbContext.Client.Insertable(gainfo).AS("GainfoLeave").ExecuteCommandAsync(); } await _dbContext.Deleteable() .Where(x => x.UserNo == gainfo.No) .ExecuteCommandAsync(); await _dbContext.Deleteable() .Where(x => x.AssignType == AssignTypeEnumType.User && x.AssignValue == gainfo.No) .ExecuteCommandAsync(); await HandleWorkGridChangeAsync(gainfo, DateOnly.FromDateTime(gainfo.ResignationDate!.Value)); gainfo.Id = gainfo.IdentityUserId; await _dbContext.DeleteReallyAsync(gainfo); await _identityUserSyncService.LockAccountAsync(gainfo.Code); } } if (useTransaction) { await _dbContext.Client.Ado.CommitTranAsync(); } } catch (Exception ex) { if (useTransaction) { await _dbContext.Client.Ado.RollbackTranAsync(); } _logger.LogError(ex, "更新人员失败:{message}", ex.Message); throw; } } private async Task HandleWorkGridChangeAsync(GainfoEntity gainfo, DateOnly effectiveDate) { try { var workGridUser = await _dbContext.Queryable() .Where(x => x.UserCode == gainfo.Code) .SingleAsync(); if (workGridUser == null) { return; } var workGridSchedules = await _dbContext.Queryable() .Where(x => x.UserCode == gainfo.Code) .Where(x => x.Date >= effectiveDate) .ToListAsync(); if (workGridSchedules.Any()) { try { var workGrid = await _dbContext.Queryable() .Where(x => x.Code == workGridUser.WorkGridCode) .Select(x => new { x.LeaderUserCode, x.Name }) .SingleAsync(); if (workGrid != null) { var dates = workGridSchedules .Select(x => $"{x.Date:yyyy年-MM月-dd日}的{x.PeriodType.GetDescription()}") .Join(","); var content = $"{workGrid.Name}的网格长,你好,检测到你网格中 {gainfo.Name}({gainfo.Code}),因发生离职或者异动,现已经删除该员工{dates}的排班信息,请前往系统处理!"; await _dingTalkApi.SendWorkMessageByWorkGridAsync(new DingTalkWorkMessageRequest { Content = content, WorkCodes = new List { workGrid.LeaderUserCode } }); } } catch (Exception ex) { _logger.LogError(ex, "工作网格消息通知失败,工号:{workCode},错误:{error}", gainfo.Code, ex.Message); } } await _dbContext.DeleteAsync(workGridUser); await _dbContext.DeleteAsync(workGridSchedules); } catch (Exception ex) { _logger.LogError(ex, "网格异动处理异常,工号:{workCode},错误:{error}", gainfo.Code, ex.Message); } } private async Task> GetModifiedPersonAsync(List sapList) { if (sapList.Count <= 0) { return new List(); } var comparer = new SapPersonComparer(); var workCodes = sapList .Select(x => x.ZWORKCODE) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct() .ToList(); if (workCodes.Count <= 0) { return sapList; } var last = await _gainfoSapRepository.GetLatestPersonsAsync(workCodes); if (last.Count <= 0) { return sapList; } var lastMap = last .Where(x => !string.IsNullOrWhiteSpace(x.ZWORKCODE)) .GroupBy(x => x.ZWORKCODE) .ToDictionary(x => x.Key, x => x.First()); var existingCodes = await _dbContext.Queryable() .Where(x => workCodes.Contains(x.Code)) .Select(x => x.Code) .ToListAsync(); var modifiedPersons = new List(); foreach (var person in sapList) { if (string.IsNullOrWhiteSpace(person.ZWORKCODE)) { modifiedPersons.Add(person); continue; } var existsInGainfo = existingCodes.Contains(person.ZWORKCODE); var isOwner = int.Parse(person.ZSTATUS) < 5; if ((isOwner && !existsInGainfo) || (!isOwner && existsInGainfo)) { modifiedPersons.Add(person); continue; } if (!lastMap.TryGetValue(person.ZWORKCODE, out var latestHistory)) { modifiedPersons.Add(person); continue; } if (comparer.Equals(person, latestHistory)) { continue; } modifiedPersons.Add(person); } if (modifiedPersons.Count > 0) { var first = modifiedPersons[0]; var historyCandidates = !string.IsNullOrWhiteSpace(first.ZWORKCODE) && lastMap.TryGetValue(first.ZWORKCODE, out var currentHistoryPerson) ? currentHistoryPerson : null; var hasHistory = historyCandidates != null; var diffType = hasHistory ? "Update" : "Insert"; var diffMessage = hasHistory ? string.Join("; ", GetFirstDiffFields(first, historyCandidates)) : "历史记录不存在"; _logger.LogWarning("SAP首条差异 DiffType:{diffType}, ZWORKCODE:{workCode}, HasHistory:{hasHistory}, Diffs:{diffs}", diffType, first.ZWORKCODE, hasHistory, diffMessage); } return modifiedPersons; } private static GainfoSapEntity ToGainfoSap(SapPersonItem person) { var entryDate = person.ZRZRQ; var resignDate = person.ZLZRQ; var rmk = $"入职时间:{entryDate.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture)} "; if (resignDate != null && resignDate.Value.ToString("yyyy-MM-dd") != "1900-01-01") rmk += $"离职时间:{resignDate.Value.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture)} "; if (!string.IsNullOrWhiteSpace(person.MGTXT)) rmk += $"变更原因:{person.MGTXT}"; var native = GetNative(person.ZZ_SFZH); var gender = person.GESCH == "2" ? "F" : "M"; person.ZZ_SFZH = person.ZZ_SFZH?.TrimStart(); return new GainfoSapEntity { gname = person.NAME, gname2 = person.NAME2, ename = person.ZWORKCODE, idno = person.ZZ_SFZH, birthday = person.GBDAT, email = person.ZXUELI, appellation = person.ORGEH, phone = person.ZMOBILE, ZSTATUS = person.ZSTATUS, createdate = person.ZRZRQ, PERNR = person.PERNR, WERKS = person.WERKS, KOSTL = person.KOSTL_XJ, ZZ_ZD = person.ZZ_ZD, Z_ZJ = person.Z_ZJ, STELL = person.PLANS, //person.STELL, 20251129变更 STELL_NM = person.STELL_NM ?? string.Empty, ZLZRQ = person.ZLZRQ, MASSN = person.MASSN ?? string.Empty, MNTXT = person.MNTXT ?? string.Empty, remark = rmk, native = native, sex = gender, logdate = DateTime.Now, isowner = int.Parse(person.ZSTATUS) < 5 ? "T" : "F", country = person.ZZ_COUNNM, address = "", occupation = "99", nation = "01", idcls = "ID", ZZ_COUNNM = person.ZZ_COUNNM, ZZ_GZDJD = person.ZZ_GZDJD, ZZ_GZDCQ = person.ZZ_GZDCQ }; } private static string GetNative(string idNo) { if (string.IsNullOrWhiteSpace(idNo) || idNo.Length < 6) return null; idNo = idNo.TrimStart(); return idNo[..6]; } private static List GetFirstDiffFields(SapPersonItem current, SapPersonItem history) { var diffs = new List(); AddDiff(diffs, "PERNR", current.PERNR, history.PERNR); AddDiff(diffs, "WERKS", current.WERKS, history.WERKS); AddDiff(diffs, "ORGEH", current.ORGEH, history.ORGEH); AddDiff(diffs, "NAME", current.NAME, history.NAME); AddDiff(diffs, "GBDAT", FormatDate(current.GBDAT), FormatDate(history.GBDAT)); AddDiff(diffs, "ZZ_SFZH", NormalizeIdNo(current.ZZ_SFZH), NormalizeIdNo(history.ZZ_SFZH)); AddDiff(diffs, "ZWORKCODE", current.ZWORKCODE, history.ZWORKCODE); AddDiff(diffs, "ZMOBILE", Normalize(current.ZMOBILE), Normalize(history.ZMOBILE)); AddDiff(diffs, "ZSTATUS", Normalize(current.ZSTATUS), Normalize(history.ZSTATUS)); AddDiff(diffs, "KOSTL_XJ", Normalize(current.KOSTL_XJ), Normalize(history.KOSTL_XJ)); AddDiff(diffs, "NAME2", Normalize(current.NAME2), Normalize(history.NAME2)); AddDiff(diffs, "ZZ_ZD", Normalize(current.ZZ_ZD), Normalize(history.ZZ_ZD)); AddDiff(diffs, "Z_ZJ", Normalize(current.Z_ZJ), Normalize(history.Z_ZJ)); AddDiff(diffs, "ZRZRQ", FormatDate(current.ZRZRQ), FormatDate(history.ZRZRQ)); AddDiff(diffs, "ZLZRQ", FormatDate(current.ZLZRQ), FormatDate(history.ZLZRQ)); AddDiff(diffs, "PLANS", Normalize(current.PLANS), Normalize(history.PLANS)); AddDiff(diffs, "STELL_NM", Normalize(current.STELL_NM), Normalize(history.STELL_NM)); AddDiff(diffs, "MASSN", Normalize(current.MASSN), Normalize(history.MASSN)); AddDiff(diffs, "MNTXT", Normalize(current.MNTXT), Normalize(history.MNTXT)); AddDiff(diffs, "ZZ_GZDJD", Normalize(current.ZZ_GZDJD), Normalize(history.ZZ_GZDJD)); AddDiff(diffs, "ZZ_GZDCQ", Normalize(current.ZZ_GZDCQ), Normalize(history.ZZ_GZDCQ)); return diffs; } private static void AddDiff(List diffs, string field, string current, string history) { if (!string.Equals(current ?? string.Empty, history ?? string.Empty, StringComparison.Ordinal)) { diffs.Add($"{field}: [{history}] -> [{current}]"); } } private static string Normalize(string value) => value?.Trim() ?? string.Empty; private static string NormalizeIdNo(string value) => value?.TrimStart() ?? string.Empty; private static string FormatDate(DateTime? value) => value?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty; private static string FormatDate(DateTime value) => value.ToString("yyyy-MM-dd HH:mm:ss"); }