|
|
@@ -1,5 +1,9 @@
|
|
|
using System.Globalization;
|
|
|
+using Admin.NET.Plugin.AiDOP.DataPlatform;
|
|
|
+using Admin.NET.Plugin.AiDOP.DataPlatform.Wms;
|
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure;
|
|
|
+using Microsoft.Extensions.Logging;
|
|
|
+using Microsoft.Extensions.Options;
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.WorkOrder;
|
|
|
|
|
|
@@ -16,21 +20,36 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
private readonly ISqlSugarClient _db;
|
|
|
private readonly UserManager _userManager;
|
|
|
private readonly WorkOrderKittingCheckService _kittingCheck;
|
|
|
- private readonly WorkOrderPickBillService _pickBill;
|
|
|
+ private readonly IPickBillCreator _pickBillCreator;
|
|
|
+ private readonly AidopPickBillOptions _pickBillOpt;
|
|
|
+ private readonly MdpSourceScopeFactory _scopeFactory;
|
|
|
+ private readonly KeeperResolveService _keeper;
|
|
|
private readonly AidopActionRunLogWriter _runLog;
|
|
|
+ private readonly ILogger<WorkOrderDispatchService> _logger;
|
|
|
+
|
|
|
+ /// <summary>单次下达请求内的诊断上下文(写入 run_log.detail_json,不回传前端)。</summary>
|
|
|
+ private ReleaseDiagContext? _diag;
|
|
|
|
|
|
public WorkOrderDispatchService(
|
|
|
ISqlSugarClient db,
|
|
|
UserManager userManager,
|
|
|
WorkOrderKittingCheckService kittingCheck,
|
|
|
- WorkOrderPickBillService pickBill,
|
|
|
- AidopActionRunLogWriter runLog)
|
|
|
+ IPickBillCreator pickBillCreator,
|
|
|
+ IOptions<AidopPickBillOptions> pickBillOpt,
|
|
|
+ MdpSourceScopeFactory scopeFactory,
|
|
|
+ KeeperResolveService keeper,
|
|
|
+ AidopActionRunLogWriter runLog,
|
|
|
+ ILogger<WorkOrderDispatchService> logger)
|
|
|
{
|
|
|
_db = db;
|
|
|
_userManager = userManager;
|
|
|
_kittingCheck = kittingCheck;
|
|
|
- _pickBill = pickBill;
|
|
|
+ _pickBillCreator = pickBillCreator;
|
|
|
+ _pickBillOpt = pickBillOpt.Value;
|
|
|
+ _scopeFactory = scopeFactory;
|
|
|
+ _keeper = keeper;
|
|
|
_runLog = runLog;
|
|
|
+ _logger = logger;
|
|
|
}
|
|
|
|
|
|
private long ResolveTenantId(long? requestTenantId) =>
|
|
|
@@ -186,7 +205,7 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
// ── 下达前置校验 ──
|
|
|
// 1. 生产批号必须有值
|
|
|
if (string.IsNullOrWhiteSpace(input.LotSerial))
|
|
|
- throw Oops.Oh("工单下达失败:生产批号不能为空,请先获取或输入生产批号");
|
|
|
+ throw Oops.Oh("下达失败!生产批号不能为空,请先获取或输入生产批号。");
|
|
|
|
|
|
// 2. 工单物料明细至少一条
|
|
|
var detailCount = await _db.Ado.GetIntAsync(
|
|
|
@@ -197,7 +216,7 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
new SugarParameter("@TenantId", tid),
|
|
|
new SugarParameter("@WorkOrd", wo));
|
|
|
if (detailCount == 0)
|
|
|
- throw Oops.Oh("工单下达失败:工单物料明细为空,请先同步物料明细");
|
|
|
+ throw Oops.Oh("下达失败!工单物料明细为空,请先同步或维护物料后重试。");
|
|
|
|
|
|
// 3. 工单工序明细至少一条
|
|
|
var routingCount = await _db.Ado.GetIntAsync(
|
|
|
@@ -208,7 +227,7 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
new SugarParameter("@TenantId", tid),
|
|
|
new SugarParameter("@WorkOrd", wo));
|
|
|
if (routingCount == 0)
|
|
|
- throw Oops.Oh("工单下达失败:工单工序明细为空,请先同步工艺路线");
|
|
|
+ throw Oops.Oh("下达失败!工单工序明细为空,请先同步工艺路线后重试。");
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(input.LotSerial))
|
|
|
{
|
|
|
@@ -224,7 +243,7 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
new SugarParameter("@LotSerial", ls),
|
|
|
new SugarParameter("@WorkOrd", wo));
|
|
|
if (dup > 0)
|
|
|
- throw Oops.Oh("生产批号已被本租户下其他工单占用,请重新获取或修改");
|
|
|
+ throw Oops.Oh("下达失败!生产批号已被其他工单占用,请重新获取或修改。");
|
|
|
}
|
|
|
|
|
|
DateTime? ordDate = string.IsNullOrWhiteSpace(input.OrdDate)
|
|
|
@@ -232,68 +251,47 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
: DateTime.Parse(input.OrdDate.Trim(), CultureInfo.InvariantCulture).Date;
|
|
|
var releaseDate = DateTime.Now;
|
|
|
var account = _userManager.Account ?? "system";
|
|
|
+ var domain = await ResolveWorkOrderDomainAsync(tid, wo);
|
|
|
|
|
|
var kitting = await _kittingCheck.CheckSingleAsync(tid, wo, account);
|
|
|
|
|
|
- WorkOrderPickBillService.PickBillResult pickResult;
|
|
|
- await _db.Ado.BeginTranAsync();
|
|
|
- try
|
|
|
- {
|
|
|
- pickResult = await _pickBill.CreateForWorkOrderAsync(tid, wo, account);
|
|
|
+ // WP9 S4:Mes 权威时先校验 165/保管员,再建领料(失败则本库仍为 p),最后写下达字段
|
|
|
+ if (_pickBillOpt.IsMes)
|
|
|
+ await EnsureMesPickPrereqsAsync(tid, domain, wo);
|
|
|
|
|
|
- var updatePars = new List<SugarParameter>
|
|
|
+ PickBillResult pickResult;
|
|
|
+ if (_pickBillOpt.IsMes)
|
|
|
+ {
|
|
|
+ pickResult = await CreatePickBillForReleaseAsync(tid, domain, wo, account);
|
|
|
+ await _db.Ado.BeginTranAsync();
|
|
|
+ try
|
|
|
{
|
|
|
- new SugarParameter("@Status", "r"),
|
|
|
- new SugarParameter("@OrdDate", ordDate ?? (object)DBNull.Value),
|
|
|
- new SugarParameter("@LotSerial", string.IsNullOrWhiteSpace(input.LotSerial) ? (object)DBNull.Value : input.LotSerial.Trim()),
|
|
|
- new SugarParameter("@ReleaseDate", releaseDate),
|
|
|
- new SugarParameter("@UpdateUser", account),
|
|
|
- new SugarParameter("@UpdateTime", releaseDate),
|
|
|
- new SugarParameter("@WorkOrd", wo),
|
|
|
- new SugarParameter("@TenantId", tid)
|
|
|
- };
|
|
|
-
|
|
|
- var affected = await _db.Ado.ExecuteCommandAsync(
|
|
|
- """
|
|
|
- UPDATE WorkOrdMaster
|
|
|
- SET Status = @Status,
|
|
|
- OrdDate = @OrdDate,
|
|
|
- DueDate = CASE
|
|
|
- WHEN OrdDate IS NOT NULL AND @OrdDate IS NOT NULL AND DueDate IS NOT NULL THEN
|
|
|
- DATE_ADD(DATE(DueDate), INTERVAL DATEDIFF(DATE(@OrdDate), DATE(OrdDate)) DAY)
|
|
|
- ELSE DueDate
|
|
|
- END,
|
|
|
- LotSerial = @LotSerial,
|
|
|
- ReleaseDate = @ReleaseDate,
|
|
|
- Eff = @ReleaseDate,
|
|
|
- UpdateUser = @UpdateUser,
|
|
|
- UpdateTime = @UpdateTime,
|
|
|
- Batch = SUBSTRING(@WorkOrd, 2)
|
|
|
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
- """,
|
|
|
- updatePars);
|
|
|
-
|
|
|
- if (affected == 0)
|
|
|
- throw Oops.Oh("工单下达失败:未更新到对应工单,请确认工单号、租户及状态仍为初始(p)");
|
|
|
-
|
|
|
- await _db.Ado.ExecuteCommandAsync(
|
|
|
- """
|
|
|
- UPDATE WorkOrdRouting
|
|
|
- SET Status = 'r', UpdateUser = @User, UpdateTime = @Now
|
|
|
- WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
- AND IFNULL(MilestoneOp, 0) = 1 AND IFNULL(IsActive, 0) = 1
|
|
|
- """,
|
|
|
- new SugarParameter("@User", account),
|
|
|
- new SugarParameter("@Now", releaseDate),
|
|
|
- new SugarParameter("@TenantId", tid),
|
|
|
- new SugarParameter("@WorkOrd", wo));
|
|
|
-
|
|
|
- await _db.Ado.CommitTranAsync();
|
|
|
+ await ApplyReleaseLocalAsync(tid, wo, account, ordDate, input.LotSerial, releaseDate, requireStatusP: false);
|
|
|
+ await _db.Ado.CommitTranAsync();
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ await _db.Ado.RollbackTranAsync();
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure(null, "本库下达字段更新失败:" + TruncMsg(ex.Message), "请联系系统管理员。"),
|
|
|
+ "LOCAL_UPDATE_FAILED", tid, domain, wo, ex);
|
|
|
+ }
|
|
|
}
|
|
|
- catch
|
|
|
+ else
|
|
|
{
|
|
|
- await _db.Ado.RollbackTranAsync();
|
|
|
- throw;
|
|
|
+ await _db.Ado.BeginTranAsync();
|
|
|
+ try
|
|
|
+ {
|
|
|
+ pickResult = await CreatePickBillForReleaseAsync(tid, domain, wo, account);
|
|
|
+ await ApplyReleaseLocalAsync(tid, wo, account, ordDate, input.LotSerial, releaseDate, requireStatusP: true);
|
|
|
+ await _db.Ado.CommitTranAsync();
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ await _db.Ado.RollbackTranAsync();
|
|
|
+ CaptureDiag("LOCAL_TRAN_FAILED", tid, domain, wo, ex, ex.Message);
|
|
|
+ throw;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
var response = new
|
|
|
@@ -302,14 +300,30 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
materialSituation = kitting.MaterialSituation,
|
|
|
pickBillNbr = pickResult.Nbr,
|
|
|
pickBillCreated = pickResult.Created,
|
|
|
- pickBillLineCount = pickResult.LineCount
|
|
|
+ pickBillLineCount = pickResult.LineCount,
|
|
|
+ pickBillSource = pickResult.Source,
|
|
|
+ pickBillMode = _pickBillOpt.Mode
|
|
|
};
|
|
|
await _runLog.SuccessAsync(logId, "工单下达成功", response);
|
|
|
return response;
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- await _runLog.FailedAsync(logId, ex.Message, new { workOrd = wo, tenantId = tid });
|
|
|
+ if (_diag == null)
|
|
|
+ CaptureDiag(null, tid, null, wo, ex, ex.Message);
|
|
|
+
|
|
|
+ await _runLog.FailedAsync(logId, ex.Message, new
|
|
|
+ {
|
|
|
+ workOrd = wo,
|
|
|
+ tenantId = tid,
|
|
|
+ mode = _pickBillOpt.Mode,
|
|
|
+ reasonCode = _diag?.ReasonCode,
|
|
|
+ domain = _diag?.Domain,
|
|
|
+ userMessage = ex.Message,
|
|
|
+ exceptionType = _diag?.ExceptionType ?? ex.GetType().FullName,
|
|
|
+ techDetail = _diag?.TechDetail ?? TruncMsg(ex.ToString(), 4000),
|
|
|
+ loggedAt = DateTime.Now.ToString("O")
|
|
|
+ });
|
|
|
throw;
|
|
|
}
|
|
|
}
|
|
|
@@ -377,6 +391,270 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private async Task<string> ResolveWorkOrderDomainAsync(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 List<SugarParameter>
|
|
|
+ {
|
|
|
+ new("@TenantId", tenantId),
|
|
|
+ new("@WorkOrd", workOrd)
|
|
|
+ });
|
|
|
+ return string.IsNullOrWhiteSpace(domain) ? "8010" : domain;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task<PickBillResult> CreatePickBillForReleaseAsync(
|
|
|
+ long tenantId, string domain, string workOrd, string account)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ return await _pickBillCreator.CreateAsync(tenantId, domain, workOrd, account);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ var raw = ex.Message ?? "";
|
|
|
+ var reasonCode = GuessReasonCode(raw);
|
|
|
+ if (raw.Contains("下达失败", StringComparison.Ordinal) || raw.Contains("建领料失败", StringComparison.Ordinal))
|
|
|
+ {
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ raw.Replace("建领料失败!", "下达失败!", StringComparison.Ordinal),
|
|
|
+ reasonCode, tenantId, domain, workOrd, ex);
|
|
|
+ }
|
|
|
+
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure(reasonCode, raw, "请联系系统管理员。"),
|
|
|
+ reasonCode ?? "PICK_CREATE_FAILED", tenantId, domain, workOrd, ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>WP9 S4:下达前校验 165 可达 + 保管员可解析。</summary>
|
|
|
+ private async Task EnsureMesPickPrereqsAsync(long tenantId, string domain, string workOrd)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ await _scopeFactory.GetScopeAsync(CreatePickBillService.TargetSource);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure("MES_UNREACHABLE"),
|
|
|
+ "MES_UNREACHABLE", tenantId, domain, workOrd, ex);
|
|
|
+ }
|
|
|
+
|
|
|
+ var itemNums = await _db.Ado.SqlQueryAsync<string>(
|
|
|
+ """
|
|
|
+ SELECT DISTINCT TRIM(ItemNum) AS ItemNum
|
|
|
+ FROM WorkOrdDetail
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
+ AND IFNULL(IsActive, 0) = 1 AND IFNULL(ItemNum, '') <> ''
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+
|
|
|
+ if (itemNums.Count == 0)
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ "下达失败!工单物料明细为空,请先同步或维护物料后重试。",
|
|
|
+ "NO_DETAIL", tenantId, domain, workOrd);
|
|
|
+
|
|
|
+ KeeperResolveService.KeeperResolveResult keeper;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ keeper = await _keeper.ResolveByItemsAsync(domain, itemNums);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure("MES_UNREACHABLE"),
|
|
|
+ "MES_UNREACHABLE", tenantId, domain, workOrd, ex);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (keeper.UnsafeItems.Count > 0)
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure("KEEPER_RULE_UNSAFE", items: keeper.UnsafeItems),
|
|
|
+ "KEEPER_RULE_UNSAFE", tenantId, domain, workOrd,
|
|
|
+ techNote: "unsafeItems=" + string.Join(",", keeper.UnsafeItems.Take(30)));
|
|
|
+
|
|
|
+ if (keeper.EmptyLocationItems.Count > 0)
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure("NO_KEEPER", items: keeper.EmptyLocationItems),
|
|
|
+ "NO_KEEPER", tenantId, domain, workOrd,
|
|
|
+ techNote: "emptyLocationItems=" + string.Join(",", keeper.EmptyLocationItems.Take(30)));
|
|
|
+
|
|
|
+ if (keeper.MissingItems.Count > 0)
|
|
|
+ throw ThrowReleaseFail(
|
|
|
+ PickBillUserMessages.ForReleaseFailure("NO_KEEPER", items: keeper.MissingItems),
|
|
|
+ "NO_KEEPER", tenantId, domain, workOrd,
|
|
|
+ techNote: "missingItems=" + string.Join(",", keeper.MissingItems.Take(30)));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Exception ThrowReleaseFail(
|
|
|
+ string userMessage,
|
|
|
+ string? reasonCode,
|
|
|
+ long tenantId,
|
|
|
+ string? domain,
|
|
|
+ string workOrd,
|
|
|
+ Exception? techEx = null,
|
|
|
+ string? techNote = null)
|
|
|
+ {
|
|
|
+ CaptureDiag(reasonCode, tenantId, domain, workOrd, techEx, userMessage, techNote);
|
|
|
+ return Oops.Oh(userMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CaptureDiag(
|
|
|
+ string? reasonCode,
|
|
|
+ long tenantId,
|
|
|
+ string? domain,
|
|
|
+ string workOrd,
|
|
|
+ Exception? techEx,
|
|
|
+ string? userMessage,
|
|
|
+ string? techNote = null)
|
|
|
+ {
|
|
|
+ var tech = techEx != null
|
|
|
+ ? TruncMsg(techEx.ToString(), 4000)
|
|
|
+ : TruncMsg(techNote, 2000);
|
|
|
+
|
|
|
+ _diag = new ReleaseDiagContext
|
|
|
+ {
|
|
|
+ ReasonCode = reasonCode,
|
|
|
+ Domain = domain,
|
|
|
+ ExceptionType = techEx?.GetType().FullName,
|
|
|
+ TechDetail = tech,
|
|
|
+ UserMessage = userMessage
|
|
|
+ };
|
|
|
+
|
|
|
+ if (techEx != null)
|
|
|
+ {
|
|
|
+ _logger.LogError(
|
|
|
+ techEx,
|
|
|
+ "[WorkOrderRelease] reasonCode={ReasonCode} workOrd={WorkOrd} tenantId={TenantId} domain={Domain} mode={Mode} userMsg={UserMsg}",
|
|
|
+ reasonCode, workOrd, tenantId, domain, _pickBillOpt.Mode, userMessage);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _logger.LogWarning(
|
|
|
+ "[WorkOrderRelease] reasonCode={ReasonCode} workOrd={WorkOrd} tenantId={TenantId} domain={Domain} mode={Mode} userMsg={UserMsg} note={Note}",
|
|
|
+ reasonCode, workOrd, tenantId, domain, _pickBillOpt.Mode, userMessage, techNote);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string? GuessReasonCode(string message)
|
|
|
+ {
|
|
|
+ if (message.Contains("无法连接 MES", StringComparison.Ordinal) ||
|
|
|
+ message.Contains("MES_UNREACHABLE", StringComparison.OrdinalIgnoreCase))
|
|
|
+ return "MES_UNREACHABLE";
|
|
|
+ if (message.Contains("保管员", StringComparison.Ordinal) ||
|
|
|
+ message.Contains("NO_KEEPER", StringComparison.OrdinalIgnoreCase))
|
|
|
+ return "NO_KEEPER";
|
|
|
+ if (message.Contains("KEEPER_RULE_UNSAFE", StringComparison.OrdinalIgnoreCase) ||
|
|
|
+ message.Contains("不支持的字符", StringComparison.Ordinal))
|
|
|
+ return "KEEPER_RULE_UNSAFE";
|
|
|
+ if (message.Contains("取号", StringComparison.Ordinal) ||
|
|
|
+ message.Contains("SEQ_FAILED", StringComparison.OrdinalIgnoreCase))
|
|
|
+ return "SEQ_FAILED";
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string TruncMsg(string? s, int max = 120)
|
|
|
+ {
|
|
|
+ s ??= "";
|
|
|
+ return s.Length <= max ? s : s[..max] + "…";
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class ReleaseDiagContext
|
|
|
+ {
|
|
|
+ public string? ReasonCode { get; set; }
|
|
|
+ public string? Domain { get; set; }
|
|
|
+ public string? ExceptionType { get; set; }
|
|
|
+ public string? TechDetail { get; set; }
|
|
|
+ public string? UserMessage { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 本库下达字段:Status/批号/日期/里程碑工序/mes_morder。
|
|
|
+ /// Mes 路径下建单服务可能已将 Status 置 r,故 <paramref name="requireStatusP"/> 为 false。
|
|
|
+ /// </summary>
|
|
|
+ private async Task ApplyReleaseLocalAsync(
|
|
|
+ long tenantId,
|
|
|
+ string workOrd,
|
|
|
+ string account,
|
|
|
+ DateTime? ordDate,
|
|
|
+ string? lotSerial,
|
|
|
+ DateTime releaseDate,
|
|
|
+ bool requireStatusP)
|
|
|
+ {
|
|
|
+ var updatePars = new List<SugarParameter>
|
|
|
+ {
|
|
|
+ new("@Status", "r"),
|
|
|
+ new("@OrdDate", ordDate ?? (object)DBNull.Value),
|
|
|
+ new("@LotSerial", string.IsNullOrWhiteSpace(lotSerial) ? (object)DBNull.Value : lotSerial.Trim()),
|
|
|
+ new("@ReleaseDate", releaseDate),
|
|
|
+ new("@UpdateUser", account),
|
|
|
+ new("@UpdateTime", releaseDate),
|
|
|
+ new("@WorkOrd", workOrd),
|
|
|
+ new("@TenantId", tenantId)
|
|
|
+ };
|
|
|
+
|
|
|
+ var statusFilter = requireStatusP
|
|
|
+ ? " AND LOWER(TRIM(IFNULL(Status,''))) = 'p'"
|
|
|
+ : "";
|
|
|
+
|
|
|
+ var affected = await _db.Ado.ExecuteCommandAsync(
|
|
|
+ $"""
|
|
|
+ UPDATE WorkOrdMaster
|
|
|
+ SET Status = @Status,
|
|
|
+ OrdDate = @OrdDate,
|
|
|
+ DueDate = CASE
|
|
|
+ WHEN OrdDate IS NOT NULL AND @OrdDate IS NOT NULL AND DueDate IS NOT NULL THEN
|
|
|
+ DATE_ADD(DATE(DueDate), INTERVAL DATEDIFF(DATE(@OrdDate), DATE(OrdDate)) DAY)
|
|
|
+ ELSE DueDate
|
|
|
+ END,
|
|
|
+ LotSerial = @LotSerial,
|
|
|
+ ReleaseDate = @ReleaseDate,
|
|
|
+ Eff = @ReleaseDate,
|
|
|
+ UpdateUser = @UpdateUser,
|
|
|
+ UpdateTime = @UpdateTime,
|
|
|
+ Batch = SUBSTRING(@WorkOrd, 2)
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
+ {statusFilter}
|
|
|
+ """,
|
|
|
+ updatePars);
|
|
|
+
|
|
|
+ if (affected == 0)
|
|
|
+ throw Oops.Oh("下达失败!未更新到对应工单,请确认工单仍为初始状态后重试。");
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE WorkOrdRouting
|
|
|
+ SET Status = 'r', UpdateUser = @User, UpdateTime = @Now
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
+ AND IFNULL(MilestoneOp, 0) = 1 AND IFNULL(IsActive, 0) = 1
|
|
|
+ """,
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", releaseDate),
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE mes_morder
|
|
|
+ SET morder_state = '下达',
|
|
|
+ MaterialSituation = CASE
|
|
|
+ WHEN MaterialSituation = '缺料' THEN '未备料'
|
|
|
+ ELSE IFNULL(MaterialSituation, '齐套')
|
|
|
+ END,
|
|
|
+ update_by_name = @User,
|
|
|
+ update_time = @Now
|
|
|
+ WHERE tenant_id = @TenantId AND morder_no = @WorkOrd AND IsDeleted = 0
|
|
|
+ """,
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", releaseDate),
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+ }
|
|
|
+
|
|
|
// ──────────────── 内部查询结果映射类 ────────────────
|
|
|
|
|
|
private sealed class WorkOrderDispatchRow
|