|
|
@@ -1,3 +1,5 @@
|
|
|
+using System.Globalization;
|
|
|
+
|
|
|
namespace Admin.NET.Plugin.AiDOP.WorkOrder;
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -19,6 +21,31 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
_userManager = userManager;
|
|
|
}
|
|
|
|
|
|
+ private long ResolveTenantId(long? requestTenantId)
|
|
|
+ {
|
|
|
+ var tid = requestTenantId is > 0 ? requestTenantId!.Value : _userManager.TenantId;
|
|
|
+ if (tid == 0)
|
|
|
+ throw Oops.Oh("租户ID无效,请传入 tenantId 或登录后重试");
|
|
|
+ return tid;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>校验:本租户下存在处于初始状态(p)的该工单。</summary>
|
|
|
+ private async Task AssertInitialWorkOrderExistsAsync(long tenantId, string workOrd)
|
|
|
+ {
|
|
|
+ var wo = workOrd.Trim();
|
|
|
+ var cnt = await _db.Ado.GetIntAsync(
|
|
|
+ """
|
|
|
+ SELECT COUNT(*) FROM WorkOrdMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND WorkOrd = @WorkOrd
|
|
|
+ AND LOWER(TRIM(IFNULL(Status,''))) = 'p'
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
+ new SugarParameter("@WorkOrd", wo));
|
|
|
+ if (cnt == 0)
|
|
|
+ throw Oops.Oh("未找到处于初始状态(p)的本租户工单,请确认工单号与租户是否正确");
|
|
|
+ }
|
|
|
+
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
// 列表 GET /api/WorkOrder/dispatch/list
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
@@ -55,6 +82,8 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
|
|
|
var whereClause = "WHERE " + string.Join(" AND ", conditions);
|
|
|
|
|
|
+ // mes_morder 与工单的关联:历史上用 factory_id=Domain 直连,但部分库 factory_id 存租户/组织数值而 Domain 为工厂编码,
|
|
|
+ // 会导致 JOIN 匹配不到、齐套列为空。改为按工单号取齐套,优先 factory_id 与 Domain 文本一致的那条。
|
|
|
var baseSql = $"""
|
|
|
SELECT
|
|
|
a.RecID AS Id,
|
|
|
@@ -74,16 +103,11 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
IFNULL(nm1.Nbr,'') AS Nbr,
|
|
|
CONCAT(IFNULL(b.ItemNum,''), IFNULL(b.Descr,''), IFNULL(b.Descr1,'')) AS Wl
|
|
|
FROM WorkOrdMaster a
|
|
|
- LEFT JOIN ItemMaster b ON a.ItemNum COLLATE utf8mb4_general_ci = b.ItemNum COLLATE utf8mb4_general_ci
|
|
|
- LEFT JOIN mes_morder m ON a.WorkOrd COLLATE utf8mb4_general_ci = m.morder_no COLLATE utf8mb4_general_ci
|
|
|
- AND CAST(a.Domain AS CHAR(64)) COLLATE utf8mb4_general_ci = CAST(m.factory_id AS CHAR(64)) COLLATE utf8mb4_general_ci
|
|
|
- LEFT JOIN NbrMaster nm
|
|
|
- ON a.Domain COLLATE utf8mb4_general_ci = nm.Domain COLLATE utf8mb4_general_ci
|
|
|
- AND a.WorkOrd COLLATE utf8mb4_general_ci = nm.WorkOrd COLLATE utf8mb4_general_ci
|
|
|
+ LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum
|
|
|
+ LEFT JOIN mes_morder m ON a.WorkOrd = m.morder_no
|
|
|
+ LEFT JOIN NbrMaster nm ON a.WorkOrd= nm.WorkOrd
|
|
|
AND nm.Type = 'SM' AND nm.TransType = 'PrevProcess'
|
|
|
- LEFT JOIN NbrMaster nm1
|
|
|
- ON a.Domain COLLATE utf8mb4_general_ci = nm1.Domain COLLATE utf8mb4_general_ci
|
|
|
- AND a.WorkOrd COLLATE utf8mb4_general_ci = nm1.WorkOrd COLLATE utf8mb4_general_ci
|
|
|
+ LEFT JOIN NbrMaster nm1 ON a.WorkOrd = nm1.WorkOrd
|
|
|
AND nm1.Type = 'SM' AND nm1.TransType = ''
|
|
|
{whereClause}
|
|
|
""";
|
|
|
@@ -98,57 +122,112 @@ public class WorkOrderDispatchService : IDynamicApiController, ITransient
|
|
|
return new { total, page = input.Page, pageSize = input.PageSize, list };
|
|
|
}
|
|
|
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ // 下一生产批号 GET /api/WorkOrder/dispatch/next-lot-serial
|
|
|
+ // 规则:以服务器「当前日期」的 yyMMdd + 三位流水;同一 tenant_id 下按已占用流水递增,避免重复。
|
|
|
+ // 须存在:tenant_id + 工单号 + 状态 p。
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ /// <summary>获取下一可用生产批号(当前日期 + 三位流水)🏭</summary>
|
|
|
+ [DisplayName("获取下一生产批号")]
|
|
|
+ [HttpGet("dispatch/next-lot-serial")]
|
|
|
+ public async Task<object> GetNextLotSerial([FromQuery] long? tenantId, [FromQuery] string workOrd)
|
|
|
+ {
|
|
|
+ var tid = ResolveTenantId(tenantId);
|
|
|
+ if (string.IsNullOrWhiteSpace(workOrd))
|
|
|
+ throw Oops.Oh("工单编号不能为空");
|
|
|
+
|
|
|
+ await AssertInitialWorkOrderExistsAsync(tid, workOrd);
|
|
|
+
|
|
|
+ var prefix = DateTime.Today.ToString("yyMMdd", CultureInfo.InvariantCulture);
|
|
|
+
|
|
|
+ var maxSql = """
|
|
|
+ SELECT IFNULL(MAX(CAST(RIGHT(TRIM(w.LotSerial), 3) AS UNSIGNED)), 0)
|
|
|
+ FROM WorkOrdMaster w
|
|
|
+ WHERE w.tenant_id = @TenantId
|
|
|
+ AND TRIM(w.LotSerial) REGEXP '^[0-9]{9}$'
|
|
|
+ AND LEFT(TRIM(w.LotSerial), 6) = @Prefix
|
|
|
+ """;
|
|
|
+
|
|
|
+ var maxSeq = await _db.Ado.GetIntAsync(maxSql,
|
|
|
+ new SugarParameter("@TenantId", tid),
|
|
|
+ new SugarParameter("@Prefix", prefix));
|
|
|
+ var next = maxSeq + 1;
|
|
|
+ if (next > 999)
|
|
|
+ throw Oops.Oh($"当日生产批号流水已超过 999(前缀 {prefix}),请联系管理员");
|
|
|
+
|
|
|
+ var lotSerial = prefix + next.ToString("D3", CultureInfo.InvariantCulture);
|
|
|
+ return new { lotSerial };
|
|
|
+ }
|
|
|
+
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
// 工单下达 POST /api/WorkOrder/dispatch/release
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
- /// <summary>工单下达(更新开工日期、生产批号,状态改为 r)🏭</summary>
|
|
|
+ /// <summary>工单下达(更新开工日期、生产批号,状态改为 r)。若原开工日、新开工日、原完工日均非空,则完工日按日历同天数平移。</summary>
|
|
|
[DisplayName("工单下达")]
|
|
|
[ApiDescriptionSettings(Name = "ReleaseWorkOrder"), HttpPost("dispatch/release")]
|
|
|
public async Task<object> ReleaseWorkOrder([FromBody] WorkOrderReleaseInput input)
|
|
|
{
|
|
|
- // 参照 SysJobService.UpdateJobDetail:先查再改
|
|
|
- var pars = new List<SugarParameter>
|
|
|
+ var tid = ResolveTenantId(input.TenantId > 0 ? input.TenantId : null);
|
|
|
+ var wo = input.WorkOrd.Trim();
|
|
|
+
|
|
|
+ await AssertInitialWorkOrderExistsAsync(tid, wo);
|
|
|
+
|
|
|
+ if (!string.IsNullOrWhiteSpace(input.LotSerial))
|
|
|
{
|
|
|
- new SugarParameter("@WorkOrd", input.WorkOrd),
|
|
|
- new SugarParameter("@Domain", input.Domain)
|
|
|
- };
|
|
|
- var exists = await _db.Ado.GetIntAsync(
|
|
|
- "SELECT COUNT(*) FROM WorkOrdMaster WHERE WorkOrd = @WorkOrd AND Domain = @Domain AND Status = 'p'",
|
|
|
- pars);
|
|
|
- if (exists == 0)
|
|
|
- throw Oops.Oh("工单不存在或状态不为初始(p),无法下达");
|
|
|
+ var ls = input.LotSerial.Trim();
|
|
|
+ var dup = await _db.Ado.GetIntAsync(
|
|
|
+ """
|
|
|
+ SELECT COUNT(*) FROM WorkOrdMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND TRIM(IFNULL(LotSerial,'')) = @LotSerial
|
|
|
+ AND WorkOrd <> @WorkOrd
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", tid),
|
|
|
+ new SugarParameter("@LotSerial", ls),
|
|
|
+ new SugarParameter("@WorkOrd", wo));
|
|
|
+ if (dup > 0)
|
|
|
+ throw Oops.Oh("生产批号已被本租户下其他工单占用,请重新获取或修改");
|
|
|
+ }
|
|
|
|
|
|
- var ordDate = string.IsNullOrWhiteSpace(input.OrdDate)
|
|
|
- ? (DateTime?)null
|
|
|
- : DateTime.Parse(input.OrdDate);
|
|
|
+ // 开工日仅取日期部分;与库中原开工日做日历差,同天数平移 DueDate(保持原「完工−开工」间隔)
|
|
|
+ DateTime? ordDate = string.IsNullOrWhiteSpace(input.OrdDate)
|
|
|
+ ? null
|
|
|
+ : DateTime.Parse(input.OrdDate.Trim(), CultureInfo.InvariantCulture).Date;
|
|
|
var releaseDate = DateTime.Now;
|
|
|
var account = _userManager.Account ?? "system";
|
|
|
|
|
|
var updatePars = new List<SugarParameter>
|
|
|
{
|
|
|
new SugarParameter("@Status", "r"),
|
|
|
- new SugarParameter("@OrdDate", ordDate),
|
|
|
- new SugarParameter("@LotSerial", input.LotSerial ?? (object)DBNull.Value),
|
|
|
+ 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", input.WorkOrd),
|
|
|
- new SugarParameter("@Domain", input.Domain)
|
|
|
+ new SugarParameter("@WorkOrd", wo),
|
|
|
+ new SugarParameter("@TenantId", tid)
|
|
|
};
|
|
|
|
|
|
- await _db.Ado.ExecuteCommandAsync(
|
|
|
+ var affected = await _db.Ado.ExecuteCommandAsync(
|
|
|
"""
|
|
|
UPDATE WorkOrdMaster
|
|
|
- SET Status = @Status,
|
|
|
- OrdDate = @OrdDate,
|
|
|
+ SET 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,
|
|
|
UpdateUser = @UpdateUser,
|
|
|
UpdateTime = @UpdateTime
|
|
|
- WHERE WorkOrd = @WorkOrd AND Domain = @Domain
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
""",
|
|
|
updatePars);
|
|
|
|
|
|
+ if (affected == 0)
|
|
|
+ throw Oops.Oh("工单下达失败:未更新到对应工单,请确认工单号、租户及状态仍为初始(p)");
|
|
|
+
|
|
|
return new { message = "工单下达成功" };
|
|
|
}
|
|
|
|