|
|
@@ -281,7 +281,8 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
throw Oops.Oh("工单号与公司域名不能为空");
|
|
|
|
|
|
string sql = $"""
|
|
|
- SELECT d.ItemNum, d.Op, d.QtyRequired, IFNULL(d.FrozenBOMQty, 0) AS FrozenBOMQty
|
|
|
+ SELECT d.RecID, d.ItemNum, d.Op, d.QtyRequired, IFNULL(d.FrozenBOMQty, 0) AS FrozenBOMQty,
|
|
|
+ IFNULL(d.Location, '') AS Location, IFNULL(d.LotSerial, '') AS LotSerial, d.WorkOrd
|
|
|
FROM WorkOrdDetail d
|
|
|
WHERE {SqlWorkOrdChildMatchDomainOrTenant("d", "m")}
|
|
|
ORDER BY d.LineNum
|
|
|
@@ -299,15 +300,32 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
|
|
|
string sql = $"""
|
|
|
SELECT
|
|
|
- r.OP AS Op,
|
|
|
- r.Descr AS Descr,
|
|
|
+ r.RecID, r.OP AS Op, r.Descr AS Descr,
|
|
|
IFNULL(r.ParentOp, '') AS ParentOp,
|
|
|
IFNULL(r.MilestoneOp, 0) AS MilestoneOp,
|
|
|
IFNULL(r.PackingQty, 0) AS PackingQty,
|
|
|
IFNULL(r.QtyComplete, 0) AS QtyComplete,
|
|
|
IFNULL(r.QtyReject, 0) AS QtyReject,
|
|
|
IFNULL(r.QtyScrap, 0) AS QtyScrap,
|
|
|
- r.Status AS Status
|
|
|
+ r.Status AS Status, r.WorkOrd,
|
|
|
+ IFNULL(r.WorkCtr, '') AS WorkCtr,
|
|
|
+ IFNULL(r.ProdLine, '') AS ProdLine,
|
|
|
+ IFNULL(r.StdSetupTime, 0) AS StdSetupTime,
|
|
|
+ IFNULL(r.QtyOrded, 0) AS QtyOrded,
|
|
|
+ IFNULL(r.ProcessOut, 0) AS ProcessOut,
|
|
|
+ IFNULL(r.ProcessOutSupp, '') AS ProcessOutSupp,
|
|
|
+ IFNULL(r.ProcessOutDay, 0) AS ProcessOutDay,
|
|
|
+ IFNULL(r.Machine, '') AS Machine,
|
|
|
+ IFNULL(r.ToolCode, '') AS ToolCode,
|
|
|
+ IFNULL(r.RunCrew, 0) AS RunCrew,
|
|
|
+ IFNULL(r.Engineer, '') AS Engineer,
|
|
|
+ IFNULL(r.MachBdnRate, 0) AS MachBdnRate,
|
|
|
+ IFNULL(r.WorkCode, '') AS WorkCode,
|
|
|
+ IFNULL(r.Setup, 0) AS Setup,
|
|
|
+ IFNULL(r.MachinesperOp, 0) AS MachinesperOp,
|
|
|
+ IFNULL(r.Labor, 0) AS Labor,
|
|
|
+ IFNULL(r.ChargeCode, '') AS ChargeCode,
|
|
|
+ IFNULL(r.ItemNum, '') AS ItemNum
|
|
|
FROM WorkOrdRouting r
|
|
|
WHERE {SqlWorkOrdChildMatchDomainOrTenant("r", "m")}
|
|
|
ORDER BY (r.OP + 0) ASC, r.OP ASC, r.Line ASC, r.ColumnNum ASC
|
|
|
@@ -625,6 +643,19 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
public async Task<object> Save([FromBody] WorkOrderSchedulingSaveInput input)
|
|
|
{
|
|
|
var account = _userManager.Account ?? "system";
|
|
|
+ var workOrd = input.WorkOrd.Trim();
|
|
|
+ var domain = input.Domain.Trim();
|
|
|
+
|
|
|
+ // ── 1. 获取旧的 QtyOrded,用于判断数量是否变更 ──
|
|
|
+ var oldQtyOrded = await _db.Ado.GetDecimalAsync(
|
|
|
+ $"SELECT IFNULL(QtyOrded, 0) FROM WorkOrdMaster WHERE {SqlWorkOrdMasterMatchDomainOrTenant}",
|
|
|
+ new SugarParameter("@WorkOrd", workOrd),
|
|
|
+ new SugarParameter("@Domain", domain));
|
|
|
+
|
|
|
+ var newQtyOrded = input.QtyOrded;
|
|
|
+ var qtyChanged = newQtyOrded.HasValue && oldQtyOrded > 0 && newQtyOrded.Value != oldQtyOrded;
|
|
|
+
|
|
|
+ // ── 2. 更新 WorkOrdMaster ──
|
|
|
var pars = new List<SugarParameter>
|
|
|
{
|
|
|
new("@QtyOrded", input.QtyOrded ?? (object)DBNull.Value),
|
|
|
@@ -633,8 +664,8 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
new("@Urgent", input.Urgent ?? (object)DBNull.Value),
|
|
|
new("@UpdateUser", account),
|
|
|
new("@UpdateTime", DateTime.Now),
|
|
|
- new("@WorkOrd", input.WorkOrd.Trim()),
|
|
|
- new("@Domain", input.Domain.Trim())
|
|
|
+ new("@WorkOrd", workOrd),
|
|
|
+ new("@Domain", domain)
|
|
|
};
|
|
|
|
|
|
var n = await _db.Ado.ExecuteCommandAsync(
|
|
|
@@ -653,6 +684,76 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
if (n == 0)
|
|
|
throw Oops.Oh("工单不存在或未更新");
|
|
|
|
|
|
+ // ── 3. 数量变更时同步子表 ──
|
|
|
+ if (qtyChanged)
|
|
|
+ {
|
|
|
+ var ratio = newQtyOrded!.Value / oldQtyOrded;
|
|
|
+ var now = DateTime.Now;
|
|
|
+
|
|
|
+ // 3a. WorkOrdDetail: 按比例调整 QtyRequired
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE WorkOrdDetail
|
|
|
+ SET QtyRequired = ROUND(QtyRequired * @Ratio, 6),
|
|
|
+ UpdateUser = @User, UpdateTime = @Now
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
+ AND IFNULL(IsActive, 0) = 1
|
|
|
+ """,
|
|
|
+ new SugarParameter("@Ratio", ratio),
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", domain),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+
|
|
|
+ // 3b. WorkOrdRouting: 同步 QtyOrded
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE WorkOrdRouting
|
|
|
+ SET QtyOrded = @QtyOrded,
|
|
|
+ UpdateUser = @User, UpdateTime = @Now
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
|
|
|
+ AND IFNULL(IsActive, 0) = 1
|
|
|
+ """,
|
|
|
+ new SugarParameter("@QtyOrded", newQtyOrded.Value),
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", domain),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+
|
|
|
+ // 3c. NbrDetail: 按比例调整 QtyOrd 和 CurrQtyOpened(仅下达/投产/暂停状态)
|
|
|
+ var woStatus = await _db.Ado.GetStringAsync(
|
|
|
+ """
|
|
|
+ SELECT IFNULL(LOWER(TRIM(Status)), '') FROM WorkOrdMaster
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd LIMIT 1
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", domain),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+
|
|
|
+ if (woStatus is "r" or "w" or "s")
|
|
|
+ {
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE NbrDetail
|
|
|
+ SET QtyOrd = ROUND(QtyOrd * @Ratio, 6),
|
|
|
+ CurrQtyOpened = ROUND(CurrQtyOpened * @Ratio, 6),
|
|
|
+ UpdateUser = @User, UpdateTime = @Now
|
|
|
+ WHERE tenant_id = @TenantId AND Type = 'SM'
|
|
|
+ AND Nbr IN (
|
|
|
+ SELECT Nbr FROM NbrMaster
|
|
|
+ WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd AND Type = 'SM'
|
|
|
+ AND IFNULL(IsActive, 0) = 1
|
|
|
+ )
|
|
|
+ AND IFNULL(IsActive, 0) = 1
|
|
|
+ AND IFNULL(Status, '') <> 'C'
|
|
|
+ """,
|
|
|
+ new SugarParameter("@Ratio", ratio),
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", domain),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return new { message = "保存成功" };
|
|
|
}
|
|
|
|
|
|
@@ -908,6 +1009,659 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
return new { message = "同步工艺路线已执行" };
|
|
|
}
|
|
|
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ // 下拉数据 GET endpoints
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取工单下拉选项(状态为空/P/R/W/S 且类型为空/RW/TEST/TCN)
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("工单下拉选项")]
|
|
|
+ [HttpGet("scheduling/work-ord-options")]
|
|
|
+ public async Task<object> GetWorkOrdOptions()
|
|
|
+ {
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+ var list = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT WorkOrd FROM WorkOrdMaster
|
|
|
+ WHERE UPPER(IFNULL(Status, '')) IN ('', 'P', 'R', 'W', 'S')
|
|
|
+ AND UPPER(IFNULL(Typed, '')) IN ('', 'RW', 'TEST', 'TCN')
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ ORDER BY WorkOrd DESC
|
|
|
+ """,
|
|
|
+ new { TenantId = tenantId });
|
|
|
+ return new { list };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取工作中心下拉选项
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("工作中心下拉选项")]
|
|
|
+ [HttpGet("scheduling/work-ctr-options")]
|
|
|
+ public async Task<object> GetWorkCtrOptions()
|
|
|
+ {
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+ var list = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT WorkCtr, IFNULL(Descr, '') AS Descr
|
|
|
+ FROM WorkCtrMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ ORDER BY WorkCtr
|
|
|
+ """,
|
|
|
+ new { TenantId = tenantId });
|
|
|
+ return new { list };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取生产线下拉选项
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("生产线下拉选项")]
|
|
|
+ [HttpGet("scheduling/line-options")]
|
|
|
+ public async Task<object> GetLineOptions()
|
|
|
+ {
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+ var list = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT Line, IFNULL(`Describe`, '') AS `Describe`
|
|
|
+ FROM LineMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ ORDER BY Line
|
|
|
+ """,
|
|
|
+ new { TenantId = tenantId });
|
|
|
+ return new { list };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取供应商下拉选项
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("供应商下拉选项")]
|
|
|
+ [HttpGet("scheduling/supp-options")]
|
|
|
+ public async Task<object> GetSuppOptions()
|
|
|
+ {
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+ var list = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT Supp, TRIM(CONCAT(Supp, ' ', IFNULL(SortName, ''))) AS SuppName
|
|
|
+ FROM SuppMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ ORDER BY Supp
|
|
|
+ """,
|
|
|
+ new { TenantId = tenantId });
|
|
|
+ return new { list };
|
|
|
+ }
|
|
|
+
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ // 工单物料明细 CRUD
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 保存工单物料明细(新增/编辑)
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("保存工单物料明细")]
|
|
|
+ [HttpPost("scheduling/material-save")]
|
|
|
+ public async Task<object> SaveMaterialDetail([FromBody] WorkOrdDetailSaveInput input)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(input.WorkOrd))
|
|
|
+ throw Oops.Oh("工单编号不能为空");
|
|
|
+ if (string.IsNullOrWhiteSpace(input.ItemNum))
|
|
|
+ throw Oops.Oh("物料号不能为空");
|
|
|
+
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+ var account = _userManager.Account ?? "system";
|
|
|
+ var now = DateTime.Now;
|
|
|
+ var workOrd = input.WorkOrd.Trim();
|
|
|
+
|
|
|
+ // 获取工单主记录信息和域
|
|
|
+ var master = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ $"""
|
|
|
+ SELECT RecID, UPPER(IFNULL(Status, '')) AS Status, IFNULL(`Domain`, '') AS Domain
|
|
|
+ FROM WorkOrdMaster
|
|
|
+ WHERE WorkOrd = @WorkOrd AND tenant_id = @TenantId
|
|
|
+ LIMIT 1
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, TenantId = tenantId });
|
|
|
+ if (master == null || master.Count == 0)
|
|
|
+ throw Oops.Oh("工单不存在");
|
|
|
+ var masterRow = master[0];
|
|
|
+ var masterRecId = (long)masterRow.RecID;
|
|
|
+ var woStatus = (string)masterRow.Status;
|
|
|
+ var domain = (string)masterRow.Domain ?? "";
|
|
|
+
|
|
|
+ // 查找物料是否存在于ItemMaster
|
|
|
+ var itemExists = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ """
|
|
|
+ SELECT COUNT(1) FROM ItemMaster
|
|
|
+ WHERE ItemNum = @ItemNum AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { ItemNum = input.ItemNum.Trim(), TenantId = tenantId });
|
|
|
+ if (itemExists[0] == 0)
|
|
|
+ throw Oops.Oh($"物料 {input.ItemNum} 在基础数据中不存在,不能新增");
|
|
|
+
|
|
|
+ // 查找物料的Descr和Unit
|
|
|
+ var itemInfo = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT IFNULL(Descr, '') AS ItemName, IFNULL(UM, '') AS UM
|
|
|
+ FROM ItemMaster
|
|
|
+ WHERE ItemNum = @ItemNum AND tenant_id = @TenantId
|
|
|
+ LIMIT 1
|
|
|
+ """,
|
|
|
+ new { ItemNum = input.ItemNum.Trim(), TenantId = tenantId });
|
|
|
+ var itemName = itemInfo.Count > 0 ? (string)itemInfo[0].ItemName : "";
|
|
|
+ var um = itemInfo.Count > 0 ? (string)itemInfo[0].UM : "";
|
|
|
+
|
|
|
+ var isP = woStatus == "P";
|
|
|
+
|
|
|
+ if (input.RecID == 0)
|
|
|
+ {
|
|
|
+ // ── 新增 ──
|
|
|
+ // 检查工单中是否已有此物料
|
|
|
+ var dupCheck = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ """
|
|
|
+ SELECT COUNT(1) FROM WorkOrdDetail
|
|
|
+ WHERE WorkOrd = @WorkOrd AND ItemNum = @ItemNum
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, ItemNum = input.ItemNum.Trim(), TenantId = tenantId });
|
|
|
+ if (dupCheck[0] > 0)
|
|
|
+ throw Oops.Oh($"工单 {workOrd} 中已有物料 {input.ItemNum},不能重复新增");
|
|
|
+
|
|
|
+ // 获取下一个RecID
|
|
|
+ var nextIdRows = await _db.Ado.SqlQueryAsync<long>(
|
|
|
+ "SELECT IFNULL(MAX(RecID), 0) + 1 FROM WorkOrdDetail");
|
|
|
+ var newRecId = nextIdRows[0];
|
|
|
+
|
|
|
+ // 获取下一个LineNum
|
|
|
+ var lineNumRows = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ "SELECT IFNULL(MAX(LineNum), 0) + 1 FROM WorkOrdDetail WHERE WorkOrd = @WorkOrd",
|
|
|
+ new { WorkOrd = workOrd });
|
|
|
+ var lineNum = lineNumRows[0];
|
|
|
+
|
|
|
+ // 插入WorkOrdDetail
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ INSERT INTO WorkOrdDetail (
|
|
|
+ RecID, WorkOrdMasterRecID, `Domain`, WorkOrd, LineNum, ItemNum, Op,
|
|
|
+ Location, QtyRequired, QtyPosted, UM, LotSerial,
|
|
|
+ IsActive, IsConfirm, CreateUser, CreateTime, tenant_id
|
|
|
+ ) VALUES (
|
|
|
+ @RecID, @MasterRecId, @Domain, @WorkOrd, @LineNum, @ItemNum, @Op,
|
|
|
+ @Location, @QtyRequired, 0, @UM, @LotSerial,
|
|
|
+ 1, 0, @User, @Now, @TenantId
|
|
|
+ )
|
|
|
+ """,
|
|
|
+ new SugarParameter("@RecID", newRecId),
|
|
|
+ new SugarParameter("@MasterRecId", masterRecId),
|
|
|
+ new SugarParameter("@Domain", domain.Length > 8 ? domain[..8] : domain),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd),
|
|
|
+ new SugarParameter("@LineNum", lineNum),
|
|
|
+ new SugarParameter("@ItemNum", input.ItemNum.Trim()),
|
|
|
+ new SugarParameter("@Op", (object?)input.Op ?? DBNull.Value),
|
|
|
+ new SugarParameter("@QtyRequired", input.QtyRequired),
|
|
|
+ new SugarParameter("@Location", (object?)input.Location ?? DBNull.Value),
|
|
|
+ new SugarParameter("@LotSerial", (object?)input.LotSerial ?? DBNull.Value),
|
|
|
+ new SugarParameter("@UM", string.IsNullOrEmpty(um) ? DBNull.Value : um),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ // 非P状态需同步创建领料单NbrDetail
|
|
|
+ if (!isP)
|
|
|
+ {
|
|
|
+ await CreateOrUpdateNbrDetailForMaterial(workOrd, domain, input.ItemNum.Trim(),
|
|
|
+ input.QtyRequired, um, itemName, tenantId, account, now, isNew: true);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new { message = "新增成功", recID = newRecId };
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // ── 编辑 ──
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE WorkOrdDetail
|
|
|
+ SET ItemNum = @ItemNum, Op = @Op, QtyRequired = @QtyRequired,
|
|
|
+ Location = @Location, LotSerial = @LotSerial,
|
|
|
+ UM = @UM,
|
|
|
+ UpdateUser = @User, UpdateTime = @Now
|
|
|
+ WHERE RecID = @RecID AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new SugarParameter("@ItemNum", input.ItemNum.Trim()),
|
|
|
+ new SugarParameter("@Op", (object?)input.Op ?? DBNull.Value),
|
|
|
+ new SugarParameter("@QtyRequired", input.QtyRequired),
|
|
|
+ new SugarParameter("@Location", (object?)input.Location ?? DBNull.Value),
|
|
|
+ new SugarParameter("@LotSerial", (object?)input.LotSerial ?? DBNull.Value),
|
|
|
+ new SugarParameter("@UM", string.IsNullOrEmpty(um) ? DBNull.Value : um),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@RecID", input.RecID),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ // 非P状态需同步修改领料单NbrDetail
|
|
|
+ if (!isP)
|
|
|
+ {
|
|
|
+ await CreateOrUpdateNbrDetailForMaterial(workOrd, domain, input.ItemNum.Trim(),
|
|
|
+ input.QtyRequired, um, itemName, tenantId, account, now, isNew: false);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new { message = "保存成功" };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 删除工单物料明细
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("删除工单物料明细")]
|
|
|
+ [HttpPost("scheduling/material-delete")]
|
|
|
+ public async Task<object> DeleteMaterialDetail([FromBody] WorkOrdDetailDeleteInput input)
|
|
|
+ {
|
|
|
+ if (input.RecID == 0)
|
|
|
+ throw Oops.Oh("主键不能为空");
|
|
|
+
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+
|
|
|
+ // 获取物料明细信息用于NbrDetail同步
|
|
|
+ var detailRows = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT d.WorkOrd, d.ItemNum, IFNULL(d.`Domain`, '') AS Domain, m.Status
|
|
|
+ FROM WorkOrdDetail d
|
|
|
+ LEFT JOIN WorkOrdMaster m ON d.WorkOrd = m.WorkOrd AND d.tenant_id = m.tenant_id
|
|
|
+ WHERE d.RecID = @RecID AND d.tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { RecID = input.RecID, TenantId = tenantId });
|
|
|
+ if (detailRows == null || detailRows.Count == 0)
|
|
|
+ throw Oops.Oh("物料明细不存在");
|
|
|
+
|
|
|
+ var row = detailRows[0];
|
|
|
+ var workOrd = (string)row.WorkOrd;
|
|
|
+ var itemNum = (string)row.ItemNum;
|
|
|
+ var domain = (string)row.Domain ?? "";
|
|
|
+ var woStatus = (string)(row.Status ?? "");
|
|
|
+
|
|
|
+ // 删除WorkOrdDetail
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ "DELETE FROM WorkOrdDetail WHERE RecID = @RecID AND tenant_id = @TenantId",
|
|
|
+ new SugarParameter("@RecID", input.RecID),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ // 非P状态需同步删除NbrDetail
|
|
|
+ if (woStatus != "P")
|
|
|
+ {
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ DELETE FROM NbrDetail
|
|
|
+ WHERE WorkOrd = @WorkOrd AND ItemNum = @ItemNum AND Type = 'SM'
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new SugarParameter("@WorkOrd", workOrd),
|
|
|
+ new SugarParameter("@ItemNum", itemNum),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+ }
|
|
|
+
|
|
|
+ return new { message = "删除成功" };
|
|
|
+ }
|
|
|
+
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ // 工单工序明细 CRUD
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 保存工单工序明细(新增/编辑)
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("保存工单工序明细")]
|
|
|
+ [HttpPost("scheduling/routing-save")]
|
|
|
+ public async Task<object> SaveRoutingDetail([FromBody] WorkOrdRoutingSaveInput input)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(input.WorkOrd))
|
|
|
+ throw Oops.Oh("工单编号不能为空");
|
|
|
+ if (string.IsNullOrWhiteSpace(input.OP))
|
|
|
+ throw Oops.Oh("工序不能为空");
|
|
|
+
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+ var account = _userManager.Account ?? "system";
|
|
|
+ var now = DateTime.Now;
|
|
|
+ var workOrd = input.WorkOrd.Trim();
|
|
|
+
|
|
|
+ // 获取工单主记录信息和域
|
|
|
+ var master = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT RecID, IFNULL(`Domain`, '') AS Domain, IFNULL(ERPfld1, '') AS ERPfld1,
|
|
|
+ IFNULL(QtyOrded, 0) AS QtyOrded
|
|
|
+ FROM WorkOrdMaster
|
|
|
+ WHERE WorkOrd = @WorkOrd AND tenant_id = @TenantId
|
|
|
+ LIMIT 1
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, TenantId = tenantId });
|
|
|
+ if (master == null || master.Count == 0)
|
|
|
+ throw Oops.Oh("工单不存在");
|
|
|
+ var masterRow = master[0];
|
|
|
+ var masterRecId = (long)masterRow.RecID;
|
|
|
+ var domain = (string)masterRow.Domain ?? "";
|
|
|
+ var erpfld1 = (string)masterRow.ERPfld1 ?? "";
|
|
|
+ var qtyOrded = (decimal)masterRow.QtyOrded;
|
|
|
+
|
|
|
+ // 检查是否已有相同工序编号(新增时检查,编辑时排除自身)
|
|
|
+ var dupCheck = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ $"""
|
|
|
+ SELECT COUNT(1) FROM WorkOrdRouting
|
|
|
+ WHERE WorkOrd = @WorkOrd AND OP = @OP
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ {(input.RecID > 0 ? "AND RecID <> @RecID" : "")}
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, OP = input.OP.Trim(), TenantId = tenantId, RecID = input.RecID });
|
|
|
+ if (dupCheck[0] > 0)
|
|
|
+ throw Oops.Oh($"工单 {workOrd} 已有相同工序编号 {input.OP},不允许保存");
|
|
|
+
|
|
|
+ // 检查后序是否已有完成数量(编辑时)
|
|
|
+ if (input.RecID > 0)
|
|
|
+ {
|
|
|
+ var currentOpRows = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ $"""
|
|
|
+ SELECT CAST(IFNULL(OP, 0) AS SIGNED) FROM WorkOrdRouting
|
|
|
+ WHERE RecID = @RecID AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { RecID = input.RecID, TenantId = tenantId });
|
|
|
+ if (currentOpRows.Count > 0)
|
|
|
+ {
|
|
|
+ var currentOp = currentOpRows[0];
|
|
|
+ var afterComplete = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ """
|
|
|
+ SELECT COUNT(1) FROM WorkOrdRouting
|
|
|
+ WHERE WorkOrd = @WorkOrd
|
|
|
+ AND CAST(IFNULL(OP, 0) AS SIGNED) > @CurrentOp
|
|
|
+ AND IFNULL(QtyComplete, 0) > 0
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, CurrentOp = currentOp, TenantId = tenantId });
|
|
|
+ if (afterComplete[0] > 0)
|
|
|
+ throw Oops.Oh("后序已有完成数量,不允许保存");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (input.RecID == 0)
|
|
|
+ {
|
|
|
+ // ── 新增 ──
|
|
|
+ var nextIdRows = await _db.Ado.SqlQueryAsync<long>(
|
|
|
+ "SELECT IFNULL(MAX(RecID), 0) + 1 FROM WorkOrdRouting");
|
|
|
+ var newRecId = nextIdRows[0];
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ INSERT INTO WorkOrdRouting (
|
|
|
+ RecID, `Descr`, `Domain`, `ChargeCode`, `Machine`, `RunCrew`, `MilestoneOp`,
|
|
|
+ `OP`, `ItemNum`, `WorkCtr`, `Setup`, `MachinesperOp`, `Labor`,
|
|
|
+ `MachBdnRate`, `WorkCode`, `StdSetupTime`, `Engineer`,
|
|
|
+ `WorkOrd`, `WorkOrdMasterRecID`, `ERPfld1`, `QtyOrded`,
|
|
|
+ `ProcessOut`, `ProcessOutDay`, `ProcessOutSupp`,
|
|
|
+ `IsActive`, `Status`, `ProdLine`, `ToolCode`,
|
|
|
+ `CreateTime`, `CreateUser`, `tenant_id`, `CommentIndex`, `WaitTime`, `QtyComplete`, `ParentOp`
|
|
|
+ ) VALUES (
|
|
|
+ @RecID, @Descr, @Domain, @ChargeCode, @Machine, @RunCrew, @MilestoneOp,
|
|
|
+ @OP, @ItemNum, @WorkCtr, @Setup, @MachinesperOp, @Labor,
|
|
|
+ @MachBdnRate, @WorkCode, @StdSetupTime, @Engineer,
|
|
|
+ @WorkOrd, @MasterRecId, @ERPfld1, @QtyOrded,
|
|
|
+ @ProcessOut, @ProcessOutDay, @ProcessOutSupp,
|
|
|
+ b'1', 'r', @ProdLine, @ToolCode,
|
|
|
+ @Now, @User, @TenantId, 0, 0, 0, 0
|
|
|
+ )
|
|
|
+ """,
|
|
|
+ new SugarParameter("@RecID", newRecId),
|
|
|
+ new SugarParameter("@Descr", (object?)input.Descr ?? DBNull.Value),
|
|
|
+ new SugarParameter("@Domain", domain.Length > 8 ? domain[..8] : domain),
|
|
|
+ new SugarParameter("@ChargeCode", (object?)input.ChargeCode ?? DBNull.Value),
|
|
|
+ new SugarParameter("@Machine", (object?)input.Machine ?? DBNull.Value),
|
|
|
+ new SugarParameter("@RunCrew", input.RunCrew),
|
|
|
+ new SugarParameter("@MilestoneOp", input.MilestoneOp ? 1 : 0),
|
|
|
+ new SugarParameter("@OP", input.OP.Trim()),
|
|
|
+ new SugarParameter("@ItemNum", (object?)input.ItemNum ?? DBNull.Value),
|
|
|
+ new SugarParameter("@WorkCtr", (object?)input.WorkCtr ?? DBNull.Value),
|
|
|
+ new SugarParameter("@Setup", input.Setup),
|
|
|
+ new SugarParameter("@MachinesperOp", input.MachinesperOp),
|
|
|
+ new SugarParameter("@Labor", input.Labor),
|
|
|
+ new SugarParameter("@MachBdnRate", input.MachBdnRate),
|
|
|
+ new SugarParameter("@WorkCode", (object?)input.WorkCode ?? DBNull.Value),
|
|
|
+ new SugarParameter("@StdSetupTime", input.StdSetupTime),
|
|
|
+ new SugarParameter("@Engineer", (object?)input.Engineer ?? DBNull.Value),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd),
|
|
|
+ new SugarParameter("@MasterRecId", masterRecId),
|
|
|
+ new SugarParameter("@ERPfld1", erpfld1),
|
|
|
+ new SugarParameter("@QtyOrded", input.QtyOrded > 0 ? input.QtyOrded : qtyOrded),
|
|
|
+ new SugarParameter("@ProcessOut", input.ProcessOut ? 1 : 0),
|
|
|
+ new SugarParameter("@ProcessOutDay", input.ProcessOutDay),
|
|
|
+ new SugarParameter("@ProcessOutSupp", (object?)input.ProcessOutSupp ?? DBNull.Value),
|
|
|
+ new SugarParameter("@ProdLine", (object?)input.ProdLine ?? DBNull.Value),
|
|
|
+ new SugarParameter("@ToolCode", (object?)input.ToolCode ?? DBNull.Value),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ return new { message = "新增成功", recID = newRecId };
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // ── 编辑 ──
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE WorkOrdRouting
|
|
|
+ SET `Descr` = @Descr, `ChargeCode` = @ChargeCode, `Machine` = @Machine,
|
|
|
+ `RunCrew` = @RunCrew, `MilestoneOp` = @MilestoneOp, `OP` = @OP,
|
|
|
+ `ItemNum` = @ItemNum, `WorkCtr` = @WorkCtr,
|
|
|
+ `Setup` = @Setup, `MachinesperOp` = @MachinesperOp, `Labor` = @Labor,
|
|
|
+ `MachBdnRate` = @MachBdnRate, `WorkCode` = @WorkCode,
|
|
|
+ `StdSetupTime` = @StdSetupTime, `Engineer` = @Engineer,
|
|
|
+ `QtyOrded` = @QtyOrded, `ProcessOut` = @ProcessOut,
|
|
|
+ `ProcessOutDay` = @ProcessOutDay, `ProcessOutSupp` = @ProcessOutSupp,
|
|
|
+ `ProdLine` = @ProdLine, `ToolCode` = @ToolCode,
|
|
|
+ `UpdateUser` = @User, `UpdateTime` = @Now
|
|
|
+ WHERE RecID = @RecID AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new SugarParameter("@Descr", (object?)input.Descr ?? DBNull.Value),
|
|
|
+ new SugarParameter("@ChargeCode", (object?)input.ChargeCode ?? DBNull.Value),
|
|
|
+ new SugarParameter("@Machine", (object?)input.Machine ?? DBNull.Value),
|
|
|
+ new SugarParameter("@RunCrew", input.RunCrew),
|
|
|
+ new SugarParameter("@MilestoneOp", input.MilestoneOp ? 1 : 0),
|
|
|
+ new SugarParameter("@OP", input.OP.Trim()),
|
|
|
+ new SugarParameter("@ItemNum", (object?)input.ItemNum ?? DBNull.Value),
|
|
|
+ new SugarParameter("@WorkCtr", (object?)input.WorkCtr ?? DBNull.Value),
|
|
|
+ new SugarParameter("@Setup", input.Setup),
|
|
|
+ new SugarParameter("@MachinesperOp", input.MachinesperOp),
|
|
|
+ new SugarParameter("@Labor", input.Labor),
|
|
|
+ new SugarParameter("@MachBdnRate", input.MachBdnRate),
|
|
|
+ new SugarParameter("@WorkCode", (object?)input.WorkCode ?? DBNull.Value),
|
|
|
+ new SugarParameter("@StdSetupTime", input.StdSetupTime),
|
|
|
+ new SugarParameter("@Engineer", (object?)input.Engineer ?? DBNull.Value),
|
|
|
+ new SugarParameter("@QtyOrded", input.QtyOrded),
|
|
|
+ new SugarParameter("@ProcessOut", input.ProcessOut ? 1 : 0),
|
|
|
+ new SugarParameter("@ProcessOutDay", input.ProcessOutDay),
|
|
|
+ new SugarParameter("@ProcessOutSupp", (object?)input.ProcessOutSupp ?? DBNull.Value),
|
|
|
+ new SugarParameter("@ProdLine", (object?)input.ProdLine ?? DBNull.Value),
|
|
|
+ new SugarParameter("@ToolCode", (object?)input.ToolCode ?? DBNull.Value),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@RecID", input.RecID),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ return new { message = "保存成功" };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 删除工单工序明细
|
|
|
+ /// </summary>
|
|
|
+ [DisplayName("删除工单工序明细")]
|
|
|
+ [HttpPost("scheduling/routing-delete")]
|
|
|
+ public async Task<object> DeleteRoutingDetail([FromBody] WorkOrdRoutingDeleteInput input)
|
|
|
+ {
|
|
|
+ if (input.RecID == 0)
|
|
|
+ throw Oops.Oh("主键不能为空");
|
|
|
+
|
|
|
+ var tenantId = _userManager.TenantId;
|
|
|
+
|
|
|
+ // 检查后序是否已有完成数量
|
|
|
+ var detailRows = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT r.WorkOrd, CAST(IFNULL(r.OP, 0) AS SIGNED) AS OP, IFNULL(r.`Domain`, '') AS Domain
|
|
|
+ FROM WorkOrdRouting r
|
|
|
+ WHERE r.RecID = @RecID AND r.tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { RecID = input.RecID, TenantId = tenantId });
|
|
|
+ if (detailRows == null || detailRows.Count == 0)
|
|
|
+ throw Oops.Oh("工序明细不存在");
|
|
|
+
|
|
|
+ var row = detailRows[0];
|
|
|
+ var workOrd = (string)row.WorkOrd;
|
|
|
+ var currentOp = (int)row.OP;
|
|
|
+ var domain = (string)row.Domain ?? "";
|
|
|
+
|
|
|
+ var afterComplete = await _db.Ado.SqlQueryAsync<int>(
|
|
|
+ """
|
|
|
+ SELECT COUNT(1) FROM WorkOrdRouting
|
|
|
+ WHERE WorkOrd = @WorkOrd
|
|
|
+ AND CAST(IFNULL(OP, 0) AS SIGNED) > @CurrentOp
|
|
|
+ AND IFNULL(QtyComplete, 0) > 0
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, CurrentOp = currentOp, TenantId = tenantId });
|
|
|
+ if (afterComplete[0] > 0)
|
|
|
+ throw Oops.Oh("后序已有完成数量,不允许删除");
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ "DELETE FROM WorkOrdRouting WHERE RecID = @RecID AND tenant_id = @TenantId",
|
|
|
+ new SugarParameter("@RecID", input.RecID),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ return new { message = "删除成功" };
|
|
|
+ }
|
|
|
+
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ // 辅助方法
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 为物料明细创建或更新领料单NbrDetail
|
|
|
+ /// </summary>
|
|
|
+ private async Task CreateOrUpdateNbrDetailForMaterial(
|
|
|
+ string workOrd, string domain, string itemNum, decimal qtyRequired,
|
|
|
+ string um, string itemName, long tenantId, string account, DateTime now, bool isNew)
|
|
|
+ {
|
|
|
+ // 查找此工单此物料的SM类型领料单明细
|
|
|
+ var existRows = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT nd.RecID, nd.NbrRecID
|
|
|
+ FROM NbrDetail nd
|
|
|
+ WHERE nd.WorkOrd = @WorkOrd AND nd.ItemNum = @ItemNum AND nd.Type = 'SM'
|
|
|
+ AND nd.tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, ItemNum = itemNum, TenantId = tenantId });
|
|
|
+
|
|
|
+ if (existRows == null || existRows.Count == 0)
|
|
|
+ {
|
|
|
+ // 没有领料单明细,创建一条
|
|
|
+ // 先查找此工单是否有SM类型的领料单主记录
|
|
|
+ var nbrMasterRows = await _db.Ado.SqlQueryAsync<dynamic>(
|
|
|
+ """
|
|
|
+ SELECT RecID, Nbr FROM NbrMaster
|
|
|
+ WHERE WorkOrd = @WorkOrd AND Type = 'SM' AND tenant_id = @TenantId
|
|
|
+ ORDER BY RecID DESC LIMIT 1
|
|
|
+ """,
|
|
|
+ new { WorkOrd = workOrd, TenantId = tenantId });
|
|
|
+
|
|
|
+ long nbrRecId = 0;
|
|
|
+ string nbrNo = "";
|
|
|
+
|
|
|
+ if (nbrMasterRows == null || nbrMasterRows.Count == 0)
|
|
|
+ {
|
|
|
+ // 创建领料单主记录
|
|
|
+ var nextMasterIdRows = await _db.Ado.SqlQueryAsync<long>(
|
|
|
+ "SELECT IFNULL(MAX(RecID), 0) + 1 FROM NbrMaster");
|
|
|
+ nbrRecId = nextMasterIdRows[0];
|
|
|
+ nbrNo = $"SM-{workOrd}-{DateTime.Now:yyyyMMddHHmmss}";
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ INSERT INTO NbrMaster (
|
|
|
+ RecID, `Domain`, Type, Nbr, WorkOrd, NbrDate,
|
|
|
+ Status, IsActive, CreateUser, CreateTime, tenant_id
|
|
|
+ ) VALUES (
|
|
|
+ @RecID, @Domain, 'SM', @Nbr, @WorkOrd, @Now,
|
|
|
+ '', 1, @User, @Now, @TenantId
|
|
|
+ )
|
|
|
+ """,
|
|
|
+ new SugarParameter("@RecID", nbrRecId),
|
|
|
+ new SugarParameter("@Domain", domain.Length > 8 ? domain[..8] : domain),
|
|
|
+ new SugarParameter("@Nbr", nbrNo),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ nbrRecId = (long)nbrMasterRows[0].RecID;
|
|
|
+ nbrNo = (string)nbrMasterRows[0].Nbr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建领料单明细
|
|
|
+ var nextDetailIdRows = await _db.Ado.SqlQueryAsync<long>(
|
|
|
+ "SELECT IFNULL(MAX(RecID), 0) + 1 FROM NbrDetail");
|
|
|
+ var newDetailId = nextDetailIdRows[0];
|
|
|
+
|
|
|
+ var maxLineRows = await _db.Ado.SqlQueryAsync<short>(
|
|
|
+ "SELECT IFNULL(MAX(Line), 0) + 1 FROM NbrDetail WHERE NbrRecID = @NbrRecID AND tenant_id = @TenantId",
|
|
|
+ new { NbrRecID = nbrRecId, TenantId = tenantId });
|
|
|
+ var newLine = maxLineRows[0];
|
|
|
+
|
|
|
+ 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,
|
|
|
+ @QtyOrd, @UM, @WorkOrd, @ItemName, '',
|
|
|
+ 1, 0, 1, 0, @NbrRecID,
|
|
|
+ @User, @Now, @User, @Now, @TenantId
|
|
|
+ )
|
|
|
+ """,
|
|
|
+ new SugarParameter("@RecID", newDetailId),
|
|
|
+ new SugarParameter("@Domain", domain.Length > 8 ? domain[..8] : domain),
|
|
|
+ new SugarParameter("@Nbr", nbrNo),
|
|
|
+ new SugarParameter("@Line", newLine),
|
|
|
+ new SugarParameter("@ItemNum", itemNum),
|
|
|
+ new SugarParameter("@QtyOrd", qtyRequired),
|
|
|
+ new SugarParameter("@UM", string.IsNullOrEmpty(um) ? DBNull.Value : um),
|
|
|
+ new SugarParameter("@WorkOrd", workOrd),
|
|
|
+ new SugarParameter("@ItemName", string.IsNullOrEmpty(itemName) ? DBNull.Value : itemName),
|
|
|
+ new SugarParameter("@NbrRecID", nbrRecId),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 更新已有的领料单明细
|
|
|
+ var ndRecId = (long)existRows[0].RecID;
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE NbrDetail
|
|
|
+ SET QtyOrd = @QtyOrd, CurrQtyOpened = @QtyOrd,
|
|
|
+ UM = @UM, ItemName = @ItemName,
|
|
|
+ UpdateUser = @User, UpdateTime = @Now
|
|
|
+ WHERE RecID = @RecID AND tenant_id = @TenantId
|
|
|
+ """,
|
|
|
+ new SugarParameter("@QtyOrd", qtyRequired),
|
|
|
+ new SugarParameter("@UM", string.IsNullOrEmpty(um) ? DBNull.Value : um),
|
|
|
+ new SugarParameter("@ItemName", string.IsNullOrEmpty(itemName) ? DBNull.Value : itemName),
|
|
|
+ new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@RecID", ndRecId),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
// 加急 POST /api/Production/scheduling/urgent
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
@@ -959,6 +1713,58 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
public int? Urgent { get; set; }
|
|
|
}
|
|
|
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+ // Input/Output DTOs
|
|
|
+ // ══════════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+ public class WorkOrdDetailSaveInput
|
|
|
+ {
|
|
|
+ public long RecID { get; set; }
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public string? Op { get; set; }
|
|
|
+ public decimal QtyRequired { get; set; }
|
|
|
+ public string? Location { get; set; }
|
|
|
+ public string? LotSerial { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class WorkOrdDetailDeleteInput
|
|
|
+ {
|
|
|
+ public long RecID { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class WorkOrdRoutingSaveInput
|
|
|
+ {
|
|
|
+ public long RecID { get; set; }
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public string? OP { get; set; }
|
|
|
+ public string? Descr { get; set; }
|
|
|
+ public string? WorkCtr { get; set; }
|
|
|
+ public string? ProdLine { get; set; }
|
|
|
+ public decimal StdSetupTime { get; set; }
|
|
|
+ public decimal QtyOrded { get; set; }
|
|
|
+ public bool MilestoneOp { get; set; } = true;
|
|
|
+ public bool ProcessOut { get; set; }
|
|
|
+ public string? ProcessOutSupp { get; set; }
|
|
|
+ public decimal ProcessOutDay { get; set; }
|
|
|
+ public string? Machine { get; set; }
|
|
|
+ public string? ToolCode { get; set; }
|
|
|
+ public decimal RunCrew { get; set; }
|
|
|
+ public string? Engineer { get; set; }
|
|
|
+ public decimal MachBdnRate { get; set; }
|
|
|
+ public string? WorkCode { get; set; }
|
|
|
+ public decimal Setup { get; set; }
|
|
|
+ public decimal MachinesperOp { get; set; }
|
|
|
+ public decimal Labor { get; set; }
|
|
|
+ public string? ChargeCode { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class WorkOrdRoutingDeleteInput
|
|
|
+ {
|
|
|
+ public long RecID { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
private sealed class WorkOrderEditPreviewRow
|
|
|
{
|
|
|
public string? WorkOrd { get; set; }
|
|
|
@@ -986,6 +1792,7 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
|
|
|
private sealed class WorkOrderViewRoutingRow
|
|
|
{
|
|
|
+ public long RecID { get; set; }
|
|
|
public string? Op { get; set; }
|
|
|
public string? Descr { get; set; }
|
|
|
public string? ParentOp { get; set; }
|
|
|
@@ -995,14 +1802,37 @@ public class WorkOrderSchedulingService : IDynamicApiController, ITransient
|
|
|
public decimal QtyReject { get; set; }
|
|
|
public decimal QtyScrap { get; set; }
|
|
|
public string? Status { get; set; }
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
+ public string? WorkCtr { get; set; }
|
|
|
+ public string? ProdLine { get; set; }
|
|
|
+ public decimal StdSetupTime { get; set; }
|
|
|
+ public decimal QtyOrded { get; set; }
|
|
|
+ public int ProcessOut { get; set; }
|
|
|
+ public string? ProcessOutSupp { get; set; }
|
|
|
+ public decimal ProcessOutDay { get; set; }
|
|
|
+ public string? Machine { get; set; }
|
|
|
+ public string? ToolCode { get; set; }
|
|
|
+ public decimal RunCrew { get; set; }
|
|
|
+ public string? Engineer { get; set; }
|
|
|
+ public decimal MachBdnRate { get; set; }
|
|
|
+ public string? WorkCode { get; set; }
|
|
|
+ public decimal Setup { get; set; }
|
|
|
+ public decimal MachinesperOp { get; set; }
|
|
|
+ public decimal Labor { get; set; }
|
|
|
+ public string? ChargeCode { get; set; }
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
}
|
|
|
|
|
|
private sealed class WorkOrderViewDetailRow
|
|
|
{
|
|
|
+ public long RecID { get; set; }
|
|
|
public string? ItemNum { get; set; }
|
|
|
public string? Op { get; set; }
|
|
|
public decimal? QtyRequired { get; set; }
|
|
|
public decimal FrozenBOMQty { get; set; }
|
|
|
+ public string? Location { get; set; }
|
|
|
+ public string? LotSerial { get; set; }
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
}
|
|
|
|
|
|
private sealed class WorkOrderTraceMasterRow
|