AdoS0MfgProductionLinesController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Manufacturing;
  2. using Admin.NET.Plugin.AiDOP.Dto.S0.Sales;
  3. using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
  4. using Admin.NET.Plugin.AiDOP.Infrastructure;
  5. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
  6. /// <summary>
  7. /// 生产线维护(LineMaster 语义,表 ado_s0_mfg_line_master)。
  8. /// </summary>
  9. [ApiController]
  10. [Route("api/s0/manufacturing/production-lines")]
  11. [NonUnify]
  12. public class AdoS0MfgProductionLinesController : ControllerBase
  13. {
  14. private readonly SqlSugarRepository<AdoS0LineMaster> _rep;
  15. private readonly AdoS0ReferenceChecker _refChecker;
  16. public AdoS0MfgProductionLinesController(SqlSugarRepository<AdoS0LineMaster> rep, AdoS0ReferenceChecker refChecker)
  17. {
  18. _rep = rep;
  19. _refChecker = refChecker;
  20. }
  21. /// <summary>
  22. /// B1-9:LineMaster 6 个 Location 变体的统一 existence check。
  23. /// 全部复用 AdoS0ReferenceChecker.LocationExistsAsync,空值放行。
  24. ///
  25. /// LEGACY REFERENCE PRESERVATION(与 D-03 并列的独立兼容合同,不改 D-03 语义):
  26. /// UPDATE 时,若某个库位字段的值与库中原值**完全未变化**,且该引用的校验作用域
  27. /// (LocationExistsAsync 只取 tenantId,UPDATE 中租户不可变)也未变化,
  28. /// 则允许原值 preserve,跳过这一个字段的存在性校验。
  29. /// 任何被**修改过**的值仍走严格校验;CREATE 传 existing = null,永远严格。
  30. ///
  31. /// 动机:T01 的主库位是历史描述文本「脱包车间周转仓」,不在 LocationMaster 中。
  32. /// 在此之前,用户即使什么都不改也无法保存该行。
  33. /// 本豁免只允许「旧值 → 同一个旧值」,绝不放行任何新的非法值。
  34. /// </summary>
  35. private async Task<IActionResult?> ValidateLocationReferencesAsync(
  36. AdoS0LineMasterUpsertDto dto, long tenantId, AdoS0LineMaster? existing = null)
  37. {
  38. var checks = new (string? Value, string? Original, string Label)[]
  39. {
  40. (dto.Location, existing?.Location, "主库位"),
  41. (dto.VLocation, existing?.VLocation, "虚拟库位"),
  42. (dto.Location2, existing?.Location2, "辅助库位2"),
  43. (dto.Location3, existing?.Location3, "辅助库位3"),
  44. (dto.PickingLocation, existing?.PickingLocation, "拣料库位"),
  45. (dto.MidLocation, existing?.MidLocation, "中间库位"),
  46. };
  47. // 与落库归一化保持一致(Trim + 空串视为 null),避免仅因空白差异被判为「已修改」
  48. static string? Normalize(string? v) => string.IsNullOrWhiteSpace(v) ? null : v.Trim();
  49. foreach (var (value, original, label) in checks)
  50. {
  51. // 值未变化(UPDATE 且与原值逐字相同)→ preserve,不重新校验
  52. if (existing != null && string.Equals(Normalize(value), Normalize(original), StringComparison.Ordinal))
  53. continue;
  54. if (!await _refChecker.LocationExistsAsync(tenantId, value))
  55. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
  56. $"{label}编码 '{value}' 不存在于库位主数据");
  57. }
  58. return null;
  59. }
  60. [HttpGet]
  61. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0LineMasterQueryDto q)
  62. {
  63. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  64. var page = q.EffectivePage;
  65. var pageSize = q.PageSize;
  66. (page, pageSize) = PagingGuard.Normalize(page, pageSize);
  67. var query = _rep.ScopedTo(tenantId)
  68. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
  69. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
  70. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
  71. x => x.Line.Contains(q.Keyword!) || (x.Describe != null && x.Describe.Contains(q.Keyword!)))
  72. .WhereIF(!string.IsNullOrWhiteSpace(q.Line), x => x.Line.Contains(q.Line!))
  73. .WhereIF(!string.IsNullOrWhiteSpace(q.Workshop),
  74. x => x.Workshop != null && x.Workshop.Contains(q.Workshop!))
  75. .WhereIF(q.IsEnabled.HasValue, x => x.IsActive == q.IsEnabled!.Value);
  76. var total = await query.CountAsync();
  77. var entities = await query
  78. .OrderByDescending(x => x.Id)
  79. .Skip((page - 1) * pageSize)
  80. .Take(pageSize)
  81. .ToListAsync();
  82. // GeneralizedCodeMaster 中文说明:当前库无码表实体,占位供列表展示;后续可 JOIN 补全。
  83. var list = entities.Select(x => new
  84. {
  85. x.Id,
  86. x.CompanyRefId,
  87. x.FactoryRefId,
  88. x.Domain,
  89. x.Line,
  90. describe = x.Describe,
  91. x.LineType,
  92. x.LineCategory,
  93. x.Location,
  94. x.Workshop,
  95. vLocation = x.VLocation,
  96. x.Location2,
  97. x.Location3,
  98. pickingLocation = x.PickingLocation,
  99. midLocation = x.MidLocation,
  100. isActive = x.IsActive,
  101. x.CreateUser,
  102. x.CreateTime,
  103. x.UpdateUser,
  104. x.UpdateTime,
  105. lineCategoryComments = (string?)null,
  106. workshopComments = (string?)null
  107. }).ToList();
  108. return Ok(new { total, page, pageSize, list });
  109. }
  110. [HttpGet("{id:long}")]
  111. public async Task<IActionResult> GetAsync(long id)
  112. {
  113. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  114. var item = await _rep.ByIdScopedAsync(id, tenantId);
  115. return item == null ? NotFound() : Ok(item);
  116. }
  117. [HttpPost]
  118. public async Task<IActionResult> CreateAsync([FromBody] AdoS0LineMasterUpsertDto dto)
  119. {
  120. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  121. var locErr = await ValidateLocationReferencesAsync(dto, tenantId);
  122. if (locErr != null) return locErr;
  123. var now = DateTime.Now;
  124. var entity = new AdoS0LineMaster
  125. {
  126. TenantId = tenantId,
  127. CompanyRefId = dto.CompanyRefId,
  128. FactoryRefId = dto.FactoryRefId,
  129. Domain = dto.Domain.Trim(),
  130. Line = dto.Line.Trim(),
  131. Describe = string.IsNullOrWhiteSpace(dto.Describe) ? null : dto.Describe.Trim(),
  132. LineType = string.IsNullOrWhiteSpace(dto.LineType) ? null : dto.LineType.Trim(),
  133. LineCategory = string.IsNullOrWhiteSpace(dto.LineCategory) ? null : dto.LineCategory.Trim(),
  134. Location = string.IsNullOrWhiteSpace(dto.Location) ? null : dto.Location.Trim(),
  135. Workshop = string.IsNullOrWhiteSpace(dto.Workshop) ? null : dto.Workshop.Trim(),
  136. VLocation = string.IsNullOrWhiteSpace(dto.VLocation) ? null : dto.VLocation.Trim(),
  137. Location2 = string.IsNullOrWhiteSpace(dto.Location2) ? null : dto.Location2.Trim(),
  138. Location3 = string.IsNullOrWhiteSpace(dto.Location3) ? null : dto.Location3.Trim(),
  139. PickingLocation = string.IsNullOrWhiteSpace(dto.PickingLocation) ? null : dto.PickingLocation.Trim(),
  140. MidLocation = string.IsNullOrWhiteSpace(dto.MidLocation) ? null : dto.MidLocation.Trim(),
  141. IsActive = dto.IsActive,
  142. CreateUser = dto.CreateUser,
  143. CreateTime = now,
  144. UpdateUser = null,
  145. UpdateTime = null
  146. };
  147. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  148. return Ok(entity);
  149. }
  150. [HttpPut("{id:long}")]
  151. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0LineMasterUpsertDto dto)
  152. {
  153. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  154. var entity = await _rep.ByIdScopedAsync(id, tenantId);
  155. if (entity == null) return NotFound();
  156. // 传入库中原记录 → 未变化的历史库位值可 preserve;被修改的值仍严格校验
  157. var locErr = await ValidateLocationReferencesAsync(dto, tenantId, entity);
  158. if (locErr != null) return locErr;
  159. entity.CompanyRefId = dto.CompanyRefId;
  160. entity.FactoryRefId = dto.FactoryRefId;
  161. entity.Domain = dto.Domain.Trim();
  162. entity.Line = dto.Line.Trim();
  163. entity.Describe = string.IsNullOrWhiteSpace(dto.Describe) ? null : dto.Describe.Trim();
  164. entity.LineType = string.IsNullOrWhiteSpace(dto.LineType) ? null : dto.LineType.Trim();
  165. entity.LineCategory = string.IsNullOrWhiteSpace(dto.LineCategory) ? null : dto.LineCategory.Trim();
  166. entity.Location = string.IsNullOrWhiteSpace(dto.Location) ? null : dto.Location.Trim();
  167. entity.Workshop = string.IsNullOrWhiteSpace(dto.Workshop) ? null : dto.Workshop.Trim();
  168. entity.VLocation = string.IsNullOrWhiteSpace(dto.VLocation) ? null : dto.VLocation.Trim();
  169. entity.Location2 = string.IsNullOrWhiteSpace(dto.Location2) ? null : dto.Location2.Trim();
  170. entity.Location3 = string.IsNullOrWhiteSpace(dto.Location3) ? null : dto.Location3.Trim();
  171. entity.PickingLocation = string.IsNullOrWhiteSpace(dto.PickingLocation) ? null : dto.PickingLocation.Trim();
  172. entity.MidLocation = string.IsNullOrWhiteSpace(dto.MidLocation) ? null : dto.MidLocation.Trim();
  173. entity.IsActive = dto.IsActive;
  174. entity.UpdateUser = dto.UpdateUser;
  175. entity.UpdateTime = DateTime.Now;
  176. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  177. return Ok(entity);
  178. }
  179. [HttpPatch("{id:long}/toggle-enabled")]
  180. public async Task<IActionResult> ToggleEnabledAsync(long id, [FromBody] AdoS0ToggleEnabledDto dto)
  181. {
  182. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  183. var entity = await _rep.ByIdScopedAsync(id, tenantId);
  184. if (entity == null) return NotFound();
  185. entity.IsActive = dto.IsEnabled;
  186. entity.UpdateTime = DateTime.Now;
  187. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  188. return Ok(new { entity.Id, isActive = entity.IsActive, entity.UpdateTime });
  189. }
  190. [HttpDelete("{id:long}")]
  191. public async Task<IActionResult> DeleteAsync(long id)
  192. {
  193. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  194. var item = await _rep.ByIdScopedAsync(id, tenantId);
  195. if (item == null) return NotFound();
  196. var refInfo = await _refChecker.ProductionLineReferencesAsync(tenantId, item.Id, item.Line);
  197. if (refInfo is { } r)
  198. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DeleteBlocked,
  199. $"存在 {r.Count} 条 {r.Table} 引用该生产线,无法删除");
  200. await _rep.DeleteAsync(item);
  201. return Ok(new { message = "删除成功" });
  202. }
  203. }