AdoS0TaskAssignmentsController.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Warehouse;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
  3. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  4. using Admin.NET.Plugin.AiDOP.Infrastructure;
  5. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Warehouse;
  6. /// <summary>
  7. /// S0 物料状态任务指派(wms_rwzp 语义)。
  8. /// 多租户隔离:所有读写显式限定当前请求租户(AdoS0TenantScope.TryResolveRequired),不依赖全局 AOP 兜底。
  9. /// </summary>
  10. [ApiController]
  11. [Route("api/s0/warehouse/task-assignments")]
  12. [NonUnify]
  13. public class AdoS0TaskAssignmentsController : ControllerBase
  14. {
  15. private readonly SqlSugarRepository<AdoS0TaskAssignment> _rep;
  16. public AdoS0TaskAssignmentsController(SqlSugarRepository<AdoS0TaskAssignment> rep)
  17. {
  18. _rep = rep;
  19. }
  20. [HttpGet]
  21. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0TaskAssignmentQueryDto q)
  22. {
  23. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  24. (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
  25. var query = _rep.AsQueryable()
  26. .Where(x => x.TenantId == tenantId)
  27. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
  28. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
  29. .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
  30. .WhereIF(!string.IsNullOrWhiteSpace(q.Rwlx), x => x.Rwlx == q.Rwlx)
  31. .WhereIF(!string.IsNullOrWhiteSpace(q.Zt), x => x.Zt == q.Zt)
  32. .WhereIF(!string.IsNullOrWhiteSpace(q.Sqr), x => x.Sqr != null && x.Sqr.Contains(q.Sqr!))
  33. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
  34. x => (x.Wlbm != null && x.Wlbm.Contains(q.Keyword!)) || (x.Sqr != null && x.Sqr.Contains(q.Keyword!)));
  35. var total = await query.CountAsync();
  36. var list = await query
  37. .OrderByDescending(x => x.Tcrq)
  38. .Skip((q.Page - 1) * q.PageSize)
  39. .Take(q.PageSize)
  40. .ToListAsync();
  41. // 补显 ItemDescr:实体已按设计声明该 [SugarColumn(IsIgnore = true)] 展示字段
  42. //(注释即写明「来自 ItemMaster」),但此前 controller 从未填充,页面「物料名称」列恒为空。
  43. // 单次 ItemMaster 查询 + 字典映射,无 N+1;显式限定当前租户。
  44. await FillItemDisplayAsync(list, tenantId);
  45. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  46. }
  47. [HttpGet("{id:long}")]
  48. public async Task<IActionResult> GetAsync(long id)
  49. {
  50. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  51. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  52. return item == null ? NotFound() : Ok(item);
  53. }
  54. [HttpPost]
  55. public async Task<IActionResult> CreateAsync([FromBody] AdoS0TaskAssignmentUpsertDto dto)
  56. {
  57. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  58. if (string.IsNullOrWhiteSpace(dto.DomainCode))
  59. return AdoS0ApiErrors.InvalidRequest("请填写域编码");
  60. var (companyId, factoryId) = await AdoS0OrgScope.ResolveAsync(_rep.Context, tenantId);
  61. if (companyId <= 0 || factoryId <= 0) return AdoS0OrgScope.NotConfiguredError("任务指派");
  62. // 唯一口径对齐数据库 uk_wms_rwzp_tenant_org_task
  63. // (tenant_id, company_ref_id, factory_ref_id, domain_code, rwlx, wlbm, rqpc, yskw, mdkw)
  64. // 组织维度用后端解析值(与落库口径一致,不用 payload 组织值)。
  65. if (await IsDuplicateAsync(tenantId, companyId, factoryId, dto, null))
  66. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "相同工厂域下该物料任务指派已存在");
  67. var entity = new AdoS0TaskAssignment
  68. {
  69. TenantId = tenantId,
  70. // 组织字段后端强制 stamp 当前租户内解析出的组织,忽略 payload 组织值(杜绝跨租户组织引用)。
  71. CompanyRefId = companyId,
  72. FactoryRefId = factoryId,
  73. DomainCode = dto.DomainCode ?? string.Empty,
  74. Tcrq = dto.Tcrq,
  75. Sqr = dto.Sqr,
  76. Rwlx = dto.Rwlx,
  77. Wlbm = dto.Wlbm,
  78. Sl = dto.Sl,
  79. Rqpc = dto.Rqpc,
  80. Yskw = dto.Yskw,
  81. Mdkw = dto.Mdkw,
  82. Xqsj = dto.Xqsj,
  83. Clr = dto.Clr,
  84. Bz = dto.Bz,
  85. Zt = dto.Zt,
  86. CreateUser = dto.CreateUser,
  87. CreateTime = DateTime.Now
  88. };
  89. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  90. return Ok(entity);
  91. }
  92. [HttpPut("{id:long}")]
  93. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0TaskAssignmentUpsertDto dto)
  94. {
  95. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  96. if (string.IsNullOrWhiteSpace(dto.DomainCode))
  97. return AdoS0ApiErrors.InvalidRequest("请填写域编码");
  98. var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  99. if (entity == null) return NotFound();
  100. // 唯一性预检的组织维度用既有记录原值(组织 pin 原值,不接受 payload 迁移工厂)。
  101. if (await IsDuplicateAsync(tenantId, entity.CompanyRefId, entity.FactoryRefId, dto, id))
  102. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "相同工厂域下该物料任务指派已存在");
  103. // entity.TenantId 保持不变(== tenantId);不接受前端修改 tenant
  104. // 组织字段 pin 到既有原值,忽略 payload 组织值:既杜绝篡改成他租户组织,
  105. // 也不因一次普通编辑就顺手迁移历史 CROSS-TENANT 存量(存量迁移属 B-2,另批处理)。
  106. entity.DomainCode = dto.DomainCode ?? string.Empty;
  107. entity.Tcrq = dto.Tcrq;
  108. entity.Sqr = dto.Sqr;
  109. entity.Rwlx = dto.Rwlx;
  110. entity.Wlbm = dto.Wlbm;
  111. entity.Sl = dto.Sl;
  112. entity.Rqpc = dto.Rqpc;
  113. entity.Yskw = dto.Yskw;
  114. entity.Mdkw = dto.Mdkw;
  115. entity.Xqsj = dto.Xqsj;
  116. entity.Clr = dto.Clr;
  117. entity.Bz = dto.Bz;
  118. entity.Zt = dto.Zt;
  119. entity.UpdateUser = dto.UpdateUser;
  120. entity.UpdateTime = DateTime.Now;
  121. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  122. return Ok(entity);
  123. }
  124. [HttpDelete("{id:long}")]
  125. public async Task<IActionResult> DeleteAsync(long id)
  126. {
  127. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  128. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  129. if (item == null) return NotFound();
  130. await _rep.DeleteAsync(item);
  131. return Ok(new { message = "删除成功" });
  132. }
  133. /// <summary>
  134. /// 租户内业务唯一校验:对齐 uk_wms_rwzp_tenant_org_task
  135. /// (tenant_id, company_ref_id, factory_ref_id, domain_code, rwlx, wlbm, rqpc, yskw, mdkw)。
  136. /// excludeId 非空时排除自身(更新场景)。
  137. /// </summary>
  138. private async Task<bool> IsDuplicateAsync(long tenantId, long companyRefId, long factoryRefId, AdoS0TaskAssignmentUpsertDto dto, long? excludeId)
  139. {
  140. var domainCode = dto.DomainCode ?? string.Empty;
  141. return await _rep.AsQueryable()
  142. .Where(x => x.TenantId == tenantId
  143. && x.CompanyRefId == companyRefId
  144. && x.FactoryRefId == factoryRefId
  145. && x.DomainCode == domainCode
  146. && x.Rwlx == dto.Rwlx
  147. && x.Wlbm == dto.Wlbm
  148. && x.Rqpc == dto.Rqpc
  149. && x.Yskw == dto.Yskw
  150. && x.Mdkw == dto.Mdkw)
  151. .WhereIF(excludeId.HasValue, x => x.Id != excludeId!.Value)
  152. .AnyAsync();
  153. }
  154. /// <summary>批量补显物料名称:一次 ItemMaster 查询 + 字典映射(无 N+1),显式限定当前租户。</summary>
  155. private async Task FillItemDisplayAsync(List<AdoS0TaskAssignment> rows, long tenantId)
  156. {
  157. var itemNums = rows.Select(x => x.Wlbm)
  158. .Where(x => !string.IsNullOrWhiteSpace(x))
  159. .Select(x => x!)
  160. .Distinct()
  161. .ToList();
  162. if (itemNums.Count == 0) return;
  163. var items = await _rep.Context.Queryable<AdoS0ItemMaster>()
  164. .Where(m => m.TenantId == tenantId && itemNums.Contains(m.ItemNum))
  165. .OrderByDescending(m => m.IsActive)
  166. .OrderBy(m => m.Id)
  167. .Select(m => new { m.ItemNum, m.Descr })
  168. .ToListAsync();
  169. var map = items.GroupBy(x => x.ItemNum).ToDictionary(g => g.Key, g => g.First().Descr);
  170. foreach (var r in rows)
  171. if (r.Wlbm != null && map.TryGetValue(r.Wlbm, out var d)) r.ItemDescr = d;
  172. }
  173. }