PurchaseOrderTransferService.cs 17 KB

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