ProductDesignService.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. using Yitter.IdGenerator;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
  3. namespace Admin.NET.Plugin.AiDOP.Order;
  4. /// <summary>
  5. /// 产品设计服务(常规/非标;合同维度跟踪设计负责人、图号、BOM、工艺、图纸计划/实际时间)
  6. /// 路由前缀:/api/Order/productdesign/...
  7. /// </summary>
  8. [ApiDescriptionSettings(Order = 255, Description = "产品设计")]
  9. [Route("api/Order")]
  10. [AllowAnonymous]
  11. [NonUnify]
  12. public class ProductDesignService : IDynamicApiController, ITransient
  13. {
  14. private readonly ISqlSugarClient _db;
  15. private readonly SqlSugarRepository<ProductDesign> _designRep;
  16. private readonly SqlSugarRepository<ProductDesignBom> _bomRep;
  17. private readonly SqlSugarRepository<ProductDesignRouting> _routingRep;
  18. private readonly UserManager _userManager;
  19. public ProductDesignService(
  20. ISqlSugarClient db,
  21. SqlSugarRepository<ProductDesign> designRep,
  22. SqlSugarRepository<ProductDesignBom> bomRep,
  23. SqlSugarRepository<ProductDesignRouting> routingRep,
  24. UserManager userManager)
  25. {
  26. _db = db;
  27. _designRep = designRep;
  28. _bomRep = bomRep;
  29. _routingRep = routingRep;
  30. _userManager = userManager;
  31. }
  32. [DisplayName("获取产品设计列表")]
  33. [HttpGet("productdesign/list")]
  34. public async Task<object> GetProductDesignList([FromQuery] ProductDesignListInput input)
  35. {
  36. var q = _db.Queryable<ProductDesign>()
  37. .WhereIF(!string.IsNullOrWhiteSpace(input.BillNo), u => u.BillNo.Contains(input.BillNo!.Trim()))
  38. .WhereIF(!string.IsNullOrWhiteSpace(input.ContractNo), u => u.ContractNo != null && u.ContractNo.Contains(input.ContractNo!.Trim()))
  39. .WhereIF(input.ProductKind is 1 or 2, u => u.ProductKind == input.ProductKind!.Value)
  40. .WhereIF(!string.IsNullOrWhiteSpace(input.DesignLeadName), u => u.DesignLeadName != null && u.DesignLeadName.Contains(input.DesignLeadName!.Trim()));
  41. var paged = await q.OrderByDescending(u => u.Id).ToPagedListAsync(input.Page, input.PageSize);
  42. var list = paged.Items.Select(u => new
  43. {
  44. id = u.Id,
  45. billNo = u.BillNo,
  46. contractNo = u.ContractNo,
  47. productKind = u.ProductKind,
  48. designLeadAccount = u.DesignLeadAccount,
  49. designLeadName = u.DesignLeadName,
  50. drawingNo = u.DrawingNo,
  51. drawingPlanStart = u.DrawingPlanStart?.ToString("yyyy-MM-dd HH:mm"),
  52. drawingPlanEnd = u.DrawingPlanEnd?.ToString("yyyy-MM-dd HH:mm"),
  53. drawingActualStart = u.DrawingActualStart?.ToString("yyyy-MM-dd HH:mm"),
  54. drawingActualEnd = u.DrawingActualEnd?.ToString("yyyy-MM-dd HH:mm"),
  55. drawingDesignCycle = u.DrawingDesignCycle,
  56. applicant = u.Applicant,
  57. applyDate = u.ApplyDate?.ToString("yyyy-MM-dd"),
  58. productModel = u.ProductModel,
  59. itemNum = u.ItemNum,
  60. productName = u.ProductName,
  61. createTime = u.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  62. updateTime = u.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  63. }).ToList();
  64. return new { total = paged.Total, page = input.Page, pageSize = input.PageSize, list };
  65. }
  66. [DisplayName("获取产品设计详情")]
  67. [HttpGet("productdesign/{id:long}")]
  68. public async Task<object> GetProductDesignDetail(long id)
  69. {
  70. var m = await _designRep.GetFirstAsync(u => u.Id == id)
  71. ?? throw Oops.Oh("产品设计记录不存在");
  72. var boms = await _bomRep.GetListAsync(u => u.ProductDesignId == id);
  73. var routings = await _routingRep.GetListAsync(u => u.ProductDesignId == id);
  74. return new
  75. {
  76. id = m.Id,
  77. billNo = m.BillNo,
  78. contractNo = m.ContractNo,
  79. productKind = m.ProductKind,
  80. designLeadAccount = m.DesignLeadAccount,
  81. designLeadName = m.DesignLeadName,
  82. drawingNo = m.DrawingNo,
  83. drawingPlanStart = m.DrawingPlanStart?.ToString("yyyy-MM-dd HH:mm:ss"),
  84. drawingPlanEnd = m.DrawingPlanEnd?.ToString("yyyy-MM-dd HH:mm:ss"),
  85. drawingActualStart = m.DrawingActualStart?.ToString("yyyy-MM-dd HH:mm:ss"),
  86. drawingActualEnd = m.DrawingActualEnd?.ToString("yyyy-MM-dd HH:mm:ss"),
  87. drawingDesignCycle = m.DrawingDesignCycle,
  88. applicant = m.Applicant,
  89. applyDate = m.ApplyDate?.ToString("yyyy-MM-dd"),
  90. productModel = m.ProductModel,
  91. itemNum = m.ItemNum,
  92. productName = m.ProductName,
  93. language = m.Language,
  94. lineRemark = m.LineRemark,
  95. createUser = m.CreateUser,
  96. createTime = m.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  97. updateUser = m.UpdateUser,
  98. updateTime = m.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  99. boms = boms.OrderBy(x => x.Seq).ThenBy(x => x.Id).Select(x => new
  100. {
  101. id = x.Id.ToString(),
  102. parentBomId = x.ParentBomId.HasValue ? x.ParentBomId.Value.ToString() : null,
  103. seq = x.Seq,
  104. itemNum = x.ItemNum,
  105. itemName = x.ItemName,
  106. processCode = x.ProcessCode,
  107. fixedLossQty = x.FixedLossQty,
  108. batchNo = x.BatchNo,
  109. }),
  110. routings = routings.OrderBy(x => x.Seq).ThenBy(x => x.Id).Select(x => new
  111. {
  112. id = x.Id,
  113. seq = x.Seq,
  114. opName = x.OpName,
  115. opCode = x.OpCode,
  116. isKeyProcess = x.IsKeyProcess,
  117. productionLine = x.ProductionLine,
  118. routeCode = x.RouteCode,
  119. }),
  120. };
  121. }
  122. [DisplayName("保存产品设计")]
  123. [ApiDescriptionSettings(Name = "SaveProductDesign"), HttpPost("productdesign/save")]
  124. public async Task<object> SaveProductDesign([FromBody] ProductDesignSaveInput input)
  125. {
  126. if (input.ProductKind is not (1 or 2))
  127. throw Oops.Oh("产品类型无效,请选择常规产品或非标产品");
  128. var now = DateTime.Now;
  129. var user = _userManager.Account ?? "system";
  130. await _db.Ado.BeginTranAsync();
  131. try
  132. {
  133. long designId;
  134. if (input.Id is null or 0)
  135. {
  136. var month = now.ToString("yyyyMM");
  137. var maxSeq = await _db.Ado.GetIntAsync(
  138. $"SELECT IFNULL(MAX(CAST(SUBSTRING(BillNo, 9) AS UNSIGNED)), 0) FROM ado_product_design WHERE BillNo LIKE 'PD{month}%'");
  139. var billNo = $"PD{month}{(maxSeq + 1):D4}";
  140. designId = YitIdHelper.NextId();
  141. var entity = MapMaster(new ProductDesign { Id = designId, BillNo = billNo }, input, user, now, isNew: true);
  142. await _designRep.InsertAsync(entity);
  143. await SaveBomsAsync(designId, input.Boms ?? new List<ProductDesignBomInput>(), isNew: true);
  144. await SaveRoutingsAsync(designId, input.Routings ?? new List<ProductDesignRoutingInput>(), isNew: true);
  145. await _db.Ado.CommitTranAsync();
  146. return new { id = designId, billNo, message = "新增成功" };
  147. }
  148. var existing = await _designRep.GetFirstAsync(u => u.Id == input.Id!.Value)
  149. ?? throw Oops.Oh("产品设计记录不存在");
  150. designId = existing.Id;
  151. MapMaster(existing, input, user, now, isNew: false);
  152. await _designRep.UpdateAsync(existing);
  153. await SaveBomsAsync(designId, input.Boms ?? new List<ProductDesignBomInput>(), isNew: false);
  154. await SaveRoutingsAsync(designId, input.Routings ?? new List<ProductDesignRoutingInput>(), isNew: false);
  155. await _db.Ado.CommitTranAsync();
  156. return new { id = designId, billNo = existing.BillNo, message = "编辑成功" };
  157. }
  158. catch
  159. {
  160. await _db.Ado.RollbackTranAsync();
  161. throw;
  162. }
  163. }
  164. [DisplayName("删除产品设计")]
  165. [ApiDescriptionSettings(Name = "DeleteProductDesign"), HttpPost("productdesign/delete")]
  166. public async Task<object> DeleteProductDesign([FromBody] ProductDesignDeleteInput input)
  167. {
  168. var entity = await _designRep.GetFirstAsync(u => u.Id == input.Id)
  169. ?? throw Oops.Oh("产品设计记录不存在");
  170. await _db.Ado.BeginTranAsync();
  171. try
  172. {
  173. await _bomRep.DeleteAsync(u => u.ProductDesignId == entity.Id);
  174. await _routingRep.DeleteAsync(u => u.ProductDesignId == entity.Id);
  175. await _designRep.DeleteAsync(u => u.Id == entity.Id);
  176. await _db.Ado.CommitTranAsync();
  177. return new { message = "删除成功" };
  178. }
  179. catch
  180. {
  181. await _db.Ado.RollbackTranAsync();
  182. throw;
  183. }
  184. }
  185. [DisplayName("根据产品编码获取BOM和工艺")]
  186. [HttpGet("productdesign/bom-routing")]
  187. public async Task<BomAndRoutingOutput> GetBomAndRouting([FromQuery] string itemNum)
  188. {
  189. if (string.IsNullOrWhiteSpace(itemNum))
  190. return new BomAndRoutingOutput();
  191. // 查询图纸设计周期
  192. var drawingDesignCycle = await _db.Queryable<AdoS0ItemMaster>()
  193. .Where(x => x.ItemNum == itemNum && x.IsActive == true)
  194. .Select(x => x.DrawingDesign)
  195. .FirstAsync();
  196. var bomSql = @"
  197. WITH RECURSIVE temp(ParentItem,ComponentItem,op,qty,StructureType,QtyConsumed) AS (
  198. SELECT ParentItem,ComponentItem,op,qty,StructureType,QtyConsumed
  199. FROM ProductStructureMaster WHERE ParentItem=@itemNum
  200. UNION ALL
  201. SELECT c.ParentItem,c.ComponentItem,c.op,
  202. CAST(c.qty+(c.qty*(c.Scrap+c.QtyExchd)/100) AS DECIMAL(15,8)) AS qty,
  203. c.StructureType,c.QtyConsumed
  204. FROM ProductStructureMaster c
  205. INNER JOIN temp p ON c.ParentItem=p.ComponentItem
  206. INNER JOIN ItemMaster parentIm ON c.ParentItem=parentIm.ItemNum AND parentIm.PurMfg<>'P'
  207. )
  208. SELECT psm.ParentItem,psm.ComponentItem AS ItemNum,im.Descr AS ItemName,
  209. CASE WHEN IFNULL(pso.Op,0)=0 THEN psm.Op ELSE pso.Op END AS Op,
  210. psm.qty AS Qty,psm.StructureType,im.EMTType AS EmtType,psm.QtyConsumed
  211. FROM temp psm
  212. LEFT JOIN ItemMaster im ON psm.ComponentItem=im.ItemNum
  213. LEFT JOIN ProductStructureOp pso ON pso.ParentItem=psm.ParentItem
  214. AND pso.ComponentItem=psm.ComponentItem AND pso.ProductItem=@itemNum";
  215. var routingSql = @"
  216. SELECT Descr,Op,ParentOp,CAST(MilestoneOp AS CHAR(5)) AS MilestoneOp
  217. FROM RoutingOpDetail WHERE RoutingCode=@itemNum";
  218. var boms = await _db.Ado.SqlQueryAsync<BomQueryRow>(bomSql, new { itemNum });
  219. var routings = await _db.Ado.SqlQueryAsync<RoutingQueryRow>(routingSql, new { itemNum });
  220. return new BomAndRoutingOutput
  221. {
  222. Boms = boms,
  223. Routings = routings,
  224. DrawingDesignCycle = drawingDesignCycle
  225. };
  226. }
  227. private static ProductDesign MapMaster(ProductDesign entity, ProductDesignSaveInput input, string user, DateTime now, bool isNew)
  228. {
  229. entity.ContractNo = string.IsNullOrWhiteSpace(input.ContractNo) ? null : input.ContractNo.Trim();
  230. entity.ProductKind = input.ProductKind;
  231. entity.DesignLeadAccount = string.IsNullOrWhiteSpace(input.DesignLeadAccount) ? null : input.DesignLeadAccount.Trim();
  232. entity.DesignLeadName = string.IsNullOrWhiteSpace(input.DesignLeadName) ? null : input.DesignLeadName.Trim();
  233. entity.DrawingNo = string.IsNullOrWhiteSpace(input.DrawingNo) ? null : input.DrawingNo.Trim();
  234. entity.DrawingPlanStart = ParseOptionalDate(input.DrawingPlanStart);
  235. entity.DrawingPlanEnd = ParseOptionalDate(input.DrawingPlanEnd);
  236. entity.DrawingActualStart = ParseOptionalDate(input.DrawingActualStart);
  237. entity.DrawingActualEnd = ParseOptionalDate(input.DrawingActualEnd);
  238. entity.DrawingDesignCycle = input.DrawingDesignCycle;
  239. entity.Applicant = string.IsNullOrWhiteSpace(input.Applicant) ? null : input.Applicant.Trim();
  240. entity.ApplyDate = ParseOptionalDate(input.ApplyDate);
  241. entity.ProductModel = string.IsNullOrWhiteSpace(input.ProductModel) ? null : input.ProductModel.Trim();
  242. entity.ItemNum = string.IsNullOrWhiteSpace(input.ItemNum) ? null : input.ItemNum.Trim();
  243. entity.ProductName = string.IsNullOrWhiteSpace(input.ProductName) ? null : input.ProductName.Trim();
  244. entity.Language = string.IsNullOrWhiteSpace(input.Language) ? null : input.Language.Trim();
  245. entity.Qty = null;
  246. entity.LineRemark = string.IsNullOrWhiteSpace(input.LineRemark) ? null : input.LineRemark.Trim();
  247. if (isNew)
  248. {
  249. entity.CreateUser = user;
  250. entity.CreateTime = now;
  251. entity.IsActive = 1;
  252. }
  253. else
  254. {
  255. entity.UpdateUser = user;
  256. entity.UpdateTime = now;
  257. }
  258. return entity;
  259. }
  260. private static DateTime? ParseOptionalDate(string? s)
  261. {
  262. if (string.IsNullOrWhiteSpace(s)) return null;
  263. return DateTime.TryParse(s.Trim(), out var dt) ? dt : null;
  264. }
  265. private async Task SaveBomsAsync(long designId, List<ProductDesignBomInput> items, bool isNew)
  266. {
  267. var dbRows = isNew ? new List<ProductDesignBom>()
  268. : await _bomRep.GetListAsync(u => u.ProductDesignId == designId);
  269. var dbById = dbRows.ToDictionary(u => u.Id);
  270. if (items.Any(x => !x.Id.HasValue))
  271. throw Oops.Oh("BOM 每行必须包含 Id(已保存行为正数,新行请使用负数临时 Id)");
  272. var ordered = OrderBomsForSave(items);
  273. var tempToReal = new Dictionary<long, long>();
  274. var keptIds = new HashSet<long>();
  275. var idx = 0;
  276. foreach (var d in ordered)
  277. {
  278. idx++;
  279. var seq = d.Seq ?? idx;
  280. var parentResolved = ResolveBomParentId(d.ParentBomId, tempToReal);
  281. if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
  282. {
  283. if (existing.ProductDesignId != designId) continue;
  284. existing.ParentBomId = parentResolved;
  285. existing.Seq = seq;
  286. existing.ItemNum = string.IsNullOrWhiteSpace(d.ItemNum) ? null : d.ItemNum.Trim();
  287. existing.ItemName = string.IsNullOrWhiteSpace(d.ItemName) ? null : d.ItemName.Trim();
  288. existing.ProcessCode = string.IsNullOrWhiteSpace(d.ProcessCode) ? null : d.ProcessCode.Trim();
  289. existing.Qty = d.Qty;
  290. existing.FixedLossQty = d.FixedLossQty;
  291. existing.BatchNo = string.IsNullOrWhiteSpace(d.BatchNo) ? null : d.BatchNo.Trim();
  292. await _bomRep.UpdateAsync(existing);
  293. keptIds.Add(existing.Id);
  294. }
  295. else if (d.Id is < 0)
  296. {
  297. var newId = YitIdHelper.NextId();
  298. tempToReal[d.Id.Value] = newId;
  299. var row = new ProductDesignBom
  300. {
  301. Id = newId,
  302. ProductDesignId = designId,
  303. ParentBomId = parentResolved,
  304. Seq = seq,
  305. ItemNum = string.IsNullOrWhiteSpace(d.ItemNum) ? null : d.ItemNum.Trim(),
  306. ItemName = string.IsNullOrWhiteSpace(d.ItemName) ? null : d.ItemName.Trim(),
  307. ProcessCode = string.IsNullOrWhiteSpace(d.ProcessCode) ? null : d.ProcessCode.Trim(),
  308. Qty = d.Qty,
  309. FixedLossQty = d.FixedLossQty,
  310. BatchNo = string.IsNullOrWhiteSpace(d.BatchNo) ? null : d.BatchNo.Trim(),
  311. };
  312. await _bomRep.InsertAsync(row);
  313. keptIds.Add(newId);
  314. }
  315. }
  316. foreach (var row in dbRows.Where(u => !keptIds.Contains(u.Id)))
  317. await _bomRep.DeleteAsync(u => u.Id == row.Id);
  318. }
  319. /// <summary>按父子顺序排列:父(含库中已有 Id)先于子;负数临时 Id 先于引用它的子行。</summary>
  320. private static List<ProductDesignBomInput> OrderBomsForSave(List<ProductDesignBomInput> items)
  321. {
  322. var withId = items.Where(x => x.Id.HasValue).ToList();
  323. var inBatch = withId.Select(x => x.Id!.Value).ToHashSet();
  324. var done = new HashSet<long>();
  325. var result = new List<ProductDesignBomInput>();
  326. var remaining = withId.ToList();
  327. bool CanTake(ProductDesignBomInput r)
  328. {
  329. var p = r.ParentBomId;
  330. if (p is null or 0) return true;
  331. if (p < 0) return done.Contains(p.Value);
  332. if (inBatch.Contains(p.Value)) return done.Contains(p.Value);
  333. return true;
  334. }
  335. while (remaining.Count > 0)
  336. {
  337. var batch = remaining.Where(CanTake).ToList();
  338. if (batch.Count == 0)
  339. throw Oops.Oh("BOM 父子关系无效(循环或缺失父节点)");
  340. foreach (var b in batch)
  341. {
  342. result.Add(b);
  343. remaining.Remove(b);
  344. if (b.Id.HasValue)
  345. done.Add(b.Id.Value);
  346. }
  347. }
  348. return result;
  349. }
  350. private static long? ResolveBomParentId(long? parentBomId, Dictionary<long, long> tempToReal)
  351. {
  352. if (parentBomId is null or 0) return null;
  353. if (parentBomId.Value < 0)
  354. {
  355. if (tempToReal.TryGetValue(parentBomId.Value, out var real))
  356. return real;
  357. throw Oops.Oh("BOM 父节点未找到,请检查父子顺序");
  358. }
  359. return parentBomId;
  360. }
  361. private async Task SaveRoutingsAsync(long designId, List<ProductDesignRoutingInput> items, bool isNew)
  362. {
  363. var dbRows = isNew ? new List<ProductDesignRouting>()
  364. : await _routingRep.GetListAsync(u => u.ProductDesignId == designId);
  365. var dbById = dbRows.ToDictionary(u => u.Id);
  366. var inputIds = new HashSet<long>(items.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
  367. var idx = 0;
  368. foreach (var d in items)
  369. {
  370. idx++;
  371. var seq = d.Seq ?? idx;
  372. if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
  373. {
  374. if (existing.ProductDesignId != designId) continue;
  375. existing.Seq = seq;
  376. existing.OpName = string.IsNullOrWhiteSpace(d.OpName) ? null : d.OpName.Trim();
  377. existing.OpCode = string.IsNullOrWhiteSpace(d.OpCode) ? null : d.OpCode.Trim();
  378. existing.ParentOpCode = null;
  379. existing.IsKeyProcess = d.IsKeyProcess;
  380. existing.ProductionLine = string.IsNullOrWhiteSpace(d.ProductionLine) ? null : d.ProductionLine.Trim();
  381. existing.RouteCode = string.IsNullOrWhiteSpace(d.RouteCode) ? null : d.RouteCode.Trim();
  382. await _routingRep.UpdateAsync(existing);
  383. }
  384. else
  385. {
  386. var row = new ProductDesignRouting
  387. {
  388. Id = YitIdHelper.NextId(),
  389. ProductDesignId = designId,
  390. Seq = seq,
  391. OpName = string.IsNullOrWhiteSpace(d.OpName) ? null : d.OpName.Trim(),
  392. OpCode = string.IsNullOrWhiteSpace(d.OpCode) ? null : d.OpCode.Trim(),
  393. ParentOpCode = null,
  394. IsKeyProcess = d.IsKeyProcess,
  395. ProductionLine = string.IsNullOrWhiteSpace(d.ProductionLine) ? null : d.ProductionLine.Trim(),
  396. RouteCode = string.IsNullOrWhiteSpace(d.RouteCode) ? null : d.RouteCode.Trim(),
  397. };
  398. await _routingRep.InsertAsync(row);
  399. }
  400. }
  401. foreach (var toDelete in dbRows.Where(u => !inputIds.Contains(u.Id)))
  402. await _routingRep.DeleteAsync(u => u.Id == toDelete.Id);
  403. }
  404. /// <summary>获取合同下拉选项</summary>
  405. [DisplayName("获取合同下拉选项")]
  406. [HttpGet("productdesign/contract-options")]
  407. public async Task<List<object>> GetContractOptions([FromQuery] string? keyword)
  408. {
  409. var tenantId = _userManager.TenantId;
  410. var sql = string.IsNullOrWhiteSpace(keyword)
  411. ? "SELECT BillNo, CONCAT(BillNo, ' | ', IFNULL(Title,'')) AS Label FROM ado_contract_review WHERE tenant_id = @tenantId ORDER BY RecID DESC LIMIT 50"
  412. : "SELECT BillNo, CONCAT(BillNo, ' | ', IFNULL(Title,'')) AS Label FROM ado_contract_review WHERE tenant_id = @tenantId AND (BillNo LIKE @kw OR Title LIKE @kw) ORDER BY RecID DESC LIMIT 50";
  413. var rows = await _db.Ado.SqlQueryAsync<ContractOptionRow>(sql,
  414. new { tenantId, kw = string.IsNullOrWhiteSpace(keyword) ? null : $"%{keyword.Trim()}%" });
  415. return rows.Select(r => (object)new { value = r.BillNo, label = r.Label }).ToList();
  416. }
  417. /// <summary>获取用户下拉选项</summary>
  418. [DisplayName("获取用户下拉选项")]
  419. [HttpGet("productdesign/user-options")]
  420. public async Task<List<object>> GetUserOptions([FromQuery] string? keyword)
  421. {
  422. var tenantId = _userManager.TenantId;
  423. var sql = string.IsNullOrWhiteSpace(keyword)
  424. ? "SELECT Id, Account, RealName FROM SysUser WHERE TenantId = @tenantId AND Status = 1 ORDER BY OrderNo LIMIT 200"
  425. : "SELECT Id, Account, RealName FROM SysUser WHERE TenantId = @tenantId AND Status = 1 AND (Account LIKE @kw OR RealName LIKE @kw) ORDER BY OrderNo LIMIT 200";
  426. var rows = await _db.Ado.SqlQueryAsync<UserOptionRow>(sql,
  427. new { tenantId, kw = string.IsNullOrWhiteSpace(keyword) ? null : $"%{keyword.Trim()}%" });
  428. return rows.Select(r => (object)new { value = r.Account, label = $"{r.RealName} ({r.Account})" }).ToList();
  429. }
  430. private sealed class ContractOptionRow
  431. {
  432. public string BillNo { get; set; } = string.Empty;
  433. public string Label { get; set; } = string.Empty;
  434. }
  435. private sealed class UserOptionRow
  436. {
  437. public long Id { get; set; }
  438. public string Account { get; set; } = string.Empty;
  439. public string RealName { get; set; } = string.Empty;
  440. }
  441. }