| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- namespace Admin.NET.Plugin.AiDOP.Order;
- /// <summary>
- /// S1 订单评审编排:资源检查、状态更新、工单生成、运行日志。
- /// </summary>
- public class OrderReviewOrchestrationService : ITransient
- {
- public const string ActionReview = "S1_ORDER_REVIEW";
- public const string ActionConfirm = "S1_DELIVERY_CONFIRM";
- public const string ActionRefresh = "S1_ORDER_REFRESH_PLAN";
- private readonly ISqlSugarClient _db;
- private readonly UserManager _userManager;
- private readonly OrderWorkOrderGenerationService _workOrderGen;
- private readonly OrderResourceCheckService _resourceCheck;
- private readonly S1MdpSyncTransformService _mdpSync;
- private readonly AidopActionRunLogWriter _runLog;
- public OrderReviewOrchestrationService(
- ISqlSugarClient db,
- UserManager userManager,
- OrderWorkOrderGenerationService workOrderGen,
- OrderResourceCheckService resourceCheck,
- S1MdpSyncTransformService mdpSync,
- AidopActionRunLogWriter runLog)
- {
- _db = db;
- _userManager = userManager;
- _workOrderGen = workOrderGen;
- _resourceCheck = resourceCheck;
- _mdpSync = mdpSync;
- _runLog = runLog;
- }
- public Task<SeOrderReviewExecuteResult> ReviewAsync(IReadOnlyList<long> orderIds) =>
- ExecuteBatchAsync(ActionReview, orderIds, ReviewOneOrderAsync);
- public Task<SeOrderReviewExecuteResult> ConfirmDeliveryAsync(IReadOnlyList<long> orderIds) =>
- ExecuteBatchAsync(ActionConfirm, orderIds, ConfirmOneOrderAsync);
- public Task<SeOrderReviewExecuteResult> RefreshPlanAsync(long orderId, string? reason) =>
- ExecuteSingleAsync(ActionRefresh, orderId, async (order, result, warnings, account) =>
- {
- var entries = await LoadReviewableEntriesAsync(order.Id, order.TenantId, ["3", "0"]);
- if (entries.Count == 0)
- throw Oops.Oh("订单没有可重排的确认/再评审明细行");
- foreach (var entry in entries)
- {
- ValidateEntryForResourceCheck(entry);
- var wo = await _workOrderGen.CreateOrUpdateForEntryAsync(order, entry, account, warnings);
- if (wo.Created) result.WorkOrderCreatedCount++;
- else result.WorkOrderUpdatedCount++;
- result.WorkOrders.Add(wo.WorkOrd);
- var check = await _resourceCheck.RunForEntryAsync(order, entry, wo.WorkOrd, account, warnings);
- result.ResourceCheckCount++;
- result.ResourceCheckLineCount += check.LineCount;
- }
- await UpdateEntriesProgressAsync(entries.Select(e => e.Id).ToList(), "0", account);
- result.EntryCount = entries.Count;
- result.Message = string.IsNullOrWhiteSpace(reason)
- ? "3级计划重排完成"
- : $"3级计划重排完成:{reason.Trim()}";
- });
- private async Task<SeOrderReviewExecuteResult> ExecuteBatchAsync(
- string actionCode,
- IReadOnlyList<long> orderIds,
- Func<OrderWorkOrderGenerationService.OrderHeader, SeOrderReviewExecuteResult, List<string>, string, Task> handler)
- {
- if (orderIds is null || orderIds.Count == 0)
- throw Oops.Oh("至少选择一条订单");
- var tenantId = _userManager.TenantId > 0
- ? _userManager.TenantId
- : AidopTenantHelper.Resolve(App.HttpContext);
- var account = _userManager.Account ?? "system";
- var distinctIds = orderIds.Distinct().ToList();
- var aggregate = new SeOrderReviewExecuteResult
- {
- ActionCode = actionCode,
- OrderCount = distinctIds.Count,
- Message = "执行成功"
- };
- var allWarnings = new List<string>();
- long? firstLogId = null;
- foreach (var orderId in distinctIds)
- {
- var order = await LoadOrderAsync(orderId, tenantId)
- ?? throw Oops.Oh($"订单 {orderId} 不存在或不属于当前租户");
- var runLogId = await _runLog.StartAsync(actionCode, tenantId, "crm_seorder", order.Id, order.BillNo);
- if (firstLogId is null)
- firstLogId = runLogId;
- var perOrder = new SeOrderReviewExecuteResult { ActionCode = actionCode };
- var warnings = new List<string>();
- try
- {
- await _db.Ado.BeginTranAsync();
- await handler(order, perOrder, warnings, account);
- await _db.Ado.CommitTranAsync();
- aggregate.EntryCount += perOrder.EntryCount;
- aggregate.WorkOrderCreatedCount += perOrder.WorkOrderCreatedCount;
- aggregate.WorkOrderUpdatedCount += perOrder.WorkOrderUpdatedCount;
- aggregate.ResourceCheckCount += perOrder.ResourceCheckCount;
- aggregate.ResourceCheckLineCount += perOrder.ResourceCheckLineCount;
- aggregate.WorkOrders.AddRange(perOrder.WorkOrders);
- allWarnings.AddRange(warnings);
- await _runLog.SuccessAsync(runLogId, perOrder.Message, new
- {
- orderId = order.Id,
- billNo = order.BillNo,
- perOrder.EntryCount,
- perOrder.WorkOrderCreatedCount,
- perOrder.WorkOrderUpdatedCount,
- workOrders = perOrder.WorkOrders,
- warnings
- });
- }
- catch (Exception ex)
- {
- await _db.Ado.RollbackTranAsync();
- await _runLog.FailedAsync(runLogId, ex.Message, new { orderId = order.Id, billNo = order.BillNo });
- throw Oops.Oh(ex.Message);
- }
- }
- aggregate.RunLogId = firstLogId ?? 0;
- aggregate.Warnings = allWarnings.Distinct().ToList();
- aggregate.Message = BuildAggregateMessage(actionCode, aggregate);
- if (actionCode == ActionReview && aggregate.ResourceCheckCount > 0)
- aggregate.Warnings.AddRange(await TryTriggerMdpRefreshAsync());
- return aggregate;
- }
- private async Task<SeOrderReviewExecuteResult> ExecuteSingleAsync(
- string actionCode,
- long orderId,
- Func<OrderWorkOrderGenerationService.OrderHeader, SeOrderReviewExecuteResult, List<string>, string, Task> handler)
- {
- var tenantId = _userManager.TenantId > 0
- ? _userManager.TenantId
- : AidopTenantHelper.Resolve(App.HttpContext);
- var account = _userManager.Account ?? "system";
- var order = await LoadOrderAsync(orderId, tenantId)
- ?? throw Oops.Oh("订单不存在或不属于当前租户");
- var result = new SeOrderReviewExecuteResult
- {
- ActionCode = actionCode,
- OrderCount = 1
- };
- var warnings = new List<string>();
- var runLogId = await _runLog.StartAsync(actionCode, tenantId, "crm_seorder", order.Id, order.BillNo);
- result.RunLogId = runLogId;
- try
- {
- await _db.Ado.BeginTranAsync();
- await handler(order, result, warnings, account);
- await _db.Ado.CommitTranAsync();
- result.Warnings = warnings;
- result.Message = BuildSingleMessage(result);
- await _runLog.SuccessAsync(runLogId, result.Message, new
- {
- orderId = order.Id,
- billNo = order.BillNo,
- result.EntryCount,
- result.WorkOrderCreatedCount,
- result.WorkOrderUpdatedCount,
- workOrders = result.WorkOrders,
- warnings
- });
- if (actionCode == ActionRefresh && result.ResourceCheckCount > 0)
- result.Warnings.AddRange(await TryTriggerMdpRefreshAsync());
- return result;
- }
- catch (Exception ex)
- {
- await _db.Ado.RollbackTranAsync();
- await _runLog.FailedAsync(runLogId, ex.Message, new { orderId = order.Id, billNo = order.BillNo });
- throw Oops.Oh(ex.Message);
- }
- }
- private async Task ReviewOneOrderAsync(
- OrderWorkOrderGenerationService.OrderHeader order,
- SeOrderReviewExecuteResult result,
- List<string> warnings,
- string account)
- {
- var entries = await LoadReviewableEntriesAsync(order.Id, order.TenantId, ["1", "0"]);
- if (entries.Count == 0)
- throw Oops.Oh($"订单 {order.BillNo} 没有可评审的明细行(须为新建或再评审状态)");
- foreach (var entry in entries)
- {
- ValidateEntryForResourceCheck(entry);
- if (entry.PlanDate is null)
- throw Oops.Oh($"订单行 {entry.EntrySeq} 缺少客户要求交期(plan_date)");
- var wo = await _workOrderGen.CreateOrUpdateForEntryAsync(order, entry, account, warnings);
- if (wo.Created) result.WorkOrderCreatedCount++;
- else result.WorkOrderUpdatedCount++;
- result.WorkOrders.Add(wo.WorkOrd);
- var check = await _resourceCheck.RunForEntryAsync(order, entry, wo.WorkOrd, account, warnings);
- result.ResourceCheckCount++;
- result.ResourceCheckLineCount += check.LineCount;
- if (check.HasShortage)
- warnings.Add($"订单行 {entry.EntrySeq} 存在缺料(工单 {wo.WorkOrd})");
- var capacityDate = entry.SysCapacityDate ?? entry.PlanDate;
- await UpdateEntryAfterReviewAsync(entry.Id, capacityDate, account);
- }
- result.EntryCount = entries.Count;
- result.Message = $"订单 {order.BillNo} 评审完成(资源检查 {result.ResourceCheckCount} 条)";
- }
- private async Task ConfirmOneOrderAsync(
- OrderWorkOrderGenerationService.OrderHeader order,
- SeOrderReviewExecuteResult result,
- List<string> warnings,
- string account)
- {
- var entries = await LoadReviewableEntriesAsync(order.Id, order.TenantId, ["2"]);
- if (entries.Count == 0)
- throw Oops.Oh($"订单 {order.BillNo} 没有处于评审状态的明细行,请先完成订单评审");
- foreach (var entry in entries)
- {
- var woCnt = await _db.Ado.GetIntAsync(
- """
- SELECT COUNT(*) FROM WorkOrdMaster
- WHERE tenant_id = @TenantId AND BusinessID = @EntryId
- AND LOWER(TRIM(IFNULL(Status,''))) <> 'c'
- """,
- new SugarParameter("@TenantId", entry.TenantId),
- new SugarParameter("@EntryId", entry.Id));
- if (woCnt == 0)
- throw Oops.Oh($"订单行 {entry.EntrySeq} 尚未生成工单,无法交期确认");
- var confirmDate = entry.SysCapacityDate ?? entry.PlanDate;
- await UpdateEntryAfterConfirmAsync(entry.Id, confirmDate, account);
- result.EntryCount++;
- result.WorkOrders.Add(await LoadWorkOrdForEntryAsync(entry.TenantId, entry.Id));
- }
- result.WorkOrders = result.WorkOrders.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
- result.Message = $"订单 {order.BillNo} 交期确认完成";
- }
- private async Task<string> LoadWorkOrdForEntryAsync(long tenantId, long entryId)
- {
- var rows = await _db.Ado.SqlQueryAsync<string>(
- """
- SELECT WorkOrd FROM WorkOrdMaster
- WHERE tenant_id = @TenantId AND BusinessID = @EntryId
- ORDER BY RecID DESC LIMIT 1
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@EntryId", entryId));
- return rows.FirstOrDefault() ?? string.Empty;
- }
- private static void ValidateEntryForResourceCheck(OrderWorkOrderGenerationService.OrderEntryLine entry)
- {
- if (string.IsNullOrWhiteSpace(entry.ItemNumber))
- throw Oops.Oh($"订单行 {entry.EntrySeq} 物料编码不能为空");
- if (entry.Qty is null or <= 0)
- throw Oops.Oh($"订单行 {entry.EntrySeq} 数量必须大于 0");
- if (entry.PlanDate is null && entry.SysCapacityDate is null)
- throw Oops.Oh($"订单行 {entry.EntrySeq} 缺少计划交期");
- }
- private async Task<OrderWorkOrderGenerationService.OrderHeader?> LoadOrderAsync(long orderId, long tenantId)
- {
- var rows = await _db.Ado.SqlQueryAsync<OrderWorkOrderGenerationService.OrderHeader>(
- """
- SELECT Id, bill_no AS BillNo, custom_no AS CustomNo, urgent AS Urgent,
- factory_id AS FactoryId, tenant_id AS TenantId
- FROM crm_seorder
- WHERE Id = @Id AND tenant_id = @TenantId AND IsDeleted = 0
- LIMIT 1
- """,
- new SugarParameter("@Id", orderId),
- new SugarParameter("@TenantId", tenantId));
- return rows.FirstOrDefault();
- }
- private async Task<List<OrderWorkOrderGenerationService.OrderEntryLine>> LoadReviewableEntriesAsync(
- long orderId,
- long tenantId,
- IReadOnlyList<string> progressList)
- {
- if (progressList.Count == 0)
- return new List<OrderWorkOrderGenerationService.OrderEntryLine>();
- var inClause = string.Join(", ", progressList.Select((_, i) => $"@P{i}"));
- var pars = new List<SugarParameter>
- {
- new("@OrderId", orderId),
- new("@TenantId", tenantId)
- };
- for (var i = 0; i < progressList.Count; i++)
- pars.Add(new SugarParameter($"@P{i}", progressList[i]));
- return await _db.Ado.SqlQueryAsync<OrderWorkOrderGenerationService.OrderEntryLine>(
- $"""
- SELECT
- Id, seorder_id AS SeOrderId, bill_no AS BillNo, entry_seq AS EntrySeq,
- item_number AS ItemNumber, item_name AS ItemName, specification AS Specification,
- unit AS Unit, bom_number AS BomNumber, qty AS Qty,
- plan_date AS PlanDate, sys_capacity_date AS SysCapacityDate,
- progress AS Progress, urgent AS Urgent,
- factory_id AS FactoryId, company_id AS CompanyId, tenant_id AS TenantId
- FROM crm_seorderentry
- WHERE seorder_id = @OrderId AND tenant_id = @TenantId AND IsDeleted = 0
- AND COALESCE(NULLIF(progress, ''), '1') IN ({inClause})
- ORDER BY entry_seq, Id
- """,
- pars);
- }
- private async Task UpdateEntryAfterReviewAsync(long entryId, DateTime? capacityDate, string account)
- {
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE crm_seorderentry
- SET progress = '2',
- sys_capacity_date = COALESCE(sys_capacity_date, @CapacityDate),
- update_time = @Now
- WHERE Id = @Id AND IsDeleted = 0
- """,
- new SugarParameter("@CapacityDate", capacityDate ?? (object)DBNull.Value),
- new SugarParameter("@Now", DateTime.Now),
- new SugarParameter("@Id", entryId));
- }
- private async Task UpdateEntryAfterConfirmAsync(long entryId, DateTime? confirmDate, string account)
- {
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE crm_seorderentry
- SET progress = '3',
- date = COALESCE(date, @ConfirmDate),
- update_time = @Now
- WHERE Id = @Id AND IsDeleted = 0
- """,
- new SugarParameter("@ConfirmDate", confirmDate ?? (object)DBNull.Value),
- new SugarParameter("@Now", DateTime.Now),
- new SugarParameter("@Id", entryId));
- }
- private async Task UpdateEntriesProgressAsync(IReadOnlyList<long> entryIds, string progress, string account)
- {
- if (entryIds.Count == 0) return;
- var idList = string.Join(",", entryIds);
- await _db.Ado.ExecuteCommandAsync(
- $"""
- UPDATE crm_seorderentry
- SET progress = @Progress, update_time = @Now
- WHERE Id IN ({idList}) AND IsDeleted = 0
- """,
- new SugarParameter("@Progress", progress),
- new SugarParameter("@Now", DateTime.Now));
- }
- private static string BuildAggregateMessage(string actionCode, SeOrderReviewExecuteResult r)
- {
- var woPart = r.WorkOrders.Count > 0
- ? $",工单:{string.Join("、", r.WorkOrders.Distinct())}"
- : string.Empty;
- return actionCode switch
- {
- ActionReview => $"评审完成 {r.OrderCount} 单、{r.EntryCount} 行,新建工单 {r.WorkOrderCreatedCount}、更新 {r.WorkOrderUpdatedCount}、资源检查 {r.ResourceCheckCount} 条{woPart}",
- ActionConfirm => $"交期确认完成 {r.OrderCount} 单、{r.EntryCount} 行{woPart}",
- _ => r.Message
- };
- }
- private static string BuildSingleMessage(SeOrderReviewExecuteResult r)
- {
- if (r.WorkOrders.Count == 0)
- return r.Message;
- return $"{r.Message},工单:{string.Join("、", r.WorkOrders.Distinct())}";
- }
- private async Task<List<string>> TryTriggerMdpRefreshAsync()
- {
- var warnings = new List<string>();
- try
- {
- await _mdpSync.RunFullAsync(triggerType: "ORDER_REVIEW");
- }
- catch (Exception ex)
- {
- warnings.Add($"S1 MDP/DWD 刷新未完成:{ex.Message}");
- }
- return warnings;
- }
- }
|