AdoS0EmpWorkDutiesController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Warehouse;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Warehouse;
  5. /// <summary>
  6. /// S0 物料职责维护(EmpWorkDutyMaster 语义)。员工 + 库位关联。
  7. /// 多租户隔离:所有读写显式限定当前请求租户;员工/库位存在性与补显均限定当前租户,杜绝跨租户绑定;不依赖全局 AOP。
  8. /// </summary>
  9. [ApiController]
  10. [Route("api/s0/warehouse/emp-work-duties")]
  11. [NonUnify]
  12. public class AdoS0EmpWorkDutiesController : ControllerBase
  13. {
  14. private readonly SqlSugarRepository<AdoS0EmpWorkDutyMaster> _rep;
  15. private readonly SqlSugarRepository<AdoS0EmployeeMaster> _empRep;
  16. private readonly SqlSugarRepository<AdoS0LocationMaster> _locRep;
  17. private readonly AdoS0ReferenceChecker _refChecker;
  18. public AdoS0EmpWorkDutiesController(
  19. SqlSugarRepository<AdoS0EmpWorkDutyMaster> rep,
  20. SqlSugarRepository<AdoS0EmployeeMaster> empRep,
  21. SqlSugarRepository<AdoS0LocationMaster> locRep,
  22. AdoS0ReferenceChecker refChecker)
  23. {
  24. _rep = rep;
  25. _empRep = empRep;
  26. _locRep = locRep;
  27. _refChecker = refChecker;
  28. }
  29. [HttpGet]
  30. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0EmpWorkDutyQueryDto q)
  31. {
  32. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  33. (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
  34. var query = _rep.AsQueryable()
  35. .Where(x => x.TenantId == tenantId)
  36. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
  37. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
  38. .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
  39. .WhereIF(!string.IsNullOrWhiteSpace(q.Employee), x => x.Employee.Contains(q.Employee!))
  40. .WhereIF(!string.IsNullOrWhiteSpace(q.Location), x => x.Location == q.Location)
  41. .WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum),
  42. x => (x.ItemNum != null && x.ItemNum.Contains(q.ItemNum!))
  43. || (x.ItemNum1 != null && x.ItemNum1.Contains(q.ItemNum!))
  44. || (x.ItemNum2 != null && x.ItemNum2.Contains(q.ItemNum!)))
  45. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
  46. x => x.Employee.Contains(q.Keyword!)
  47. || (x.ItemNum != null && x.ItemNum.Contains(q.Keyword!))
  48. || (x.ItemNum1 != null && x.ItemNum1.Contains(q.Keyword!))
  49. || (x.ItemNum2 != null && x.ItemNum2.Contains(q.Keyword!)));
  50. var total = await query.CountAsync();
  51. var list = await query
  52. .OrderBy(x => x.Employee)
  53. .Skip((q.Page - 1) * q.PageSize)
  54. .Take(q.PageSize)
  55. .ToListAsync();
  56. await ApplyDisplayFieldsAsync(list, tenantId);
  57. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  58. }
  59. [HttpGet("{id:long}")]
  60. public async Task<IActionResult> GetAsync(long id)
  61. {
  62. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  63. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  64. if (item == null) return NotFound();
  65. await ApplyDisplayFieldsAsync(new List<AdoS0EmpWorkDutyMaster> { item }, tenantId);
  66. return Ok(item);
  67. }
  68. [HttpPost]
  69. public async Task<IActionResult> CreateAsync([FromBody] AdoS0EmpWorkDutyUpsertDto dto)
  70. {
  71. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  72. var scopeError = ValidateMaterialScope(dto, out var itemNum, out var itemNum1, out var itemNum2);
  73. if (scopeError != null) return AdoS0ApiErrors.InvalidRequest(scopeError);
  74. var relError = await ValidateRelationsInTenantAsync(tenantId, dto);
  75. if (relError != null) return relError;
  76. var entity = new AdoS0EmpWorkDutyMaster
  77. {
  78. TenantId = tenantId,
  79. CompanyRefId = dto.CompanyRefId,
  80. FactoryRefId = dto.FactoryRefId,
  81. DomainCode = dto.DomainCode ?? string.Empty,
  82. Employee = dto.Employee,
  83. ItemNum = itemNum,
  84. ItemNum1 = itemNum1,
  85. ItemNum2 = itemNum2,
  86. Location = dto.Location,
  87. ProdLine = dto.ProdLine,
  88. Duty = dto.Duty,
  89. EmpType = dto.EmpType,
  90. Ufld2 = dto.Ufld2,
  91. CreateUser = dto.CreateUser,
  92. CreateTime = DateTime.Now
  93. };
  94. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  95. return Ok(entity);
  96. }
  97. [HttpPut("{id:long}")]
  98. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0EmpWorkDutyUpsertDto dto)
  99. {
  100. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  101. var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  102. if (entity == null) return NotFound();
  103. var scopeError = ValidateMaterialScope(dto, out var itemNum, out var itemNum1, out var itemNum2);
  104. if (scopeError != null) return AdoS0ApiErrors.InvalidRequest(scopeError);
  105. var relError = await ValidateRelationsInTenantAsync(tenantId, dto);
  106. if (relError != null) return relError;
  107. // entity.TenantId 保持不变(== tenantId);不接受前端修改 tenant
  108. entity.CompanyRefId = dto.CompanyRefId;
  109. entity.FactoryRefId = dto.FactoryRefId;
  110. entity.DomainCode = dto.DomainCode ?? string.Empty;
  111. entity.Employee = dto.Employee;
  112. entity.ItemNum = itemNum;
  113. entity.ItemNum1 = itemNum1;
  114. entity.ItemNum2 = itemNum2;
  115. entity.Location = dto.Location;
  116. entity.ProdLine = dto.ProdLine;
  117. entity.Duty = dto.Duty;
  118. entity.EmpType = dto.EmpType;
  119. entity.Ufld2 = dto.Ufld2;
  120. entity.UpdateUser = dto.UpdateUser;
  121. entity.UpdateTime = DateTime.Now;
  122. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  123. return Ok(entity);
  124. }
  125. [HttpDelete("{id:long}")]
  126. public async Task<IActionResult> DeleteAsync(long id)
  127. {
  128. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  129. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  130. if (item == null) return NotFound();
  131. await _rep.DeleteAsync(item);
  132. return Ok(new { message = "删除成功" });
  133. }
  134. /// <summary>员工与库位必须属于当前租户(同 domain),杜绝为他租户员工分配职责/绑定他租户库位。</summary>
  135. private async Task<IActionResult?> ValidateRelationsInTenantAsync(long tenantId, AdoS0EmpWorkDutyUpsertDto dto)
  136. {
  137. if (!string.IsNullOrWhiteSpace(dto.Employee)
  138. && !await _refChecker.EmployeeExistsInTenantAsync(tenantId, dto.DomainCode, dto.Employee))
  139. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
  140. $"员工编码 '{dto.Employee}' 不存在于当前租户雇员主数据");
  141. if (!string.IsNullOrWhiteSpace(dto.Location)
  142. && !await _refChecker.LocationExistsInTenantAsync(tenantId, dto.DomainCode, dto.Location))
  143. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
  144. $"库位编码 '{dto.Location}' 不存在于当前租户库位主数据");
  145. return null;
  146. }
  147. private static readonly Dictionary<string, string> DutyMap = new(StringComparer.OrdinalIgnoreCase)
  148. {
  149. ["iss-po"] = "采购收货",
  150. ["iss-so"] = "销售发货",
  151. ["iss-wo"] = "发料",
  152. ["packing"] = "退料",
  153. ["rct-unp"] = "计划外入库",
  154. ["rct-wo"] = "入库",
  155. ["Up-Shelf"] = "上架"
  156. };
  157. /// <summary>补显员工名 / 库位名。关联 EmployeeMaster、LocationMaster 均限定当前租户,避免跨租户补错。</summary>
  158. private async Task ApplyDisplayFieldsAsync(List<AdoS0EmpWorkDutyMaster> duties, long tenantId)
  159. {
  160. if (duties.Count == 0) return;
  161. var domainCodes = duties.Select(d => d.DomainCode).Distinct().ToList();
  162. var employees = await _empRep.AsQueryable()
  163. .Where(e => e.TenantId == tenantId && domainCodes.Contains(e.DomainCode))
  164. .ToListAsync();
  165. var locations = await _locRep.AsQueryable()
  166. .Where(l => l.TenantId == tenantId && domainCodes.Contains(l.DomainCode))
  167. .ToListAsync();
  168. foreach (var duty in duties)
  169. {
  170. var emp = employees.Find(e =>
  171. string.Equals(e.DomainCode, duty.DomainCode, StringComparison.OrdinalIgnoreCase)
  172. && string.Equals(e.Employee, duty.Employee, StringComparison.OrdinalIgnoreCase));
  173. duty.EmployeeName = emp?.Name;
  174. if (!string.IsNullOrWhiteSpace(duty.Location))
  175. {
  176. var loc = locations.Find(l =>
  177. string.Equals(l.DomainCode, duty.DomainCode, StringComparison.OrdinalIgnoreCase)
  178. && string.Equals(l.Location, duty.Location, StringComparison.OrdinalIgnoreCase));
  179. duty.LocationName = $"{duty.Location} {loc?.Descr ?? ""}".Trim();
  180. }
  181. // Duty 翻译(原始多值逗号/空格拼接场景兼容)
  182. if (!string.IsNullOrWhiteSpace(duty.Duty))
  183. {
  184. var parts = duty.Duty.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
  185. duty.DutyDisplay = string.Join("、", parts.Select(p => DutyMap.TryGetValue(p.Trim(), out var cn) ? cn : p.Trim()));
  186. }
  187. duty.ItemNumDisplay = !string.IsNullOrWhiteSpace(duty.ItemNum)
  188. ? duty.ItemNum
  189. : $"{duty.ItemNum1 ?? ""} ~ {duty.ItemNum2 ?? ""}".Trim(' ', '~');
  190. }
  191. }
  192. private static string? ValidateMaterialScope(
  193. AdoS0EmpWorkDutyUpsertDto dto,
  194. out string? itemNum,
  195. out string? itemNum1,
  196. out string? itemNum2)
  197. {
  198. itemNum = string.IsNullOrWhiteSpace(dto.ItemNum) ? null : dto.ItemNum.Trim();
  199. itemNum1 = string.IsNullOrWhiteSpace(dto.ItemNum1) ? null : dto.ItemNum1.Trim();
  200. itemNum2 = string.IsNullOrWhiteSpace(dto.ItemNum2) ? null : dto.ItemNum2.Trim();
  201. if (!string.IsNullOrWhiteSpace(itemNum))
  202. {
  203. itemNum1 = null;
  204. itemNum2 = null;
  205. return null;
  206. }
  207. if (string.IsNullOrWhiteSpace(itemNum1) || string.IsNullOrWhiteSpace(itemNum2))
  208. return "请填写单物料编码,或完整填写物料编码起/止区间";
  209. if (string.Compare(itemNum1, itemNum2, StringComparison.OrdinalIgnoreCase) > 0)
  210. return "物料编码起不能大于物料编码止";
  211. return null;
  212. }
  213. }