|
@@ -0,0 +1,571 @@
|
|
|
|
|
+using System.Text.Json;
|
|
|
|
|
+using Admin.NET.Plugin.AiDOP.DataPlatform.Executors;
|
|
|
|
|
+using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
|
|
|
|
|
+using Admin.NET.Plugin.AiDOP.WorkOrder;
|
|
|
|
|
+using Microsoft.Extensions.Logging;
|
|
|
|
|
+using SqlSugar;
|
|
|
|
|
+
|
|
|
|
|
+namespace Admin.NET.Plugin.AiDOP.DataPlatform.Wms;
|
|
|
|
|
+
|
|
|
|
|
+/// <summary>
|
|
|
|
|
+/// WP4 · 计划侧工单入 165:本库 WorkOrdMaster/Detail/Routing 快照 → Outbox UPSERT。
|
|
|
|
|
+/// 挂接点:订单评审 / 计划刷新 / 交期确认;下达侧只推进状态(CreatePickBill)。
|
|
|
|
|
+/// </summary>
|
|
|
|
|
+[ApiDescriptionSettings(Order = 328, Description = "工单推送 MES")]
|
|
|
|
|
+[Route("api/aidop/wms-workord")]
|
|
|
|
|
+[AllowAnonymous]
|
|
|
|
|
+[NonUnify]
|
|
|
|
|
+public class WorkOrdMesPushService : IDynamicApiController, ITransient
|
|
|
|
|
+{
|
|
|
|
|
+ public const string TargetSource = "DOPDEMORQ_SQLSERVER";
|
|
|
|
|
+ public const string ActionWom = "WO_MES_WOM";
|
|
|
|
|
+ public const string ActionWod = "WO_MES_WOD";
|
|
|
|
|
+ public const string ActionWor = "WO_MES_WOR";
|
|
|
|
|
+
|
|
|
|
|
+ private readonly ISqlSugarClient _db;
|
|
|
|
|
+ private readonly MdpSourceScopeFactory _scopeFactory;
|
|
|
|
|
+ private readonly MdpOutboxEnqueueService _enqueue;
|
|
|
|
|
+ private readonly MdpOutboxWakeSignal _wake;
|
|
|
|
|
+ private readonly ILogger<WorkOrdMesPushService> _logger;
|
|
|
|
|
+
|
|
|
|
|
+ public WorkOrdMesPushService(
|
|
|
|
|
+ ISqlSugarClient db,
|
|
|
|
|
+ MdpSourceScopeFactory scopeFactory,
|
|
|
|
|
+ MdpOutboxEnqueueService enqueue,
|
|
|
|
|
+ MdpOutboxWakeSignal wake,
|
|
|
|
|
+ ILogger<WorkOrdMesPushService> logger)
|
|
|
|
|
+ {
|
|
|
|
|
+ _db = db;
|
|
|
|
|
+ _scopeFactory = scopeFactory;
|
|
|
|
|
+ _enqueue = enqueue;
|
|
|
|
|
+ _wake = wake;
|
|
|
|
|
+ _logger = logger;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public sealed class PushInput
|
|
|
|
|
+ {
|
|
|
|
|
+ public string WorkOrd { get; set; } = "";
|
|
|
|
|
+ public long TenantId { get; set; }
|
|
|
|
|
+ public string? Domain { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public sealed class PushResult
|
|
|
|
|
+ {
|
|
|
|
|
+ public bool Ok { get; set; }
|
|
|
|
|
+ public string WorkOrd { get; set; } = "";
|
|
|
|
|
+ public string Domain { get; set; } = "";
|
|
|
|
|
+ public int Enqueued { get; set; }
|
|
|
|
|
+ public int DetailCount { get; set; }
|
|
|
|
|
+ public int RoutingCount { get; set; }
|
|
|
|
|
+ public string? Message { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>补偿 / 联调:按本库快照入队推送 165。</summary>
|
|
|
|
|
+ [DisplayName("推送工单到 MES")]
|
|
|
|
|
+ [HttpPost("push")]
|
|
|
|
|
+ public async Task<PushResult> Push([FromBody] PushInput input, CancellationToken ct = default)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (input == null || string.IsNullOrWhiteSpace(input.WorkOrd))
|
|
|
|
|
+ throw Oops.Oh("工单号不能为空");
|
|
|
|
|
+
|
|
|
|
|
+ var tenantId = input.TenantId;
|
|
|
|
|
+ var workOrd = input.WorkOrd.Trim();
|
|
|
|
|
+ var domain = string.IsNullOrWhiteSpace(input.Domain)
|
|
|
|
|
+ ? await ResolveDomainAsync(tenantId, workOrd)
|
|
|
|
|
+ : input.Domain.Trim();
|
|
|
|
|
+
|
|
|
|
|
+ return await EnqueueSnapshotAsync(tenantId, domain, workOrd, ct);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>评审/刷新/确认后调用:失败只记日志,不抛(避免拖垮本库评审事务)。</summary>
|
|
|
|
|
+ public async Task TryEnqueueSnapshotSafeAsync(long tenantId, string workOrd, List<string>? warnings = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrWhiteSpace(workOrd)) return;
|
|
|
|
|
+ var domain = await ResolveDomainAsync(tenantId, workOrd);
|
|
|
|
|
+ var r = await EnqueueSnapshotAsync(tenantId, domain, workOrd);
|
|
|
|
|
+ warnings?.Add($"工单 {workOrd} 已入队推送 MES({r.Enqueued} 条 Outbox,明细 {r.DetailCount}/工序 {r.RoutingCount})");
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ _logger.LogWarning(ex, "[WorkOrdMesPush] enqueue failed tenant={Tenant} wo={WorkOrd}", tenantId, workOrd);
|
|
|
|
|
+ warnings?.Add($"工单 {workOrd} 推送 MES 入队失败:{ex.Message}(可稍后调用 /api/aidop/wms-workord/push 补偿)");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>从本库读三人组并入队(主表 → 明细 → 工序);末尾 Pulse 一次。</summary>
|
|
|
|
|
+ public async Task<PushResult> EnqueueSnapshotAsync(
|
|
|
|
|
+ long tenantId, string domain, string workOrd, CancellationToken ct = default)
|
|
|
|
|
+ {
|
|
|
|
|
+ domain = string.IsNullOrWhiteSpace(domain) ? "8010" : domain.Trim();
|
|
|
|
|
+ workOrd = workOrd.Trim();
|
|
|
|
|
+
|
|
|
|
|
+ var masters = await _db.Queryable<WorkOrdMaster>()
|
|
|
|
|
+ .Where(m => m.TenantId == tenantId && m.WorkOrd == workOrd)
|
|
|
|
|
+ .Take(1)
|
|
|
|
|
+ .ToListAsync(ct);
|
|
|
|
|
+ if (masters.Count == 0)
|
|
|
|
|
+ throw Oops.Oh($"本库未找到工单 {workOrd}");
|
|
|
|
|
+ var master = masters[0];
|
|
|
|
|
+
|
|
|
|
|
+ var details = await _db.Ado.SqlQueryAsync<DetailRow>(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT
|
|
|
|
|
+ TRIM(IFNULL(Domain, '')) AS Domain,
|
|
|
|
|
+ WorkOrd,
|
|
|
|
|
+ IFNULL(LineNum, 0) AS LineNum,
|
|
|
|
|
+ TRIM(IFNULL(ItemNum, '')) AS ItemNum,
|
|
|
|
|
+ IFNULL(Op, 0) AS Op,
|
|
|
|
|
+ TRIM(IFNULL(Location, '')) AS Location,
|
|
|
|
|
+ TRIM(IFNULL(Site, '')) AS Site,
|
|
|
|
|
+ IFNULL(QtyRequired, 0) AS QtyRequired,
|
|
|
|
|
+ IFNULL(FrozenBOMQty, 0) AS FrozenBOMQty,
|
|
|
|
|
+ TRIM(IFNULL(LotSerial, '')) AS LotSerial,
|
|
|
|
|
+ TRIM(IFNULL(Status, '')) AS Status,
|
|
|
|
|
+ TRIM(IFNULL(Typed, '')) AS Typed,
|
|
|
|
|
+ TRIM(IFNULL(UM, '')) AS UM,
|
|
|
|
|
+ IFNULL(IsActive, 0) AS IsActive,
|
|
|
|
|
+ IFNULL(BusinessID, 0) AS BusinessID,
|
|
|
|
|
+ CreateUser, CreateTime, UpdateUser, UpdateTime
|
|
|
|
|
+ FROM WorkOrdDetail
|
|
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
|
|
+ AND IFNULL(IsActive, 0) = 1
|
|
|
|
|
+ ORDER BY LineNum, Op, ItemNum
|
|
|
|
|
+ """,
|
|
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
|
|
+
|
|
|
|
|
+ var routings = await _db.Ado.SqlQueryAsync<RoutingRow>(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT
|
|
|
|
|
+ TRIM(IFNULL(Domain, '')) AS Domain,
|
|
|
|
|
+ WorkOrd,
|
|
|
|
|
+ OP,
|
|
|
|
|
+ TRIM(IFNULL(ItemNum, '')) AS ItemNum,
|
|
|
|
|
+ TRIM(IFNULL(Descr, '')) AS Descr,
|
|
|
|
|
+ IFNULL(MilestoneOp, 0) AS MilestoneOp,
|
|
|
|
|
+ IFNULL(ParentOp, 0) AS ParentOp,
|
|
|
|
|
+ IFNULL(QtyOrded, 0) AS QtyOrded,
|
|
|
|
|
+ TRIM(IFNULL(Status, '')) AS Status,
|
|
|
|
|
+ TRIM(IFNULL(ProdLine, '')) AS ProdLine,
|
|
|
|
|
+ TRIM(IFNULL(WorkCtr, '')) AS WorkCtr,
|
|
|
|
|
+ TRIM(IFNULL(Machine, '')) AS Machine,
|
|
|
|
|
+ IFNULL(RunCrew, 0) AS RunCrew,
|
|
|
|
|
+ IFNULL(StdSetupTime, 0) AS StdSetupTime,
|
|
|
|
|
+ IFNULL(ProcessOut, 0) AS ProcessOut,
|
|
|
|
|
+ IFNULL(IsActive, 0) AS IsActive,
|
|
|
|
|
+ CreateUser, CreateTime, UpdateUser, UpdateTime
|
|
|
|
|
+ FROM WorkOrdRouting
|
|
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
|
|
+ AND IFNULL(IsActive, 0) = 1
|
|
|
|
|
+ ORDER BY OP
|
|
|
|
|
+ """,
|
|
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
|
|
+
|
|
|
|
|
+ if (details.Count == 0)
|
|
|
|
|
+ throw Oops.Oh($"工单 {workOrd} 无活跃物料明细,无法推送 MES");
|
|
|
|
|
+ if (routings.Count == 0)
|
|
|
|
|
+ throw Oops.Oh($"工单 {workOrd} 无活跃工序,无法推送 MES");
|
|
|
|
|
+
|
|
|
|
|
+ var masterStatus = StatusForMasterPush(master.Status);
|
|
|
|
|
+ var now = DateTime.Now;
|
|
|
|
|
+ var enqueued = 0;
|
|
|
|
|
+
|
|
|
|
|
+ var womInsert = BuildMasterInsert(domain, master, masterStatus, now);
|
|
|
|
|
+ var womUpdate = BuildMasterUpdate(master, masterStatus, now);
|
|
|
|
|
+ // 165 唯一索引 IX_WorkOrdMaster = (Domain, Typed, WorkOrd)
|
|
|
|
|
+ var typed = string.IsNullOrWhiteSpace(master.Typed) ? "" : master.Typed.Trim();
|
|
|
|
|
+ if (await EnqueueRowAsync(tenantId, $"wo|{domain}|{workOrd}|wom", ActionWom, "WorkOrdMaster",
|
|
|
|
|
+ new Dictionary<string, object?>
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Domain"] = domain,
|
|
|
|
|
+ ["Typed"] = typed,
|
|
|
|
|
+ ["WorkOrd"] = workOrd
|
|
|
|
|
+ },
|
|
|
|
|
+ womInsert, womUpdate, ct))
|
|
|
|
|
+ enqueued++;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var d in details)
|
|
|
|
|
+ {
|
|
|
|
|
+ var line = d.LineNum;
|
|
|
|
|
+ var item = d.ItemNum ?? "";
|
|
|
|
|
+ var op = d.Op;
|
|
|
|
|
+ // 165 唯一索引 IX_WorkOrdDetail = (Domain, ID, WorkOrd, Op, Line, ItemNum, Dimension1, Dimension2)
|
|
|
|
|
+ var keys = new Dictionary<string, object?>
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Domain"] = domain,
|
|
|
|
|
+ ["ID"] = "",
|
|
|
|
|
+ ["WorkOrd"] = workOrd,
|
|
|
|
|
+ ["Op"] = op,
|
|
|
|
|
+ ["Line"] = line,
|
|
|
|
|
+ ["ItemNum"] = item,
|
|
|
|
|
+ ["Dimension1"] = "",
|
|
|
|
|
+ ["Dimension2"] = ""
|
|
|
|
|
+ };
|
|
|
|
|
+ var insert = BuildDetailInsert(domain, workOrd, d, now);
|
|
|
|
|
+ var update = BuildDetailUpdate(d, now);
|
|
|
|
|
+ var idem = $"wo|{domain}|{workOrd}|wod|{line}|{op}|{item}";
|
|
|
|
|
+ if (await EnqueueRowAsync(tenantId, idem, ActionWod, "WorkOrdDetail", keys, insert, update, ct))
|
|
|
|
|
+ enqueued++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var r in routings)
|
|
|
|
|
+ {
|
|
|
|
|
+ var routingStatus = StatusForRoutingPush(masterStatus, r.Status);
|
|
|
|
|
+ // 165 唯一索引 IX_WorkOrdRouting_1 = (Domain, ID, OP, WorkOrd)
|
|
|
|
|
+ var keys = new Dictionary<string, object?>
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Domain"] = domain,
|
|
|
|
|
+ ["ID"] = "",
|
|
|
|
|
+ ["OP"] = r.OP,
|
|
|
|
|
+ ["WorkOrd"] = workOrd
|
|
|
|
|
+ };
|
|
|
|
|
+ var insert = BuildRoutingInsert(domain, workOrd, r, routingStatus, now);
|
|
|
|
|
+ var update = BuildRoutingUpdate(r, routingStatus, now);
|
|
|
|
|
+ var idem = $"wo|{domain}|{workOrd}|wor|{r.OP}";
|
|
|
|
|
+ if (await EnqueueRowAsync(tenantId, idem, ActionWor, "WorkOrdRouting", keys, insert, update, ct))
|
|
|
|
|
+ enqueued++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _wake.Pulse();
|
|
|
|
|
+
|
|
|
|
|
+ return new PushResult
|
|
|
|
|
+ {
|
|
|
|
|
+ Ok = true,
|
|
|
|
|
+ WorkOrd = workOrd,
|
|
|
|
|
+ Domain = domain,
|
|
|
|
|
+ Enqueued = enqueued,
|
|
|
|
|
+ DetailCount = details.Count,
|
|
|
|
|
+ RoutingCount = routings.Count,
|
|
|
|
|
+ Message = $"已入队 {enqueued} 条(明细 {details.Count} / 工序 {routings.Count})"
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>探测 165 是否已有完整工单三人组。</summary>
|
|
|
|
|
+ public async Task<(bool Ready, int MasterCnt, int DetailCnt, int RoutingCnt)> ProbeReadyOnMesAsync(
|
|
|
|
|
+ string domain, string workOrd, CancellationToken ct = default)
|
|
|
|
|
+ {
|
|
|
|
|
+ domain = string.IsNullOrWhiteSpace(domain) ? "8010" : domain.Trim();
|
|
|
|
|
+ workOrd = workOrd.Trim();
|
|
|
|
|
+ var ss = await _scopeFactory.GetScopeAsync(TargetSource, ct);
|
|
|
|
|
+ var masterCnt = await ss.Ado.GetIntAsync(
|
|
|
|
|
+ "SELECT COUNT(1) FROM WorkOrdMaster WHERE Domain=@d AND WorkOrd=@w",
|
|
|
|
|
+ new SugarParameter("@d", domain),
|
|
|
|
|
+ new SugarParameter("@w", workOrd));
|
|
|
|
|
+ var detailCnt = await ss.Ado.GetIntAsync(
|
|
|
|
|
+ "SELECT COUNT(1) FROM WorkOrdDetail WHERE Domain=@d AND WorkOrd=@w AND ISNULL(IsActive,0)=1",
|
|
|
|
|
+ new SugarParameter("@d", domain),
|
|
|
|
|
+ new SugarParameter("@w", workOrd));
|
|
|
|
|
+ var routingCnt = await ss.Ado.GetIntAsync(
|
|
|
|
|
+ "SELECT COUNT(1) FROM WorkOrdRouting WHERE Domain=@d AND WorkOrd=@w AND ISNULL(IsActive,0)=1",
|
|
|
|
|
+ new SugarParameter("@d", domain),
|
|
|
|
|
+ new SugarParameter("@w", workOrd));
|
|
|
|
|
+ return (masterCnt > 0 && detailCnt > 0 && routingCnt > 0, masterCnt, detailCnt, routingCnt);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>Mes 下达前置:165 上须已有完整工单三人组。</summary>
|
|
|
|
|
+ public async Task AssertReadyOnMesAsync(string domain, string workOrd, CancellationToken ct = default)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ var probe = await ProbeReadyOnMesAsync(domain, workOrd, ct);
|
|
|
|
|
+ if (!probe.Ready)
|
|
|
|
|
+ {
|
|
|
|
|
+ throw Oops.Oh(PickBillUserMessages.ForReleaseFailure("WO_MES_NOT_READY"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex) when (ex is not AppFriendlyException)
|
|
|
|
|
+ {
|
|
|
|
|
+ throw Oops.Oh(PickBillUserMessages.ForReleaseFailure("MES_UNREACHABLE"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task<string> ResolveDomainAsync(long tenantId, string workOrd)
|
|
|
|
|
+ {
|
|
|
|
|
+ var domain = await _db.Ado.GetStringAsync(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT TRIM(IFNULL(`Domain`, '')) FROM WorkOrdMaster
|
|
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd LIMIT 1
|
|
|
|
|
+ """,
|
|
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
|
|
+ return string.IsNullOrWhiteSpace(domain) ? "8010" : domain;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task<bool> EnqueueRowAsync(
|
|
|
|
|
+ long tenantId,
|
|
|
|
|
+ string idem,
|
|
|
|
|
+ string action,
|
|
|
|
|
+ string table,
|
|
|
|
|
+ Dictionary<string, object?> keys,
|
|
|
|
|
+ Dictionary<string, object?> insert,
|
|
|
|
|
+ Dictionary<string, object?> update,
|
|
|
|
|
+ CancellationToken ct)
|
|
|
|
|
+ {
|
|
|
|
|
+ var payload = new Dictionary<string, object?>
|
|
|
|
|
+ {
|
|
|
|
|
+ ["op"] = "UPSERT",
|
|
|
|
|
+ ["table"] = table,
|
|
|
|
|
+ ["keys"] = keys,
|
|
|
|
|
+ ["insert"] = insert,
|
|
|
|
|
+ ["update"] = update,
|
|
|
|
|
+ ["expect"] = new Dictionary<string, object?>()
|
|
|
|
|
+ };
|
|
|
|
|
+ var item = new MdpOutbox
|
|
|
|
|
+ {
|
|
|
|
|
+ TenantId = tenantId,
|
|
|
|
|
+ TargetSourceCode = TargetSource,
|
|
|
|
|
+ ActionCode = action,
|
|
|
|
|
+ IdemKey = idem.Length > 200 ? idem[..200] : idem,
|
|
|
|
|
+ PayloadJson = JsonSerializer.Serialize(payload)
|
|
|
|
|
+ };
|
|
|
|
|
+ return await _enqueue.TryEnqueueOrRefreshAsync(item, ct, pulse: false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string StatusForMasterPush(string? local)
|
|
|
|
|
+ {
|
|
|
|
|
+ var t = (local ?? "").Trim().ToLowerInvariant();
|
|
|
|
|
+ return t is "r" or "w" or "s" or "c" or "p" ? t : "p";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string StatusForRoutingPush(string masterStatus, string? localRouting)
|
|
|
|
|
+ {
|
|
|
|
|
+ var m = (masterStatus ?? "").Trim().ToLowerInvariant();
|
|
|
|
|
+ if (m is "r" or "w" or "s")
|
|
|
|
|
+ {
|
|
|
|
|
+ var r = (localRouting ?? "").Trim().ToLowerInvariant();
|
|
|
|
|
+ return string.IsNullOrEmpty(r) ? "r" : r;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 计划态:不写执行态;空串表示未下达
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Dictionary<string, object?> BuildMasterInsert(
|
|
|
|
|
+ string domain, WorkOrdMaster m, string status, DateTime now)
|
|
|
|
|
+ {
|
|
|
|
|
+ var insert = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Domain"] = domain,
|
|
|
|
|
+ ["WorkOrd"] = m.WorkOrd,
|
|
|
|
|
+ ["ID"] = m.WorkOrd,
|
|
|
|
|
+ ["Status"] = status,
|
|
|
|
|
+ ["Batch"] = m.Batch ?? "",
|
|
|
|
|
+ ["ItemNum"] = m.ItemNum ?? "",
|
|
|
|
|
+ ["Site"] = m.Site ?? "",
|
|
|
|
|
+ ["Typed"] = string.IsNullOrWhiteSpace(m.Typed) ? "" : m.Typed.Trim(),
|
|
|
|
|
+ ["QtyOrded"] = m.QtyOrded,
|
|
|
|
|
+ ["ProdLine"] = m.ProdLine ?? "",
|
|
|
|
|
+ ["Location"] = m.Location ?? "",
|
|
|
|
|
+ ["Priority"] = m.Priority,
|
|
|
|
|
+ ["Urgent"] = m.Urgent,
|
|
|
|
|
+ ["ItemName"] = Trunc(m.ItemName, 256),
|
|
|
|
|
+ ["Department"] = Trunc(m.Department, 64),
|
|
|
|
|
+ ["RoutingCode"] = m.RoutingCode ?? "",
|
|
|
|
|
+ ["BOMFormula"] = m.BOMFormula ?? "",
|
|
|
|
|
+ ["Project"] = m.Project ?? "",
|
|
|
|
|
+ ["Drawing"] = Trunc(m.Drawing, 64),
|
|
|
|
|
+ ["Rev"] = m.Rev ?? "",
|
|
|
|
|
+ ["CustNo"] = Trunc(m.CustNo, 64),
|
|
|
|
|
+ ["BusinessID"] = m.BusinessID,
|
|
|
|
|
+ ["IsActive"] = m.IsActive,
|
|
|
|
|
+ ["IsConfirm"] = m.IsConfirm,
|
|
|
|
|
+ ["CreateUser"] = Trunc(m.CreateUser ?? "aidop", 24),
|
|
|
|
|
+ ["UpdateUser"] = Trunc(m.UpdateUser ?? m.CreateUser ?? "aidop", 24),
|
|
|
|
|
+ ["CreateTime"] = Fmt(m.CreateTime ?? now),
|
|
|
|
|
+ ["UpdateTime"] = Fmt(m.UpdateTime ?? now)
|
|
|
|
|
+ };
|
|
|
|
|
+ if (m.OrdDate != null) insert["OrdDate"] = Fmt(m.OrdDate);
|
|
|
|
|
+ if (m.DueDate != null) insert["DueDate"] = Fmt(m.DueDate);
|
|
|
|
|
+ if (m.ReleaseDate != null) insert["ReleaseDate"] = Fmt(m.ReleaseDate);
|
|
|
|
|
+ return insert;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Dictionary<string, object?> BuildMasterUpdate(WorkOrdMaster m, string status, DateTime now)
|
|
|
|
|
+ {
|
|
|
|
|
+ var update = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Status"] = status,
|
|
|
|
|
+ ["Batch"] = m.Batch ?? "",
|
|
|
|
|
+ ["ItemNum"] = m.ItemNum ?? "",
|
|
|
|
|
+ ["Site"] = m.Site ?? "",
|
|
|
|
|
+ ["Typed"] = m.Typed ?? "",
|
|
|
|
|
+ ["QtyOrded"] = m.QtyOrded,
|
|
|
|
|
+ ["ProdLine"] = m.ProdLine ?? "",
|
|
|
|
|
+ ["Location"] = m.Location ?? "",
|
|
|
|
|
+ ["Priority"] = m.Priority,
|
|
|
|
|
+ ["Urgent"] = m.Urgent,
|
|
|
|
|
+ ["ItemName"] = Trunc(m.ItemName, 256),
|
|
|
|
|
+ ["Department"] = Trunc(m.Department, 64),
|
|
|
|
|
+ ["RoutingCode"] = m.RoutingCode ?? "",
|
|
|
|
|
+ ["BOMFormula"] = m.BOMFormula ?? "",
|
|
|
|
|
+ ["Project"] = m.Project ?? "",
|
|
|
|
|
+ ["Drawing"] = Trunc(m.Drawing, 64),
|
|
|
|
|
+ ["Rev"] = m.Rev ?? "",
|
|
|
|
|
+ ["CustNo"] = Trunc(m.CustNo, 64),
|
|
|
|
|
+ ["IsActive"] = m.IsActive,
|
|
|
|
|
+ ["UpdateUser"] = Trunc(m.UpdateUser ?? "aidop", 24),
|
|
|
|
|
+ ["UpdateTime"] = Fmt(now)
|
|
|
|
|
+ };
|
|
|
|
|
+ if (m.OrdDate != null) update["OrdDate"] = Fmt(m.OrdDate);
|
|
|
|
|
+ if (m.DueDate != null) update["DueDate"] = Fmt(m.DueDate);
|
|
|
|
|
+ if (m.ReleaseDate != null) update["ReleaseDate"] = Fmt(m.ReleaseDate);
|
|
|
|
|
+ return update;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Dictionary<string, object?> BuildDetailInsert(string domain, string workOrd, DetailRow d, DateTime now)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Domain"] = domain,
|
|
|
|
|
+ ["ID"] = "",
|
|
|
|
|
+ ["WorkOrd"] = workOrd,
|
|
|
|
|
+ ["LineNum"] = d.LineNum,
|
|
|
|
|
+ ["Line"] = d.LineNum,
|
|
|
|
|
+ ["ItemNum"] = d.ItemNum ?? "",
|
|
|
|
|
+ ["Op"] = d.Op,
|
|
|
|
|
+ ["Dimension1"] = "",
|
|
|
|
|
+ ["Dimension2"] = "",
|
|
|
|
|
+ ["Location"] = d.Location ?? "",
|
|
|
|
|
+ ["Site"] = d.Site ?? "",
|
|
|
|
|
+ ["QtyRequired"] = d.QtyRequired,
|
|
|
|
|
+ ["FrozenBOMQty"] = d.FrozenBOMQty,
|
|
|
|
|
+ ["LotSerial"] = Trunc(d.LotSerial, 25),
|
|
|
|
|
+ ["Status"] = "",
|
|
|
|
|
+ ["Typed"] = d.Typed ?? "",
|
|
|
|
|
+ ["UM"] = d.UM ?? "",
|
|
|
|
|
+ ["IsActive"] = d.IsActive != 0,
|
|
|
|
|
+ ["BusinessID"] = d.BusinessID,
|
|
|
|
|
+ ["QtyReturned"] = 0m,
|
|
|
|
|
+ ["CreateUser"] = Trunc(d.CreateUser ?? "aidop", 24),
|
|
|
|
|
+ ["UpdateUser"] = Trunc(d.UpdateUser ?? d.CreateUser ?? "aidop", 24),
|
|
|
|
|
+ ["CreateTime"] = Fmt(d.CreateTime ?? now),
|
|
|
|
|
+ ["UpdateTime"] = Fmt(d.UpdateTime ?? now)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Dictionary<string, object?> BuildDetailUpdate(DetailRow d, DateTime now)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Location"] = d.Location ?? "",
|
|
|
|
|
+ ["Site"] = d.Site ?? "",
|
|
|
|
|
+ ["QtyRequired"] = d.QtyRequired,
|
|
|
|
|
+ ["FrozenBOMQty"] = d.FrozenBOMQty,
|
|
|
|
|
+ ["LotSerial"] = Trunc(d.LotSerial, 25),
|
|
|
|
|
+ ["Typed"] = d.Typed ?? "",
|
|
|
|
|
+ ["UM"] = d.UM ?? "",
|
|
|
|
|
+ ["IsActive"] = d.IsActive != 0,
|
|
|
|
|
+ ["UpdateUser"] = Trunc(d.UpdateUser ?? "aidop", 24),
|
|
|
|
|
+ ["UpdateTime"] = Fmt(now)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Dictionary<string, object?> BuildRoutingInsert(
|
|
|
|
|
+ string domain, string workOrd, RoutingRow r, string status, DateTime now)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
+ {
|
|
|
|
|
+ ["Domain"] = domain,
|
|
|
|
|
+ ["ID"] = "",
|
|
|
|
|
+ ["WorkOrd"] = workOrd,
|
|
|
|
|
+ ["OP"] = r.OP,
|
|
|
|
|
+ ["ItemNum"] = r.ItemNum ?? "",
|
|
|
|
|
+ ["Descr"] = Trunc(r.Descr, 256),
|
|
|
|
|
+ ["MilestoneOp"] = r.MilestoneOp != 0 ? 1 : 0,
|
|
|
|
|
+ ["ParentOp"] = r.ParentOp,
|
|
|
|
|
+ ["QtyOrded"] = r.QtyOrded,
|
|
|
|
|
+ ["Status"] = status,
|
|
|
|
|
+ ["ProdLine"] = Trunc(r.ProdLine, 8),
|
|
|
|
|
+ ["WorkCtr"] = Trunc(r.WorkCtr, 8),
|
|
|
|
|
+ ["Machine"] = Trunc(r.Machine, 800),
|
|
|
|
|
+ ["RunCrew"] = r.RunCrew,
|
|
|
|
|
+ ["StdSetupTime"] = r.StdSetupTime,
|
|
|
|
|
+ ["ProcessOut"] = r.ProcessOut,
|
|
|
|
|
+ ["IsActive"] = r.IsActive != 0,
|
|
|
|
|
+ ["CreateUser"] = Trunc(r.CreateUser ?? "aidop", 24),
|
|
|
|
|
+ ["UpdateUser"] = Trunc(r.UpdateUser ?? r.CreateUser ?? "aidop", 24),
|
|
|
|
|
+ ["CreateTime"] = Fmt(r.CreateTime ?? now),
|
|
|
|
|
+ ["UpdateTime"] = Fmt(r.UpdateTime ?? now)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Dictionary<string, object?> BuildRoutingUpdate(RoutingRow r, string status, DateTime now)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
+ {
|
|
|
|
|
+ ["ItemNum"] = r.ItemNum ?? "",
|
|
|
|
|
+ ["Descr"] = Trunc(r.Descr, 256),
|
|
|
|
|
+ ["MilestoneOp"] = r.MilestoneOp != 0 ? 1 : 0,
|
|
|
|
|
+ ["ParentOp"] = r.ParentOp,
|
|
|
|
|
+ ["QtyOrded"] = r.QtyOrded,
|
|
|
|
|
+ ["Status"] = status,
|
|
|
|
|
+ ["ProdLine"] = Trunc(r.ProdLine, 8),
|
|
|
|
|
+ ["WorkCtr"] = Trunc(r.WorkCtr, 8),
|
|
|
|
|
+ ["Machine"] = Trunc(r.Machine, 800),
|
|
|
|
|
+ ["RunCrew"] = r.RunCrew,
|
|
|
|
|
+ ["StdSetupTime"] = r.StdSetupTime,
|
|
|
|
|
+ ["ProcessOut"] = r.ProcessOut,
|
|
|
|
|
+ ["IsActive"] = r.IsActive != 0,
|
|
|
|
|
+ ["UpdateUser"] = Trunc(r.UpdateUser ?? "aidop", 24),
|
|
|
|
|
+ ["UpdateTime"] = Fmt(now)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string Fmt(DateTime? dt) =>
|
|
|
|
|
+ (dt ?? DateTime.Now).ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
+
|
|
|
|
|
+ private static string? Trunc(string? s, int max)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(s)) return s ?? "";
|
|
|
|
|
+ return s.Length <= max ? s : s[..max];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private sealed class DetailRow
|
|
|
|
|
+ {
|
|
|
|
|
+ public string? Domain { get; set; }
|
|
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
|
|
+ public int LineNum { get; set; }
|
|
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
|
|
+ public int Op { get; set; }
|
|
|
|
|
+ public string? Location { get; set; }
|
|
|
|
|
+ public string? Site { get; set; }
|
|
|
|
|
+ public decimal QtyRequired { get; set; }
|
|
|
|
|
+ public decimal FrozenBOMQty { get; set; }
|
|
|
|
|
+ public string? LotSerial { get; set; }
|
|
|
|
|
+ public string? Status { get; set; }
|
|
|
|
|
+ public string? Typed { get; set; }
|
|
|
|
|
+ public string? UM { get; set; }
|
|
|
|
|
+ public int IsActive { get; set; }
|
|
|
|
|
+ public long BusinessID { get; set; }
|
|
|
|
|
+ public string? CreateUser { get; set; }
|
|
|
|
|
+ public DateTime? CreateTime { get; set; }
|
|
|
|
|
+ public string? UpdateUser { get; set; }
|
|
|
|
|
+ public DateTime? UpdateTime { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private sealed class RoutingRow
|
|
|
|
|
+ {
|
|
|
|
|
+ public string? Domain { get; set; }
|
|
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
|
|
+ public int OP { get; set; }
|
|
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
|
|
+ public string? Descr { get; set; }
|
|
|
|
|
+ public int MilestoneOp { get; set; }
|
|
|
|
|
+ public int ParentOp { get; set; }
|
|
|
|
|
+ public decimal QtyOrded { get; set; }
|
|
|
|
|
+ public string? Status { get; set; }
|
|
|
|
|
+ public string? ProdLine { get; set; }
|
|
|
|
|
+ public string? WorkCtr { get; set; }
|
|
|
|
|
+ public string? Machine { get; set; }
|
|
|
|
|
+ public decimal RunCrew { get; set; }
|
|
|
|
|
+ public decimal StdSetupTime { get; set; }
|
|
|
|
|
+ public int ProcessOut { get; set; }
|
|
|
|
|
+ public int IsActive { get; set; }
|
|
|
|
|
+ public string? CreateUser { get; set; }
|
|
|
|
|
+ public DateTime? CreateTime { get; set; }
|
|
|
|
|
+ public string? UpdateUser { get; set; }
|
|
|
|
|
+ public DateTime? UpdateTime { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|