ProductDesignService.cs 23 KB

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