AdoS0ProcessFlowCardsController.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Manufacturing;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
  5. /// <summary>
  6. /// 工序流转卡主数据 CRUD(数据源:S0ProcessFlowCard 主表)。
  7. ///
  8. /// 恢复主表链路:本 Controller 完全读写 S0ProcessFlowCard,替换此前误接 legacy MissedPrint 的只读投影。
  9. /// 路由沿用 /api/s0/manufacturing/process-flow-cards,接口地址不变 → 前端 / 菜单 / 权限链路兼容。
  10. ///
  11. /// 组织兜底(后端强制,不只依赖前端 payload):
  12. /// - 新增:公司/工厂由后端在**当前租户内**解析组织并 stamp,忽略 payload 组织值 → 杜绝跨厂注入与跨租户组织引用;
  13. /// - 编辑:公司/工厂 pin 到既有记录原值,忽略 payload 组织值 → 杜绝跨公司/跨工厂篡改;
  14. /// - 详情/删除/启停:按 id 操作,不接受组织入参。
  15. /// 唯一性:依赖 DB 唯一索引 uk_S0ProcessFlowCard_factory_flowcard (factory_ref_id, flow_card_no);
  16. /// 预检友好 409,并发竞态由唯一索引兜底(AdoS0ExceptionFilter 将重复键映射为友好 409)。
  17. /// </summary>
  18. [ApiController]
  19. [Route("api/s0/manufacturing/process-flow-cards")]
  20. [NonUnify]
  21. public class AdoS0ProcessFlowCardsController : ControllerBase
  22. {
  23. private readonly SqlSugarRepository<AdoS0ProcessFlowCard> _rep;
  24. private readonly SqlSugarRepository<AdoS0MfgRoutingOpDetail> _routingRep;
  25. public AdoS0ProcessFlowCardsController(
  26. SqlSugarRepository<AdoS0ProcessFlowCard> rep,
  27. SqlSugarRepository<AdoS0MfgRoutingOpDetail> routingRep)
  28. {
  29. _rep = rep;
  30. _routingRep = routingRep;
  31. }
  32. [HttpGet]
  33. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0ProcessFlowCardQueryDto q)
  34. {
  35. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  36. var page = q.EffectivePage;
  37. var pageSize = q.PageSize;
  38. (page, pageSize) = PagingGuard.Normalize(page, pageSize);
  39. var query = _rep.AsQueryable()
  40. .Where(x => x.TenantId == tenantId)
  41. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
  42. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
  43. .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
  44. .WhereIF(!string.IsNullOrWhiteSpace(q.WorkOrderNo), x => x.WorkOrderNo.Contains(q.WorkOrderNo!))
  45. .WhereIF(!string.IsNullOrWhiteSpace(q.FlowCardNo), x => x.FlowCardNo.Contains(q.FlowCardNo!))
  46. .WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum), x => x.ItemNum.Contains(q.ItemNum!))
  47. .WhereIF(q.IsActive.HasValue, x => x.IsActive == q.IsActive!.Value)
  48. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
  49. x => x.WorkOrderNo.Contains(q.Keyword!)
  50. || x.FlowCardNo.Contains(q.Keyword!)
  51. || x.ItemNum.Contains(q.Keyword!)
  52. || (x.ItemName != null && x.ItemName.Contains(q.Keyword!)));
  53. var total = await query.CountAsync();
  54. var list = await query
  55. .OrderByDescending(x => x.CreateTime)
  56. .OrderByDescending(x => x.Id)
  57. .Skip((page - 1) * pageSize).Take(pageSize)
  58. .ToListAsync();
  59. return Ok(new { total, page, pageSize, list });
  60. }
  61. [HttpGet("{id:long}")]
  62. public async Task<IActionResult> GetAsync(long id)
  63. {
  64. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  65. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  66. return item == null ? AdoS0ApiErrors.NotFound("流转卡不存在") : Ok(item);
  67. }
  68. [HttpPost]
  69. public async Task<IActionResult> CreateAsync([FromBody] AdoS0ProcessFlowCardUpsertDto dto)
  70. {
  71. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  72. var (companyId, factoryId) = await AdoS0OrgScope.ResolveAsync(_rep.Context, tenantId);
  73. if (companyId <= 0 || factoryId <= 0) return AdoS0OrgScope.NotConfiguredError("流转卡");
  74. var flowCardNo = dto.FlowCardNo?.Trim() ?? string.Empty;
  75. if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == factoryId && x.FlowCardNo == flowCardNo))
  76. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "流转卡号已存在");
  77. var now = DateTime.Now;
  78. var entity = new AdoS0ProcessFlowCard
  79. {
  80. TenantId = tenantId,
  81. // 组织字段后端强制 stamp 当前租户内解析出的组织,忽略 payload 组织值(杜绝跨厂注入与跨租户组织引用)。
  82. CompanyRefId = companyId,
  83. FactoryRefId = factoryId,
  84. DomainCode = dto.DomainCode?.Trim(),
  85. WorkOrderNo = dto.WorkOrderNo?.Trim() ?? string.Empty,
  86. WorkOrderBatchNo = dto.WorkOrderBatchNo?.Trim(),
  87. FlowCardNo = flowCardNo,
  88. Nature = dto.Nature?.Trim(),
  89. ItemNum = dto.ItemNum?.Trim() ?? string.Empty,
  90. ItemName = dto.ItemName?.Trim(),
  91. DrawingNo = dto.DrawingNo?.Trim(),
  92. BomVersion = dto.BomVersion?.Trim(),
  93. RoutingCode = dto.RoutingCode?.Trim(),
  94. Remarks = dto.Remarks?.Trim(),
  95. IsActive = dto.IsActive,
  96. CreateUser = dto.CreateUser,
  97. CreateTime = now,
  98. UpdateUser = dto.UpdateUser,
  99. UpdateTime = null
  100. };
  101. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  102. return Ok(entity);
  103. }
  104. [HttpPut("{id:long}")]
  105. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0ProcessFlowCardUpsertDto dto)
  106. {
  107. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  108. var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  109. if (entity == null) return AdoS0ApiErrors.NotFound("流转卡不存在");
  110. var flowCardNo = dto.FlowCardNo?.Trim() ?? string.Empty;
  111. // 唯一性预检以既有记录的工厂为界(组织 pin 原值,不接受 payload 迁移工厂)。
  112. if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == entity.FactoryRefId && x.FlowCardNo == flowCardNo))
  113. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "流转卡号已存在");
  114. // 组织字段 pin 到既有原值,忽略 payload 组织值(杜绝跨公司/跨工厂篡改)。
  115. entity.WorkOrderNo = dto.WorkOrderNo?.Trim() ?? string.Empty;
  116. entity.WorkOrderBatchNo = dto.WorkOrderBatchNo?.Trim();
  117. entity.FlowCardNo = flowCardNo;
  118. entity.Nature = dto.Nature?.Trim();
  119. entity.ItemNum = dto.ItemNum?.Trim() ?? string.Empty;
  120. entity.ItemName = dto.ItemName?.Trim();
  121. entity.DrawingNo = dto.DrawingNo?.Trim();
  122. entity.BomVersion = dto.BomVersion?.Trim();
  123. entity.RoutingCode = dto.RoutingCode?.Trim();
  124. entity.Remarks = dto.Remarks?.Trim();
  125. entity.IsActive = dto.IsActive;
  126. entity.DomainCode = dto.DomainCode?.Trim();
  127. entity.UpdateUser = dto.UpdateUser;
  128. entity.UpdateTime = DateTime.Now;
  129. await _rep.AsUpdateable(entity)
  130. .IgnoreColumns(x => new { x.CompanyRefId, x.FactoryRefId, x.CreateUser, x.CreateTime })
  131. .ExecuteCommandAsync();
  132. return Ok(entity);
  133. }
  134. [HttpPatch("{id:long}/toggle-enabled")]
  135. public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0ProcessFlowCardToggleActiveDto dto)
  136. {
  137. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  138. var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  139. if (entity == null) return AdoS0ApiErrors.NotFound("流转卡不存在");
  140. entity.IsActive = dto.IsEnabled;
  141. entity.UpdateTime = DateTime.Now;
  142. await _rep.AsUpdateable(entity)
  143. .UpdateColumns(x => new { x.IsActive, x.UpdateTime })
  144. .ExecuteCommandAsync();
  145. return Ok(entity);
  146. }
  147. [HttpDelete("{id:long}")]
  148. public async Task<IActionResult> DeleteAsync(long id)
  149. {
  150. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  151. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  152. if (item == null) return AdoS0ApiErrors.NotFound("流转卡不存在");
  153. await _rep.DeleteAsync(item);
  154. return Ok(new { message = "删除成功" });
  155. }
  156. }