PurchaseOrderTransferService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. namespace Admin.NET.Plugin.AiDOP.Supply;
  2. /// <summary>
  3. /// 采购申请转 DO/PO 服务。对齐旧 PrAutoApprove:处理待处理要货令 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.IsRequireGoods == 1
  20. && (x.State ?? 0) == 1
  21. && !string.IsNullOrWhiteSpace(x.PrBillNo))
  22. .ToList();
  23. if (candidates.Count == 0) return new PurchaseOrderTransferResult();
  24. var createdOrders = new List<string>();
  25. var transferredPrIds = new List<long>();
  26. var poOccupyRehangedCount = 0;
  27. var groups = candidates
  28. .GroupBy(x => new
  29. {
  30. x.TenantId,
  31. x.CompanyId,
  32. x.FactoryId,
  33. x.PrPurchaseId,
  34. x.PrPurchaseNumber,
  35. x.PrPurchaseName,
  36. SupplierType = x.SupplierType ?? string.Empty
  37. })
  38. .ToList();
  39. foreach (var tenantGroup in groups.GroupBy(x => x.Key.TenantId.ToString()))
  40. {
  41. var groupList = tenantGroup.ToList();
  42. var purOrdNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync("DO", tenantGroup.Key, groupList.Count, account);
  43. if (purOrdNumbers.Count < groupList.Count || purOrdNumbers.Any(string.IsNullOrWhiteSpace))
  44. throw Oops.Oh($"当前DO/PO单号生成失败,请检查DO编号规则维护。Domain={tenantGroup.Key}");
  45. for (var i = 0; i < groupList.Count; i++)
  46. {
  47. var group = groupList[i];
  48. var purOrd = purOrdNumbers[i].Trim();
  49. var now = DateTime.Now;
  50. var rows = group.OrderBy(x => x.PrSarriveDate).ThenBy(x => x.PrBillNo).ToList();
  51. var supplier = await LoadSupplierContextAsync(group.Key.PrPurchaseId, rows[0].IcitemId, group.Key.TenantId);
  52. var supplierCode = supplier?.SupplierNumber ?? group.Key.PrPurchaseNumber;
  53. var supplierType = string.IsNullOrWhiteSpace(group.Key.SupplierType)
  54. ? supplier?.SupplierType ?? string.Empty
  55. : group.Key.SupplierType;
  56. var isOutsource = string.Equals(supplierType, "委外", StringComparison.OrdinalIgnoreCase);
  57. var poType = isOutsource ? "PW" : "po";
  58. var usage = isOutsource ? "委外加工" : supplierType;
  59. var buyer = ResolveBuyer(supplierType);
  60. await InsertPurchaseOrderMasterAsync(purOrd, poType, usage, buyer, supplierCode, now, account, group.Key.TenantId);
  61. var masterId = await _db.Ado.GetIntAsync(
  62. "SELECT IFNULL(MAX(RecID),0) FROM PurOrdMaster WHERE PurOrd=@PurOrd",
  63. new SugarParameter("@PurOrd", purOrd));
  64. if (masterId <= 0) throw Oops.Oh("DO/PO主表生成失败。");
  65. for (var lineIndex = 0; lineIndex < rows.Count; lineIndex++)
  66. {
  67. var pr = rows[lineIndex];
  68. var line = lineIndex + 1;
  69. await InsertPurchaseOrderDetailAsync(purOrd, poType, masterId, line, pr, account);
  70. var detailRecId = await _db.Ado.GetIntAsync(
  71. "SELECT IFNULL(MAX(RecID),0) FROM PurOrdDetail WHERE PurOrd=@PurOrd AND Line=@Line",
  72. new SugarParameter("@PurOrd", purOrd),
  73. new SugarParameter("@Line", line));
  74. if (detailRecId > 0)
  75. poOccupyRehangedCount += await RehangPoOccupyFromPrToDetailAsync(pr.Id, detailRecId, pr.TenantId, account);
  76. pr.State = 4;
  77. pr.UpdateByName = account;
  78. pr.UpdateTime = now;
  79. transferredPrIds.Add(pr.Id);
  80. }
  81. createdOrders.Add(purOrd);
  82. }
  83. }
  84. return new PurchaseOrderTransferResult
  85. {
  86. CreatedOrderCount = createdOrders.Count,
  87. TransferredPrCount = transferredPrIds.Count,
  88. PoOccupyRehangedCount = poOccupyRehangedCount,
  89. CreatedOrders = createdOrders,
  90. TransferredPrIds = transferredPrIds
  91. };
  92. }
  93. private async Task<SupplierContextRow?> LoadSupplierContextAsync(long? supplierId, long? icitemId, long tenantId)
  94. {
  95. if (supplierId is null or <= 0) return null;
  96. var rows = await _db.Ado.SqlQueryAsync<SupplierContextRow>(
  97. """
  98. SELECT
  99. IFNULL(sp.supplier_number, '') AS SupplierNumber,
  100. IFNULL(sp.supplier_type, '') AS SupplierType
  101. FROM srm_purchase sp
  102. WHERE sp.tenant_id = @TenantId
  103. AND sp.supplier_id = @SupplierId
  104. AND (@IcitemId IS NULL OR sp.icitem_id = @IcitemId)
  105. ORDER BY sp.Id
  106. LIMIT 1
  107. """,
  108. new SugarParameter("@TenantId", tenantId),
  109. new SugarParameter("@SupplierId", supplierId),
  110. new SugarParameter("@IcitemId", icitemId));
  111. return rows.FirstOrDefault();
  112. }
  113. private async Task<int> RehangPoOccupyFromPrToDetailAsync(long prId, int detailRecId, long tenantId, string account)
  114. {
  115. return await _db.Ado.ExecuteCommandAsync(
  116. """
  117. UPDATE srm_po_occupy
  118. SET polist_id = @DetailRecId,
  119. update_by_name = @User,
  120. update_time = @Now
  121. WHERE tenant_id = @TenantId
  122. AND polist_id = @PrId
  123. """,
  124. new SugarParameter("@DetailRecId", detailRecId),
  125. new SugarParameter("@User", account),
  126. new SugarParameter("@Now", DateTime.Now),
  127. new SugarParameter("@TenantId", tenantId),
  128. new SugarParameter("@PrId", prId));
  129. }
  130. private async Task InsertPurchaseOrderMasterAsync(
  131. string purOrd,
  132. string poType,
  133. string? usage,
  134. string buyer,
  135. string? supplierCode,
  136. DateTime now,
  137. string account,
  138. long tenantId)
  139. {
  140. await _db.Ado.ExecuteCommandAsync(
  141. """
  142. INSERT INTO PurOrdMaster
  143. (
  144. Confirming, CreditTermsInt, Disc, ExchRate, EstVal, ExchRate1, ExchRate2,
  145. FixedPrice, FixedRate, Frt, PartialOK, AmtPrepaid, PrintPO, PST, Recurr,
  146. `Release`, Revision, Scheduled, ServiceCharge, SpecialCharge, Taxable,
  147. Tax1, Tax2, Tax3, TransportDays, IsActive, IsConfirm, Potype, IsChanged,
  148. TaxIn, Amt, IsPriceChanged,
  149. Buyer, Domain, PurOrd, OrdDate, ReqBy, Status, Supp, CreateUser, CreateTime,
  150. UpdateUser, UpdateTime, `Usage`, FSTID, Typed, tenant_id
  151. )
  152. VALUES
  153. (
  154. 0, 0, 0, 1, 0, 1, 1,
  155. 0, 0, 0, 1, 0, 0, 0, 0,
  156. 0, 0, 0, 0, 0, 1,
  157. 0, 0, 0, 0, 1, 1, @PoType, 0,
  158. 1, 0, 0,
  159. @Buyer, @Domain, @PurOrd, @Now, 'DO', '', @Supp, @CreateUser, @Now,
  160. @UpdateUser, @Now, @Usage, @FSTID, @Typed, @TenantId
  161. )
  162. """,
  163. new SugarParameter("@PoType", poType),
  164. new SugarParameter("@Buyer", buyer),
  165. new SugarParameter("@Domain", tenantId.ToString()),
  166. new SugarParameter("@PurOrd", purOrd),
  167. new SugarParameter("@Now", now),
  168. new SugarParameter("@Supp", supplierCode),
  169. new SugarParameter("@CreateUser", account),
  170. new SugarParameter("@UpdateUser", account),
  171. new SugarParameter("@Usage", usage),
  172. new SugarParameter("@FSTID", string.Equals(usage, "VMI", StringComparison.OrdinalIgnoreCase) ? "3" : string.Empty),
  173. new SugarParameter("@Typed", poType == "PW" ? "s" : string.Empty),
  174. new SugarParameter("@TenantId", tenantId <= 0 ? null : tenantId));
  175. }
  176. private async Task InsertPurchaseOrderDetailAsync(string purOrd, string poType, int masterId, int line, PurchaseRequestMain pr, string account)
  177. {
  178. var item = (await _db.Ado.SqlQueryAsync<ItemLookupRow>(
  179. """
  180. SELECT
  181. COALESCE(NULLIF(im.ItemNum,''), ic.number) AS ItemNum,
  182. COALESCE(NULLIF(im.Descr,''), ic.name) AS Descr,
  183. COALESCE(NULLIF(im.UM,''), ic.unit) AS UM,
  184. IFNULL(im.Location, '') AS Location,
  185. IFNULL(im.Rev, '') AS Rev,
  186. IFNULL(im.Drawing, '') AS Drawing
  187. FROM ic_item ic
  188. LEFT JOIN ItemMaster im ON ic.number = im.ItemNum
  189. WHERE ic.Id = @IcitemId
  190. LIMIT 1
  191. """,
  192. new SugarParameter("@IcitemId", pr.IcitemId))).FirstOrDefault();
  193. await _db.Ado.ExecuteCommandAsync(
  194. """
  195. INSERT INTO PurOrdDetail
  196. (
  197. QtyBO, RctCost, CreditTermsInt, UpdateCurrentCost, CumReceived1, CumReceived2,
  198. CumReceived3, CumReceived4, Disc, FixedPrice, InspectReq, SingleLot, SupplyPer,
  199. PurOrd, PST, PackingSlipQty, PayUMConv, PurCost, RctQty, QtyOrded, QtyReceived,
  200. QtyReturned, Active, QtyReleased, RctUMConversion, Scheduled, ScheduledChanged,
  201. SchedMRPReq, SafetyDays, SafetyHours, StdCost, Taxable, TaxIn, MaxTaxableAmt,
  202. TransportHours, UMConversion, VAT, IsActive, IsConfirm, Potype, IsChanged,
  203. TaxRate, IsRounding, ReceiptQty, BarCodeQty, IsClosed, QtyReturnedRefund, CumQtyBO,
  204. Line, ItemNum, Descr, UM, Rev, Drawing, Location, DueDate, NeedDate, LotSerial, PurOrdRecID, Status, Req,
  205. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  206. )
  207. VALUES
  208. (
  209. 0, 0, 0, 0, 0, 0,
  210. 0, 0, 0, 0, 0, 0, 0,
  211. @PurOrd, 0, 0, 1, 0, 0, @QtyOrded, 0,
  212. 0, 1, 0, 1, 0, 0,
  213. 0, 0, 0, 0, 1, 1, 0,
  214. 0, 1, 0, 1, 1, @PoType, 0,
  215. 0, 0, 0, 0, 0, 0, 0,
  216. @Line, @ItemNum, @Descr, @UM, @Rev, @Drawing, @Location, @DueDate, @NeedDate, '', @PurOrdRecID, 'R', @Req,
  217. @CreateUser, @Now, @UpdateUser, @Now, @TenantId
  218. )
  219. """,
  220. new SugarParameter("@PurOrd", purOrd),
  221. new SugarParameter("@PoType", poType),
  222. new SugarParameter("@QtyOrded", pr.PrAqty ?? pr.PrSqty ?? pr.PrRqty ?? 0),
  223. new SugarParameter("@Line", line),
  224. new SugarParameter("@ItemNum", item?.ItemNum ?? pr.IcitemName ?? string.Empty),
  225. new SugarParameter("@Descr", item?.Descr ?? pr.IcitemName ?? string.Empty),
  226. new SugarParameter("@UM", item?.UM ?? pr.PrUnit ?? string.Empty),
  227. new SugarParameter("@Rev", item?.Rev ?? string.Empty),
  228. new SugarParameter("@Drawing", item?.Drawing ?? string.Empty),
  229. new SugarParameter("@Location", string.IsNullOrWhiteSpace(item?.Location) ? "1001" : item.Location),
  230. new SugarParameter("@DueDate", pr.PrSarriveDate),
  231. new SugarParameter("@NeedDate", pr.PrSarriveDate),
  232. new SugarParameter("@PurOrdRecID", masterId),
  233. new SugarParameter("@Req", pr.PrBillNo),
  234. new SugarParameter("@CreateUser", account),
  235. new SugarParameter("@UpdateUser", account),
  236. new SugarParameter("@Now", DateTime.Now),
  237. new SugarParameter("@TenantId", pr.TenantId <= 0 ? null : pr.TenantId));
  238. }
  239. private static string ResolveBuyer(string? supplierType)
  240. {
  241. return supplierType switch
  242. {
  243. "研发" => "130",
  244. "ECR" => "170",
  245. _ => "110"
  246. };
  247. }
  248. private sealed class SupplierContextRow
  249. {
  250. public string? SupplierNumber { get; set; }
  251. public string? SupplierType { get; set; }
  252. }
  253. private sealed class ItemLookupRow
  254. {
  255. public string? ItemNum { get; set; }
  256. public string? Descr { get; set; }
  257. public string? UM { get; set; }
  258. public string? Location { get; set; }
  259. public string? Rev { get; set; }
  260. public string? Drawing { get; set; }
  261. }
  262. }
  263. public sealed class PurchaseOrderTransferResult
  264. {
  265. public int CreatedOrderCount { get; set; }
  266. public int TransferredPrCount { get; set; }
  267. public int PoOccupyRehangedCount { get; set; }
  268. public List<string> CreatedOrders { get; set; } = new();
  269. public List<long> TransferredPrIds { get; set; } = new();
  270. }