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; }
}