|
|
@@ -1,5 +1,6 @@
|
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure;
|
|
|
using Admin.NET.Plugin.AiDOP.ProcurementExecution;
|
|
|
+using Admin.NET.Plugin.AiDOP.WorkOrder;
|
|
|
using Yitter.IdGenerator;
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.Supply;
|
|
|
@@ -49,7 +50,7 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
_readPathCheck = readPathCheck;
|
|
|
}
|
|
|
|
|
|
- /// <summary>执行采购闭环(合并待处理 PR、转单、写 QadTracking)。</summary>
|
|
|
+ /// <summary>执行采购闭环(合并待处理 PR、转单)。</summary>
|
|
|
[DisplayName("采购执行闭环")]
|
|
|
[HttpPost("procurement/execute-pipeline")]
|
|
|
public async Task<ProcurementPipelineResult> ExecutePipeline(
|
|
|
@@ -125,8 +126,8 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
await AssignPrNumbersAsync(individualPrs, account);
|
|
|
await AssignPrNumbersAsync(mergedPrs, account);
|
|
|
|
|
|
- // 独立 PR: state=5(参考,不参与 DO/PO 转单)
|
|
|
- foreach (var pr in individualPrs) pr.State = 5;
|
|
|
+ // 独立 PR: state=0(合并后旧PR关闭,不参与 DO/PO 转单)
|
|
|
+ foreach (var pr in individualPrs) pr.State = 0;
|
|
|
// 关联独立 PR 到合并 PR 编号
|
|
|
foreach (var group in individualPrs.GroupBy(x => new { x.IcitemId, x.PrPurchaseId }))
|
|
|
{
|
|
|
@@ -175,13 +176,15 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
result.PrDeletedCount = deleteVouchers.Count;
|
|
|
}
|
|
|
|
|
|
+ // ── 生成物料交货计划(ic_demandschedule)──
|
|
|
+ result.DemandScheduleCount = await GenerateDemandScheduleAsync(tenantId, account);
|
|
|
+
|
|
|
var pending = await LoadPendingPurchaseRequestsAsync(tenantId);
|
|
|
result.PendingPrCount = pending.Count;
|
|
|
|
|
|
- if (pending.Any(x => x.IsRequireGoods == 1))
|
|
|
+ if (pending.Count > 0)
|
|
|
{
|
|
|
- var transfer = await _transferService.TransferGeneratedRequireGoodsAsync(
|
|
|
- pending.Where(x => x.IsRequireGoods == 1).ToList(), account);
|
|
|
+ var transfer = await _transferService.TransferGeneratedRequireGoodsAsync(pending, account);
|
|
|
result.PoCreatedCount = transfer.CreatedOrderCount;
|
|
|
result.PoTransferredPrCount = transfer.TransferredPrCount;
|
|
|
result.PoOccupyRehangedCount = transfer.PoOccupyRehangedCount;
|
|
|
@@ -189,19 +192,6 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
await PersistPrStateUpdatesAsync(transfer.TransferredPrIds, 4, account);
|
|
|
}
|
|
|
|
|
|
- var pushCandidates = pending
|
|
|
- .Where(x => x.IsRequireGoods == 0 && (x.State ?? 0) == 1)
|
|
|
- .ToList();
|
|
|
-
|
|
|
- if (enableExternalPush && pushCandidates.Count > 0)
|
|
|
- {
|
|
|
- var push = await _pushService.CreateQadTrackingForGeneratedRequestsAsync(pushCandidates, account);
|
|
|
- result.QadTrackingCount = push.TrackingCount;
|
|
|
- result.QadTrackingSkippedCount = push.SkippedCandidateCount;
|
|
|
- result.Warnings.AddRange(push.Warnings);
|
|
|
- await PersistPrStateUpdatesAsync(push.PushedPrIds, 2, account);
|
|
|
- }
|
|
|
-
|
|
|
await _db.Ado.CommitTranAsync();
|
|
|
}
|
|
|
catch
|
|
|
@@ -340,7 +330,7 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
new SugarParameter("@PrSsendDate", pr.PrSsendDate),
|
|
|
new SugarParameter("@PrSarriveDate", pr.PrSarriveDate),
|
|
|
new SugarParameter("@PrUnit", pr.PrUnit),
|
|
|
- new SugarParameter("@State", pr.State ?? 5),
|
|
|
+ new SugarParameter("@State", pr.State ?? 0),
|
|
|
new SugarParameter("@PrType", pr.PrType ?? 3),
|
|
|
new SugarParameter("@CurrencyType", pr.CurrencyType),
|
|
|
new SugarParameter("@CreateByName", pr.CreateByName),
|
|
|
@@ -385,7 +375,7 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
private async Task<List<string>> TryTriggerS4MdpRefreshAsync(ProcurementPipelineResult result)
|
|
|
{
|
|
|
var warnings = new List<string>();
|
|
|
- if (result.PrCreatedCount == 0 && result.PoCreatedCount == 0 && result.QadTrackingCount == 0)
|
|
|
+ if (result.PrCreatedCount == 0 && result.PoCreatedCount == 0)
|
|
|
return warnings;
|
|
|
|
|
|
try
|
|
|
@@ -401,6 +391,479 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
return warnings;
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 生成物料交货计划(ic_demandschedule):按 8 周窗口汇总工单缺料与领料单待发料,
|
|
|
+ /// 扣减库存和已发布交货单在途后写入需求计划。
|
|
|
+ /// </summary>
|
|
|
+ private async Task<int> GenerateDemandScheduleAsync(long tenantId, string account)
|
|
|
+ {
|
|
|
+ var now = DateTime.Now;
|
|
|
+
|
|
|
+ // ── 1. 软删除旧未发布需求计划 + 禁用旧未发布交货单 + 标记历史版本 ──
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE ic_demandschedule
|
|
|
+ SET IsDeleted = 1, update_by_name = @User, update_time = @Now,
|
|
|
+ remarks = CONCAT(@NowStr, ',重新生成交货计划把未发布交货计划软删除')
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND IFNULL(IsDeleted, 0) = 0
|
|
|
+ AND IFNULL(ishistoryversion, 'N') <> 'Y'
|
|
|
+ AND IFNULL(status, '') <> 'P'
|
|
|
+ """,
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@NowStr", now.ToString("yyyy-MM-dd HH:mm:ss")),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE srm_polist_ds
|
|
|
+ SET isactive = 0, updateuser = @User, updatetime = @Now,
|
|
|
+ remarks = CONCAT(@NowStr, ',重新生成交货计划把未发布交货单禁用')
|
|
|
+ WHERE tenant_id = @TenantId AND isactive = 1 AND IFNULL(status, 'N') = 'N'
|
|
|
+ """,
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@NowStr", now.ToString("yyyy-MM-dd HH:mm:ss")),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ UPDATE ic_demandschedule
|
|
|
+ SET ishistoryversion = 'Y', historyversionTime = @Now
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND IFNULL(IsDeleted, 0) = 0
|
|
|
+ AND IFNULL(ishistoryversion, 'N') <> 'Y'
|
|
|
+ """,
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ // ── 2. 计算 8 周窗口(从下周一开始) ──
|
|
|
+ var weekday = (int)now.DayOfWeek;
|
|
|
+ var addDays = weekday == 0 ? 1 : 8 - weekday;
|
|
|
+ var beginTime = now.Date.AddDays(addDays);
|
|
|
+ var endTime = beginTime.AddDays(21 + 28 + 6).Add(new TimeSpan(23, 59, 59));
|
|
|
+
|
|
|
+ // ── 3. 加载窗口内工单(Status 为空或 P) ──
|
|
|
+ var workOrds = await _db.Ado.SqlQueryAsync<WorkOrdRow>(
|
|
|
+ """
|
|
|
+ SELECT WorkOrd, OrdDate, Status
|
|
|
+ FROM WorkOrdMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND OrdDate >= @Begin AND OrdDate <= @End
|
|
|
+ AND (Status IS NULL OR UPPER(Status) = 'P')
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
+ new SugarParameter("@Begin", beginTime),
|
|
|
+ new SugarParameter("@End", endTime));
|
|
|
+
|
|
|
+ // ── 4. 加载已下达/投产/暂停工单(提前开工,用于领料单) ──
|
|
|
+ var pickBillWorkOrds = await _db.Ado.SqlQueryAsync<string>(
|
|
|
+ """
|
|
|
+ SELECT DISTINCT WorkOrd
|
|
|
+ FROM WorkOrdMaster
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND Status IS NOT NULL AND UPPER(Status) NOT IN ('C', 'P')
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ var allWorkOrdList = workOrds.Select(w => w.WorkOrd!).ToList();
|
|
|
+ var pickBillWorkOrdList = pickBillWorkOrds.ToList();
|
|
|
+ allWorkOrdList = allWorkOrdList.Union(pickBillWorkOrdList).Distinct().ToList();
|
|
|
+
|
|
|
+ if (allWorkOrdList.Count == 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ // ── 5. 加载每个工单的最新资源检查结果 ──
|
|
|
+ var examineResults = await _db.Ado.SqlQueryAsync<ExamineResultRow>(
|
|
|
+ """
|
|
|
+ SELECT ber.morder_no AS MorderNo, MAX(ber.Id) AS ExamineId, MAX(ber.bangid) AS BangId
|
|
|
+ FROM b_examine_result ber
|
|
|
+ WHERE ber.tenant_id = @TenantId AND ber.IsDeleted = 0
|
|
|
+ AND ber.morder_no IN (@WorkOrds)
|
|
|
+ GROUP BY ber.morder_no
|
|
|
+ """.Replace("@WorkOrds", string.Join(",", allWorkOrdList.Select((_, i) => $"@Wo{i}"))),
|
|
|
+ BuildInParams(allWorkOrdList, tenantId));
|
|
|
+
|
|
|
+ var examineIds = examineResults.Select(e => e.ExamineId).ToList();
|
|
|
+
|
|
|
+ // ── 6. 加载 BOM 子件检查行(erp_cls=3 外购 或 2 委外) ──
|
|
|
+ var examines = examineIds.Count == 0
|
|
|
+ ? new List<BomChildExamineRow>()
|
|
|
+ : await _db.Ado.SqlQueryAsync<BomChildExamineRow>(
|
|
|
+ """
|
|
|
+ SELECT item_number AS ItemNumber, examine_id AS ExamineId,
|
|
|
+ IFNULL(lack_qty, 0) AS LackQty, IFNULL(needCount, 0) AS NeedCount,
|
|
|
+ tenant_id AS TenantId, company_id AS CompanyId
|
|
|
+ FROM b_bom_child_examine
|
|
|
+ WHERE tenant_id = @TenantId AND is_use = 1
|
|
|
+ AND (erp_cls = 3 OR erp_cls = 2)
|
|
|
+ AND examine_id IN (@ExamineIds)
|
|
|
+ """.Replace("@ExamineIds", string.Join(",", examineIds.Select((_, i) => $"@Ei{i}"))),
|
|
|
+ BuildExamineParams(examineIds, tenantId));
|
|
|
+
|
|
|
+ // ── 7. 加载领料单待发料明细(NbrDetail Type=SM, QtyOrd-QtyRec>0, Status!=C) ──
|
|
|
+ var pickBills = await _db.Ado.SqlQueryAsync<PickBillRow>(
|
|
|
+ """
|
|
|
+ SELECT ItemNum, QtyOrd, QtyRec
|
|
|
+ FROM NbrDetail
|
|
|
+ WHERE tenant_id = @TenantId AND UPPER(Type) = 'SM'
|
|
|
+ AND (QtyOrd - QtyRec) > 0 AND UPPER(IFNULL(Status, '')) <> 'C'
|
|
|
+ AND WorkOrd IN (@PickWorkOrds)
|
|
|
+ """.Replace("@PickWorkOrds", string.Join(",", pickBillWorkOrdList.Select((_, i) => $"@Pw{i}"))),
|
|
|
+ BuildInParams(pickBillWorkOrdList, tenantId, "Pw"));
|
|
|
+
|
|
|
+ // ── 8. 加载已发布交货单在途(srm_polist_ds isactive=1, status='P') ──
|
|
|
+ var publishedDsList = await _db.Ado.SqlQueryAsync<PublishedDsRow>(
|
|
|
+ """
|
|
|
+ SELECT itemnum, IFNULL(SUM(schedqty - sentqty), 0) AS PendingQty
|
|
|
+ FROM srm_polist_ds
|
|
|
+ WHERE tenant_id = @TenantId AND isactive = 1 AND IFNULL(status, 'N') = 'P'
|
|
|
+ GROUP BY itemnum
|
|
|
+ """,
|
|
|
+ new SugarParameter("@TenantId", tenantId));
|
|
|
+
|
|
|
+ // ── 9. 收集所有涉及物料编号 ──
|
|
|
+ var items = examines.Select(e => e.ItemNumber!).Distinct().ToList();
|
|
|
+ items.AddRange(pickBills.Select(p => p.ItemNum!));
|
|
|
+ items = items.Distinct().ToList();
|
|
|
+
|
|
|
+ if (items.Count == 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ // ── 10. 加载物料主数据 ──
|
|
|
+ var itemList = await _db.Ado.SqlQueryAsync<ItemMasterRow>(
|
|
|
+ """
|
|
|
+ SELECT ItemNum, IFNULL(Rev, '') AS Rev, IFNULL(Drawing, '') AS Drawing,
|
|
|
+ IFNULL(InsLT, 0) AS InsLT, IFNULL(MFGMTTR, 0) AS MFGMTTR
|
|
|
+ FROM ItemMaster
|
|
|
+ WHERE tenant_id = @TenantId AND ItemNum IN (@Items)
|
|
|
+ """.Replace("@Items", string.Join(",", items.Select((_, i) => $"@It{i}"))),
|
|
|
+ BuildInParams(items, tenantId, "It"));
|
|
|
+
|
|
|
+ // ── 11. 加载库存(InvMaster,按配置的库位范围) ──
|
|
|
+ var locationList = new List<string> { "1001", "5007", "5008", "8000", "8001" };
|
|
|
+ var locationInClause = string.Join(",", locationList.Select(l => $"'{l}'"));
|
|
|
+
|
|
|
+ var stockRows = await _db.Ado.SqlQueryAsync<StockRow>(
|
|
|
+ $"""
|
|
|
+ SELECT ItemNum,
|
|
|
+ IFNULL(SUM(IFNULL(AvailStatusQty, 0) + IFNULL(Assay, 0)), 0) AS Qty
|
|
|
+ FROM InvMaster
|
|
|
+ WHERE ItemNum IN ({string.Join(",", items.Select((_, i) => $"@Sk{i}"))})
|
|
|
+ AND IsActive = 1
|
|
|
+ AND Location IN ({locationInClause})
|
|
|
+ AND tenant_id = @TenantId
|
|
|
+ GROUP BY ItemNum
|
|
|
+ """,
|
|
|
+ BuildInParams(items, tenantId, "Sk"));
|
|
|
+
|
|
|
+ var weekStockQty = stockRows.ToDictionary(s => s.ItemNum!, s => s.Qty, StringComparer.OrdinalIgnoreCase);
|
|
|
+ var weekDsQty = publishedDsList.ToDictionary(d => d.ItemNum!, d => d.PendingQty, StringComparer.OrdinalIgnoreCase);
|
|
|
+
|
|
|
+ // ── 12. 按 8 周循环生成需求计划 ──
|
|
|
+ var dsRecords = new List<DemandScheduleInsertRow>();
|
|
|
+ var remainingPickBills = pickBills.ToList();
|
|
|
+
|
|
|
+ for (var i = 0; i < 8; i++)
|
|
|
+ {
|
|
|
+ var itemBegin = beginTime.AddDays(i * 7);
|
|
|
+ var itemEnd = endTime.AddDays(7 * i - 21 - 28);
|
|
|
+ var weekWorkOrds = workOrds.Where(a => a.OrdDate >= itemBegin && a.OrdDate <= itemEnd).ToList();
|
|
|
+ var itemQtyList = new List<DemandItemDto>();
|
|
|
+
|
|
|
+ if (weekWorkOrds.Count > 0)
|
|
|
+ {
|
|
|
+ foreach (var wo in weekWorkOrds)
|
|
|
+ {
|
|
|
+ // 已下达工单(R)的缺料已计入领料单,不在此重复
|
|
|
+ if (!string.IsNullOrEmpty(wo.Status) && wo.Status.ToUpper() == "R")
|
|
|
+ continue;
|
|
|
+
|
|
|
+ var exam = examineResults.FirstOrDefault(e => e.MorderNo == wo.WorkOrd);
|
|
|
+ if (exam is null || exam.ExamineId <= 0)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ var itemLackList = examines.Where(a => a.ExamineId == exam.ExamineId).ToList();
|
|
|
+ foreach (var lack in itemLackList)
|
|
|
+ {
|
|
|
+ var existing = itemQtyList.FirstOrDefault(x => x.ItemNum == lack.ItemNumber);
|
|
|
+ if (existing != null)
|
|
|
+ {
|
|
|
+ existing.LackQty += lack.LackQty;
|
|
|
+ existing.NeedQty += lack.NeedCount;
|
|
|
+ if (!existing.WorkOrds.Contains(wo.WorkOrd!))
|
|
|
+ existing.WorkOrds += "," + wo.WorkOrd;
|
|
|
+ if (!existing.BangId.Contains(exam.BangId?.ToString() ?? ""))
|
|
|
+ existing.BangId += "," + (exam.BangId?.ToString() ?? "");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ itemQtyList.Add(new DemandItemDto
|
|
|
+ {
|
|
|
+ ItemNum = lack.ItemNumber!,
|
|
|
+ LackQty = lack.LackQty,
|
|
|
+ NeedQty = lack.NeedCount,
|
|
|
+ WorkOrds = wo.WorkOrd!,
|
|
|
+ BangId = exam.BangId?.ToString() ?? ""
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var requestDate = weekWorkOrds.Min(a => a.OrdDate) ?? DateTime.Today;
|
|
|
+
|
|
|
+ foreach (var d in itemQtyList)
|
|
|
+ {
|
|
|
+ var im = itemList.FirstOrDefault(a => a.ItemNum == d.ItemNum);
|
|
|
+ var arrivalDate = requestDate.AddDays(-1).AddDays(-(im?.InsLT ?? 0)).AddDays(-(im?.MFGMTTR ?? 0));
|
|
|
+
|
|
|
+ decimal mesQty = d.NeedQty;
|
|
|
+ // 第一周:工单需求 + 已下达工单领料单待发料(只算一次)
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ var pickQty = remainingPickBills.Where(p => p.ItemNum == d.ItemNum).Sum(q => q.QtyOrd - q.QtyRec);
|
|
|
+ mesQty = d.NeedQty + pickQty;
|
|
|
+ remainingPickBills.RemoveAll(p => p.ItemNum == d.ItemNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 库存扣减
|
|
|
+ decimal stockDeduction = 0;
|
|
|
+ weekStockQty.TryGetValue(d.ItemNum, out var stock);
|
|
|
+ var locQty = stock;
|
|
|
+ if (mesQty >= locQty)
|
|
|
+ {
|
|
|
+ weekStockQty[d.ItemNum] = 0;
|
|
|
+ stockDeduction = locQty;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ weekStockQty[d.ItemNum] = locQty - mesQty;
|
|
|
+ stockDeduction = mesQty;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 交货单在途扣减
|
|
|
+ decimal sechedQty = 0;
|
|
|
+ if (weekDsQty.TryGetValue(d.ItemNum, out var dsPending))
|
|
|
+ {
|
|
|
+ sechedQty = dsPending;
|
|
|
+ if (mesQty - stockDeduction - sechedQty >= 0)
|
|
|
+ weekDsQty[d.ItemNum] = 0;
|
|
|
+ else
|
|
|
+ weekDsQty[d.ItemNum] = dsPending - (mesQty - stockDeduction);
|
|
|
+ }
|
|
|
+
|
|
|
+ var toSechedQty = mesQty - (locQty + sechedQty);
|
|
|
+ dsRecords.Add(new DemandScheduleInsertRow
|
|
|
+ {
|
|
|
+ ItemNum = d.ItemNum,
|
|
|
+ FVersion = im?.Rev ?? "",
|
|
|
+ Drawing = im?.Drawing ?? "",
|
|
|
+ RequestDate = requestDate,
|
|
|
+ ArrivalDate = arrivalDate,
|
|
|
+ ShortQty = d.LackQty,
|
|
|
+ MesQty = mesQty,
|
|
|
+ LocQty = locQty,
|
|
|
+ SechedQty = sechedQty,
|
|
|
+ ToSechedQty = toSechedQty,
|
|
|
+ WoList = d.WorkOrds,
|
|
|
+ BangId = d.BangId
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第一周额外处理:领料单中剩余的物料(不在资源检查缺料中的已下达工单待发料)
|
|
|
+ if (i == 0 && remainingPickBills.Count > 0)
|
|
|
+ {
|
|
|
+ var pickbillItems = remainingPickBills.Select(a => a.ItemNum!).Distinct().ToList();
|
|
|
+ var requestDate = itemBegin;
|
|
|
+ foreach (var item in pickbillItems)
|
|
|
+ {
|
|
|
+ var im = itemList.FirstOrDefault(a => a.ItemNum == item);
|
|
|
+ if (im is null) continue;
|
|
|
+
|
|
|
+ var arrivalDate = requestDate.AddDays(-1).AddDays(-im.InsLT).AddDays(-im.MFGMTTR);
|
|
|
+ var mesQty = remainingPickBills.Where(p => p.ItemNum == item).Sum(q => q.QtyOrd - q.QtyRec);
|
|
|
+
|
|
|
+ decimal stockDeduction = 0;
|
|
|
+ weekStockQty.TryGetValue(item, out var stock);
|
|
|
+ var locQty = stock;
|
|
|
+ if (mesQty >= locQty)
|
|
|
+ {
|
|
|
+ weekStockQty[item] = 0;
|
|
|
+ stockDeduction = locQty;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ weekStockQty[item] = locQty - mesQty;
|
|
|
+ stockDeduction = mesQty;
|
|
|
+ }
|
|
|
+
|
|
|
+ decimal sechedQty = 0;
|
|
|
+ if (weekDsQty.TryGetValue(item, out var dsPending))
|
|
|
+ {
|
|
|
+ sechedQty = dsPending;
|
|
|
+ if (mesQty - stockDeduction - sechedQty >= 0)
|
|
|
+ weekDsQty[item] = 0;
|
|
|
+ else
|
|
|
+ weekDsQty[item] = dsPending - (mesQty - stockDeduction);
|
|
|
+ }
|
|
|
+
|
|
|
+ var toSechedQty = mesQty - (locQty + sechedQty);
|
|
|
+ dsRecords.Add(new DemandScheduleInsertRow
|
|
|
+ {
|
|
|
+ ItemNum = item,
|
|
|
+ FVersion = im.Rev,
|
|
|
+ Drawing = im.Drawing,
|
|
|
+ RequestDate = requestDate,
|
|
|
+ ArrivalDate = arrivalDate,
|
|
|
+ ShortQty = 0,
|
|
|
+ MesQty = mesQty,
|
|
|
+ LocQty = locQty,
|
|
|
+ SechedQty = sechedQty,
|
|
|
+ ToSechedQty = toSechedQty,
|
|
|
+ WoList = "",
|
|
|
+ BangId = ""
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 13. 批量插入 ic_demandschedule ──
|
|
|
+ foreach (var ds in dsRecords)
|
|
|
+ {
|
|
|
+ await _db.Ado.ExecuteCommandAsync(
|
|
|
+ """
|
|
|
+ INSERT INTO ic_demandschedule
|
|
|
+ (Id, itemnum, fversion, drawing, requestdate, arrivaldate,
|
|
|
+ shortqty, mesqty, locqty, sechedqty, tosechedqty,
|
|
|
+ status, remarks, ishistoryversion,
|
|
|
+ create_by_name, create_time, update_by_name, update_time,
|
|
|
+ tenant_id, factory_id, org_id, company_id, IsDeleted,
|
|
|
+ wolist, bangid)
|
|
|
+ VALUES
|
|
|
+ (@Id, @ItemNum, @FVersion, @Drawing, @RequestDate, @ArrivalDate,
|
|
|
+ @ShortQty, @MesQty, @LocQty, @SechedQty, @ToSechedQty,
|
|
|
+ '', '', 'N',
|
|
|
+ @User, @Now, @User, @Now,
|
|
|
+ @TenantId, @TenantId, @TenantId, 1000, 0,
|
|
|
+ @WoList, @BangId)
|
|
|
+ """,
|
|
|
+ new SugarParameter("@Id", YitIdHelper.NextId()),
|
|
|
+ new SugarParameter("@ItemNum", ds.ItemNum),
|
|
|
+ new SugarParameter("@FVersion", ds.FVersion ?? ""),
|
|
|
+ new SugarParameter("@Drawing", ds.Drawing ?? ""),
|
|
|
+ new SugarParameter("@RequestDate", ds.RequestDate),
|
|
|
+ new SugarParameter("@ArrivalDate", ds.ArrivalDate),
|
|
|
+ new SugarParameter("@ShortQty", ds.ShortQty),
|
|
|
+ new SugarParameter("@MesQty", ds.MesQty),
|
|
|
+ new SugarParameter("@LocQty", ds.LocQty),
|
|
|
+ new SugarParameter("@SechedQty", ds.SechedQty),
|
|
|
+ new SugarParameter("@ToSechedQty", ds.ToSechedQty),
|
|
|
+ new SugarParameter("@User", account),
|
|
|
+ new SugarParameter("@Now", now),
|
|
|
+ new SugarParameter("@TenantId", tenantId),
|
|
|
+ new SugarParameter("@WoList", ds.WoList ?? ""),
|
|
|
+ new SugarParameter("@BangId", ds.BangId ?? ""));
|
|
|
+ }
|
|
|
+
|
|
|
+ return dsRecords.Count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<SugarParameter> BuildInParams(List<string> values, long tenantId, string prefix = "Wo")
|
|
|
+ {
|
|
|
+ var ps = new List<SugarParameter> { new("@TenantId", tenantId) };
|
|
|
+ for (var i = 0; i < values.Count; i++)
|
|
|
+ ps.Add(new SugarParameter($"@{prefix}{i}", values[i]));
|
|
|
+ return ps;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<SugarParameter> BuildExamineParams(List<long> ids, long tenantId)
|
|
|
+ {
|
|
|
+ var ps = new List<SugarParameter> { new("@TenantId", tenantId) };
|
|
|
+ for (var i = 0; i < ids.Count; i++)
|
|
|
+ ps.Add(new SugarParameter($"@Ei{i}", ids[i]));
|
|
|
+ return ps;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── DTO for demand schedule generation ──
|
|
|
+ private sealed class WorkOrdRow
|
|
|
+ {
|
|
|
+ public string? WorkOrd { get; set; }
|
|
|
+ public DateTime? OrdDate { get; set; }
|
|
|
+ public string? Status { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class ExamineResultRow
|
|
|
+ {
|
|
|
+ public string? MorderNo { get; set; }
|
|
|
+ public long ExamineId { get; set; }
|
|
|
+ public long? BangId { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class BomChildExamineRow
|
|
|
+ {
|
|
|
+ public string? ItemNumber { get; set; }
|
|
|
+ public long? ExamineId { get; set; }
|
|
|
+ public decimal LackQty { get; set; }
|
|
|
+ public decimal NeedCount { get; set; }
|
|
|
+ public long TenantId { get; set; }
|
|
|
+ public long CompanyId { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class PickBillRow
|
|
|
+ {
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public decimal QtyOrd { get; set; }
|
|
|
+ public decimal QtyRec { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class PublishedDsRow
|
|
|
+ {
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public decimal PendingQty { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class ItemMasterRow
|
|
|
+ {
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public string? Rev { get; set; }
|
|
|
+ public string? Drawing { get; set; }
|
|
|
+ public int InsLT { get; set; }
|
|
|
+ public int MFGMTTR { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class StockRow
|
|
|
+ {
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public decimal Qty { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class DemandItemDto
|
|
|
+ {
|
|
|
+ public string ItemNum { get; set; } = string.Empty;
|
|
|
+ public decimal LackQty { get; set; }
|
|
|
+ public decimal NeedQty { get; set; }
|
|
|
+ public string WorkOrds { get; set; } = string.Empty;
|
|
|
+ public string BangId { get; set; } = string.Empty;
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class DemandScheduleInsertRow
|
|
|
+ {
|
|
|
+ public string? ItemNum { get; set; }
|
|
|
+ public string? FVersion { get; set; }
|
|
|
+ public string? Drawing { get; set; }
|
|
|
+ public DateTime RequestDate { get; set; }
|
|
|
+ public DateTime ArrivalDate { get; set; }
|
|
|
+ public decimal ShortQty { get; set; }
|
|
|
+ public decimal MesQty { get; set; }
|
|
|
+ public decimal LocQty { get; set; }
|
|
|
+ public decimal SechedQty { get; set; }
|
|
|
+ public decimal ToSechedQty { get; set; }
|
|
|
+ public string? WoList { get; set; }
|
|
|
+ public string? BangId { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
private static string BuildMessage(ProcurementPipelineResult r)
|
|
|
{
|
|
|
var parts = new List<string>();
|
|
|
@@ -409,7 +872,8 @@ public class ProcurementPipelineService : IDynamicApiController, ITransient
|
|
|
if (r.PrUpdatedCount > 0) parts.Add($"更新 PR {r.PrUpdatedCount}");
|
|
|
if (r.PrDeletedCount > 0) parts.Add($"删除 PR {r.PrDeletedCount}");
|
|
|
if (r.PoCreatedCount > 0) parts.Add($"转 DO/PO {r.PoCreatedCount}");
|
|
|
- if (r.QadTrackingCount > 0) parts.Add($"外部推送 {r.QadTrackingCount}");
|
|
|
+
|
|
|
+ if (r.DemandScheduleCount > 0) parts.Add($"交货计划 {r.DemandScheduleCount}");
|
|
|
return parts.Count > 0 ? string.Join(",", parts) : "无待处理采购申请";
|
|
|
}
|
|
|
}
|
|
|
@@ -429,6 +893,7 @@ public sealed class ProcurementPipelineResult
|
|
|
public int PoOccupyRehangedCount { get; set; }
|
|
|
public int QadTrackingCount { get; set; }
|
|
|
public int QadTrackingSkippedCount { get; set; }
|
|
|
+ public int DemandScheduleCount { get; set; }
|
|
|
public List<string> CreatedPoNumbers { get; set; } = new();
|
|
|
public List<string> Warnings { get; set; } = new();
|
|
|
public bool S4MdpRefreshed { get; set; }
|