PurchaseRequestService.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. using Yitter.IdGenerator;
  2. namespace Admin.NET.Plugin.AiDOP.Supply;
  3. /// <summary>
  4. /// 物料采购申请服务
  5. ///
  6. /// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退。
  8. /// <para>
  9. /// 原实现:类级 <c>[AllowAnonymous]</c> + 多处以 <c>if (_userManager.TenantId > 0)</c> 决定是否拼接租户条件。
  10. /// 匿名请求下 TenantId=0,租户过滤整体被跳过,等价于未认证可跨租户读/软删采购申请。
  11. /// </para>
  12. /// </summary>
  13. [ApiDescriptionSettings(Order = 308, Description = "物料采购申请")]
  14. [Route("api/Supply")]
  15. [NonUnify]
  16. public class PurchaseRequestService : IDynamicApiController, ITransient
  17. {
  18. private readonly ISqlSugarClient _db;
  19. private readonly UserManager _userManager;
  20. public PurchaseRequestService(ISqlSugarClient db, UserManager userManager)
  21. {
  22. _db = db;
  23. _userManager = userManager;
  24. }
  25. [DisplayName("物料采购申请列表")]
  26. [HttpGet("purchase-request/list")]
  27. public async Task<object> GetList([FromQuery] PurchaseRequestListInput input)
  28. {
  29. var page = input.Page <= 0 ? 1 : input.Page;
  30. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  31. var offset = (page - 1) * pageSize;
  32. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  33. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  34. var where = new List<string>
  35. {
  36. "IFNULL(pm.IsDeleted,0)=0",
  37. "IFNULL(pm.state,0)<>0",
  38. "IFNULL(pm.analogcalcversion,'')=''",
  39. "pm.tenant_id=@TenantId"
  40. };
  41. if (!string.IsNullOrWhiteSpace(input.PrBillNo))
  42. {
  43. where.Add("pm.pr_billno LIKE @PrBillNo");
  44. pars.Add(new SugarParameter("@PrBillNo", $"%{input.PrBillNo.Trim()}%"));
  45. }
  46. if (!string.IsNullOrWhiteSpace(input.ItemNumber))
  47. {
  48. where.Add("ic.number LIKE @ItemNumber");
  49. pars.Add(new SugarParameter("@ItemNumber", $"%{input.ItemNumber.Trim()}%"));
  50. }
  51. if (!string.IsNullOrWhiteSpace(input.SupplierName))
  52. {
  53. where.Add("pm.pr_purchasename LIKE @SupplierName");
  54. pars.Add(new SugarParameter("@SupplierName", $"%{input.SupplierName.Trim()}%"));
  55. }
  56. if (input.State.HasValue)
  57. {
  58. where.Add("pm.state=@State");
  59. pars.Add(new SugarParameter("@State", input.State.Value));
  60. }
  61. var orderBy = BuildOrderBy(input.SortField, input.SortOrder);
  62. var fromSql = $"""
  63. FROM srm_pr_main pm
  64. LEFT JOIN ic_item ic ON pm.icitem_id=ic.Id
  65. LEFT JOIN srm_purchase pur ON pm.icitem_id=pur.icitem_id AND pm.pr_purchaseid = pur.supplier_id AND pur.tenant_id=pm.tenant_id
  66. LEFT JOIN ic_item_stock its ON ic.Id=its.icitem_id
  67. LEFT JOIN PurOrdDetail pod ON (pm.pr_billno=pod.Req OR pm.SAP_pr_billno=pod.Req) AND pod.tenant_id=pm.tenant_id
  68. WHERE {string.Join(" AND ", where)}
  69. """;
  70. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM (SELECT DISTINCT pm.Id {fromSql}) t", pars);
  71. var list = await _db.Ado.SqlQueryAsync<PurchaseRequestListRow>(
  72. $"""
  73. SELECT
  74. pm.id AS Id,
  75. pm.pr_billno AS PrBillNo,
  76. pod.PurOrd AS PurOrd,
  77. pm.pr_type AS PrType,
  78. pm.supplier_type AS SupplierType,
  79. pm.IsRequireGoods AS IsRequireGoods,
  80. pm.state AS State,
  81. ic.number AS Number,
  82. ic.name AS Name,
  83. ic.model AS Model,
  84. pm.pr_unit AS PrUnit,
  85. pm.pr_purchasenumber AS PrPurchaseNumber,
  86. pm.pr_purchasename AS PrPurchaseName,
  87. pm.pr_sqty AS PrSqty,
  88. pm.pr_aqty AS PrAqty,
  89. its.sqty AS StockQty,
  90. pm.pr_ssend_date AS PrSsendDate,
  91. pm.pr_sarrive_date AS PrSarriveDate,
  92. pm.pr_purchaser AS PrPurchaser,
  93. pm.create_time AS CreateTime
  94. {fromSql}
  95. ORDER BY {orderBy}
  96. LIMIT {pageSize} OFFSET {offset}
  97. """,
  98. pars);
  99. return new { total, page, pageSize, list };
  100. }
  101. [DisplayName("获取物料采购申请详情")]
  102. [HttpGet("purchase-request/{id:long}")]
  103. public async Task<object> GetDetail(long id)
  104. {
  105. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  106. var pars = new List<SugarParameter> { new("@Id", id), new("@TenantId", tenantId) };
  107. var row = (await _db.Ado.SqlQueryAsync<PurchaseRequestDetailRow>(
  108. $"""
  109. SELECT
  110. pm.id AS Id,
  111. pm.pr_billno AS PrBillNo,
  112. CAST(pm.icitem_id AS CHAR(30)) AS IcitemId,
  113. pm.icitem_name AS IcitemName,
  114. pm.pr_unit AS PrUnit,
  115. pm.pr_purchasenumber AS PrPurchaseNumber,
  116. pm.pr_purchasename AS PrPurchaseName,
  117. pm.supplier_type AS SupplierType,
  118. pm.pr_aqty AS PrAqty,
  119. pm.pr_ssend_date AS PrSsendDate,
  120. pm.pr_sarrive_date AS PrSarriveDate,
  121. pm.pr_type AS PrType,
  122. pm.state AS State,
  123. pm.pr_purchaseid AS PrPurchaseId,
  124. pm.IsRequireGoods AS IsRequireGoods,
  125. pm.pr_purchaser_num AS PrPurchaserNum,
  126. pm.pr_purchaser AS PrPurchaser,
  127. pm.currencytype AS CurrencyType
  128. FROM srm_pr_main pm
  129. WHERE pm.Id=@Id AND IFNULL(pm.IsDeleted,0)=0 AND pm.tenant_id=@TenantId
  130. LIMIT 1
  131. """,
  132. pars)).FirstOrDefault();
  133. return row ?? throw Oops.Oh("记录不存在");
  134. }
  135. [DisplayName("保存物料采购申请")]
  136. [ApiDescriptionSettings(Name = "SavePurchaseRequest"), HttpPost("purchase-request/save")]
  137. public async Task<object> Save([FromBody] PurchaseRequestSaveInput input)
  138. {
  139. // icitem_id 为雪花ID,前端以字符串传递避免 JS Number 精度丢失,这里解析回 long
  140. if (!long.TryParse(input.IcitemId?.Trim(), out var icitemId) || icitemId <= 0) throw Oops.Oh("请选择物料");
  141. if (input.PrPurchaseId is null or <= 0) throw Oops.Oh("请选择供应商");
  142. if (!input.PrAqty.HasValue || input.PrAqty.Value <= 0) throw Oops.Oh("申请数量必须大于0");
  143. var now = DateTime.Now;
  144. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  145. var aqty = input.PrAqty.Value;
  146. var prSsendDate = ParseDate(input.PrSsendDate) ?? now.Date;
  147. var prSarriveDate = ParseDate(input.PrSarriveDate);
  148. var prType = input.PrType ?? 3;
  149. var state = input.State ?? 1;
  150. var currency = input.CurrencyType ?? 1;
  151. var companyId = _userManager.OrgId > 0 ? _userManager.OrgId : 1000;
  152. if (input.Id is null or 0)
  153. {
  154. var newId = YitIdHelper.NextId();
  155. var billNo = string.IsNullOrWhiteSpace(input.PrBillNo) ? GenerateBillNo() : input.PrBillNo.Trim();
  156. await _db.Ado.ExecuteCommandAsync(
  157. """
  158. INSERT INTO srm_pr_main
  159. (Id,pr_billno,pr_purchaseid,pr_purchasenumber,pr_purchasename,pr_purchaser,pr_purchaser_num,
  160. pr_rqty,pr_aqty,pr_sqty,icitem_id,icitem_name,pr_ssend_date,pr_sarrive_date,pr_unit,state,pr_type,currencytype,
  161. create_by,create_by_name,create_time,update_by,update_by_name,update_time,tenant_id,factory_id,org_id,IsDeleted,company_id,IsRequireGoods,supplier_type)
  162. VALUES
  163. (@Id,@PrBillNo,@PrPurchaseId,@PrPurchaseNumber,@PrPurchaseName,@PrPurchaser,@PrPurchaserNum,
  164. @PrRqty,@PrAqty,@PrSqty,@IcitemId,@IcitemName,@PrSsendDate,@PrSarriveDate,@PrUnit,@State,@PrType,@CurrencyType,
  165. @CreateBy,@CreateByName,@CreateTime,@UpdateBy,@UpdateByName,@UpdateTime,@TenantId,@FactoryId,@OrgId,@IsDeleted,@CompanyId,@IsRequireGoods,@SupplierType)
  166. """,
  167. new SugarParameter("@Id", newId),
  168. new SugarParameter("@PrBillNo", billNo),
  169. new SugarParameter("@PrPurchaseId", input.PrPurchaseId!.Value),
  170. new SugarParameter("@PrPurchaseNumber", input.PrPurchaseNumber?.Trim()),
  171. new SugarParameter("@PrPurchaseName", input.PrPurchaseName?.Trim()),
  172. new SugarParameter("@PrPurchaser", input.PrPurchaser?.Trim()),
  173. new SugarParameter("@PrPurchaserNum", input.PrPurchaserNum?.Trim()),
  174. new SugarParameter("@PrRqty", aqty),
  175. new SugarParameter("@PrAqty", aqty),
  176. new SugarParameter("@PrSqty", aqty),
  177. new SugarParameter("@IcitemId", icitemId),
  178. new SugarParameter("@IcitemName", input.IcitemName?.Trim()),
  179. new SugarParameter("@PrSsendDate", prSsendDate),
  180. new SugarParameter("@PrSarriveDate", prSarriveDate),
  181. new SugarParameter("@PrUnit", input.PrUnit?.Trim()),
  182. new SugarParameter("@State", state),
  183. new SugarParameter("@PrType", prType),
  184. new SugarParameter("@CurrencyType", currency),
  185. new SugarParameter("@CreateBy", _userManager.UserId),
  186. new SugarParameter("@CreateByName", _userManager.Account),
  187. new SugarParameter("@CreateTime", now),
  188. new SugarParameter("@UpdateBy", _userManager.UserId),
  189. new SugarParameter("@UpdateByName", _userManager.Account),
  190. new SugarParameter("@UpdateTime", now),
  191. new SugarParameter("@TenantId", tenantId),
  192. new SugarParameter("@FactoryId", _userManager.OrgId),
  193. new SugarParameter("@OrgId", _userManager.OrgId),
  194. new SugarParameter("@IsDeleted", 0),
  195. new SugarParameter("@CompanyId", companyId),
  196. new SugarParameter("@IsRequireGoods", input.IsRequireGoods ?? 0),
  197. new SugarParameter("@SupplierType", input.SupplierType?.Trim())
  198. );
  199. return new { id = newId, prBillNo = billNo, message = "新增成功" };
  200. }
  201. else
  202. {
  203. var existPars = new List<SugarParameter> { new("@Id", input.Id.Value), new("@TenantId", tenantId) };
  204. var exist = await _db.Ado.GetIntAsync(
  205. "SELECT COUNT(1) FROM srm_pr_main WHERE Id=@Id AND IFNULL(IsDeleted,0)=0 AND tenant_id=@TenantId",
  206. existPars);
  207. if (exist <= 0) throw Oops.Oh("记录不存在");
  208. await _db.Ado.ExecuteCommandAsync(
  209. $"""
  210. UPDATE srm_pr_main
  211. SET
  212. pr_purchaseid=@PrPurchaseId,
  213. pr_purchasenumber=@PrPurchaseNumber,
  214. pr_purchasename=@PrPurchaseName,
  215. pr_purchaser=@PrPurchaser,
  216. pr_purchaser_num=@PrPurchaserNum,
  217. pr_rqty=@PrRqty,
  218. pr_aqty=@PrAqty,
  219. pr_sqty=@PrSqty,
  220. icitem_id=@IcitemId,
  221. icitem_name=@IcitemName,
  222. pr_ssend_date=@PrSsendDate,
  223. pr_sarrive_date=@PrSarriveDate,
  224. pr_unit=@PrUnit,
  225. state=@State,
  226. pr_type=@PrType,
  227. currencytype=@CurrencyType,
  228. update_by=@UpdateBy,
  229. update_by_name=@UpdateByName,
  230. update_time=@UpdateTime,
  231. IsRequireGoods=@IsRequireGoods,
  232. supplier_type=@SupplierType
  233. WHERE Id=@Id AND IFNULL(IsDeleted,0)=0 AND tenant_id=@TenantId
  234. """,
  235. new SugarParameter("@Id", input.Id.Value),
  236. new SugarParameter("@PrPurchaseId", input.PrPurchaseId!.Value),
  237. new SugarParameter("@PrPurchaseNumber", input.PrPurchaseNumber?.Trim()),
  238. new SugarParameter("@PrPurchaseName", input.PrPurchaseName?.Trim()),
  239. new SugarParameter("@PrPurchaser", input.PrPurchaser?.Trim()),
  240. new SugarParameter("@PrPurchaserNum", input.PrPurchaserNum?.Trim()),
  241. new SugarParameter("@PrRqty", aqty),
  242. new SugarParameter("@PrAqty", aqty),
  243. new SugarParameter("@PrSqty", aqty),
  244. new SugarParameter("@IcitemId", icitemId),
  245. new SugarParameter("@IcitemName", input.IcitemName?.Trim()),
  246. new SugarParameter("@PrSsendDate", prSsendDate),
  247. new SugarParameter("@PrSarriveDate", prSarriveDate),
  248. new SugarParameter("@PrUnit", input.PrUnit?.Trim()),
  249. new SugarParameter("@State", state),
  250. new SugarParameter("@PrType", prType),
  251. new SugarParameter("@CurrencyType", currency),
  252. new SugarParameter("@UpdateBy", _userManager.UserId),
  253. new SugarParameter("@UpdateByName", _userManager.Account),
  254. new SugarParameter("@UpdateTime", now),
  255. new SugarParameter("@IsRequireGoods", input.IsRequireGoods ?? 0),
  256. new SugarParameter("@SupplierType", input.SupplierType?.Trim()),
  257. new SugarParameter("@TenantId", tenantId)
  258. );
  259. return new { id = input.Id, message = "编辑成功" };
  260. }
  261. }
  262. [DisplayName("删除物料采购申请")]
  263. [HttpPost("purchase-request/delete/{id:long}")]
  264. public async Task<object> Delete(long id)
  265. {
  266. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  267. var pars = new List<SugarParameter>
  268. {
  269. new("@Id", id),
  270. new("@TenantId", tenantId),
  271. new("@UpdateBy", _userManager.UserId),
  272. new("@UpdateByName", _userManager.Account),
  273. new("@UpdateTime", DateTime.Now)
  274. };
  275. var affected = await _db.Ado.ExecuteCommandAsync(
  276. """
  277. UPDATE srm_pr_main
  278. SET IsDeleted=1,state=0,update_by=@UpdateBy,update_by_name=@UpdateByName,update_time=@UpdateTime
  279. WHERE Id=@Id AND IFNULL(IsDeleted,0)=0 AND tenant_id=@TenantId
  280. """,
  281. pars);
  282. if (affected <= 0) throw Oops.Oh("记录不存在");
  283. return new { message = "删除成功" };
  284. }
  285. private static string BuildOrderBy(string? sortField, string? sortOrder)
  286. {
  287. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  288. return sortField?.ToLowerInvariant() switch
  289. {
  290. "prbillno" => $"pm.pr_billno {dir}",
  291. "purord" => $"pod.PurOrd {dir}",
  292. "prtype" => $"pm.pr_type {dir}",
  293. "state" => $"pm.state {dir}",
  294. "number" => $"ic.number {dir}",
  295. "name" => $"ic.name {dir}",
  296. "prpurchasename" => $"pm.pr_purchasename {dir}",
  297. "prsqty" => $"pm.pr_sqty {dir}",
  298. "praqty" => $"pm.pr_aqty {dir}",
  299. "stockqty" => $"its.sqty {dir}",
  300. "prssenddate" => $"pm.pr_ssend_date {dir}",
  301. "prsarrivedate" => $"pm.pr_sarrive_date {dir}",
  302. "createtime" => $"pm.create_time {dir}",
  303. _ => "pm.create_time DESC"
  304. };
  305. }
  306. private static DateTime? ParseDate(string? value) => DateTime.TryParse(value, out var dt) ? dt : null;
  307. private static string GenerateBillNo()
  308. {
  309. return $"PR{DateTime.Now:yyyyMMddHHmmssfff}";
  310. }
  311. private sealed class PurchaseRequestListRow
  312. {
  313. public long Id { get; set; }
  314. public string? PrBillNo { get; set; }
  315. public string? PurOrd { get; set; }
  316. public int? PrType { get; set; }
  317. public string? SupplierType { get; set; }
  318. public int? IsRequireGoods { get; set; }
  319. public int? State { get; set; }
  320. public string? Number { get; set; }
  321. public string? Name { get; set; }
  322. public string? Model { get; set; }
  323. public string? PrUnit { get; set; }
  324. public string? PrPurchaseNumber { get; set; }
  325. public string? PrPurchaseName { get; set; }
  326. public decimal? PrSqty { get; set; }
  327. public decimal? PrAqty { get; set; }
  328. public decimal? StockQty { get; set; }
  329. public DateTime? PrSsendDate { get; set; }
  330. public DateTime? PrSarriveDate { get; set; }
  331. public string? PrPurchaser { get; set; }
  332. public DateTime? CreateTime { get; set; }
  333. }
  334. private sealed class PurchaseRequestDetailRow
  335. {
  336. public long Id { get; set; }
  337. public string? PrBillNo { get; set; }
  338. public string? IcitemId { get; set; }
  339. public string? IcitemName { get; set; }
  340. public string? PrUnit { get; set; }
  341. public string? PrPurchaseNumber { get; set; }
  342. public string? PrPurchaseName { get; set; }
  343. public string? SupplierType { get; set; }
  344. public decimal? PrAqty { get; set; }
  345. public DateTime? PrSsendDate { get; set; }
  346. public DateTime? PrSarriveDate { get; set; }
  347. public int? PrType { get; set; }
  348. public int? State { get; set; }
  349. public long? PrPurchaseId { get; set; }
  350. public int? IsRequireGoods { get; set; }
  351. public string? PrPurchaserNum { get; set; }
  352. public string? PrPurchaser { get; set; }
  353. public long? CurrencyType { get; set; }
  354. }
  355. }