DemandOrderService.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. namespace Admin.NET.Plugin.AiDOP.Supply;
  2. /// <summary>
  3. /// 要货令服务(PurOrdMaster/PurOrdDetail,Potype=PO,ReqBy=DO)
  4. ///
  5. /// 【租户安全边界】需认证访问(已移除类级 <c>[AllowAnonymous]</c>);
  6. /// 租户一律经 <c>AidopTenantScope.ResolveOrThrow</c> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退。
  8. /// </summary>
  9. [ApiDescriptionSettings(Order = 311, Description = "要货令")]
  10. [Route("api/Supply")]
  11. [NonUnify]
  12. public class DemandOrderService : IDynamicApiController, ITransient
  13. {
  14. private const int PurOrdSerialWidth = 3;
  15. private readonly ISqlSugarClient _db;
  16. private readonly UserManager _userManager;
  17. public DemandOrderService(ISqlSugarClient db, UserManager userManager)
  18. {
  19. _db = db;
  20. _userManager = userManager;
  21. }
  22. private long ResolveTenantOrThrow() => Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  23. [DisplayName("要货令列表")]
  24. [HttpGet("demand-order/list")]
  25. public async Task<object> GetList([FromQuery] DemandOrderListInput input)
  26. {
  27. var page = input.Page <= 0 ? 1 : input.Page;
  28. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  29. var offset = (page - 1) * pageSize;
  30. var where = new List<string>
  31. {
  32. "p.Potype='PO'",
  33. "IFNULL(p.ReqBy,'')='DO'",
  34. "p.IsActive<=1"
  35. };
  36. var pars = new List<SugarParameter>();
  37. var tenantId = ResolveTenantOrThrow();
  38. where.Add("p.tenant_id = @TenantId");
  39. pars.Add(new SugarParameter("@TenantId", tenantId));
  40. if (!string.IsNullOrWhiteSpace(input.PurOrd))
  41. {
  42. where.Add("p.PurOrd LIKE @PurOrd");
  43. pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
  44. }
  45. if (!string.IsNullOrWhiteSpace(input.Buyer))
  46. {
  47. where.Add("p.Buyer=@Buyer");
  48. pars.Add(new SugarParameter("@Buyer", input.Buyer.Trim()));
  49. }
  50. if (!string.IsNullOrWhiteSpace(input.Supp))
  51. {
  52. where.Add("p.Supp=@Supp");
  53. pars.Add(new SugarParameter("@Supp", input.Supp.Trim()));
  54. }
  55. if (!string.IsNullOrWhiteSpace(input.Status))
  56. {
  57. var st = input.Status.Trim();
  58. if (string.Equals(st, "R", StringComparison.OrdinalIgnoreCase))
  59. where.Add("(IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL)");
  60. else
  61. {
  62. where.Add("p.Status=@Status");
  63. pars.Add(new SugarParameter("@Status", st));
  64. }
  65. }
  66. var fromSql = $"""
  67. FROM PurOrdMaster p
  68. LEFT JOIN SuppMaster s ON p.Supp=s.Supp AND s.tenant_id=p.tenant_id
  69. LEFT JOIN DepartmentMaster d ON p.Department=d.Department AND d.tenant_id=p.tenant_id
  70. LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee AND e.tenant_id=p.tenant_id
  71. LEFT JOIN (SELECT DISTINCT PurOrd FROM PurOrdDetail WHERE IFNULL(RctQty,0)>0) pdhr ON pdhr.PurOrd=p.PurOrd
  72. WHERE {string.Join(" AND ", where)}
  73. """;
  74. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  75. var list = await _db.Ado.SqlQueryAsync<DemandOrderListRow>(
  76. $"""
  77. SELECT
  78. p.RecID AS Id,
  79. p.PurOrd AS PurOrd,
  80. CONCAT(TRIM(IFNULL(p.Supp,'')),' ',TRIM(IFNULL(s.SortName,''))) AS SuppName,
  81. p.Potype AS Potype,
  82. p.ReqBy AS ReqBy,
  83. CONCAT(TRIM(IFNULL(p.Buyer,'')),' ',TRIM(IFNULL(e.Name,''))) AS Buyer,
  84. CONCAT(TRIM(IFNULL(p.Department,'')),' ',TRIM(IFNULL(d.Descr,''))) AS DepartmentDescr,
  85. p.OrdDate AS OrdDate,
  86. p.Contract AS Contract,
  87. p.DeliverTo AS DeliverTo,
  88. p.DueDate AS DueDate,
  89. p.Contact AS Contact,
  90. p.ShipTo AS ShipTo,
  91. p.Curr AS Curr,
  92. p.TaxClass AS TaxClass,
  93. p.TaxIn AS TaxIn,
  94. CASE
  95. WHEN IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL THEN 'R'
  96. ELSE p.Status
  97. END AS Status,
  98. p.Remark AS Remark,
  99. p.Supp AS Supp,
  100. p.Buyer AS BuyerCode,
  101. p.Department AS Department,
  102. p.`Usage` AS `Usage`,
  103. p.FSTID AS Fstid,
  104. CASE WHEN pdhr.PurOrd IS NOT NULL THEN 1 ELSE 0 END AS HasReceived
  105. {fromSql}
  106. ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)}
  107. LIMIT {pageSize} OFFSET {offset}
  108. """,
  109. pars);
  110. return new { total, page, pageSize, list };
  111. }
  112. [DisplayName("要货令详情")]
  113. [HttpGet("demand-order/{id:int}")]
  114. public async Task<object> GetDetail(int id)
  115. {
  116. var master = await GetMasterRowAsync(id);
  117. var details = await _db.Ado.SqlQueryAsync<DemandOrderDetailRow>(
  118. """
  119. SELECT
  120. d.RecID AS Id,
  121. d.Line AS Line,
  122. d.ItemNum AS ItemNum,
  123. d.UM AS UM,
  124. d.Location AS Location,
  125. d.QtyOrded AS QtyOrded,
  126. d.RctQty AS RctQty,
  127. d.ReceiptQty AS ReceiptQty,
  128. d.DueDate AS DueDate,
  129. d.Rev AS Rev,
  130. d.Drawing AS Drawing,
  131. d.LotSerial AS LotSerial,
  132. d.Potype AS Potype,
  133. d.PurOrd AS PurOrd,
  134. d.PurOrdRecID AS PurOrdRecID
  135. FROM PurOrdDetail d
  136. WHERE d.PurOrd=@PurOrd AND d.Potype='PO' AND d.tenant_id=@TenantId
  137. ORDER BY d.Line
  138. """,
  139. new SugarParameter("@PurOrd", master.PurOrd),
  140. new SugarParameter("@TenantId", ResolveTenantOrThrow()));
  141. return new { master, details };
  142. }
  143. [DisplayName("保存要货令")]
  144. [HttpPost("demand-order/save")]
  145. public async Task<object> Save([FromBody] DemandOrderSaveInput input)
  146. {
  147. if (string.IsNullOrWhiteSpace(input.Supp)) throw Oops.Oh("请选择供应商");
  148. if (string.IsNullOrWhiteSpace(input.Department)) throw Oops.Oh("请选择部门");
  149. var now = DateTime.Now;
  150. var ordDate = ParseDate(input.OrdDate) ?? now.Date;
  151. var buyer = string.IsNullOrWhiteSpace(input.Buyer) ? "110" : input.Buyer.Trim();
  152. var reqBy = string.IsNullOrWhiteSpace(input.ReqBy) ? "DO" : input.ReqBy.Trim();
  153. var usage = string.IsNullOrWhiteSpace(input.Usage) ? "标准" : input.Usage.Trim();
  154. var fstid = string.Equals(usage, "VMI", StringComparison.OrdinalIgnoreCase) ? "3" : "1";
  155. if (!string.IsNullOrWhiteSpace(input.Fstid)) fstid = input.Fstid.Trim();
  156. var tenantId = ResolveTenantOrThrow();
  157. try
  158. {
  159. _db.Ado.BeginTran();
  160. int masterId;
  161. string purOrd;
  162. if (input.Id is null or <= 0)
  163. {
  164. purOrd = await PurOrdNumberGenerator.NextAsync(_db, "DO", DateTime.Today, PurOrdSerialWidth);
  165. await _db.Ado.ExecuteCommandAsync(
  166. """
  167. INSERT INTO PurOrdMaster
  168. (
  169. Confirming, CreditTermsInt, Disc, ExchRate, EstVal, ExchRate1, ExchRate2,
  170. FixedPrice, FixedRate, Frt, PartialOK, AmtPrepaid, PrintPO, PST, Recurr,
  171. `Release`, Revision, Scheduled, ServiceCharge, SpecialCharge, Taxable,
  172. Tax1, Tax2, Tax3, TransportDays, IsActive, IsConfirm, Potype, IsChanged,
  173. TaxIn, Amt, IsPriceChanged,
  174. PurOrd, OrdDate, ReqBy, Status, Buyer, Supp, Department, `Usage`, FSTID, Remark,
  175. CreateUser, CreateTime, UpdateUser, UpdateTime, Domain, tenant_id
  176. )
  177. VALUES
  178. (
  179. 0, 0, 0, 1, 0, 1, 1,
  180. 0, 0, 0, 1, 0, 0, 0, 0,
  181. 0, 0, 0, 0, 0, 1,
  182. 0, 0, 0, 0, 1, 1, 'PO', 0,
  183. 1, 0, 0,
  184. @PurOrd, @OrdDate, @ReqBy, 'R', @Buyer, @Supp, @Department, @Usage, @FSTID, @Remark,
  185. @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @Domain, @TenantId
  186. )
  187. """,
  188. new SugarParameter("@PurOrd", purOrd),
  189. new SugarParameter("@OrdDate", ordDate),
  190. new SugarParameter("@ReqBy", reqBy),
  191. new SugarParameter("@Buyer", buyer),
  192. new SugarParameter("@Supp", input.Supp.Trim()),
  193. new SugarParameter("@Department", input.Department.Trim()),
  194. new SugarParameter("@Usage", usage),
  195. new SugarParameter("@FSTID", fstid),
  196. new SugarParameter("@Remark", input.Remark?.Trim()),
  197. new SugarParameter("@CreateUser", _userManager.Account),
  198. new SugarParameter("@CreateTime", now),
  199. new SugarParameter("@UpdateUser", _userManager.Account),
  200. new SugarParameter("@UpdateTime", now),
  201. new SugarParameter("@Domain", "100"),
  202. new SugarParameter("@TenantId", tenantId)
  203. );
  204. masterId = await _db.Ado.GetIntAsync(
  205. "SELECT IFNULL(MAX(RecID),0) FROM PurOrdMaster WHERE PurOrd=@PurOrd AND tenant_id=@TenantId",
  206. new SugarParameter("@PurOrd", purOrd),
  207. new SugarParameter("@TenantId", tenantId));
  208. }
  209. else
  210. {
  211. masterId = input.Id.Value;
  212. var exists = await _db.Ado.GetIntAsync(
  213. "SELECT COUNT(1) FROM PurOrdMaster WHERE RecID=@Id AND Potype='PO' AND IFNULL(ReqBy,'')='DO' AND tenant_id=@TenantId",
  214. new SugarParameter("@Id", masterId),
  215. new SugarParameter("@TenantId", ResolveTenantOrThrow()));
  216. if (exists <= 0) throw Oops.Oh("记录不存在");
  217. purOrd = await _db.Ado.GetStringAsync(
  218. "SELECT PurOrd FROM PurOrdMaster WHERE RecID=@Id AND tenant_id=@TenantId LIMIT 1",
  219. new SugarParameter("@Id", masterId),
  220. new SugarParameter("@TenantId", ResolveTenantOrThrow())) ?? throw Oops.Oh("记录不存在");
  221. await _db.Ado.ExecuteCommandAsync(
  222. """
  223. UPDATE PurOrdMaster
  224. SET OrdDate=@OrdDate,Supp=@Supp,ReqBy=@ReqBy,Buyer=@Buyer,Department=@Department,
  225. Curr=@Curr,`Usage`=@Usage,Remark=@Remark,FSTID=@FSTID,
  226. UpdateUser=@UpdateUser,UpdateTime=@UpdateTime
  227. WHERE RecID=@Id AND tenant_id=@TenantId
  228. """,
  229. new SugarParameter("@Id", masterId),
  230. new SugarParameter("@OrdDate", ordDate),
  231. new SugarParameter("@Supp", input.Supp.Trim()),
  232. new SugarParameter("@ReqBy", reqBy),
  233. new SugarParameter("@Buyer", buyer),
  234. new SugarParameter("@Department", input.Department.Trim()),
  235. new SugarParameter("@Curr", input.Curr?.Trim()),
  236. new SugarParameter("@Usage", usage),
  237. new SugarParameter("@Remark", input.Remark?.Trim()),
  238. new SugarParameter("@FSTID", fstid),
  239. new SugarParameter("@UpdateUser", _userManager.Account),
  240. new SugarParameter("@UpdateTime", now)
  241. );
  242. }
  243. await SaveDetailsAsync(masterId, purOrd, input.Details ?? new List<DemandOrderDetailInput>());
  244. _db.Ado.CommitTran();
  245. return new { id = masterId, purOrd, message = input.Id is null or <= 0 ? "新增成功" : "编辑成功" };
  246. }
  247. catch
  248. {
  249. _db.Ado.RollbackTran();
  250. throw;
  251. }
  252. }
  253. [DisplayName("删除要货令")]
  254. [HttpPost("demand-order/delete/{id:int}")]
  255. public async Task<object> Delete(int id, [FromQuery] string purOrd)
  256. {
  257. if (string.IsNullOrWhiteSpace(purOrd)) throw Oops.Oh("缺少要货令单号");
  258. var tenantId = ResolveTenantOrThrow();
  259. var delPars = new List<SugarParameter>
  260. {
  261. new("@PurOrd", purOrd.Trim()),
  262. new("@TenantId", tenantId)
  263. };
  264. const string tenantSuffix = " AND tenant_id=@TenantId";
  265. var hasReceipt = await _db.Ado.GetIntAsync(
  266. $"SELECT COUNT(1) FROM PurOrdRctDetail WHERE OrdNbr=@PurOrd AND IFNULL(QtyReceived,0)>0{tenantSuffix}",
  267. delPars);
  268. if (hasReceipt > 0) throw Oops.Oh("存在收货数量,不允许删除");
  269. var hasDetailRct = await _db.Ado.GetIntAsync(
  270. $"SELECT COUNT(1) FROM PurOrdDetail WHERE PurOrd=@PurOrd AND IFNULL(RctQty,0)>0{tenantSuffix}",
  271. delPars);
  272. if (hasDetailRct > 0) throw Oops.Oh("存在收货数量,不允许删除");
  273. var hasShip = await _db.Ado.GetIntAsync(
  274. $"SELECT COUNT(1) FROM scm_shdzb WHERE po_bill=@PurOrd{tenantSuffix}",
  275. delPars);
  276. if (hasShip > 0) throw Oops.Oh("存在发货单,不允许删除");
  277. try
  278. {
  279. _db.Ado.BeginTran();
  280. await _db.Ado.ExecuteCommandAsync(
  281. "DELETE FROM PurOrdDetail WHERE PurOrd=@PurOrd AND Potype='PO' AND tenant_id=@TenantId",
  282. new SugarParameter("@PurOrd", purOrd.Trim()),
  283. new SugarParameter("@TenantId", tenantId));
  284. await _db.Ado.ExecuteCommandAsync(
  285. "DELETE FROM PurOrdMaster WHERE RecID=@Id AND PurOrd=@PurOrd AND tenant_id=@TenantId",
  286. new SugarParameter("@Id", id),
  287. new SugarParameter("@PurOrd", purOrd.Trim()),
  288. new SugarParameter("@TenantId", tenantId));
  289. _db.Ado.CommitTran();
  290. }
  291. catch
  292. {
  293. _db.Ado.RollbackTran();
  294. throw;
  295. }
  296. return new { message = "删除成功" };
  297. }
  298. [DisplayName("选择物料列表")]
  299. [HttpGet("demand-order/items")]
  300. public async Task<object> GetItemList([FromQuery] DemandOrderItemListInput input)
  301. {
  302. var page = input.Page <= 0 ? 1 : input.Page;
  303. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  304. var offset = (page - 1) * pageSize;
  305. var where = new List<string> { "1=1" };
  306. var pars = new List<SugarParameter>();
  307. var tenantId = ResolveTenantOrThrow();
  308. where.Add("tenant_id = @TenantId");
  309. pars.Add(new SugarParameter("@TenantId", tenantId));
  310. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  311. {
  312. where.Add("ItemNum LIKE @ItemNum");
  313. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  314. }
  315. if (!string.IsNullOrWhiteSpace(input.Descr))
  316. {
  317. where.Add("Descr LIKE @Descr");
  318. pars.Add(new SugarParameter("@Descr", $"%{input.Descr.Trim()}%"));
  319. }
  320. var fromSql = $"FROM ItemMaster WHERE {string.Join(" AND ", where)}";
  321. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  322. var list = await _db.Ado.SqlQueryAsync<ItemLookupRow>(
  323. $"""
  324. SELECT RecID, ItemNum, Descr, Descr1, UM, Location, Rev, Drawing
  325. {fromSql}
  326. ORDER BY {BuildItemOrderBy(input.SortField, input.SortOrder)}
  327. LIMIT {pageSize} OFFSET {offset}
  328. """,
  329. pars);
  330. return new { total, page, pageSize, list };
  331. }
  332. private (string Where, List<SugarParameter> Pars) BuildTenantFilter()
  333. {
  334. var tenantId = ResolveTenantOrThrow();
  335. return (" AND tenant_id = @TenantId", new List<SugarParameter> { new("@TenantId", tenantId) });
  336. }
  337. [DisplayName("采购组下拉")]
  338. [HttpGet("demand-order/options/buyers")]
  339. public async Task<object> GetBuyerOptions()
  340. {
  341. var (where, pars) = BuildTenantFilter();
  342. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  343. $"""
  344. SELECT Employee AS Value, CONCAT(TRIM(Employee),' ',TRIM(IFNULL(Name,''))) AS Label
  345. FROM EmployeeMaster WHERE 1=1 {where}
  346. ORDER BY Employee
  347. """, pars);
  348. return new { list };
  349. }
  350. [DisplayName("供应商下拉")]
  351. [HttpGet("demand-order/options/suppliers")]
  352. public async Task<object> GetSupplierOptions()
  353. {
  354. var (where, pars) = BuildTenantFilter();
  355. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  356. $"""
  357. SELECT Supp AS Value, CONCAT(TRIM(Supp),' ',TRIM(IFNULL(SortName,''))) AS Label
  358. FROM SuppMaster WHERE 1=1 {where}
  359. ORDER BY Supp
  360. """, pars);
  361. return new { list };
  362. }
  363. [DisplayName("部门下拉")]
  364. [HttpGet("demand-order/options/departments")]
  365. public async Task<object> GetDepartmentOptions()
  366. {
  367. var (where, pars) = BuildTenantFilter();
  368. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  369. $"""
  370. SELECT Department AS Value, CONCAT(TRIM(Department),' ',TRIM(IFNULL(Descr,''))) AS Label
  371. FROM DepartmentMaster WHERE 1=1 {where}
  372. ORDER BY Department
  373. """, pars);
  374. return new { list };
  375. }
  376. [DisplayName("币别下拉")]
  377. [HttpGet("demand-order/options/curr")]
  378. public async Task<object> GetCurrOptions()
  379. {
  380. var (where, pars) = BuildTenantFilter();
  381. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  382. $"""
  383. SELECT Val AS Value, CONCAT(TRIM(Val),' ',TRIM(IFNULL(Comments,''))) AS Label
  384. FROM GeneralizedCodeMaster
  385. WHERE FldName='Curr' {where}
  386. ORDER BY Val
  387. """, pars);
  388. return new { list };
  389. }
  390. [DisplayName("库位下拉")]
  391. [HttpGet("demand-order/options/locations")]
  392. public async Task<object> GetLocationOptions()
  393. {
  394. var (where, pars) = BuildTenantFilter();
  395. var list = await _db.Ado.SqlQueryAsync<OptionRow>(
  396. $"""
  397. SELECT location AS Value, CONCAT(TRIM(location),' ',TRIM(IFNULL(descr,''))) AS Label
  398. FROM LocationMaster
  399. WHERE IFNULL(typed,'')<>'Supp' {where}
  400. ORDER BY location
  401. """, pars);
  402. return new { list };
  403. }
  404. private async Task SaveDetailsAsync(int masterId, string purOrd, List<DemandOrderDetailInput> details)
  405. {
  406. var tenantId = ResolveTenantOrThrow();
  407. var dbDetails = await _db.Ado.SqlQueryAsync<DemandOrderDetailRow>(
  408. """
  409. SELECT RecID AS Id, Line AS Line
  410. FROM PurOrdDetail
  411. WHERE PurOrd=@PurOrd AND Potype='PO' AND tenant_id=@TenantId
  412. """,
  413. new SugarParameter("@PurOrd", purOrd),
  414. new SugarParameter("@TenantId", tenantId));
  415. var dbById = dbDetails.ToDictionary(d => d.Id);
  416. var inputIds = new HashSet<int>(details.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
  417. for (var i = 0; i < details.Count; i++)
  418. {
  419. var d = details[i];
  420. if (string.IsNullOrWhiteSpace(d.ItemNum)) continue;
  421. if (!d.QtyOrded.HasValue || d.QtyOrded.Value <= 0) throw Oops.Oh("订单数量必须大于0");
  422. var item = (await _db.Ado.SqlQueryAsync<ItemLookupRow>(
  423. """
  424. SELECT ItemNum, Descr, UM, Location, Rev, Drawing
  425. FROM ItemMaster
  426. WHERE ItemNum=@ItemNum
  427. LIMIT 1
  428. """,
  429. new SugarParameter("@ItemNum", d.ItemNum.Trim()))).FirstOrDefault();
  430. if (item == null) throw Oops.Oh($"物料不存在:{d.ItemNum}");
  431. var um = d.UM ?? item.UM;
  432. var location = d.Location ?? item.Location;
  433. var rev = d.Rev ?? item.Rev;
  434. var drawing = d.Drawing ?? item.Drawing;
  435. var dueDate = ParseDate(d.DueDate);
  436. var now = DateTime.Now;
  437. if (d.Id is > 0 && dbById.ContainsKey(d.Id.Value))
  438. {
  439. await _db.Ado.ExecuteCommandAsync(
  440. """
  441. UPDATE PurOrdDetail
  442. SET ItemNum=@ItemNum,Descr=@Descr,UM=@UM,Location=@Location,QtyOrded=@QtyOrded,
  443. DueDate=@DueDate,Rev=@Rev,Drawing=@Drawing,LotSerial=@LotSerial,
  444. UpdateUser=@UpdateUser,UpdateTime=@UpdateTime
  445. WHERE RecID=@Id AND tenant_id=@TenantId
  446. """,
  447. new SugarParameter("@Id", d.Id.Value),
  448. new SugarParameter("@ItemNum", item.ItemNum),
  449. new SugarParameter("@Descr", item.Descr),
  450. new SugarParameter("@UM", um),
  451. new SugarParameter("@Location", location),
  452. new SugarParameter("@QtyOrded", d.QtyOrded.Value),
  453. new SugarParameter("@DueDate", dueDate),
  454. new SugarParameter("@Rev", rev),
  455. new SugarParameter("@Drawing", drawing),
  456. new SugarParameter("@LotSerial", d.LotSerial?.Trim()),
  457. new SugarParameter("@UpdateUser", _userManager.Account),
  458. new SugarParameter("@UpdateTime", now)
  459. );
  460. }
  461. else
  462. {
  463. var line = d.Line ?? await _db.Ado.GetIntAsync(
  464. "SELECT IFNULL(MAX(Line),0)+1 FROM PurOrdDetail WHERE PurOrd=@PurOrd AND tenant_id=@TenantId",
  465. new SugarParameter("@PurOrd", purOrd),
  466. new SugarParameter("@TenantId", tenantId));
  467. await _db.Ado.ExecuteCommandAsync(
  468. """
  469. INSERT INTO PurOrdDetail
  470. (
  471. QtyBO, RctCost, CreditTermsInt, UpdateCurrentCost, CumReceived1, CumReceived2,
  472. CumReceived3, CumReceived4, Disc, FixedPrice, InspectReq, SingleLot, SupplyPer,
  473. PurOrd, PST, PackingSlipQty, PayUMConv, PurCost, RctQty, QtyOrded, QtyReceived,
  474. QtyReturned, Active, QtyReleased, RctUMConversion, Scheduled, ScheduledChanged,
  475. SchedMRPReq, SafetyDays, SafetyHours, StdCost, Taxable, TaxIn, MaxTaxableAmt,
  476. TransportHours, UMConversion, VAT, IsActive, IsConfirm, Potype, IsChanged,
  477. TaxRate, IsRounding, ReceiptQty, BarCodeQty, IsClosed, QtyReturnedRefund, CumQtyBO,
  478. Line, ItemNum, Descr, UM, Rev, Drawing, Location, DueDate, LotSerial, PurOrdRecID, Status,
  479. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  480. )
  481. VALUES
  482. (
  483. 0, 0, 0, 0, 0, 0,
  484. 0, 0, 0, 0, 0, 0, 0,
  485. @PurOrd, 0, 0, 1, 0, 0, @QtyOrded, 0,
  486. 0, 1, 0, 1, 0, 0,
  487. 0, 0, 0, 0, 1, 1, 0,
  488. 0, 1, 0, 1, 1, 'PO', 0,
  489. 0, 0, 0, 0, 0, 0, 0,
  490. @Line, @ItemNum, @Descr, @UM, @Rev, @Drawing, @Location, @DueDate, @LotSerial, @PurOrdRecID, 'R',
  491. @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @TenantId
  492. )
  493. """,
  494. new SugarParameter("@PurOrd", purOrd),
  495. new SugarParameter("@Line", line),
  496. new SugarParameter("@ItemNum", item.ItemNum),
  497. new SugarParameter("@Descr", item.Descr),
  498. new SugarParameter("@UM", um),
  499. new SugarParameter("@Rev", rev),
  500. new SugarParameter("@Drawing", drawing),
  501. new SugarParameter("@Location", location),
  502. new SugarParameter("@QtyOrded", d.QtyOrded.Value),
  503. new SugarParameter("@DueDate", dueDate),
  504. new SugarParameter("@LotSerial", d.LotSerial?.Trim()),
  505. new SugarParameter("@PurOrdRecID", masterId),
  506. new SugarParameter("@CreateUser", _userManager.Account),
  507. new SugarParameter("@CreateTime", now),
  508. new SugarParameter("@UpdateUser", _userManager.Account),
  509. new SugarParameter("@UpdateTime", now),
  510. new SugarParameter("@TenantId", ResolveTenantOrThrow())
  511. );
  512. }
  513. }
  514. foreach (var toDelete in dbDetails.Where(u => !inputIds.Contains(u.Id)))
  515. {
  516. var rctQty = await _db.Ado.GetDecimalAsync(
  517. "SELECT IFNULL(RctQty,0) FROM PurOrdDetail WHERE RecID=@Id AND tenant_id=@TenantId",
  518. new SugarParameter("@Id", toDelete.Id),
  519. new SugarParameter("@TenantId", tenantId));
  520. if (rctQty > 0) throw Oops.Oh("明细存在收货数量,不允许删除");
  521. await _db.Ado.ExecuteCommandAsync(
  522. "DELETE FROM PurOrdDetail WHERE RecID=@Id AND tenant_id=@TenantId",
  523. new SugarParameter("@Id", toDelete.Id),
  524. new SugarParameter("@TenantId", tenantId));
  525. }
  526. }
  527. private async Task<DemandOrderListRow> GetMasterRowAsync(int id)
  528. {
  529. var tenantId = ResolveTenantOrThrow();
  530. const string tenantWhere = " AND p.tenant_id=@TenantId";
  531. var pars = new List<SugarParameter> { new("@Id", id), new("@TenantId", tenantId) };
  532. var row = (await _db.Ado.SqlQueryAsync<DemandOrderListRow>(
  533. $"""
  534. SELECT
  535. p.RecID AS Id,
  536. p.PurOrd AS PurOrd,
  537. CONCAT(TRIM(IFNULL(p.Supp,'')),' ',TRIM(IFNULL(s.SortName,''))) AS SuppName,
  538. p.Potype AS Potype,
  539. p.ReqBy AS ReqBy,
  540. CONCAT(TRIM(IFNULL(p.Buyer,'')),' ',TRIM(IFNULL(e.Name,''))) AS Buyer,
  541. CONCAT(TRIM(IFNULL(p.Department,'')),' ',TRIM(IFNULL(d.Descr,''))) AS DepartmentDescr,
  542. p.OrdDate AS OrdDate,
  543. p.Contract AS Contract,
  544. p.DeliverTo AS DeliverTo,
  545. p.DueDate AS DueDate,
  546. p.Contact AS Contact,
  547. p.ShipTo AS ShipTo,
  548. p.Curr AS Curr,
  549. p.TaxClass AS TaxClass,
  550. p.TaxIn AS TaxIn,
  551. CASE WHEN IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL THEN 'R' ELSE p.Status END AS Status,
  552. p.Remark AS Remark,
  553. p.Supp AS Supp,
  554. p.Buyer AS BuyerCode,
  555. p.Department AS Department,
  556. p.`Usage` AS `Usage`,
  557. p.FSTID AS Fstid
  558. FROM PurOrdMaster p
  559. LEFT JOIN SuppMaster s ON p.Supp=s.Supp AND s.tenant_id=p.tenant_id
  560. LEFT JOIN DepartmentMaster d ON p.Department=d.Department AND d.tenant_id=p.tenant_id
  561. LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee AND e.tenant_id=p.tenant_id
  562. WHERE p.RecID=@Id AND p.Potype='PO' AND IFNULL(p.ReqBy,'')='DO'{tenantWhere}
  563. LIMIT 1
  564. """,
  565. pars)).FirstOrDefault();
  566. return row ?? throw Oops.Oh("记录不存在");
  567. }
  568. private static DateTime? ParseDate(string? v) => DateTime.TryParse(v, out var d) ? d : null;
  569. private static string BuildOrderBy(string? sortField, string? sortOrder)
  570. {
  571. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  572. return sortField?.ToLowerInvariant() switch
  573. {
  574. "purord" => $"p.PurOrd {dir}",
  575. "suppname" => $"p.Supp {dir}",
  576. "buyer" => $"p.Buyer {dir}",
  577. "departmentdescr" => $"p.Department {dir}",
  578. "orddate" => $"p.OrdDate {dir}",
  579. "contract" => $"p.Contract {dir}",
  580. "duedate" => $"p.DueDate {dir}",
  581. "contact" => $"p.Contact {dir}",
  582. "shipto" => $"p.ShipTo {dir}",
  583. "curr" => $"p.Curr {dir}",
  584. "taxclass" => $"p.TaxClass {dir}",
  585. "taxin" => $"p.TaxIn {dir}",
  586. "status" => $"p.Status {dir}",
  587. _ => "p.RecID DESC"
  588. };
  589. }
  590. private static string BuildItemOrderBy(string? sortField, string? sortOrder)
  591. {
  592. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  593. return sortField?.ToLowerInvariant() switch
  594. {
  595. "itemnum" => $"ItemNum {dir}",
  596. "descr" => $"Descr {dir}",
  597. "descr1" => $"Descr1 {dir}",
  598. "um" => $"UM {dir}",
  599. "location" => $"Location {dir}",
  600. "rev" => $"Rev {dir}",
  601. "drawing" => $"Drawing {dir}",
  602. _ => "RecID DESC"
  603. };
  604. }
  605. private sealed class DemandOrderListRow
  606. {
  607. public int Id { get; set; }
  608. public string? PurOrd { get; set; }
  609. public string? SuppName { get; set; }
  610. public string? Potype { get; set; }
  611. public string? ReqBy { get; set; }
  612. public string? Buyer { get; set; }
  613. public string? DepartmentDescr { get; set; }
  614. public DateTime? OrdDate { get; set; }
  615. public string? Contract { get; set; }
  616. public string? DeliverTo { get; set; }
  617. public DateTime? DueDate { get; set; }
  618. public string? Contact { get; set; }
  619. public string? ShipTo { get; set; }
  620. public string? Curr { get; set; }
  621. public string? TaxClass { get; set; }
  622. public bool TaxIn { get; set; }
  623. public string? Status { get; set; }
  624. public string? Remark { get; set; }
  625. public string? Supp { get; set; }
  626. public string? BuyerCode { get; set; }
  627. public string? Department { get; set; }
  628. public string? Usage { get; set; }
  629. public string? Fstid { get; set; }
  630. public int HasReceived { get; set; }
  631. }
  632. private sealed class DemandOrderDetailRow
  633. {
  634. public int Id { get; set; }
  635. public int? Line { get; set; }
  636. public string? ItemNum { get; set; }
  637. public string? UM { get; set; }
  638. public string? Location { get; set; }
  639. public decimal? QtyOrded { get; set; }
  640. public decimal? RctQty { get; set; }
  641. public decimal? ReceiptQty { get; set; }
  642. public DateTime? DueDate { get; set; }
  643. public string? Rev { get; set; }
  644. public string? Drawing { get; set; }
  645. public string? LotSerial { get; set; }
  646. public string? Potype { get; set; }
  647. public string? PurOrd { get; set; }
  648. public int? PurOrdRecID { get; set; }
  649. }
  650. private sealed class ItemLookupRow
  651. {
  652. public long RecID { get; set; }
  653. public string? ItemNum { get; set; }
  654. public string? Descr { get; set; }
  655. public string? Descr1 { get; set; }
  656. public string? UM { get; set; }
  657. public string? Location { get; set; }
  658. public string? Rev { get; set; }
  659. public string? Drawing { get; set; }
  660. }
  661. private sealed class OptionRow
  662. {
  663. public string? Value { get; set; }
  664. public string? Label { get; set; }
  665. }
  666. }