| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683 |
- using System.Globalization;
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- using Admin.NET.Plugin.AiDOP.Order;
- using Admin.NET.Plugin.AiDOP.WorkOrder;
- namespace Admin.NET.Plugin.AiDOP.Production;
- /// <summary>工单排产动作:生成排程、优先级调整并重检。</summary>
- [ApiDescriptionSettings(Order = 264, Description = "工单排产动作")]
- [Route("api/Production")]
- [AllowAnonymous]
- [NonUnify]
- public class ProductionSchedulingActionService : IDynamicApiController, ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly UserManager _userManager;
- private readonly OrderResourceCheckService _resourceCheck;
- private readonly ProductionScheduleGenerationService _scheduleGen;
- private readonly WorkOrderMaterialDetailSyncService _materialDetailSync;
- private readonly WorkOrderRoutingSyncService _routingSync;
- private readonly AidopActionRunLogWriter _runLog;
- public ProductionSchedulingActionService(
- ISqlSugarClient db,
- UserManager userManager,
- OrderResourceCheckService resourceCheck,
- ProductionScheduleGenerationService scheduleGen,
- WorkOrderMaterialDetailSyncService materialDetailSync,
- WorkOrderRoutingSyncService routingSync,
- AidopActionRunLogWriter runLog)
- {
- _db = db;
- _userManager = userManager;
- _resourceCheck = resourceCheck;
- _scheduleGen = scheduleGen;
- _materialDetailSync = materialDetailSync;
- _routingSync = routingSync;
- _runLog = runLog;
- }
- /// <summary>生成生产排程计划(写入 PeriodSequenceDet)。</summary>
- [DisplayName("生成生产排程")]
- [HttpPost("scheduling/generate")]
- public async Task<object> GenerateSchedule([FromQuery] string domain)
- {
- var tenantId = ResolveTenantId(domain);
- var account = _userManager.Account ?? "system";
- var logId = await _runLog.StartAsync("S2_SCHEDULE_GENERATE", tenantId, "WorkOrdMaster", null, $"domain={domain}");
- try
- {
- var result = await _scheduleGen.GenerateAsync(tenantId, domain, account);
- await _runLog.SuccessAsync(logId, result.Message, new
- {
- tenantId,
- domain,
- result.WorkOrderCount,
- result.ScheduleRowCount,
- result.UsedWorkCenterCalendar,
- result.SkippedWorkOrders
- });
- return new
- {
- message = result.Message,
- workOrderCount = result.WorkOrderCount,
- scheduleRowCount = result.ScheduleRowCount,
- usedWorkCenterCalendar = result.UsedWorkCenterCalendar,
- skippedWorkOrders = result.SkippedWorkOrders
- };
- }
- catch (Exception ex)
- {
- await _runLog.FailedAsync(logId, ex.Message, new { tenantId, domain });
- throw;
- }
- }
- /// <summary>优先级/数量/交期调整并重做资源检查。</summary>
- [DisplayName("优先级调整并重检")]
- [HttpPost("scheduling/update-priority-and-recheck")]
- public async Task<object> UpdatePriorityAndRecheck([FromBody] WorkOrderPriorityRecheckInput input)
- {
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
- var account = string.IsNullOrWhiteSpace(input.UserAccount)
- ? (_userManager.Account ?? "system")
- : input.UserAccount.Trim();
- var workOrd = input.Workord.Trim();
- var logId = await _runLog.StartAsync("S2_PRIORITY_RECHECK", tenantId, "WorkOrdMaster", null, workOrd);
- try
- {
- var before = await LoadWorkOrderSnapshotAsync(tenantId, workOrd);
- DateTime? dueDate = null;
- if (!string.IsNullOrWhiteSpace(input.Instockdate))
- dueDate = DateTime.Parse(input.Instockdate.Trim(), CultureInfo.InvariantCulture).Date;
- decimal? qty = null;
- if (!string.IsNullOrWhiteSpace(input.Qty) && decimal.TryParse(input.Qty.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var q))
- qty = q;
- var pars = new List<SugarParameter>
- {
- new("@WorkOrd", workOrd),
- new("@TenantId", tenantId),
- new("@Priority", string.IsNullOrWhiteSpace(input.Priority) ? (object)DBNull.Value : input.Priority.Trim()),
- new("@LotSerial", string.IsNullOrWhiteSpace(input.LotSerial) ? (object)DBNull.Value : input.LotSerial.Trim()),
- new("@Qty", qty ?? (object)DBNull.Value),
- new("@DueDate", dueDate ?? (object)DBNull.Value),
- new("@User", account),
- new("@Now", DateTime.Now)
- };
- var affected = await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE WorkOrdMaster
- SET Priority = COALESCE(@Priority, Priority),
- LotSerial = COALESCE(@LotSerial, LotSerial),
- QtyOrded = COALESCE(@Qty, QtyOrded),
- DueDate = COALESCE(@DueDate, DueDate),
- UpdateUser = @User,
- UpdateTime = @Now
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
- """,
- pars);
- if (affected == 0)
- throw Oops.Oh("工单不存在或未更新");
- var link = await LoadWorkOrderEntryLinkAsync(tenantId, workOrd);
- var warnings = new List<string>();
- var resourceRechecked = false;
- if (link is not null && (qty.HasValue || dueDate.HasValue))
- {
- if (qty.HasValue)
- link.Entry.Qty = qty;
- if (dueDate.HasValue)
- link.Entry.SysCapacityDate = dueDate;
- await _resourceCheck.RunForEntryAsync(link.Order, link.Entry, workOrd, account, warnings);
- resourceRechecked = true;
- // 资源检查后同步工单物料明细(WorkOrdDetail)和工艺路线(WorkOrdRouting)
- await _materialDetailSync.EnsureFromResourceCheckAsync(tenantId, workOrd, account);
- await _routingSync.EnsureFromRoutingAsync(tenantId, workOrd, account);
- }
- var qtyChanged = qty.HasValue && before?.QtyOrded != qty;
- var dueChanged = dueDate.HasValue && before?.DueDate?.Date != dueDate.Value.Date;
- var priorityChanged = !string.IsNullOrWhiteSpace(input.Priority)
- && !string.Equals(before?.Priority?.Trim(), input.Priority.Trim(), StringComparison.Ordinal);
- // 数量变更时同步更新 mes_morder 和 mes_moentry
- if (qtyChanged && qty.HasValue)
- {
- await UpdateMesOrderQuantityAsync(tenantId, workOrd, qty.Value, account);
- // 同步更新 WorkOrdRouting.QtyOrded(EnsureFromRoutingAsync 已有数据时不会更新数量)
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE WorkOrdRouting
- SET QtyOrded = @Qty,
- UpdateUser = @User, UpdateTime = @Now
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
- AND IFNULL(IsActive, 0) = 1
- """,
- new SugarParameter("@Qty", qty.Value),
- new SugarParameter("@User", account),
- new SugarParameter("@Now", DateTime.Now),
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@WorkOrd", workOrd));
- }
- // 数量变更时同步更新领料单明细(NbrDetail)
- if (qtyChanged)
- {
- await SyncPickingListDetailsAsync(tenantId, workOrd, account, warnings);
- }
- ProductionScheduleGenerationService.ScheduleGenerationResult? reschedule = null;
- if (qtyChanged || dueChanged)
- {
- reschedule = await _scheduleGen.RegenerateForWorkOrderAsync(tenantId, workOrd, input.Domain, account);
- warnings.Add(reschedule.Message);
- }
- else if (priorityChanged)
- {
- var woDomain = before?.Domain ?? input.Domain;
- await _scheduleGen.DeactivateExistingScheduleAsync(tenantId, workOrd, woDomain);
- warnings.Add("优先级已变更,原排程已失效,请重新执行批量排程");
- }
- await _runLog.SuccessAsync(logId, "优先级调整并重检完成", new
- {
- workOrd,
- before,
- after = new
- {
- qty,
- dueDate,
- priority = input.Priority,
- lotSerial = input.LotSerial
- },
- qtyChanged,
- dueChanged,
- priorityChanged,
- resourceRechecked,
- reschedule = reschedule is null
- ? null
- : new
- {
- reschedule.WorkOrderCount,
- reschedule.ScheduleRowCount,
- reschedule.UsedWorkCenterCalendar
- },
- warnings
- });
- return new
- {
- message = "ok",
- warnings,
- resourceRechecked,
- rescheduleTriggered = reschedule is not null,
- scheduleRowCount = reschedule?.ScheduleRowCount ?? 0
- };
- }
- catch (Exception ex)
- {
- await _runLog.FailedAsync(logId, ex.Message, new { workOrd, tenantId });
- throw;
- }
- }
- private long ResolveTenantId(string? domain)
- {
- if (!string.IsNullOrWhiteSpace(domain) && long.TryParse(domain.Trim(), out var tid) && tid > 0)
- return tid;
- return AidopTenantHelper.Resolve(App.HttpContext);
- }
- /// <summary>
- /// 数量变更时同步更新 mes_morder(制造工单)和 mes_moentry(工单明细)的数量字段。
- /// </summary>
- private async Task UpdateMesOrderQuantityAsync(long tenantId, string workOrd, decimal newQty, string account)
- {
- var now = DateTime.Now;
- // 更新 mes_morder
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE mes_morder
- SET need_number = @Qty,
- morder_production_number = @Qty,
- update_by_name = @User,
- update_time = @Now
- WHERE morder_no = @MorderNo
- AND tenant_id = @TenantId
- AND IsDeleted = 0
- """,
- new SugarParameter("@Qty", newQty),
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@MorderNo", workOrd),
- new SugarParameter("@TenantId", tenantId));
- // 更新 mes_moentry(remaining_number 仅在未开始生产时才更新)
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE mes_moentry
- SET need_number = @Qty,
- morder_production_number = @Qty,
- remaining_number = CASE
- WHEN IFNULL(remaining_number, 0) = IFNULL(need_number, 0)
- OR IFNULL(morder_production_number, 0) = 0
- THEN @Qty
- ELSE remaining_number
- END,
- update_by_name = @User,
- update_time = @Now
- WHERE moentry_mono = @Mono
- AND tenant_id = @TenantId
- AND IsDeleted = 0
- """,
- new SugarParameter("@Qty", newQty),
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@Mono", workOrd),
- new SugarParameter("@TenantId", tenantId));
- }
- /// <summary>
- /// 数量变更后同步领料单明细(NbrDetail),按物料逐行比对更新。
- /// 规则:
- /// 已发料(QtyRec>0) + 新需求>已发料 → 更新 QtyOrd/CurrQtyOpened
- /// 已发料(QtyRec>0) + 新需求≤已发料 → 关闭该行
- /// 未发料 → 直接修改 QtyOrd/CurrQtyOpened
- /// 工单明细有但领料单无 → 新增
- /// 领料单有但工单明细无 → 关闭
- /// </summary>
- private async Task SyncPickingListDetailsAsync(
- long tenantId, string workOrd, string account, List<string> warnings)
- {
- // 获取工单状态
- var status = await _db.Ado.GetStringAsync(
- """
- SELECT IFNULL(LOWER(TRIM(Status)), '') FROM WorkOrdMaster
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
- LIMIT 1
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@WorkOrd", workOrd));
- // 仅处理 下达(R)/投产(W)/暂停(S) 状态的工单
- if (status is not ("r" or "w" or "s"))
- {
- // 初始(P)状态无领料单,无需同步
- return;
- }
- // 查找领料单主记录
- var nbrRows = await _db.Ado.SqlQueryAsync<NbrMasterRow>(
- """
- SELECT RecID, Nbr, `Domain` FROM NbrMaster
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd AND Type = 'SM'
- AND IFNULL(TransType, '') = ''
- AND IFNULL(IsActive, 0) = 1
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@WorkOrd", workOrd));
- if (nbrRows.Count == 0)
- {
- warnings.Add($"工单 {workOrd} 状态为 {status.ToUpper()} 但无领料单,跳过领料单同步");
- return;
- }
- var now = DateTime.Now;
- // 从 WorkOrdDetail 汇总最新物料需求
- var details = await _db.Ado.SqlQueryAsync<PickDetailRow>(
- """
- SELECT
- d.ItemNum,
- SUM(d.QtyRequired) AS QtyRequired,
- MAX(IFNULL(d.UM, im.Um)) AS Unit,
- MAX(im.Descr) AS ItemName
- FROM WorkOrdDetail d
- LEFT JOIN ItemMaster im ON d.ItemNum = im.ItemNum AND im.tenant_id = d.tenant_id
- WHERE d.tenant_id = @TenantId AND d.WorkOrd = @WorkOrd AND IFNULL(d.IsActive, 0) = 1
- GROUP BY d.ItemNum
- HAVING SUM(d.QtyRequired) > 0
- ORDER BY d.ItemNum
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@WorkOrd", workOrd));
- foreach (var nbr in nbrRows)
- {
- var domain = string.IsNullOrWhiteSpace(nbr.Domain) ? tenantId.ToString() : nbr.Domain!.Trim();
- if (domain.Length > 8) domain = domain[..8];
- // 加载当前领料单明细行(仅未关闭的)
- var existingDetails = await _db.Ado.SqlQueryAsync<NbrDetailRow>(
- """
- SELECT RecID, ItemNum, QtyOrd, QtyRec, CurrQtyOpened, Line
- FROM NbrDetail
- WHERE tenant_id = @TenantId AND Nbr = @Nbr AND Type = 'SM'
- AND IFNULL(IsActive, 0) = 1
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@Nbr", nbr.Nbr));
- var detailMap = details.ToDictionary(d => d.ItemNum.Trim(), d => d);
- var existingMap = existingDetails.ToDictionary(d => (d.ItemNum ?? "").Trim(), d => d);
- // ── 1. 处理已有明细行:按 ItemNum 匹配 ──
- foreach (var existing in existingDetails)
- {
- var key = (existing.ItemNum ?? "").Trim();
- if (detailMap.TryGetValue(key, out var newDetail))
- {
- var newQty = newDetail.QtyRequired;
- if (existing.QtyRec > 0)
- {
- if (newQty > existing.QtyRec)
- {
- // 新需求 > 已发料 → 更新需求数
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE NbrDetail
- SET QtyOrd = @QtyOrd, CurrQtyOpened = @CurrQtyOpened,
- UM = @UM, ItemName = @ItemName,
- UpdateUser = @User, UpdateTime = @Now
- WHERE RecID = @RecId
- """,
- new SugarParameter("@QtyOrd", newQty),
- new SugarParameter("@CurrQtyOpened", newQty),
- new SugarParameter("@UM", (object?)newDetail.Unit ?? DBNull.Value),
- new SugarParameter("@ItemName", (object?)newDetail.ItemName ?? DBNull.Value),
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@RecId", existing.RecID));
- }
- else
- {
- // 新需求 <= 已发料 → 关闭当前行
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE NbrDetail
- SET Status = 'C', IsActive = 0,
- UpdateUser = @User, UpdateTime = @Now
- WHERE RecID = @RecId
- """,
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@RecId", existing.RecID));
- }
- }
- else
- {
- // 未发料 → 直接修改需求数
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE NbrDetail
- SET QtyOrd = @QtyOrd, CurrQtyOpened = @CurrQtyOpened,
- UM = @UM, ItemName = @ItemName,
- UpdateUser = @User, UpdateTime = @Now
- WHERE RecID = @RecId
- """,
- new SugarParameter("@QtyOrd", newQty),
- new SugarParameter("@CurrQtyOpened", newQty),
- new SugarParameter("@UM", (object?)newDetail.Unit ?? DBNull.Value),
- new SugarParameter("@ItemName", (object?)newDetail.ItemName ?? DBNull.Value),
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@RecId", existing.RecID));
- }
- }
- else
- {
- // 物料明细中没有,但领料单明细有 → 关闭当前领料单明细行
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE NbrDetail
- SET Status = 'C', IsActive = 0,
- UpdateUser = @User, UpdateTime = @Now
- WHERE RecID = @RecId
- """,
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@RecId", existing.RecID));
- }
- }
- // ── 2. 新增:物料明细中有,领料单明细没有的 ──
- var nextDetailId = await _db.Ado.GetIntAsync("SELECT IFNULL(MAX(RecID), 0) + 1 FROM NbrDetail");
- short newLine = (short)(existingDetails.Count > 0 ? existingDetails.Max(d => d.Line) + 1 : 1);
- foreach (var d in details)
- {
- var key = d.ItemNum.Trim();
- if (existingMap.ContainsKey(key))
- continue;
- await _db.Ado.ExecuteCommandAsync(
- """
- INSERT INTO NbrDetail (
- RecID, `Domain`, Type, Nbr, Line, ItemNum, Dimension1, Dimension2,
- LocationFrom, LocationTo, QtyFrom, QtyTo, QtyOrd, QtyRec,
- CurrQtyOpened, UM, WorkOrd, ItemName, Status,
- IsActive, IsConfirm, IsChanged, BusinessID, NbrRecID,
- CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
- ) VALUES (
- @RecId, @Domain, 'SM', @Nbr, @Line, @ItemNum, '', '',
- '', '', 0, 0, @QtyOrd, 0,
- @CurrQtyOpened, @UM, @WorkOrd, @ItemName, '',
- 1, 0, 1, 0, @NbrRecId,
- @User, @Now, @User, @Now, @TenantId
- )
- """,
- new SugarParameter("@RecId", nextDetailId++),
- new SugarParameter("@Domain", domain),
- new SugarParameter("@Nbr", nbr.Nbr),
- new SugarParameter("@Line", newLine++),
- new SugarParameter("@ItemNum", d.ItemNum),
- new SugarParameter("@QtyOrd", d.QtyRequired),
- new SugarParameter("@CurrQtyOpened", d.QtyRequired),
- new SugarParameter("@UM", (object?)d.Unit ?? DBNull.Value),
- new SugarParameter("@WorkOrd", workOrd),
- new SugarParameter("@ItemName", (object?)d.ItemName ?? DBNull.Value),
- new SugarParameter("@NbrRecId", nbr.RecID),
- new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
- new SugarParameter("@Now", now),
- new SugarParameter("@TenantId", tenantId));
- }
- // ── 3. 更新领料单主记录的更新时间和数量 ──
- await _db.Ado.ExecuteCommandAsync(
- """
- UPDATE NbrMaster
- SET QtyOrd = (SELECT IFNULL(SUM(QtyOrd), 0) FROM NbrDetail
- WHERE Nbr = @Nbr AND Type = 'SM' AND IFNULL(IsActive, 1) = 1),
- UpdateUser = @User, UpdateTime = @Now
- WHERE RecID = @RecId
- """,
- new SugarParameter("@Nbr", nbr.Nbr),
- new SugarParameter("@User", account),
- new SugarParameter("@Now", now),
- new SugarParameter("@RecId", nbr.RecID));
- }
- }
- // ══════════════════════════════════════════════════════════════
- // 内部 DTO
- // ══════════════════════════════════════════════════════════════
- private sealed class NbrMasterRow
- {
- public int RecID { get; set; }
- public string Nbr { get; set; } = string.Empty;
- public string? Domain { get; set; }
- }
- private sealed class NbrDetailRow
- {
- public int RecID { get; set; }
- public string? ItemNum { get; set; }
- public decimal QtyOrd { get; set; }
- public decimal QtyRec { get; set; }
- public decimal CurrQtyOpened { get; set; }
- public short Line { get; set; }
- }
- private sealed class PickDetailRow
- {
- public string ItemNum { get; set; } = string.Empty;
- public decimal QtyRequired { get; set; }
- public string? Unit { get; set; }
- public string? ItemName { get; set; }
- }
- private async Task<WorkOrderSnapshotRow?> LoadWorkOrderSnapshotAsync(long tenantId, string workOrd)
- {
- var rows = await _db.Ado.SqlQueryAsync<WorkOrderSnapshotRow>(
- """
- SELECT WorkOrd, Priority, QtyOrded, DueDate, LotSerial, `Domain`
- FROM WorkOrdMaster
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
- LIMIT 1
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@WorkOrd", workOrd));
- return rows.FirstOrDefault();
- }
- private async Task<WorkOrderEntryLink?> LoadWorkOrderEntryLinkAsync(long tenantId, string workOrd)
- {
- var rows = await _db.Ado.SqlQueryAsync<WorkOrderEntryLinkRow>(
- """
- SELECT
- w.BusinessID AS EntryId,
- e.seorder_id AS SeOrderId,
- e.bill_no AS BillNo,
- e.entry_seq AS EntrySeq,
- e.item_number AS ItemNumber,
- e.item_name AS ItemName,
- e.specification AS Specification,
- e.unit AS Unit,
- e.bom_number AS BomNumber,
- e.qty AS Qty,
- e.plan_date AS PlanDate,
- e.sys_capacity_date AS SysCapacityDate,
- e.progress AS Progress,
- e.urgent AS Urgent,
- e.factory_id AS FactoryId,
- e.company_id AS CompanyId,
- e.tenant_id AS TenantId,
- o.Id AS OrderId,
- o.bill_no AS OrderBillNo,
- o.custom_no AS CustomNo,
- o.urgent AS OrderUrgent,
- o.factory_id AS OrderFactoryId
- FROM WorkOrdMaster w
- INNER JOIN crm_seorderentry e ON e.Id = w.BusinessID AND e.tenant_id = w.tenant_id AND e.IsDeleted = 0
- INNER JOIN crm_seorder o ON o.Id = e.seorder_id AND o.tenant_id = e.tenant_id AND o.IsDeleted = 0
- WHERE w.tenant_id = @TenantId AND w.WorkOrd = @WorkOrd
- LIMIT 1
- """,
- new SugarParameter("@TenantId", tenantId),
- new SugarParameter("@WorkOrd", workOrd));
- var row = rows.FirstOrDefault();
- if (row is null || row.EntryId <= 0)
- return null;
- return new WorkOrderEntryLink
- {
- Order = new OrderWorkOrderGenerationService.OrderHeader
- {
- Id = row.OrderId,
- BillNo = row.OrderBillNo ?? row.BillNo,
- CustomNo = row.CustomNo,
- Urgent = row.OrderUrgent,
- FactoryId = row.OrderFactoryId,
- TenantId = tenantId
- },
- Entry = new OrderWorkOrderGenerationService.OrderEntryLine
- {
- Id = row.EntryId,
- SeOrderId = row.SeOrderId,
- BillNo = row.BillNo,
- EntrySeq = row.EntrySeq,
- ItemNumber = row.ItemNumber,
- ItemName = row.ItemName,
- Specification = row.Specification,
- Unit = row.Unit,
- BomNumber = row.BomNumber,
- Qty = row.Qty,
- PlanDate = row.PlanDate,
- SysCapacityDate = row.SysCapacityDate,
- Progress = row.Progress,
- Urgent = row.Urgent,
- FactoryId = row.FactoryId,
- CompanyId = row.CompanyId,
- TenantId = row.TenantId
- }
- };
- }
- private sealed class WorkOrderSnapshotRow
- {
- public string? WorkOrd { get; set; }
- public string? Priority { get; set; }
- public decimal? QtyOrded { get; set; }
- public DateTime? DueDate { get; set; }
- public string? LotSerial { get; set; }
- public string? Domain { get; set; }
- }
- private sealed class WorkOrderEntryLink
- {
- public OrderWorkOrderGenerationService.OrderHeader Order { get; set; } = new();
- public OrderWorkOrderGenerationService.OrderEntryLine Entry { get; set; } = new();
- }
- private sealed class WorkOrderEntryLinkRow
- {
- public long EntryId { get; set; }
- public long SeOrderId { get; set; }
- public string? BillNo { get; set; }
- public int? EntrySeq { get; set; }
- public string? ItemNumber { get; set; }
- public string? ItemName { get; set; }
- public string? Specification { get; set; }
- public string? Unit { get; set; }
- public string? BomNumber { get; set; }
- public decimal? Qty { get; set; }
- public DateTime? PlanDate { get; set; }
- public DateTime? SysCapacityDate { get; set; }
- public string? Progress { get; set; }
- public int? Urgent { get; set; }
- public long? FactoryId { get; set; }
- public long? CompanyId { get; set; }
- public long TenantId { get; set; }
- public long OrderId { get; set; }
- public string? OrderBillNo { get; set; }
- public string? CustomNo { get; set; }
- public int? OrderUrgent { get; set; }
- public long? OrderFactoryId { get; set; }
- }
- }
- public class WorkOrderPriorityRecheckInput
- {
- public string Workord { get; set; } = string.Empty;
- public string? Qty { get; set; }
- public string? Instockdate { get; set; }
- public string? Priority { get; set; }
- public string Domain { get; set; } = string.Empty;
- public string? UserAccount { get; set; }
- public string? LotSerial { get; set; }
- }
|