AdoS0EmployeesController.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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 雇员主数据(EmployeeMaster 语义)。
  7. /// 多租户隔离:所有读写显式限定当前请求租户;部门父校验与部门名补显均限定当前租户;不依赖全局 AOP。
  8. /// </summary>
  9. [ApiController]
  10. [Route("api/s0/warehouse/employees")]
  11. [NonUnify]
  12. public class AdoS0EmployeesController : ControllerBase
  13. {
  14. private readonly SqlSugarRepository<AdoS0EmployeeMaster> _rep;
  15. private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
  16. private readonly AdoS0ReferenceChecker _refChecker;
  17. public AdoS0EmployeesController(
  18. SqlSugarRepository<AdoS0EmployeeMaster> rep,
  19. SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
  20. AdoS0ReferenceChecker refChecker)
  21. {
  22. _rep = rep;
  23. _deptRep = deptRep;
  24. _refChecker = refChecker;
  25. }
  26. [HttpGet]
  27. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0EmployeeQueryDto q)
  28. {
  29. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  30. (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
  31. var query = _rep.AsQueryable()
  32. .Where(x => x.TenantId == tenantId)
  33. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
  34. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
  35. .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
  36. .WhereIF(!string.IsNullOrWhiteSpace(q.Department), x => x.Department == q.Department)
  37. .WhereIF(q.IsActive.HasValue, x => x.IsActive == q.IsActive!.Value)
  38. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
  39. x => x.Employee.Contains(q.Keyword!) || (x.Name != null && x.Name.Contains(q.Keyword!)));
  40. var total = await query.CountAsync();
  41. var list = await query
  42. .OrderBy(x => x.Employee)
  43. .Skip((q.Page - 1) * q.PageSize)
  44. .Take(q.PageSize)
  45. .ToListAsync();
  46. await ApplyDeptDescrAsync(list, tenantId);
  47. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  48. }
  49. [HttpGet("{id:long}")]
  50. public async Task<IActionResult> GetAsync(long id)
  51. {
  52. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  53. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  54. if (item == null) return NotFound();
  55. await ApplyDeptDescrAsync(new List<AdoS0EmployeeMaster> { item }, tenantId);
  56. return Ok(item);
  57. }
  58. [HttpPost]
  59. public async Task<IActionResult> CreateAsync([FromBody] AdoS0EmployeeUpsertDto dto)
  60. {
  61. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  62. if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.Employee == dto.Employee))
  63. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "员工编码已存在");
  64. // 所属部门必须存在于当前租户(同 domain + department),杜绝员工绑定他租户部门
  65. if (!string.IsNullOrWhiteSpace(dto.Department)
  66. && !await _refChecker.DepartmentExistsInTenantAsync(tenantId, dto.DomainCode, dto.Department))
  67. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
  68. $"部门编码 '{dto.Department}' 不存在于当前租户部门主数据");
  69. var entity = MapToEntity(dto);
  70. entity.TenantId = tenantId;
  71. entity.CreateTime = DateTime.Now;
  72. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  73. return Ok(entity);
  74. }
  75. [HttpPut("{id:long}")]
  76. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0EmployeeUpsertDto dto)
  77. {
  78. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  79. var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  80. if (entity == null) return NotFound();
  81. if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.Employee == dto.Employee))
  82. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "员工编码已存在");
  83. if (!string.IsNullOrWhiteSpace(dto.Department)
  84. && !await _refChecker.DepartmentExistsInTenantAsync(tenantId, dto.DomainCode, dto.Department))
  85. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
  86. $"部门编码 '{dto.Department}' 不存在于当前租户部门主数据");
  87. // entity.TenantId 保持不变(== tenantId);不接受前端修改 tenant
  88. entity.CompanyRefId = dto.CompanyRefId;
  89. entity.FactoryRefId = dto.FactoryRefId;
  90. entity.DomainCode = dto.DomainCode ?? string.Empty;
  91. entity.Employee = dto.Employee;
  92. entity.Name = dto.Name;
  93. entity.Sex = dto.Sex;
  94. entity.Phone = dto.Phone;
  95. entity.Email = dto.Email;
  96. entity.BirthDate = dto.BirthDate;
  97. entity.Department = dto.Department;
  98. entity.DateEmployed = dto.DateEmployed;
  99. entity.EmploymentStatus = dto.EmploymentStatus;
  100. entity.MaritalStatus = dto.MaritalStatus;
  101. entity.JobTitle = dto.JobTitle;
  102. entity.DateTerminated = dto.DateTerminated;
  103. entity.WorkCtr = dto.WorkCtr;
  104. entity.CarId = dto.CarId;
  105. entity.DefaultWorkLocation = dto.DefaultWorkLocation;
  106. entity.IsActive = dto.IsActive;
  107. entity.UpdateUser = dto.UpdateUser;
  108. entity.UpdateTime = DateTime.Now;
  109. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  110. return Ok(entity);
  111. }
  112. [HttpDelete("{id:long}")]
  113. public async Task<IActionResult> DeleteAsync(long id)
  114. {
  115. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  116. var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
  117. if (item == null) return NotFound();
  118. var refInfo = await _refChecker.EmployeeReferencesAsync(tenantId, item.Employee);
  119. if (refInfo is { } r)
  120. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DeleteBlocked,
  121. $"存在 {r.Count} 条 {r.Table} 引用该雇员,无法删除");
  122. await _rep.DeleteAsync(item);
  123. return Ok(new { message = "删除成功" });
  124. }
  125. private static AdoS0EmployeeMaster MapToEntity(AdoS0EmployeeUpsertDto dto) => new()
  126. {
  127. CompanyRefId = dto.CompanyRefId,
  128. FactoryRefId = dto.FactoryRefId,
  129. DomainCode = dto.DomainCode ?? string.Empty,
  130. Employee = dto.Employee,
  131. Name = dto.Name,
  132. Sex = dto.Sex,
  133. Phone = dto.Phone,
  134. Email = dto.Email,
  135. BirthDate = dto.BirthDate,
  136. Department = dto.Department,
  137. DateEmployed = dto.DateEmployed,
  138. EmploymentStatus = dto.EmploymentStatus,
  139. MaritalStatus = dto.MaritalStatus,
  140. JobTitle = dto.JobTitle,
  141. DateTerminated = dto.DateTerminated,
  142. WorkCtr = dto.WorkCtr,
  143. CarId = dto.CarId,
  144. DefaultWorkLocation = dto.DefaultWorkLocation,
  145. IsActive = dto.IsActive,
  146. CreateUser = dto.CreateUser
  147. };
  148. /// <summary>补显部门说明。关联 DepartmentMaster 限定当前租户,避免同编码部门跨租户补错描述。</summary>
  149. private async Task ApplyDeptDescrAsync(List<AdoS0EmployeeMaster> employees, long tenantId)
  150. {
  151. if (employees.Count == 0) return;
  152. var domainCodes = employees.Select(e => e.DomainCode).Distinct().ToList();
  153. var depts = await _deptRep.AsQueryable()
  154. .Where(d => d.TenantId == tenantId && domainCodes.Contains(d.DomainCode))
  155. .ToListAsync();
  156. foreach (var emp in employees)
  157. {
  158. var dept = depts.Find(d =>
  159. string.Equals(d.DomainCode, emp.DomainCode, StringComparison.OrdinalIgnoreCase)
  160. && string.Equals(d.Department, emp.Department, StringComparison.OrdinalIgnoreCase));
  161. emp.DepartDescr = dept?.Descr;
  162. // JobTitle 直接使用存储值作为展示(GeneralizedCodeMaster 暂不关联)
  163. emp.JobTitleDisplay = emp.JobTitle;
  164. }
  165. }
  166. }