PurchaseReturnService.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. using Admin.NET.Plugin.AiDOP.ProcurementExecution.Dto;
  2. using Admin.NET.Plugin.AiDOP.ProcurementExecution.Entity;
  3. namespace Admin.NET.Plugin.AiDOP.ProcurementExecution;
  4. /// <summary>
  5. /// S4 采购退货单(PurOrdRctMaster / PurOrdRctDetail,RctType=pt)
  6. /// 路由:/api/ProcurementExecution/purchase-return/...
  7. /// </summary>
  8. [ApiDescriptionSettings(Order = 321, Description = "S4采购退货单")]
  9. [Route("api/ProcurementExecution")]
  10. [AllowAnonymous]
  11. [NonUnify]
  12. public class PurchaseReturnService : IDynamicApiController, ITransient
  13. {
  14. private readonly ISqlSugarClient _db;
  15. private readonly SqlSugarRepository<PurOrdRctMaster> _masterRep;
  16. private readonly SqlSugarRepository<PurOrdRctDetail> _detailRep;
  17. private readonly UserManager _userManager;
  18. public PurchaseReturnService(
  19. ISqlSugarClient db,
  20. SqlSugarRepository<PurOrdRctMaster> masterRep,
  21. SqlSugarRepository<PurOrdRctDetail> detailRep,
  22. UserManager userManager)
  23. {
  24. _db = db;
  25. _masterRep = masterRep;
  26. _detailRep = detailRep;
  27. _userManager = userManager;
  28. }
  29. private static string NormalizeOrderBy(string field, string order)
  30. {
  31. var col = (field ?? "").Trim();
  32. var ord = string.Equals(order?.Trim(), "desc", StringComparison.OrdinalIgnoreCase) ? "DESC" : "ASC";
  33. var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
  34. {
  35. // 注意:列表 SQL 外层包了 SELECT * FROM (...) t,因此 ORDER BY 只能引用外层可见列名
  36. ["receiver"] = "Receiver",
  37. ["rctdate"] = "RctDate",
  38. ["ordnbr"] = "OrdNbr",
  39. ["itemnum"] = "ItemNum",
  40. ["rctqty"] = "RctQty",
  41. ["suppname"] = "SuppName",
  42. };
  43. if (!map.TryGetValue(col, out var sqlCol))
  44. sqlCol = "Id";
  45. return $"ORDER BY {sqlCol} {ord}, DetailRecId ASC";
  46. }
  47. [DisplayName("采购退货单分页列表")]
  48. [HttpGet("purchase-return/list")]
  49. public async Task<object> GetList([FromQuery] PurchaseReturnListInput input)
  50. {
  51. var page = input.Page <= 0 ? 1 : input.Page;
  52. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  53. var offset = (page - 1) * pageSize;
  54. var cond = new List<string> { "p.RctType='pt'", "p.IsActive=1" };
  55. var pars = new List<SugarParameter>();
  56. if (!string.IsNullOrWhiteSpace(input.Receiver))
  57. {
  58. cond.Add("p.Receiver LIKE @Receiver");
  59. pars.Add(new SugarParameter("@Receiver", $"%{input.Receiver.Trim()}%"));
  60. }
  61. if (!string.IsNullOrWhiteSpace(input.Supp))
  62. {
  63. cond.Add("p.Supp=@Supp");
  64. pars.Add(new SugarParameter("@Supp", input.Supp.Trim()));
  65. }
  66. if (!string.IsNullOrWhiteSpace(input.OrdNbr))
  67. {
  68. cond.Add("p.OrdNbr LIKE @OrdNbr");
  69. pars.Add(new SugarParameter("@OrdNbr", $"%{input.OrdNbr.Trim()}%"));
  70. }
  71. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  72. {
  73. cond.Add("d1.ItemNum LIKE @ItemNum");
  74. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  75. }
  76. var where = string.Join(" AND ", cond);
  77. var baseSql = $"""
  78. SELECT
  79. p.RecID AS Id,
  80. IFNULL(d1.RecID, 0) AS DetailRecId,
  81. p.RctType AS RctType,
  82. p.Receiver AS Receiver,
  83. p.RctDate AS RctDate,
  84. p.OrdNbr AS OrdNbr,
  85. p.TaxClass AS TaxClass,
  86. IFNULL(p.TaxIn, 0) AS TaxIn,
  87. p.Curr AS Curr,
  88. p.Terms AS Terms,
  89. IFNULL(p.IsPlan, 0) AS IsPlan,
  90. TRIM(CONCAT(TRIM(IFNULL(p.Department,'')),' ',TRIM(IFNULL(d.Descr,'')))) AS DepartmentDescr,
  91. TRIM(CONCAT(TRIM(IFNULL(p.Supp,'')),' ',TRIM(IFNULL(s.SortName,'')))) AS SuppName,
  92. d1.ItemNum AS ItemNum,
  93. IFNULL(i.Descr,'') AS Descr,
  94. IFNULL(i.Descr1,'') AS Descr1,
  95. IFNULL(d1.RctQty, 0) AS RctQty
  96. FROM PurOrdRctMaster p
  97. LEFT JOIN PurOrdRctDetail d1 ON p.RecID = d1.PurOrdRctRecID AND d1.RctType='pt' AND d1.IsActive=1
  98. LEFT JOIN SuppMaster s ON p.Domain = s.Domain AND p.Supp = s.Supp
  99. LEFT JOIN DepartmentMaster d ON p.Domain = d.Domain AND p.Department = d.Department
  100. LEFT JOIN ItemMaster i ON d1.Domain = i.Domain AND d1.ItemNum = i.ItemNum
  101. WHERE {where}
  102. """;
  103. var orderBy = NormalizeOrderBy(input.SortField, input.SortOrder);
  104. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ({baseSql}) t", pars);
  105. var list = await _db.Ado.SqlQueryAsync<PurchaseReturnListRow>(
  106. $"SELECT * FROM ({baseSql}) t {orderBy} LIMIT {pageSize} OFFSET {offset}", pars);
  107. return new { total, page, pageSize, list };
  108. }
  109. [DisplayName("采购退货供应商下拉")]
  110. [HttpGet("purchase-return/options/suppliers")]
  111. public async Task<object> GetSuppliers()
  112. {
  113. var rows = await _db.Ado.SqlQueryAsync<PurchaseReturnKvItem>(
  114. """
  115. SELECT Supp AS `Value`,
  116. TRIM(CONCAT(TRIM(IFNULL(Supp,'')),' ',TRIM(IFNULL(SortName,'')))) AS `Label`
  117. FROM SuppMaster
  118. ORDER BY Supp
  119. """);
  120. return rows;
  121. }
  122. [DisplayName("收货单号下拉(rc)")]
  123. [HttpGet("purchase-return/options/rc-receivers")]
  124. public async Task<object> GetRcReceivers([FromQuery] string supp)
  125. {
  126. if (string.IsNullOrWhiteSpace(supp))
  127. return Array.Empty<object>();
  128. var rows = await _db.Ado.SqlQueryAsync<PurchaseReturnKvItem>(
  129. """
  130. SELECT p.Receiver AS `Value`,
  131. TRIM(CONCAT(TRIM(IFNULL(p.Receiver,'')),' ',
  132. TRIM(IFNULL(d.LotSerial,'')),' ',
  133. TRIM(IFNULL(d.ItemNum,'')),' ',
  134. TRIM(IFNULL(i.Descr,'')))) AS `Label`
  135. FROM PurOrdRctMaster p
  136. LEFT JOIN PurOrdRctDetail d
  137. ON p.Domain = d.Domain AND p.RctType = d.RctType AND p.Receiver = d.Receiver
  138. LEFT JOIN ItemMaster i ON d.Domain = i.Domain AND d.ItemNum = i.ItemNum
  139. WHERE p.RctType = 'rc' AND p.Supp = @Supp AND d.RecID > 0 AND IFNULL(p.TermsofTrade,'') <> ''
  140. GROUP BY p.Receiver, d.LotSerial, d.ItemNum, i.Descr, p.Domain
  141. ORDER BY p.Receiver DESC
  142. """,
  143. new SugarParameter("@Supp", supp.Trim()));
  144. return rows;
  145. }
  146. [DisplayName("按收货单加载明细模板")]
  147. [HttpGet("purchase-return/rc-detail-lines")]
  148. public async Task<object> GetRcDetailLines([FromQuery] string receiver)
  149. {
  150. if (string.IsNullOrWhiteSpace(receiver))
  151. throw Oops.Oh("请先选择收货单号");
  152. var rows = await _db.Ado.SqlQueryAsync<PurchaseReturnRcLineRow>(
  153. """
  154. SELECT
  155. p.Domain AS Domain,
  156. p.Department AS Department,
  157. p.Terms AS Terms,
  158. p.TermsofTrade AS TermsofTrade,
  159. d.ItemNum AS ItemNum,
  160. d.UM AS UM,
  161. d.OrdNbr AS OrdNbr,
  162. CAST(d.OrdLine AS SIGNED) AS OrdLine,
  163. d.QtyOrded AS QtyOrded,
  164. d.Supp AS Supp,
  165. d.LotSerial AS LotSerial,
  166. CASE WHEN UPPER(IFNULL(d.Potype,'')) = 'PW' THEN d.Supp ELSE d.Location END AS Location,
  167. d.QtyReceived AS QtyReceived,
  168. d.Potype AS Potype,
  169. d.Typed AS Typed,
  170. p.TaxClass AS TaxClass,
  171. IFNULL(p.TaxIn, 0) AS TaxIn,
  172. p.Curr AS Curr,
  173. DATE_FORMAT(CURDATE(), '%Y-%m-%d') AS RctDate,
  174. d.UMConversion AS UMConversion,
  175. d.POCost AS POCost,
  176. d.QtyReceived AS QtyReturn,
  177. d.Receiver AS RctNbr,
  178. CAST(d.Line AS SIGNED) AS RctLine,
  179. d.CustPO AS CustPO,
  180. d.BlanketLine AS BlanketLine,
  181. IFNULL(d.RctQty, 0) AS RctQty,
  182. CAST(d.Line AS SIGNED) AS Line
  183. FROM PurOrdRctMaster p
  184. LEFT JOIN PurOrdRctDetail d
  185. ON p.Domain = d.Domain AND p.RctType = d.RctType AND p.Receiver = d.Receiver
  186. WHERE p.RctType = 'rc' AND p.Receiver = @Receiver AND d.RecID > 0
  187. ORDER BY d.Line
  188. """,
  189. new SugarParameter("@Receiver", receiver.Trim()));
  190. PurchaseReturnRcLineRow head = null;
  191. if (rows.Count > 0)
  192. head = rows[0];
  193. return new { head, lines = rows };
  194. }
  195. [DisplayName("部门下拉")]
  196. [HttpGet("purchase-return/options/departments")]
  197. public async Task<object> GetDepartments()
  198. {
  199. return await _db.Ado.SqlQueryAsync<PurchaseReturnKvItem>(
  200. """
  201. SELECT Department AS `Value`,
  202. TRIM(CONCAT(TRIM(IFNULL(Department,'')),' ',TRIM(IFNULL(Descr,'')))) AS `Label`
  203. FROM DepartmentMaster
  204. ORDER BY Department
  205. """);
  206. }
  207. [DisplayName("库位下拉")]
  208. [HttpGet("purchase-return/options/locations")]
  209. public async Task<object> GetLocations()
  210. {
  211. return await _db.Ado.SqlQueryAsync<PurchaseReturnKvItem>(
  212. """
  213. SELECT Location AS `Value`,
  214. TRIM(CONCAT(TRIM(IFNULL(Location,'')),' ',TRIM(IFNULL(Descr,'')))) AS `Label`
  215. FROM LocationMaster
  216. WHERE IFNULL(Typed,'') <> 'Supp'
  217. ORDER BY Location
  218. """);
  219. }
  220. [DisplayName("退货原因下拉")]
  221. [HttpGet("purchase-return/options/qc-reasons")]
  222. public async Task<object> GetQcReasons()
  223. {
  224. return await _db.Ado.SqlQueryAsync<PurchaseReturnKvItem>(
  225. """
  226. SELECT Val AS `Value`,
  227. CONCAT(IFNULL(Val,''),'_',IFNULL(Comments,'')) AS `Label`
  228. FROM GeneralizedCodeMaster
  229. WHERE FldName = 'SAPTransReason'
  230. AND IFNULL(Ufld1,'') <> ''
  231. AND LOCATE('122', IFNULL(Ufld1,'')) > 0
  232. ORDER BY Val
  233. """);
  234. }
  235. [DisplayName("物料选择分页")]
  236. [HttpGet("purchase-return/item-options")]
  237. public async Task<object> GetItems([FromQuery] KeywordPageInput input)
  238. {
  239. var page = input.Page <= 0 ? 1 : input.Page;
  240. var pageSize = input.PageSize <= 0 ? 20 : input.PageSize;
  241. var offset = (page - 1) * pageSize;
  242. var pars = new List<SugarParameter>();
  243. var where = "1=1";
  244. if (!string.IsNullOrWhiteSpace(input.Keyword))
  245. {
  246. where += " AND (ItemNum LIKE @Kw OR Descr LIKE @Kw OR Descr1 LIKE @Kw)";
  247. pars.Add(new SugarParameter("@Kw", $"%{input.Keyword.Trim()}%"));
  248. }
  249. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ItemMaster WHERE {where}", pars);
  250. var list = await _db.Ado.SqlQueryAsync<PurchaseReturnKvItem>(
  251. $"""
  252. SELECT ItemNum AS `Value`,
  253. TRIM(CONCAT(IFNULL(Descr,''),' ',IFNULL(Descr1,''))) AS `Label`
  254. FROM ItemMaster
  255. WHERE {where}
  256. ORDER BY ItemNum
  257. LIMIT {pageSize} OFFSET {offset}
  258. """,
  259. pars);
  260. return new { total, page, pageSize, list };
  261. }
  262. [DisplayName("采购退货单详情")]
  263. [HttpGet("purchase-return/{id:int}")]
  264. public async Task<object> GetDetail(int id)
  265. {
  266. var master = await _masterRep.GetFirstAsync(m => m.RecID == id && m.RctType == "pt")
  267. ?? throw Oops.Oh("单据不存在");
  268. var m = await _db.Ado.SqlQueryAsync<PurchaseReturnMasterDto>(
  269. """
  270. SELECT
  271. m.RecID AS RecID,
  272. m.Domain AS Domain,
  273. m.Receiver AS Receiver,
  274. DATE_FORMAT(m.RctDate,'%Y-%m-%d') AS RctDate,
  275. m.Supp AS Supp,
  276. m.OrdNbr AS OrdNbr,
  277. m.Department AS Department,
  278. IFNULL(m.Terms,'') AS Terms,
  279. IFNULL(m.Curr,'') AS Curr,
  280. IFNULL(m.TaxClass,'') AS TaxClass,
  281. IFNULL(m.TaxIn, 0) AS TaxIn,
  282. IFNULL(m.TermsofTrade,'') AS TermsofTrade,
  283. IFNULL(m.Remark,'') AS Remark,
  284. IFNULL(m.IsPlan, 0) AS IsPlan
  285. FROM PurOrdRctMaster m
  286. WHERE m.RecID = @Id
  287. LIMIT 1
  288. """,
  289. new SugarParameter("@Id", id));
  290. var head = m.FirstOrDefault() ?? throw Oops.Oh("单据不存在");
  291. var detailRows = await _db.Queryable<PurOrdRctDetail>()
  292. .Where(d => d.PurOrdRctRecID == id && d.RctType == "pt" && d.IsActive)
  293. .OrderBy(d => d.Line)
  294. .ToListAsync();
  295. var details = detailRows.Select(ToDetailDto).ToList();
  296. return new { master = head, details };
  297. }
  298. private static PurchaseReturnDetailDto ToDetailDto(PurOrdRctDetail d)
  299. {
  300. return new PurchaseReturnDetailDto
  301. {
  302. RecID = d.RecID,
  303. Line = d.Line,
  304. ItemNum = d.ItemNum,
  305. OrdNbr = d.OrdNbr,
  306. OrdLine = d.OrdLine,
  307. QtyOrded = d.QtyOrded,
  308. QtyReceived = d.QtyReceived,
  309. RctQty = d.RctQty,
  310. LotSerial = d.LotSerial,
  311. UM = d.UM,
  312. Location = d.Location,
  313. QCDescr = d.QCDescr,
  314. Remark = d.Remark,
  315. RctDate = d.RctDate?.ToString("yyyy-MM-dd"),
  316. Typed = d.Typed,
  317. TaxIn = d.TaxIn,
  318. TaxClass = d.TaxClass,
  319. Curr = d.Curr,
  320. Potype = d.Potype,
  321. IsActive = d.IsActive,
  322. IsConfirm = d.IsConfirm,
  323. UMConversion = d.UMConversion,
  324. POCost = d.POCost,
  325. RctNbr = d.RctNbr,
  326. RctLine = d.RctLine,
  327. CustPO = d.CustPO,
  328. BlanketLine = d.BlanketLine,
  329. QtyReturn = d.QtyReturn,
  330. Supp = d.Supp,
  331. };
  332. }
  333. [DisplayName("保存采购退货单")]
  334. [HttpPost("purchase-return/save")]
  335. public async Task<object> Save([FromBody] PurchaseReturnSaveInput input)
  336. {
  337. if (input == null) throw Oops.Oh("参数无效");
  338. if (string.IsNullOrWhiteSpace(input.Supp)) throw Oops.Oh("请选择供应商");
  339. if (string.IsNullOrWhiteSpace(input.OrdNbr)) throw Oops.Oh("请选择收货单号");
  340. if (string.IsNullOrWhiteSpace(input.Department)) throw Oops.Oh("请选择部门");
  341. if (input.Details == null || input.Details.Count == 0) throw Oops.Oh("请维护退货明细");
  342. var now = DateTime.Now;
  343. var account = _userManager.Account ?? "system";
  344. var domain = string.IsNullOrWhiteSpace(input.Domain)
  345. ? await ResolveRcDomainAsync(input.OrdNbr.Trim())
  346. : input.Domain.Trim();
  347. if (string.IsNullOrWhiteSpace(domain))
  348. domain = "100";
  349. if (input.Id is null or 0)
  350. {
  351. var receiver = await AllocReceiverAsync();
  352. var rctDate = ParseDate(input.RctDate) ?? now.Date;
  353. var master = new PurOrdRctMaster
  354. {
  355. Domain = domain,
  356. RctType = "pt",
  357. Receiver = receiver,
  358. RctDate = rctDate,
  359. OrdNbr = input.OrdNbr.Trim(),
  360. Supp = input.Supp.Trim(),
  361. Department = input.Department.Trim(),
  362. Remark = input.Remark?.Trim(),
  363. Terms = input.Terms?.Trim(),
  364. Curr = input.Curr?.Trim(),
  365. TaxClass = input.TaxClass?.Trim(),
  366. TaxIn = input.TaxIn,
  367. TermsofTrade = input.TermsofTrade?.Trim(),
  368. Typed = "C",
  369. IsActive = true,
  370. IsConfirm = false,
  371. IsPlan = true,
  372. CreateUser = account,
  373. CreateTime = now,
  374. UpdateUser = account,
  375. UpdateTime = now,
  376. };
  377. var newId = await _db.Insertable(master).ExecuteReturnIdentityAsync();
  378. master.RecID = Convert.ToInt32(newId);
  379. await SaveDetailsAsync(master.RecID, domain, receiver, "pt", input.Details, account, now);
  380. return new { id = master.RecID, receiver, message = "新增成功" };
  381. }
  382. var existing = await _masterRep.GetFirstAsync(m => m.RecID == input.Id!.Value && m.RctType == "pt")
  383. ?? throw Oops.Oh("单据不存在");
  384. if (!existing.IsPlan)
  385. throw Oops.Oh("该单据不允许修改");
  386. existing.RctDate = ParseDate(input.RctDate) ?? existing.RctDate;
  387. existing.OrdNbr = input.OrdNbr.Trim();
  388. existing.Supp = input.Supp.Trim();
  389. existing.Department = input.Department.Trim();
  390. existing.Remark = input.Remark?.Trim();
  391. existing.Terms = input.Terms?.Trim();
  392. existing.Curr = input.Curr?.Trim();
  393. existing.TaxClass = input.TaxClass?.Trim();
  394. existing.TaxIn = input.TaxIn;
  395. existing.TermsofTrade = input.TermsofTrade?.Trim();
  396. existing.Domain = domain;
  397. existing.UpdateUser = account;
  398. existing.UpdateTime = now;
  399. await _masterRep.UpdateAsync(existing);
  400. await SaveDetailsAsync(existing.RecID, existing.Domain, existing.Receiver, "pt", input.Details, account, now);
  401. return new { id = existing.RecID, receiver = existing.Receiver, message = "保存成功" };
  402. }
  403. private async Task SaveDetailsAsync(
  404. int masterRecId,
  405. string domain,
  406. string receiver,
  407. string rctType,
  408. List<PurchaseReturnDetailInput> entries,
  409. string account,
  410. DateTime now)
  411. {
  412. var dbRows = await _detailRep.GetListAsync(d =>
  413. d.PurOrdRctRecID == masterRecId && d.RctType == rctType);
  414. var dbById = dbRows.ToDictionary(d => d.RecID);
  415. var inputIds = new HashSet<int>(entries.Where(e => e.RecID is > 0).Select(e => e.RecID!.Value));
  416. for (var i = 0; i < entries.Count; i++)
  417. {
  418. var e = entries[i];
  419. var lineNo = e.Line > 0 ? e.Line : (short)(i + 1);
  420. if (string.IsNullOrWhiteSpace(e.ItemNum))
  421. throw Oops.Oh($"第{i + 1}行:请填写物料");
  422. var loc = string.IsNullOrWhiteSpace(e.Location) ? "00" : e.Location.Trim();
  423. if (e.RecID is > 0 && dbById.TryGetValue(e.RecID.Value, out var row))
  424. {
  425. ApplyDetailInput(row, e, domain, receiver, masterRecId, account, now, lineNo, loc);
  426. row.UpdateUser = account;
  427. row.UpdateTime = now;
  428. await _detailRep.UpdateAsync(row);
  429. }
  430. else
  431. {
  432. var detailNew = new PurOrdRctDetail();
  433. ApplyDetailInput(detailNew, e, domain, receiver, masterRecId, account, now, lineNo, loc);
  434. detailNew.CreateUser = account;
  435. detailNew.CreateTime = now;
  436. detailNew.UpdateUser = account;
  437. detailNew.UpdateTime = now;
  438. await _detailRep.InsertAsync(detailNew);
  439. }
  440. }
  441. foreach (var del in dbRows.Where(d => !inputIds.Contains(d.RecID)))
  442. await _detailRep.DeleteAsync(d => d.RecID == del.RecID);
  443. }
  444. private static void ApplyDetailInput(
  445. PurOrdRctDetail row,
  446. PurchaseReturnDetailInput e,
  447. string domain,
  448. string receiver,
  449. int masterRecId,
  450. string account,
  451. DateTime now,
  452. short lineNo,
  453. string location)
  454. {
  455. row.Domain = domain;
  456. row.RctType = "pt";
  457. row.Receiver = receiver;
  458. row.PurOrdRctRecID = masterRecId;
  459. row.Line = lineNo;
  460. row.Dimension1 = "";
  461. row.Dimension2 = "";
  462. row.ItemNum = e.ItemNum?.Trim();
  463. row.OrdNbr = string.IsNullOrWhiteSpace(e.OrdNbr) ? "" : e.OrdNbr.Trim();
  464. row.OrdLine = (short)e.OrdLine;
  465. row.RctDate = ParseDate(e.RctDate);
  466. row.UM = e.UM;
  467. row.QtyOrded = e.QtyOrded;
  468. row.QtyReceived = e.QtyReceived;
  469. row.RctQty = e.RctQty;
  470. row.LotSerial = e.LotSerial;
  471. row.Location = location;
  472. row.QCDescr = e.QCDescr;
  473. row.Remark = e.Remark;
  474. row.Typed = string.IsNullOrWhiteSpace(e.Typed) ? "C" : e.Typed.Trim();
  475. row.TaxIn = e.TaxIn;
  476. row.TaxClass = e.TaxClass;
  477. row.Curr = e.Curr;
  478. row.Potype = string.IsNullOrWhiteSpace(e.Potype) ? "P" : e.Potype.Trim();
  479. row.IsActive = true;
  480. row.IsConfirm = e.IsConfirm;
  481. row.UMConversion = e.UMConversion;
  482. row.POCost = e.POCost;
  483. row.RctNbr = e.RctNbr;
  484. row.RctLine = (short)e.RctLine;
  485. row.CustPO = e.CustPO;
  486. row.BlanketLine = e.BlanketLine;
  487. row.QtyReturn = e.QtyReturn;
  488. row.Supp = e.Supp;
  489. }
  490. [DisplayName("删除采购退货单(软删除)")]
  491. [HttpPost("purchase-return/delete/{id:int}")]
  492. public async Task<object> SoftDelete(int id)
  493. {
  494. var m = await _masterRep.GetFirstAsync(x => x.RecID == id && x.RctType == "pt")
  495. ?? throw Oops.Oh("单据不存在");
  496. if (!m.IsPlan)
  497. throw Oops.Oh("该单据不允许删除");
  498. m.IsActive = false;
  499. m.UpdateUser = _userManager.Account ?? "system";
  500. m.UpdateTime = DateTime.Now;
  501. await _masterRep.UpdateAsync(m);
  502. await _db.Ado.ExecuteCommandAsync(
  503. "UPDATE PurOrdRctDetail SET IsActive=0, UpdateUser=@U, UpdateTime=@T WHERE PurOrdRctRecID=@Id AND RctType='pt'",
  504. new SugarParameter("@U", m.UpdateUser),
  505. new SugarParameter("@T", m.UpdateTime),
  506. new SugarParameter("@Id", id));
  507. return new { message = "已删除" };
  508. }
  509. private async Task<string> ResolveRcDomainAsync(string rcReceiver)
  510. {
  511. var d = await _db.Ado.GetStringAsync(
  512. "SELECT Domain FROM PurOrdRctMaster WHERE RctType='rc' AND Receiver=@R LIMIT 1",
  513. new SugarParameter("@R", rcReceiver));
  514. return d;
  515. }
  516. private async Task<string> AllocReceiverAsync()
  517. {
  518. for (var i = 0; i < 20; i++)
  519. {
  520. var suffix = (DateTime.UtcNow.Ticks ^ Random.Shared.Next()).ToString("x");
  521. if (suffix.Length > 12)
  522. suffix = suffix[..12];
  523. var cand = $"PT{DateTime.Now:yyMMdd}{suffix}";
  524. if (cand.Length > 24)
  525. cand = cand[..24];
  526. var n = await _db.Ado.GetIntAsync(
  527. "SELECT COUNT(1) FROM PurOrdRctMaster WHERE Receiver=@R",
  528. new SugarParameter("@R", cand));
  529. if (n == 0)
  530. return cand;
  531. }
  532. return $"PT{Guid.NewGuid():N}"[..24];
  533. }
  534. private static DateTime? ParseDate(string raw)
  535. {
  536. if (string.IsNullOrWhiteSpace(raw)) return null;
  537. return DateTime.TryParse(raw.Trim(), out var d) ? d.Date : null;
  538. }
  539. }