ProductDesignService.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  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. // 产品编码必填
  132. if (string.IsNullOrWhiteSpace(input.ItemNum))
  133. throw Oops.Oh("产品编码不能为空");
  134. // BOM 子表至少一条
  135. if (input.Boms == null || input.Boms.Count == 0)
  136. throw Oops.Oh("制造 BOM 至少需要一条数据");
  137. // 工艺子表至少一条
  138. if (input.Routings == null || input.Routings.Count == 0)
  139. throw Oops.Oh("工艺路线至少需要一条数据");
  140. // BOM 物料编码必填
  141. if (input.Boms.Any(b => string.IsNullOrWhiteSpace(b.ItemNum)))
  142. throw Oops.Oh("制造 BOM 中物料编码不能为空");
  143. // 工艺工序代码必填
  144. if (input.Routings.Any(r => string.IsNullOrWhiteSpace(r.OpCode)))
  145. throw Oops.Oh("工艺路线中工序代码不能为空");
  146. var now = DateTime.Now;
  147. var user = _userManager.Account ?? "system";
  148. var tenantId = _userManager.TenantId;
  149. await _db.Ado.BeginTranAsync();
  150. try
  151. {
  152. long designId;
  153. if (input.Id is null or 0)
  154. {
  155. var month = now.ToString("yyyyMM");
  156. var maxSeq = await _db.Ado.GetIntAsync(
  157. $"SELECT IFNULL(MAX(CAST(SUBSTRING(BillNo, 9) AS UNSIGNED)), 0) FROM ado_product_design WHERE BillNo LIKE 'PD{month}%' AND tenant_id = @TenantId",
  158. new SugarParameter("@TenantId", tenantId));
  159. var billNo = $"PD{month}{(maxSeq + 1):D4}";
  160. designId = YitIdHelper.NextId();
  161. var entity = MapMaster(new ProductDesign { Id = designId, BillNo = billNo, TenantId = tenantId }, input, user, now, isNew: true);
  162. await _designRep.InsertAsync(entity);
  163. await SaveBomsAsync(designId, tenantId, input.Boms ?? new List<ProductDesignBomInput>(), isNew: true);
  164. await SaveRoutingsAsync(designId, tenantId, input.Routings ?? new List<ProductDesignRoutingInput>(), isNew: true);
  165. await _db.Ado.CommitTranAsync();
  166. return new { id = designId, billNo, message = "新增成功" };
  167. }
  168. var existing = await _designRep.GetFirstAsync(u => u.Id == input.Id!.Value && u.TenantId == tenantId)
  169. ?? throw Oops.Oh("产品设计记录不存在");
  170. designId = existing.Id;
  171. MapMaster(existing, input, user, now, isNew: false);
  172. await _designRep.UpdateAsync(existing);
  173. await SaveBomsAsync(designId, tenantId, input.Boms ?? new List<ProductDesignBomInput>(), isNew: false);
  174. await SaveRoutingsAsync(designId, tenantId, input.Routings ?? new List<ProductDesignRoutingInput>(), isNew: false);
  175. await _db.Ado.CommitTranAsync();
  176. return new { id = designId, billNo = existing.BillNo, message = "编辑成功" };
  177. }
  178. catch
  179. {
  180. await _db.Ado.RollbackTranAsync();
  181. throw;
  182. }
  183. }
  184. [DisplayName("删除产品设计")]
  185. [ApiDescriptionSettings(Name = "DeleteProductDesign"), HttpPost("productdesign/delete")]
  186. public async Task<object> DeleteProductDesign([FromBody] ProductDesignDeleteInput input)
  187. {
  188. var tenantId = _userManager.TenantId;
  189. var entity = await _designRep.GetFirstAsync(u => u.Id == input.Id && u.TenantId == tenantId)
  190. ?? throw Oops.Oh("产品设计记录不存在");
  191. await _db.Ado.BeginTranAsync();
  192. try
  193. {
  194. await _bomRep.DeleteAsync(u => u.ProductDesignId == entity.Id && u.TenantId == tenantId);
  195. await _routingRep.DeleteAsync(u => u.ProductDesignId == entity.Id && u.TenantId == tenantId);
  196. await _designRep.DeleteAsync(u => u.Id == entity.Id);
  197. await _db.Ado.CommitTranAsync();
  198. return new { message = "删除成功" };
  199. }
  200. catch
  201. {
  202. await _db.Ado.RollbackTranAsync();
  203. throw;
  204. }
  205. }
  206. [DisplayName("根据产品编码获取BOM和工艺")]
  207. [HttpGet("productdesign/bom-routing")]
  208. public async Task<BomAndRoutingOutput> GetBomAndRouting([FromQuery] string itemNum)
  209. {
  210. if (string.IsNullOrWhiteSpace(itemNum))
  211. return new BomAndRoutingOutput();
  212. // 查询图纸设计周期
  213. var drawingDesignCycle = await _db.Queryable<AdoS0ItemMaster>()
  214. .Where(x => x.ItemNum == itemNum && x.IsActive == true)
  215. .Select(x => x.DrawingDesign)
  216. .FirstAsync();
  217. var bomSql = @"
  218. WITH RECURSIVE temp(ParentItem,ComponentItem,op,qty,StructureType,QtyConsumed) AS (
  219. SELECT ParentItem,ComponentItem,op,qty,StructureType,QtyConsumed
  220. FROM ProductStructureMaster WHERE ParentItem=@itemNum
  221. UNION ALL
  222. SELECT c.ParentItem,c.ComponentItem,c.op,
  223. CAST(c.qty+(c.qty*(c.Scrap+c.QtyExchd)/100) AS DECIMAL(15,8)) AS qty,
  224. c.StructureType,c.QtyConsumed
  225. FROM ProductStructureMaster c
  226. INNER JOIN temp p ON c.ParentItem=p.ComponentItem
  227. INNER JOIN ItemMaster parentIm ON c.ParentItem=parentIm.ItemNum AND parentIm.PurMfg<>'P'
  228. )
  229. SELECT psm.ParentItem,psm.ComponentItem AS ItemNum,im.Descr AS ItemName,
  230. CASE WHEN IFNULL(pso.Op,0)=0 THEN psm.Op ELSE pso.Op END AS Op,
  231. psm.qty AS Qty,psm.StructureType,im.EMTType AS EmtType,psm.QtyConsumed
  232. FROM temp psm
  233. LEFT JOIN ItemMaster im ON psm.ComponentItem=im.ItemNum
  234. LEFT JOIN ProductStructureOp pso ON pso.ParentItem=psm.ParentItem
  235. AND pso.ComponentItem=psm.ComponentItem AND pso.ProductItem=@itemNum";
  236. var routingSql = @"
  237. SELECT r.Descr,r.Op,r.ParentOp,CAST(r.MilestoneOp AS CHAR(5)) AS MilestoneOp,p.Line,r.RouteCode
  238. FROM RoutingOpDetail as r left join ProdLineDetail as p on r.RoutingCode=p.Part and r.Op=p.Op WHERE RoutingCode=@itemNum ORDER BY r.Op";
  239. var boms = await _db.Ado.SqlQueryAsync<BomQueryRow>(bomSql, new { itemNum });
  240. var routings = await _db.Ado.SqlQueryAsync<RoutingQueryRow>(routingSql, new { itemNum });
  241. return new BomAndRoutingOutput
  242. {
  243. Boms = boms,
  244. Routings = routings,
  245. DrawingDesignCycle = drawingDesignCycle
  246. };
  247. }
  248. private static ProductDesign MapMaster(ProductDesign entity, ProductDesignSaveInput input, string user, DateTime now, bool isNew)
  249. {
  250. entity.ContractNo = string.IsNullOrWhiteSpace(input.ContractNo) ? null : input.ContractNo.Trim();
  251. entity.ProductKind = input.ProductKind;
  252. entity.DesignLeadAccount = string.IsNullOrWhiteSpace(input.DesignLeadAccount) ? null : input.DesignLeadAccount.Trim();
  253. entity.DesignLeadName = string.IsNullOrWhiteSpace(input.DesignLeadName) ? null : input.DesignLeadName.Trim();
  254. entity.DrawingNo = string.IsNullOrWhiteSpace(input.DrawingNo) ? null : input.DrawingNo.Trim();
  255. entity.DrawingPlanStart = ParseOptionalDate(input.DrawingPlanStart);
  256. entity.DrawingPlanEnd = ParseOptionalDate(input.DrawingPlanEnd);
  257. entity.DrawingActualStart = ParseOptionalDate(input.DrawingActualStart);
  258. entity.DrawingActualEnd = ParseOptionalDate(input.DrawingActualEnd);
  259. entity.DrawingDesignCycle = input.DrawingDesignCycle;
  260. entity.Applicant = string.IsNullOrWhiteSpace(input.Applicant) ? null : input.Applicant.Trim();
  261. entity.ApplyDate = ParseOptionalDate(input.ApplyDate);
  262. entity.ProductModel = string.IsNullOrWhiteSpace(input.ProductModel) ? null : input.ProductModel.Trim();
  263. entity.ItemNum = string.IsNullOrWhiteSpace(input.ItemNum) ? null : input.ItemNum.Trim();
  264. entity.ProductName = string.IsNullOrWhiteSpace(input.ProductName) ? null : input.ProductName.Trim();
  265. entity.Language = string.IsNullOrWhiteSpace(input.Language) ? null : input.Language.Trim();
  266. entity.Qty = null;
  267. entity.LineRemark = string.IsNullOrWhiteSpace(input.LineRemark) ? null : input.LineRemark.Trim();
  268. if (isNew)
  269. {
  270. entity.CreateUser = user;
  271. entity.CreateTime = now;
  272. entity.IsActive = 1;
  273. }
  274. else
  275. {
  276. entity.UpdateUser = user;
  277. entity.UpdateTime = now;
  278. }
  279. return entity;
  280. }
  281. private static DateTime? ParseOptionalDate(string? s)
  282. {
  283. if (string.IsNullOrWhiteSpace(s)) return null;
  284. return DateTime.TryParse(s.Trim(), out var dt) ? dt : null;
  285. }
  286. private async Task SaveBomsAsync(long designId, long tenantId, List<ProductDesignBomInput> items, bool isNew)
  287. {
  288. var dbRows = isNew ? new List<ProductDesignBom>()
  289. : await _bomRep.GetListAsync(u => u.ProductDesignId == designId && u.TenantId == tenantId);
  290. var dbById = dbRows.ToDictionary(u => u.Id);
  291. if (items.Any(x => !x.Id.HasValue))
  292. throw Oops.Oh("BOM 每行必须包含 Id(已保存行为正数,新行请使用负数临时 Id)");
  293. var ordered = OrderBomsForSave(items);
  294. var tempToReal = new Dictionary<long, long>();
  295. var keptIds = new HashSet<long>();
  296. var idx = 0;
  297. foreach (var d in ordered)
  298. {
  299. idx++;
  300. var seq = d.Seq ?? idx;
  301. var parentResolved = ResolveBomParentId(d.ParentBomId, tempToReal);
  302. if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
  303. {
  304. if (existing.ProductDesignId != designId) continue;
  305. existing.ParentBomId = parentResolved;
  306. existing.Seq = seq;
  307. existing.ItemNum = string.IsNullOrWhiteSpace(d.ItemNum) ? null : d.ItemNum.Trim();
  308. existing.ItemName = string.IsNullOrWhiteSpace(d.ItemName) ? null : d.ItemName.Trim();
  309. existing.ProcessCode = string.IsNullOrWhiteSpace(d.ProcessCode) ? null : d.ProcessCode.Trim();
  310. existing.Qty = d.Qty;
  311. existing.FixedLossQty = d.FixedLossQty;
  312. existing.BatchNo = string.IsNullOrWhiteSpace(d.BatchNo) ? null : d.BatchNo.Trim();
  313. await _bomRep.UpdateAsync(existing);
  314. keptIds.Add(existing.Id);
  315. }
  316. else if (d.Id is < 0)
  317. {
  318. var newId = YitIdHelper.NextId();
  319. tempToReal[d.Id.Value] = newId;
  320. var row = new ProductDesignBom
  321. {
  322. Id = newId,
  323. ProductDesignId = designId,
  324. TenantId = tenantId,
  325. ParentBomId = parentResolved,
  326. Seq = seq,
  327. ItemNum = string.IsNullOrWhiteSpace(d.ItemNum) ? null : d.ItemNum.Trim(),
  328. ItemName = string.IsNullOrWhiteSpace(d.ItemName) ? null : d.ItemName.Trim(),
  329. ProcessCode = string.IsNullOrWhiteSpace(d.ProcessCode) ? null : d.ProcessCode.Trim(),
  330. Qty = d.Qty,
  331. FixedLossQty = d.FixedLossQty,
  332. BatchNo = string.IsNullOrWhiteSpace(d.BatchNo) ? null : d.BatchNo.Trim(),
  333. };
  334. await _bomRep.InsertAsync(row);
  335. keptIds.Add(newId);
  336. }
  337. }
  338. foreach (var row in dbRows.Where(u => !keptIds.Contains(u.Id)))
  339. await _bomRep.DeleteAsync(u => u.Id == row.Id && u.TenantId == tenantId);
  340. }
  341. /// <summary>按父子顺序排列:父(含库中已有 Id)先于子;负数临时 Id 先于引用它的子行。</summary>
  342. private static List<ProductDesignBomInput> OrderBomsForSave(List<ProductDesignBomInput> items)
  343. {
  344. var withId = items.Where(x => x.Id.HasValue).ToList();
  345. var inBatch = withId.Select(x => x.Id!.Value).ToHashSet();
  346. var done = new HashSet<long>();
  347. var result = new List<ProductDesignBomInput>();
  348. var remaining = withId.ToList();
  349. bool CanTake(ProductDesignBomInput r)
  350. {
  351. var p = r.ParentBomId;
  352. if (p is null or 0) return true;
  353. if (p < 0) return done.Contains(p.Value);
  354. if (inBatch.Contains(p.Value)) return done.Contains(p.Value);
  355. return true;
  356. }
  357. while (remaining.Count > 0)
  358. {
  359. var batch = remaining.Where(CanTake).ToList();
  360. if (batch.Count == 0)
  361. throw Oops.Oh("BOM 父子关系无效(循环或缺失父节点)");
  362. foreach (var b in batch)
  363. {
  364. result.Add(b);
  365. remaining.Remove(b);
  366. if (b.Id.HasValue)
  367. done.Add(b.Id.Value);
  368. }
  369. }
  370. return result;
  371. }
  372. private static long? ResolveBomParentId(long? parentBomId, Dictionary<long, long> tempToReal)
  373. {
  374. if (parentBomId is null or 0) return null;
  375. if (parentBomId.Value < 0)
  376. {
  377. if (tempToReal.TryGetValue(parentBomId.Value, out var real))
  378. return real;
  379. throw Oops.Oh("BOM 父节点未找到,请检查父子顺序");
  380. }
  381. return parentBomId;
  382. }
  383. private async Task SaveRoutingsAsync(long designId, long tenantId, List<ProductDesignRoutingInput> items, bool isNew)
  384. {
  385. var dbRows = isNew ? new List<ProductDesignRouting>()
  386. : await _routingRep.GetListAsync(u => u.ProductDesignId == designId && u.TenantId == tenantId);
  387. var dbById = dbRows.ToDictionary(u => u.Id);
  388. var inputIds = new HashSet<long>(items.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
  389. var idx = 0;
  390. foreach (var d in items)
  391. {
  392. idx++;
  393. var seq = d.Seq ?? idx;
  394. if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
  395. {
  396. if (existing.ProductDesignId != designId) continue;
  397. existing.Seq = seq;
  398. existing.OpName = string.IsNullOrWhiteSpace(d.OpName) ? null : d.OpName.Trim();
  399. existing.OpCode = string.IsNullOrWhiteSpace(d.OpCode) ? null : d.OpCode.Trim();
  400. existing.ParentOpCode = null;
  401. existing.IsKeyProcess = d.IsKeyProcess;
  402. existing.ProductionLine = string.IsNullOrWhiteSpace(d.ProductionLine) ? null : d.ProductionLine.Trim();
  403. existing.RouteCode = string.IsNullOrWhiteSpace(d.RouteCode) ? null : d.RouteCode.Trim();
  404. await _routingRep.UpdateAsync(existing);
  405. }
  406. else
  407. {
  408. var row = new ProductDesignRouting
  409. {
  410. Id = YitIdHelper.NextId(),
  411. ProductDesignId = designId,
  412. TenantId = tenantId,
  413. Seq = seq,
  414. OpName = string.IsNullOrWhiteSpace(d.OpName) ? null : d.OpName.Trim(),
  415. OpCode = string.IsNullOrWhiteSpace(d.OpCode) ? null : d.OpCode.Trim(),
  416. ParentOpCode = null,
  417. IsKeyProcess = d.IsKeyProcess,
  418. ProductionLine = string.IsNullOrWhiteSpace(d.ProductionLine) ? null : d.ProductionLine.Trim(),
  419. RouteCode = string.IsNullOrWhiteSpace(d.RouteCode) ? null : d.RouteCode.Trim(),
  420. };
  421. await _routingRep.InsertAsync(row);
  422. }
  423. }
  424. foreach (var toDelete in dbRows.Where(u => !inputIds.Contains(u.Id)))
  425. await _routingRep.DeleteAsync(u => u.Id == toDelete.Id && u.TenantId == tenantId);
  426. }
  427. /// <summary>获取合同下拉选项</summary>
  428. [DisplayName("获取合同下拉选项")]
  429. [HttpGet("productdesign/contract-options")]
  430. public async Task<List<object>> GetContractOptions([FromQuery] string? keyword)
  431. {
  432. var tenantId = _userManager.TenantId;
  433. var sql = string.IsNullOrWhiteSpace(keyword)
  434. ? "SELECT BillNo, CONCAT(BillNo, ' | ', IFNULL(Title,'')) AS Label FROM ado_contract_review WHERE tenant_id = @tenantId ORDER BY RecID DESC LIMIT 50"
  435. : "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";
  436. var rows = await _db.Ado.SqlQueryAsync<ContractOptionRow>(sql,
  437. new { tenantId, kw = string.IsNullOrWhiteSpace(keyword) ? null : $"%{keyword.Trim()}%" });
  438. return rows.Select(r => (object)new { value = r.BillNo, label = r.Label }).ToList();
  439. }
  440. /// <summary>获取用户下拉选项</summary>
  441. [DisplayName("获取用户下拉选项")]
  442. [HttpGet("productdesign/user-options")]
  443. public async Task<List<object>> GetUserOptions([FromQuery] string? keyword)
  444. {
  445. var tenantId = _userManager.TenantId;
  446. var sql = string.IsNullOrWhiteSpace(keyword)
  447. ? "SELECT Id, Account, RealName FROM SysUser WHERE TenantId = @tenantId AND Status = 1 ORDER BY OrderNo LIMIT 200"
  448. : "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";
  449. var rows = await _db.Ado.SqlQueryAsync<UserOptionRow>(sql,
  450. new { tenantId, kw = string.IsNullOrWhiteSpace(keyword) ? null : $"%{keyword.Trim()}%" });
  451. return rows.Select(r => (object)new { value = r.Account, label = $"{r.RealName} ({r.Account})" }).ToList();
  452. }
  453. /// <summary>获取产线下拉选项</summary>
  454. [DisplayName("获取产线下拉选项")]
  455. [HttpGet("productdesign/line-options")]
  456. public async Task<List<object>> GetLineOptions([FromQuery] string? keyword)
  457. {
  458. var tenantId = _userManager.TenantId;
  459. var sql = string.IsNullOrWhiteSpace(keyword)
  460. ? "SELECT Line, `Describe` FROM LineMaster WHERE tenant_id = @tenantId AND IsActive = 1 ORDER BY Line LIMIT 200"
  461. : "SELECT Line, `Describe` FROM LineMaster WHERE tenant_id = @tenantId AND IsActive = 1 AND (Line LIKE @kw OR `Describe` LIKE @kw) ORDER BY Line LIMIT 200";
  462. var rows = await _db.Ado.SqlQueryAsync<LineOptionRow>(sql,
  463. new { tenantId, kw = string.IsNullOrWhiteSpace(keyword) ? null : $"%{keyword.Trim()}%" });
  464. return rows.Select(r => (object)new { value = r.Line, label = $"{r.Line} | {r.Describe}" }).ToList();
  465. }
  466. private sealed class ContractOptionRow
  467. {
  468. public string BillNo { get; set; } = string.Empty;
  469. public string Label { get; set; } = string.Empty;
  470. }
  471. private sealed class UserOptionRow
  472. {
  473. public long Id { get; set; }
  474. public string Account { get; set; } = string.Empty;
  475. public string RealName { get; set; } = string.Empty;
  476. }
  477. private sealed class LineOptionRow
  478. {
  479. public string Line { get; set; } = string.Empty;
  480. public string Describe { get; set; } = string.Empty;
  481. }
  482. }