PurchaseOrderTransferService.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. namespace Admin.NET.Plugin.AiDOP.Supply;
  2. /// <summary>
  3. /// 采购申请转 PO 服务。处理待处理 PR,生成 PurOrdMaster/Detail 并改挂 srm_po_occupy。
  4. /// </summary>
  5. public class PurchaseOrderTransferService : ITransient
  6. {
  7. private readonly ISqlSugarClient _db;
  8. private readonly NumberRuleService _numberRuleService;
  9. private readonly UserManager _userManager;
  10. private readonly DataPlatform.Wms.PurOrdWmsPushService _purOrdWmsPush;
  11. public PurchaseOrderTransferService(
  12. ISqlSugarClient db,
  13. NumberRuleService numberRuleService,
  14. UserManager userManager,
  15. DataPlatform.Wms.PurOrdWmsPushService purOrdWmsPush)
  16. {
  17. _db = db;
  18. _numberRuleService = numberRuleService;
  19. _userManager = userManager;
  20. _purOrdWmsPush = purOrdWmsPush;
  21. }
  22. public async Task<PurchaseOrderTransferResult> TransferGeneratedRequireGoodsAsync(List<PurchaseRequestMain> requests, string account)
  23. {
  24. var candidates = requests
  25. .Where(x => (x.State ?? 0) == 1
  26. && !string.IsNullOrWhiteSpace(x.PrBillNo))
  27. .ToList();
  28. if (candidates.Count == 0) return new PurchaseOrderTransferResult();
  29. var createdOrders = new List<string>();
  30. var createdOrderTenants = new List<(long TenantId, string PurOrd)>();
  31. var transferredPrIds = new List<long>();
  32. var poOccupyRehangedCount = 0;
  33. var groups = candidates
  34. .GroupBy(x => new
  35. {
  36. x.TenantId,
  37. x.CompanyId,
  38. x.FactoryId,
  39. x.PrPurchaseId,
  40. x.PrPurchaseNumber,
  41. x.PrPurchaseName,
  42. x.IsRequireGoods,
  43. SupplierType = x.SupplierType ?? string.Empty
  44. })
  45. .ToList();
  46. foreach (var group in groups)
  47. {
  48. var now = DateTime.Now;
  49. var rows = group.OrderBy(x => x.PrSarriveDate).ThenBy(x => x.PrBillNo).ToList();
  50. var supplier = await LoadSupplierContextAsync(group.Key.PrPurchaseId, rows[0].IcitemId, group.Key.TenantId);
  51. var supplierCode = supplier?.SupplierNumber ?? group.Key.PrPurchaseNumber;
  52. var supplierType = string.IsNullOrWhiteSpace(group.Key.SupplierType)
  53. ? supplier?.SupplierType ?? string.Empty
  54. : group.Key.SupplierType;
  55. var isOutsource = string.Equals(supplierType, "委外", StringComparison.OrdinalIgnoreCase);
  56. var isRequireGoods = group.Key.IsRequireGoods == 1;
  57. // 三类型分流:IsRequireGoods=0→PO(采购订单);IsRequireGoods=1+委外→PW(委外加工订单);IsRequireGoods=1+非委外→DO(要货令)
  58. string ruleCode, poType, reqBy, usage;
  59. if (!isRequireGoods)
  60. {
  61. ruleCode = "PO"; poType = "po"; reqBy = "PO"; usage = supplierType;
  62. }
  63. else if (isOutsource)
  64. {
  65. ruleCode = "PW"; poType = "PW"; reqBy = "DO"; usage = "委外加工";
  66. }
  67. else
  68. {
  69. ruleCode = "DO"; poType = "po"; reqBy = "DO"; usage = supplierType;
  70. }
  71. var buyer = ResolveBuyer(supplierType);
  72. var purOrdNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync(
  73. ruleCode, group.Key.TenantId.ToString(), 1, account);
  74. if (purOrdNumbers.Count == 0 || string.IsNullOrWhiteSpace(purOrdNumbers[0]))
  75. throw Oops.Oh($"当前{ruleCode}单号生成失败,请检查{ruleCode}编号规则维护。Domain={group.Key.TenantId}");
  76. var purOrd = purOrdNumbers[0].Trim();
  77. await InsertPurchaseOrderMasterAsync(purOrd, poType, reqBy, usage, buyer, supplierCode, now, account, group.Key.TenantId);
  78. var masterId = await _db.Ado.GetIntAsync(
  79. "SELECT IFNULL(MAX(RecID),0) FROM PurOrdMaster WHERE PurOrd=@PurOrd AND tenant_id=@TenantId",
  80. new SugarParameter("@PurOrd", purOrd),
  81. new SugarParameter("@TenantId", group.Key.TenantId));
  82. if (masterId <= 0) throw Oops.Oh("采购订单主表生成失败。");
  83. for (var lineIndex = 0; lineIndex < rows.Count; lineIndex++)
  84. {
  85. var pr = rows[lineIndex];
  86. var line = lineIndex + 1;
  87. var (detailRecId, itemNum) = await InsertPurchaseOrderDetailAsync(purOrd, poType, masterId, line, pr, account);
  88. if (detailRecId > 0)
  89. poOccupyRehangedCount += await RehangPoOccupyFromPrToDetailAsync(pr.Id, detailRecId, pr.TenantId, account);
  90. // 委外加工订单:BOM展开写入PurOrdDetailBatch
  91. if (isOutsource && detailRecId > 0 && !string.IsNullOrWhiteSpace(itemNum))
  92. {
  93. await InsertPurOrdDetailBatchAsync(purOrd, poType, line, detailRecId, itemNum,
  94. pr.PrAqty ?? pr.PrSqty ?? pr.PrRqty ?? 0, pr.TenantId, account, now);
  95. }
  96. pr.State = 4;
  97. pr.UpdateByName = account;
  98. pr.UpdateTime = now;
  99. transferredPrIds.Add(pr.Id);
  100. }
  101. createdOrders.Add(purOrd);
  102. createdOrderTenants.Add((group.Key.TenantId, purOrd));
  103. }
  104. // 拆库后 WMS 读 165,自建采购单须回写,否则收货侧查不到
  105. foreach (var (poTenantId, poNbr) in createdOrderTenants)
  106. await _purOrdWmsPush.TryEnqueuePurchaseOrderSafeAsync(poTenantId, poNbr);
  107. return new PurchaseOrderTransferResult
  108. {
  109. CreatedOrderCount = createdOrders.Count,
  110. TransferredPrCount = transferredPrIds.Count,
  111. PoOccupyRehangedCount = poOccupyRehangedCount,
  112. CreatedOrders = createdOrders,
  113. TransferredPrIds = transferredPrIds
  114. };
  115. }
  116. private async Task<SupplierContextRow?> LoadSupplierContextAsync(long? supplierId, long? icitemId, long tenantId)
  117. {
  118. if (supplierId is null or <= 0) return null;
  119. var rows = await _db.Ado.SqlQueryAsync<SupplierContextRow>(
  120. """
  121. SELECT
  122. IFNULL(sp.supplier_number, '') AS SupplierNumber,
  123. IFNULL(sp.supplier_type, '') AS SupplierType
  124. FROM srm_purchase sp
  125. WHERE sp.tenant_id = @TenantId
  126. AND sp.supplier_id = @SupplierId
  127. AND (@IcitemId IS NULL OR sp.icitem_id = @IcitemId)
  128. ORDER BY sp.Id
  129. LIMIT 1
  130. """,
  131. new SugarParameter("@TenantId", tenantId),
  132. new SugarParameter("@SupplierId", supplierId),
  133. new SugarParameter("@IcitemId", icitemId));
  134. return rows.FirstOrDefault();
  135. }
  136. private async Task<int> RehangPoOccupyFromPrToDetailAsync(long prId, int detailRecId, long tenantId, string account)
  137. {
  138. return await _db.Ado.ExecuteCommandAsync(
  139. """
  140. UPDATE srm_po_occupy
  141. SET polist_id = @DetailRecId,
  142. update_by_name = @User,
  143. update_time = @Now
  144. WHERE tenant_id = @TenantId
  145. AND polist_id = @PrId
  146. """,
  147. new SugarParameter("@DetailRecId", detailRecId),
  148. new SugarParameter("@User", account),
  149. new SugarParameter("@Now", DateTime.Now),
  150. new SugarParameter("@TenantId", tenantId),
  151. new SugarParameter("@PrId", prId));
  152. }
  153. private async Task InsertPurchaseOrderMasterAsync(
  154. string purOrd,
  155. string poType,
  156. string reqBy,
  157. string? usage,
  158. string buyer,
  159. string? supplierCode,
  160. DateTime now,
  161. string account,
  162. long tenantId)
  163. {
  164. await _db.Ado.ExecuteCommandAsync(
  165. """
  166. INSERT INTO PurOrdMaster
  167. (
  168. Confirming, CreditTermsInt, Disc, ExchRate, EstVal, ExchRate1, ExchRate2,
  169. FixedPrice, FixedRate, Frt, PartialOK, AmtPrepaid, PrintPO, PST, Recurr,
  170. `Release`, Revision, Scheduled, ServiceCharge, SpecialCharge, Taxable,
  171. Tax1, Tax2, Tax3, TransportDays, IsActive, IsConfirm, Potype, IsChanged,
  172. TaxIn, Amt, IsPriceChanged,
  173. Buyer, Domain, PurOrd, OrdDate, ReqBy, Status, Supp, CreateUser, CreateTime,
  174. UpdateUser, UpdateTime, `Usage`, FSTID, Typed, tenant_id
  175. )
  176. VALUES
  177. (
  178. 0, 0, 0, 1, 0, 1, 1,
  179. 0, 0, 0, 1, 0, 0, 0, 0,
  180. 0, 0, 0, 0, 0, 1,
  181. 0, 0, 0, 0, 1, 1, @PoType, 0,
  182. 1, 0, 0,
  183. @Buyer, @Domain, @PurOrd, @Now, @ReqBy, '', @Supp, @CreateUser, @Now,
  184. @UpdateUser, @Now, @Usage, @FSTID, @Typed, @TenantId
  185. )
  186. """,
  187. new SugarParameter("@PoType", poType),
  188. new SugarParameter("@ReqBy", reqBy),
  189. new SugarParameter("@Buyer", buyer),
  190. new SugarParameter("@Domain", "8010"),
  191. new SugarParameter("@PurOrd", purOrd),
  192. new SugarParameter("@Now", now),
  193. new SugarParameter("@Supp", supplierCode),
  194. new SugarParameter("@CreateUser", account),
  195. new SugarParameter("@UpdateUser", account),
  196. new SugarParameter("@Usage", usage),
  197. new SugarParameter("@FSTID", string.Equals(usage, "VMI", StringComparison.OrdinalIgnoreCase) ? "3" : string.Empty),
  198. new SugarParameter("@Typed", poType == "PW" ? "s" : string.Empty),
  199. new SugarParameter("@TenantId", tenantId <= 0 ? null : tenantId));
  200. }
  201. private async Task<(int detailRecId, string itemNum)> InsertPurchaseOrderDetailAsync(
  202. string purOrd, string poType, int masterId, int line, PurchaseRequestMain pr, string account)
  203. {
  204. var item = (await _db.Ado.SqlQueryAsync<ItemLookupRow>(
  205. """
  206. SELECT
  207. COALESCE(NULLIF(im.ItemNum,''), ic.number) AS ItemNum,
  208. COALESCE(NULLIF(im.Descr,''), ic.name) AS Descr,
  209. COALESCE(NULLIF(im.UM,''), ic.unit) AS UM,
  210. IFNULL(im.Location, '') AS Location,
  211. IFNULL(im.Rev, '') AS Rev,
  212. IFNULL(im.Drawing, '') AS Drawing
  213. FROM ic_item ic
  214. LEFT JOIN ItemMaster im ON ic.number = im.ItemNum AND im.tenant_id = @TenantId
  215. WHERE ic.Id = @IcitemId
  216. LIMIT 1
  217. """,
  218. new SugarParameter("@IcitemId", pr.IcitemId),
  219. new SugarParameter("@TenantId", pr.TenantId))).FirstOrDefault();
  220. await _db.Ado.ExecuteCommandAsync(
  221. """
  222. INSERT INTO PurOrdDetail
  223. (
  224. QtyBO, RctCost, CreditTermsInt, UpdateCurrentCost, CumReceived1, CumReceived2,
  225. CumReceived3, CumReceived4, Disc, FixedPrice, InspectReq, SingleLot, SupplyPer,
  226. PurOrd, PST, PackingSlipQty, PayUMConv, PurCost, RctQty, QtyOrded, QtyReceived,
  227. QtyReturned, Active, QtyReleased, RctUMConversion, Scheduled, ScheduledChanged,
  228. SchedMRPReq, SafetyDays, SafetyHours, StdCost, Taxable, TaxIn, MaxTaxableAmt,
  229. TransportHours, UMConversion, VAT, IsActive, IsConfirm, Potype, IsChanged,
  230. TaxRate, IsRounding, ReceiptQty, BarCodeQty, IsClosed, QtyReturnedRefund, CumQtyBO,
  231. Line, ItemNum, Descr, UM, Rev, Drawing, Location, DueDate, NeedDate, LotSerial, PurOrdRecID, Status, Req,
  232. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  233. )
  234. VALUES
  235. (
  236. 0, 0, 0, 0, 0, 0,
  237. 0, 0, 0, 0, 0, 0, 0,
  238. @PurOrd, 0, 0, 1, 0, 0, @QtyOrded, 0,
  239. 0, 1, 0, 1, 0, 0,
  240. 0, 0, 0, 0, 1, 1, 0,
  241. 0, 1, 0, 1, 1, @PoType, 0,
  242. 0, 0, 0, 0, 0, 0, 0,
  243. @Line, @ItemNum, @Descr, @UM, @Rev, @Drawing, @Location, @DueDate, @NeedDate, '', @PurOrdRecID, 'R', @Req,
  244. @CreateUser, @Now, @UpdateUser, @Now, @TenantId
  245. )
  246. """,
  247. new SugarParameter("@PurOrd", purOrd),
  248. new SugarParameter("@PoType", poType),
  249. new SugarParameter("@QtyOrded", pr.PrAqty ?? pr.PrSqty ?? pr.PrRqty ?? 0),
  250. new SugarParameter("@Line", line),
  251. new SugarParameter("@ItemNum", item?.ItemNum ?? pr.IcitemName ?? string.Empty),
  252. new SugarParameter("@Descr", item?.Descr ?? pr.IcitemName ?? string.Empty),
  253. new SugarParameter("@UM", item?.UM ?? pr.PrUnit ?? string.Empty),
  254. new SugarParameter("@Rev", item?.Rev ?? string.Empty),
  255. new SugarParameter("@Drawing", item?.Drawing ?? string.Empty),
  256. new SugarParameter("@Location", string.IsNullOrWhiteSpace(item?.Location) ? "1001" : item.Location),
  257. new SugarParameter("@DueDate", pr.PrSarriveDate),
  258. new SugarParameter("@NeedDate", pr.PrSarriveDate),
  259. new SugarParameter("@PurOrdRecID", masterId),
  260. new SugarParameter("@Req", pr.PrBillNo),
  261. new SugarParameter("@CreateUser", account),
  262. new SugarParameter("@UpdateUser", account),
  263. new SugarParameter("@Now", DateTime.Now),
  264. new SugarParameter("@TenantId", pr.TenantId <= 0 ? null : pr.TenantId));
  265. var detailRecId = await _db.Ado.GetIntAsync(
  266. "SELECT IFNULL(MAX(RecID),0) FROM PurOrdDetail WHERE PurOrd=@PurOrd AND Line=@Line AND tenant_id=@TenantId",
  267. new SugarParameter("@PurOrd", purOrd),
  268. new SugarParameter("@Line", line),
  269. new SugarParameter("@TenantId", pr.TenantId));
  270. return (detailRecId, item?.ItemNum ?? pr.IcitemName ?? string.Empty);
  271. }
  272. /// <summary>委外加工订单BOM展开:查询ProductStructureMaster并写入PurOrdDetailBatch。</summary>
  273. private async Task InsertPurOrdDetailBatchAsync(
  274. string purOrd, string poType, int line, int detailRecId, string parentItem,
  275. decimal prQty, long tenantId, string account, DateTime now)
  276. {
  277. var components = await _db.Ado.SqlQueryAsync<BomComponentRow>(
  278. """
  279. SELECT
  280. psm.ComponentItem AS SuppItem,
  281. CAST(psm.Qty AS DECIMAL(18,5)) AS QtyPerUnit,
  282. IFNULL(psm.UM, im.UM) AS UM,
  283. IFNULL(im.ItemNum, psm.ComponentItem) AS ItemNum,
  284. IFNULL(im.Location, '') AS Location
  285. FROM ProductStructureMaster psm
  286. LEFT JOIN ItemMaster im ON psm.ComponentItem = im.ItemNum AND im.tenant_id = @TenantId
  287. WHERE psm.ParentItem = @ParentItem AND psm.tenant_id = @TenantId
  288. ORDER BY psm.ComponentItem
  289. """,
  290. new SugarParameter("@ParentItem", parentItem),
  291. new SugarParameter("@TenantId", tenantId));
  292. var batchNo = 1;
  293. foreach (var comp in components)
  294. {
  295. if (string.IsNullOrWhiteSpace(comp.SuppItem)) continue;
  296. var batchQty = Math.Round(prQty * comp.QtyPerUnit, 5);
  297. await _db.Ado.ExecuteCommandAsync(
  298. """
  299. INSERT INTO PurOrdDetailBatch
  300. (
  301. Domain, PurOrd, Potype, Line, Batch, ItemNum, SuppItem, UM, Location,
  302. QtyOrded, QtyBO, QtyReleased, QtyReceived, QtyReturned, LotSerial, PurOrdDetailRecID,
  303. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  304. )
  305. VALUES
  306. (
  307. @Domain, @PurOrd, @Potype, @Line, @Batch, @ItemNum, @SuppItem, @UM, @Location,
  308. @QtyOrded, @QtyBO, @QtyReleased, 0, 0, '', @PurOrdDetailRecID,
  309. @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @TenantId
  310. )
  311. """,
  312. new SugarParameter("@Domain", "8010"),
  313. new SugarParameter("@PurOrd", purOrd),
  314. new SugarParameter("@Potype", poType),
  315. new SugarParameter("@Line", line),
  316. new SugarParameter("@Batch", batchNo),
  317. new SugarParameter("@ItemNum", comp.ItemNum ?? comp.SuppItem),
  318. new SugarParameter("@SuppItem", comp.SuppItem),
  319. new SugarParameter("@UM", comp.UM ?? string.Empty),
  320. new SugarParameter("@Location", string.IsNullOrWhiteSpace(comp.Location) ? "1001" : comp.Location),
  321. new SugarParameter("@QtyOrded", batchQty),
  322. new SugarParameter("@QtyBO", comp.QtyPerUnit),
  323. new SugarParameter("@QtyReleased", batchQty),
  324. new SugarParameter("@PurOrdDetailRecID", detailRecId),
  325. new SugarParameter("@CreateUser", account),
  326. new SugarParameter("@CreateTime", now),
  327. new SugarParameter("@UpdateUser", account),
  328. new SugarParameter("@UpdateTime", now),
  329. new SugarParameter("@TenantId", tenantId <= 0 ? null : tenantId));
  330. batchNo++;
  331. }
  332. }
  333. private static string ResolveBuyer(string? supplierType)
  334. {
  335. return supplierType switch
  336. {
  337. "研发" => "130",
  338. "ECR" => "170",
  339. _ => "110"
  340. };
  341. }
  342. private sealed class SupplierContextRow
  343. {
  344. public string? SupplierNumber { get; set; }
  345. public string? SupplierType { get; set; }
  346. }
  347. private sealed class ItemLookupRow
  348. {
  349. public string? ItemNum { get; set; }
  350. public string? Descr { get; set; }
  351. public string? UM { get; set; }
  352. public string? Location { get; set; }
  353. public string? Rev { get; set; }
  354. public string? Drawing { get; set; }
  355. }
  356. private sealed class BomComponentRow
  357. {
  358. public string? SuppItem { get; set; }
  359. public decimal QtyPerUnit { get; set; }
  360. public string? UM { get; set; }
  361. public string? ItemNum { get; set; }
  362. public string? Location { get; set; }
  363. }
  364. }
  365. public sealed class PurchaseOrderTransferResult
  366. {
  367. public int CreatedOrderCount { get; set; }
  368. public int TransferredPrCount { get; set; }
  369. public int PoOccupyRehangedCount { get; set; }
  370. public List<string> CreatedOrders { get; set; } = new();
  371. public List<long> TransferredPrIds { get; set; } = new();
  372. }