using Yitter.IdGenerator; namespace Admin.NET.Plugin.AiDOP.Supply; /// /// 采购申请合并服务。支持本次生成 PR 合并与租户待处理历史 PR 合并(对齐旧 PrAutoMerge2)。 /// public class PurchaseRequestMergeService : ITransient { private readonly ISqlSugarClient _db; private readonly NumberRuleService _numberRuleService; public PurchaseRequestMergeService(ISqlSugarClient db, NumberRuleService numberRuleService) { _db = db; _numberRuleService = numberRuleService; } public PurchaseRequestMergeResult MergeGeneratedRequests(List requests) => MergeInMemory(requests); /// 合并租户内待处理历史 PR(state=1,5 周窗口)。 public async Task MergeTenantPendingAsync(long tenantId, string account) { var tomorrow = DateTime.Today.AddDays(1); var windowEnd = GetWeekStart(DateTime.Today)?.AddDays(35) ?? DateTime.Today.AddDays(35); var pending = await _db.Ado.SqlQueryAsync( """ SELECT Id, pr_billno AS PrBillNo, pr_purchaseid AS PrPurchaseId, pr_purchasenumber AS PrPurchaseNumber, pr_purchasename AS PrPurchaseName, pr_purchaser AS PrPurchaser, pr_purchaser_num AS PrPurchaserNum, pr_rqty AS PrRqty, pr_aqty AS PrAqty, pr_sqty AS PrSqty, icitem_id AS IcitemId, icitem_name AS IcitemName, pr_ssend_date AS PrSsendDate, pr_sarrive_date AS PrSarriveDate, pr_unit AS PrUnit, state AS State, pr_type AS PrType, currencytype AS CurrencyType, tenant_id AS TenantId, factory_id AS FactoryId, org_id AS OrgId, company_id AS CompanyId, IsRequireGoods, supplier_type AS SupplierType, IsDeleted FROM srm_pr_main WHERE tenant_id = @TenantId AND IFNULL(IsDeleted, 0) = 0 AND IFNULL(state, 0) = 1 AND IFNULL(analogcalcversion, '') = '' AND IFNULL(refer_pr_billno, '') = '' AND pr_ssend_date IS NOT NULL AND pr_ssend_date <= @WindowEnd ORDER BY pr_ssend_date, Id """, new SugarParameter("@TenantId", tenantId), new SugarParameter("@WindowEnd", windowEnd)); if (pending.Count <= 1) { return new PurchaseRequestHistoricalMergeResult { PendingCount = pending.Count, MergedGroupCount = 0 }; } foreach (var pr in pending) { if (pr.PrSsendDate.HasValue && pr.PrSsendDate.Value.Date < tomorrow) { var shift = (tomorrow - pr.PrSsendDate.Value.Date).Days; pr.PrSsendDate = tomorrow; if (pr.PrSarriveDate.HasValue) pr.PrSarriveDate = pr.PrSarriveDate.Value.AddDays(shift); } } var groups = pending .GroupBy(x => new { x.TenantId, x.CompanyId, x.FactoryId, x.IcitemId, x.PrPurchaseId, x.IsRequireGoods, SupplierType = x.SupplierType ?? string.Empty, WeekStart = GetWeekStart(x.PrSsendDate) }) .Where(g => g.Count() > 1) .ToList(); if (groups.Count == 0) { return new PurchaseRequestHistoricalMergeResult { PendingCount = pending.Count, MergedGroupCount = 0 }; } var now = DateTime.Now; var createdCount = 0; var closedCount = 0; var reducedCount = 0; foreach (var group in groups) { var rows = group.OrderBy(x => x.PrSsendDate).ThenBy(x => x.PrSarriveDate).ThenBy(x => x.Id).ToList(); var merged = BuildMergedRow(rows, account, now); var numbers = await _numberRuleService.NextBatchInCurrentTransactionAsync( "PR", merged.TenantId.ToString(), 1, account); merged.PrBillNo = numbers.FirstOrDefault()?.Trim() ?? throw Oops.Oh("历史 PR 合并编号生成失败"); merged.Id = YitIdHelper.NextId(); await InsertMergedPurchaseRequestAsync(merged); foreach (var old in rows) { await _db.Ado.ExecuteCommandAsync( """ UPDATE srm_pr_main SET state = 0, refer_pr_billno = @NewBillNo, update_by_name = @User, update_time = @Now WHERE Id = @Id AND tenant_id = @TenantId """, new SugarParameter("@NewBillNo", merged.PrBillNo), new SugarParameter("@User", account), new SugarParameter("@Now", now), new SugarParameter("@Id", old.Id), new SugarParameter("@TenantId", tenantId)); await _db.Ado.ExecuteCommandAsync( """ UPDATE srm_po_occupy SET polist_id = @NewPrId, update_by_name = @User, update_time = @Now WHERE tenant_id = @TenantId AND polist_id = @OldPrId """, new SugarParameter("@NewPrId", merged.Id), new SugarParameter("@User", account), new SugarParameter("@Now", now), new SugarParameter("@TenantId", tenantId), new SugarParameter("@OldPrId", old.Id)); } createdCount++; closedCount += rows.Count; reducedCount += rows.Count - 1; } return new PurchaseRequestHistoricalMergeResult { PendingCount = pending.Count, MergedGroupCount = createdCount, ClosedPrCount = closedCount, CreatedPrCount = createdCount, ReducedCount = reducedCount }; } private static PurchaseRequestMergeResult MergeInMemory(List requests) { if (requests.Count <= 1) { return new PurchaseRequestMergeResult { Requests = requests, OriginalCount = requests.Count, MergedCount = requests.Count }; } var merged = requests .GroupBy(x => new { x.TenantId, x.CompanyId, x.FactoryId, x.IcitemId, x.PrPurchaseId, x.IsRequireGoods, SupplierType = x.SupplierType ?? string.Empty, WeekStart = GetWeekStart(x.PrSsendDate) }) .Select(group => { var rows = group.OrderBy(x => x.PrSsendDate).ThenBy(x => x.PrSarriveDate).ThenBy(x => x.Id).ToList(); return BuildMergedRow(rows, rows.First().CreateByName ?? "system", DateTime.Now); }) .ToList(); return new PurchaseRequestMergeResult { Requests = merged, OriginalCount = requests.Count, MergedCount = merged.Count }; } private static PurchaseRequestMain BuildMergedRow(List rows, string account, DateTime now) { var first = rows.First(); return new PurchaseRequestMain { Id = first.Id, PrPurchaseId = first.PrPurchaseId, PrPurchaseNumber = first.PrPurchaseNumber, PrPurchaseName = first.PrPurchaseName, PrPurchaser = first.PrPurchaser, PrPurchaserNum = first.PrPurchaserNum, PrRqty = rows.Sum(x => x.PrRqty ?? 0), PrAqty = rows.Sum(x => x.PrAqty ?? 0), PrSqty = rows.Sum(x => x.PrSqty ?? 0), IcitemId = first.IcitemId, IcitemName = first.IcitemName, PrSsendDate = rows.Min(x => x.PrSsendDate), PrSarriveDate = rows.Min(x => x.PrSarriveDate), PrUnit = first.PrUnit, State = 1, PrType = first.PrType ?? 3, CurrencyType = first.CurrencyType, CreateByName = account, CreateTime = now, UpdateByName = account, UpdateTime = now, TenantId = first.TenantId, FactoryId = first.FactoryId, OrgId = first.OrgId, CompanyId = first.CompanyId, IsDeleted = false, IsRequireGoods = first.IsRequireGoods, SupplierType = first.SupplierType }; } private async Task InsertMergedPurchaseRequestAsync(PurchaseRequestMain pr) { await _db.Ado.ExecuteCommandAsync( """ INSERT INTO srm_pr_main (Id,pr_billno,pr_purchaseid,pr_purchasenumber,pr_purchasename,pr_purchaser,pr_purchaser_num, pr_rqty,pr_aqty,pr_sqty,icitem_id,icitem_name,pr_ssend_date,pr_sarrive_date,pr_unit,state,pr_type,currencytype, create_by_name,create_time,update_by_name,update_time,tenant_id,factory_id,org_id,IsDeleted,company_id,IsRequireGoods,supplier_type) VALUES (@Id,@PrBillNo,@PrPurchaseId,@PrPurchaseNumber,@PrPurchaseName,@PrPurchaser,@PrPurchaserNum, @PrRqty,@PrAqty,@PrSqty,@IcitemId,@IcitemName,@PrSsendDate,@PrSarriveDate,@PrUnit,@State,@PrType,@CurrencyType, @CreateByName,@CreateTime,@UpdateByName,@UpdateTime,@TenantId,@FactoryId,@OrgId,0,@CompanyId,@IsRequireGoods,@SupplierType) """, new SugarParameter("@Id", pr.Id), new SugarParameter("@PrBillNo", pr.PrBillNo), new SugarParameter("@PrPurchaseId", pr.PrPurchaseId), new SugarParameter("@PrPurchaseNumber", pr.PrPurchaseNumber), new SugarParameter("@PrPurchaseName", pr.PrPurchaseName), new SugarParameter("@PrPurchaser", pr.PrPurchaser), new SugarParameter("@PrPurchaserNum", pr.PrPurchaserNum), new SugarParameter("@PrRqty", pr.PrRqty), new SugarParameter("@PrAqty", pr.PrAqty), new SugarParameter("@PrSqty", pr.PrSqty), new SugarParameter("@IcitemId", pr.IcitemId), new SugarParameter("@IcitemName", pr.IcitemName), new SugarParameter("@PrSsendDate", pr.PrSsendDate), new SugarParameter("@PrSarriveDate", pr.PrSarriveDate), new SugarParameter("@PrUnit", pr.PrUnit), new SugarParameter("@State", pr.State ?? 1), new SugarParameter("@PrType", pr.PrType ?? 3), new SugarParameter("@CurrencyType", pr.CurrencyType), new SugarParameter("@CreateByName", pr.CreateByName), new SugarParameter("@CreateTime", pr.CreateTime), new SugarParameter("@UpdateByName", pr.UpdateByName), new SugarParameter("@UpdateTime", pr.UpdateTime), new SugarParameter("@TenantId", pr.TenantId), new SugarParameter("@FactoryId", pr.FactoryId), new SugarParameter("@OrgId", pr.OrgId), new SugarParameter("@CompanyId", pr.CompanyId ?? 1000), new SugarParameter("@IsRequireGoods", pr.IsRequireGoods), new SugarParameter("@SupplierType", pr.SupplierType)); } private static DateTime? GetWeekStart(DateTime? value) { if (!value.HasValue) return null; var date = value.Value.Date; var diff = ((int)date.DayOfWeek + 6) % 7; return date.AddDays(-diff); } } public sealed class PurchaseRequestMergeResult { public List Requests { get; set; } = new(); public int OriginalCount { get; set; } public int MergedCount { get; set; } public int ReducedCount => Math.Max(OriginalCount - MergedCount, 0); } public sealed class PurchaseRequestHistoricalMergeResult { public int PendingCount { get; set; } public int MergedGroupCount { get; set; } public int ClosedPrCount { get; set; } public int CreatedPrCount { get; set; } public int ReducedCount { get; set; } }