PurchaseOrderService.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. namespace Admin.NET.Plugin.AiDOP.Supply;
  2. /// <summary>
  3. /// 物料采购订单服务
  4. ///
  5. /// 【租户安全边界】需认证访问(已移除类级 <c>[AllowAnonymous]</c>);
  6. /// 租户一律经 <c>AidopTenantScope.ResolveOrThrow</c> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退。
  8. /// </summary>
  9. [ApiDescriptionSettings(Order = 309, Description = "物料采购订单")]
  10. [Route("api/Supply")]
  11. [NonUnify]
  12. public class PurchaseOrderService : IDynamicApiController, ITransient
  13. {
  14. private readonly ISqlSugarClient _db;
  15. private readonly UserManager _userManager;
  16. public PurchaseOrderService(ISqlSugarClient db, UserManager userManager)
  17. {
  18. _db = db;
  19. _userManager = userManager;
  20. }
  21. [DisplayName("物料采购订单列表")]
  22. [HttpGet("purchase-order/list")]
  23. public async Task<object> GetList([FromQuery] PurchaseOrderListInput input)
  24. {
  25. var page = input.Page <= 0 ? 1 : input.Page;
  26. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  27. var offset = (page - 1) * pageSize;
  28. if (await ShouldUseDwdAsync())
  29. {
  30. var dwdResult = (LegacyResult)(await GetDwdListAsync(input, page, pageSize, offset));
  31. // DWD 路径有数据但当前查询未命中 → 回退 legacy 路径(覆盖 MDP 同步延迟场景)
  32. if (dwdResult.total == 0)
  33. {
  34. var legacyResult = (LegacyResult)(await GetLegacyListAsync(input, page, pageSize, offset));
  35. if (legacyResult.total > 0)
  36. return legacyResult;
  37. }
  38. return dwdResult;
  39. }
  40. return await GetLegacyListAsync(input, page, pageSize, offset);
  41. }
  42. /// <summary>Legacy 路径:查 vscm_jhjh + PurOrdMaster</summary>
  43. private async Task<object> GetLegacyListAsync(PurchaseOrderListInput input, int page, int pageSize, int offset)
  44. {
  45. var where = new List<string> { "1=1", "a.cgdd IN (SELECT PurOrd FROM PurOrdMaster WHERE Potype='PO')" };
  46. var pars = new List<SugarParameter>();
  47. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  48. where.Add("a.tenant_id = @TenantId");
  49. pars.Add(new SugarParameter("@TenantId", tenantId));
  50. if (!string.IsNullOrWhiteSpace(input.Cgdd))
  51. {
  52. where.Add("a.cgdd LIKE @Cgdd");
  53. pars.Add(new SugarParameter("@Cgdd", $"%{input.Cgdd.Trim()}%"));
  54. }
  55. if (!string.IsNullOrWhiteSpace(input.Wlbm))
  56. {
  57. where.Add("a.wlbm LIKE @Wlbm");
  58. pars.Add(new SugarParameter("@Wlbm", $"%{input.Wlbm.Trim()}%"));
  59. }
  60. if (!string.IsNullOrWhiteSpace(input.Gysdm))
  61. {
  62. where.Add("a.gysdm LIKE @Gysdm");
  63. pars.Add(new SugarParameter("@Gysdm", $"%{input.Gysdm.Trim()}%"));
  64. }
  65. if (!string.IsNullOrWhiteSpace(input.Sffbcx))
  66. {
  67. where.Add("(CASE WHEN b2.b2id IS NULL THEN 'X' ELSE 'V' END)=@Sffbcx");
  68. pars.Add(new SugarParameter("@Sffbcx", input.Sffbcx.Trim().ToUpperInvariant()));
  69. }
  70. var fromSql = $"""
  71. FROM (
  72. SELECT
  73. a.id, a.wlbm, a.wlms, a.wlgg, a.cgdd, a.jhdsl, a.wjhsl, a.jhd, a.yjjhrq, a.gysdm, a.gysmc,
  74. DATE_FORMAT(DATE(a.yjjhrq), '%Y-%m-%d') AS jhrq,
  75. CASE
  76. WHEN IFNULL(b.jqhf,'')='' AND IFNULL(b.qhdj,'')='' THEN 'wait'
  77. WHEN IFNULL(b.jqhf,'')='' AND IFNULL(b.qhdj,'')<>'' THEN 'refuse'
  78. WHEN b.jqhf IS NOT NULL AND DATEDIFF(DATE(a.yjjhrq), DATE(b.jqhf))>=0 THEN 'OK'
  79. ELSE 'NO'
  80. END AS sfyq,
  81. b.jqhf, a.ddhh, a.th, a.bbh, a.StdCost, a.PurCost, a.PurCost * a.jhdsl AS je,
  82. a.createtime, a.buyer, a.type AS ddlx, a.dw, a.payumconv, a.orddate
  83. FROM vscm_jhjh a
  84. LEFT JOIN (
  85. SELECT glid, MAX(jqhf) AS jqhf, GROUP_CONCAT(qhdj SEPARATOR '') AS qhdj
  86. FROM scm_jhjh_jq
  87. WHERE flag = 0
  88. GROUP BY glid
  89. ) b ON a.id = b.glid
  90. ) a
  91. LEFT JOIN (
  92. SELECT OrdNbr, OrdLine, SUM(ReceiptQty + yssl) AS zshl, SUM(yssl) AS rksl, SUM(QtyReturn) AS bhgsl, SUM(QtyReturned) AS thsl
  93. FROM vscm_cgshrk
  94. GROUP BY OrdNbr, OrdLine
  95. ) b ON a.cgdd = b.OrdNbr AND a.ddhh = b.OrdLine
  96. LEFT JOIN (
  97. SELECT po_bill, po_billline, SUM(sh_delivery_quantity) AS zfhl
  98. FROM scm_shdzb a
  99. INNER JOIN scm_shd b ON a.glid = b.id
  100. WHERE b.state = 0
  101. GROUP BY po_bill, po_billline
  102. ) c ON a.cgdd = c.po_bill AND a.ddhh = c.po_billline
  103. LEFT JOIN (
  104. SELECT cgdd, ddhh, MIN(id) AS b2id, MIN(hfsj) AS hfsj
  105. FROM scm_jhjh_jq
  106. WHERE flag = 2
  107. GROUP BY cgdd, ddhh
  108. ) b2 ON a.cgdd = b2.cgdd AND a.ddhh = b2.ddhh
  109. WHERE {string.Join(" AND ", where)}
  110. """;
  111. var orderBy = BuildOrderBy(input.SortField, input.SortOrder);
  112. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  113. var list = await _db.Ado.SqlQueryAsync<PurchaseOrderListRow>(
  114. $"""
  115. SELECT
  116. a.gysdm AS Gysdm,
  117. a.gysmc AS Gysmc,
  118. a.cgdd AS Cgdd,
  119. a.ddhh AS Ddhh,
  120. a.buyer AS Buyer,
  121. a.orddate AS Orddate,
  122. a.wlbm AS Wlbm,
  123. a.wlms AS Wlms,
  124. a.ddlx AS Ddlx,
  125. a.jhdsl AS Jhdsl,
  126. a.wjhsl AS Wjhsl,
  127. a.jhrq AS Jhrq,
  128. a.th AS Th,
  129. CASE WHEN b2.b2id IS NULL THEN 'X' ELSE DATE_FORMAT(b2.hfsj, '%Y.%m.%d') END AS Sffb,
  130. CASE WHEN b2.b2id IS NULL THEN 'X' ELSE 'V' END AS Sffbcx
  131. {fromSql}
  132. ORDER BY {orderBy}
  133. LIMIT {pageSize} OFFSET {offset}
  134. """,
  135. pars);
  136. return new LegacyResult { total = total, page = page, pageSize = pageSize, list = list, source = "vscm_jhjh" };
  137. }
  138. private async Task<bool> ShouldUseDwdAsync()
  139. {
  140. return await _db.Ado.GetIntAsync("SELECT COUNT(1) FROM dwd_supplier_delivery LIMIT 1") > 0;
  141. }
  142. private async Task<object> GetDwdListAsync(PurchaseOrderListInput input, int page, int pageSize, int offset)
  143. {
  144. var where = new List<string> { "1=1" };
  145. var pars = new List<SugarParameter>();
  146. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  147. where.Add("a.tenant_id = @TenantId");
  148. pars.Add(new SugarParameter("@TenantId", tenantId));
  149. if (!string.IsNullOrWhiteSpace(input.Cgdd))
  150. {
  151. where.Add("a.po_no LIKE @Cgdd");
  152. pars.Add(new SugarParameter("@Cgdd", $"%{input.Cgdd.Trim()}%"));
  153. }
  154. if (!string.IsNullOrWhiteSpace(input.Wlbm))
  155. {
  156. where.Add("a.item_code LIKE @Wlbm");
  157. pars.Add(new SugarParameter("@Wlbm", $"%{input.Wlbm.Trim()}%"));
  158. }
  159. if (!string.IsNullOrWhiteSpace(input.Gysdm))
  160. {
  161. where.Add("a.supplier_code LIKE @Gysdm");
  162. pars.Add(new SugarParameter("@Gysdm", $"%{input.Gysdm.Trim()}%"));
  163. }
  164. if (!string.IsNullOrWhiteSpace(input.Sffbcx))
  165. {
  166. where.Add("CASE WHEN IFNULL(a.delivery_status,'')='OPEN' THEN 'X' ELSE 'V' END = @Sffbcx");
  167. pars.Add(new SugarParameter("@Sffbcx", input.Sffbcx.Trim().ToUpperInvariant()));
  168. }
  169. var fromSql = $"FROM dwd_supplier_delivery a WHERE {string.Join(" AND ", where)}";
  170. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  171. var orderBy = BuildDwdOrderBy(input.SortField, input.SortOrder);
  172. var list = await _db.Ado.SqlQueryAsync<PurchaseOrderListRow>(
  173. $"""
  174. SELECT
  175. a.supplier_code AS Gysdm,
  176. a.supplier_name AS Gysmc,
  177. a.po_no AS Cgdd,
  178. a.po_line AS Ddhh,
  179. NULL AS Buyer,
  180. NULL AS Orddate,
  181. a.item_code AS Wlbm,
  182. a.item_name AS Wlms,
  183. a.po_type AS Ddlx,
  184. a.order_qty AS Jhdsl,
  185. a.remaining_qty AS Wjhsl,
  186. DATE_FORMAT(DATE(COALESCE(a.need_date, a.due_date)), '%Y-%m-%d') AS Jhrq,
  187. NULL AS Th,
  188. CASE WHEN IFNULL(a.delivery_status,'')='OPEN' THEN 'X' ELSE 'V' END AS Sffb,
  189. CASE WHEN IFNULL(a.delivery_status,'')='OPEN' THEN 'X' ELSE 'V' END AS Sffbcx
  190. {fromSql}
  191. ORDER BY {orderBy}
  192. LIMIT {pageSize} OFFSET {offset}
  193. """,
  194. pars);
  195. return new LegacyResult { total = total, page = page, pageSize = pageSize, list = list, source = "dwd_supplier_delivery" };
  196. }
  197. private static string BuildDwdOrderBy(string? sortField, string? sortOrder)
  198. {
  199. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  200. return sortField?.ToLowerInvariant() switch
  201. {
  202. "gysdm" => $"a.supplier_code {dir}",
  203. "gysmc" => $"a.supplier_name {dir}",
  204. "cgdd" => $"a.po_no {dir}",
  205. "ddhh" => $"a.po_line {dir}",
  206. "orddate" => $"a.stat_date {dir}",
  207. "wlbm" => $"a.item_code {dir}",
  208. "wlms" => $"a.item_name {dir}",
  209. "ddlx" => $"a.po_type {dir}",
  210. "jhdsl" => $"a.order_qty {dir}",
  211. "wjhsl" => $"a.remaining_qty {dir}",
  212. "jhrq" => $"a.need_date {dir}",
  213. _ => "a.stat_date DESC, a.po_no DESC, a.po_line ASC"
  214. };
  215. }
  216. private static string BuildOrderBy(string? sortField, string? sortOrder)
  217. {
  218. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  219. return sortField?.ToLowerInvariant() switch
  220. {
  221. "gysdm" => $"a.gysdm {dir}",
  222. "gysmc" => $"a.gysmc {dir}",
  223. "cgdd" => $"a.cgdd {dir}",
  224. "ddhh" => $"a.ddhh {dir}",
  225. "buyer" => $"a.buyer {dir}",
  226. "orddate" => $"a.orddate {dir}",
  227. "wlbm" => $"a.wlbm {dir}",
  228. "wlms" => $"a.wlms {dir}",
  229. "ddlx" => $"a.ddlx {dir}",
  230. "jhdsl" => $"a.jhdsl {dir}",
  231. "wjhsl" => $"a.wjhsl {dir}",
  232. "jhrq" => $"a.jhrq {dir}",
  233. "th" => $"a.th {dir}",
  234. "sffb" => $"b2.hfsj {dir}",
  235. _ => "a.jhrq DESC"
  236. };
  237. }
  238. private sealed class LegacyResult
  239. {
  240. public int total { get; set; }
  241. public int page { get; set; }
  242. public int pageSize { get; set; }
  243. public object? list { get; set; }
  244. public string source { get; set; } = "";
  245. }
  246. private sealed class PurchaseOrderListRow
  247. {
  248. public string? Gysdm { get; set; }
  249. public string? Gysmc { get; set; }
  250. public string? Cgdd { get; set; }
  251. public string? Ddhh { get; set; }
  252. public string? Buyer { get; set; }
  253. public DateTime? Orddate { get; set; }
  254. public string? Wlbm { get; set; }
  255. public string? Wlms { get; set; }
  256. public string? Ddlx { get; set; }
  257. public decimal? Jhdsl { get; set; }
  258. public decimal? Wjhsl { get; set; }
  259. public string? Jhrq { get; set; }
  260. public string? Th { get; set; }
  261. public string? Sffb { get; set; }
  262. public string? Sffbcx { get; set; }
  263. }
  264. }