|
|
@@ -1,42 +1,40 @@
|
|
|
using Admin.NET.Plugin.AiDOP.Dto.S0.Warehouse;
|
|
|
using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
|
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure;
|
|
|
-using Microsoft.Extensions.Logging;
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Warehouse;
|
|
|
|
|
|
/// <summary>
|
|
|
-/// S0 雇员主数据(EmployeeMaster 语义)
|
|
|
+/// S0 雇员主数据(EmployeeMaster 语义)。
|
|
|
+/// 多租户隔离:所有读写显式限定当前请求租户;部门父校验与部门名补显均限定当前租户;不依赖全局 AOP。
|
|
|
/// </summary>
|
|
|
[ApiController]
|
|
|
[Route("api/s0/warehouse/employees")]
|
|
|
-[AllowAnonymous]
|
|
|
[NonUnify]
|
|
|
public class AdoS0EmployeesController : ControllerBase
|
|
|
{
|
|
|
private readonly SqlSugarRepository<AdoS0EmployeeMaster> _rep;
|
|
|
private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
|
|
|
private readonly AdoS0ReferenceChecker _refChecker;
|
|
|
- private readonly ILogger<AdoS0EmployeesController> _logger;
|
|
|
|
|
|
public AdoS0EmployeesController(
|
|
|
SqlSugarRepository<AdoS0EmployeeMaster> rep,
|
|
|
SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
|
|
|
- AdoS0ReferenceChecker refChecker,
|
|
|
- ILogger<AdoS0EmployeesController> logger)
|
|
|
+ AdoS0ReferenceChecker refChecker)
|
|
|
{
|
|
|
_rep = rep;
|
|
|
_deptRep = deptRep;
|
|
|
_refChecker = refChecker;
|
|
|
- _logger = logger;
|
|
|
}
|
|
|
|
|
|
[HttpGet]
|
|
|
public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0EmployeeQueryDto q)
|
|
|
{
|
|
|
+ if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
|
|
|
(q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
|
|
|
|
|
|
var query = _rep.AsQueryable()
|
|
|
+ .Where(x => x.TenantId == tenantId)
|
|
|
.WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
|
|
|
.WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
|
|
|
@@ -52,7 +50,7 @@ public class AdoS0EmployeesController : ControllerBase
|
|
|
.Take(q.PageSize)
|
|
|
.ToListAsync();
|
|
|
|
|
|
- await ApplyDeptDescrAsync(list);
|
|
|
+ await ApplyDeptDescrAsync(list, tenantId);
|
|
|
|
|
|
return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
|
|
|
}
|
|
|
@@ -60,31 +58,29 @@ public class AdoS0EmployeesController : ControllerBase
|
|
|
[HttpGet("{id:long}")]
|
|
|
public async Task<IActionResult> GetAsync(long id)
|
|
|
{
|
|
|
- var item = await _rep.GetByIdAsync(id);
|
|
|
+ if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
|
|
|
+ var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
|
|
|
if (item == null) return NotFound();
|
|
|
- await ApplyDeptDescrAsync(new List<AdoS0EmployeeMaster> { item });
|
|
|
+ await ApplyDeptDescrAsync(new List<AdoS0EmployeeMaster> { item }, tenantId);
|
|
|
return Ok(item);
|
|
|
}
|
|
|
|
|
|
[HttpPost]
|
|
|
public async Task<IActionResult> CreateAsync([FromBody] AdoS0EmployeeUpsertDto dto)
|
|
|
{
|
|
|
- if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.Employee == dto.Employee))
|
|
|
+ if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
|
|
|
+
|
|
|
+ if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.Employee == dto.Employee))
|
|
|
return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "员工编码已存在");
|
|
|
|
|
|
- // B2:Create 一律严格作用域校验,禁止降级(参 AdoS0MfgRoutingOpDetailsController)。
|
|
|
- var deptResult = await _refChecker.DepartmentExistsInScopeAsync(dto.Department, dto.CompanyRefId, dto.FactoryRefId);
|
|
|
- switch (deptResult)
|
|
|
- {
|
|
|
- case MaterialScopeCheck.NotFound:
|
|
|
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
|
|
|
- $"部门编码 '{dto.Department}' 不存在于部门主数据");
|
|
|
- case MaterialScopeCheck.ScopeMiss:
|
|
|
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.InvalidReferenceScope,
|
|
|
- $"部门编码 '{dto.Department}' 不属于当前公司/工厂 (CompanyRefId={dto.CompanyRefId}, FactoryRefId={dto.FactoryRefId})");
|
|
|
- }
|
|
|
+ // 所属部门必须存在于当前租户(同 domain + department),杜绝员工绑定他租户部门
|
|
|
+ if (!string.IsNullOrWhiteSpace(dto.Department)
|
|
|
+ && !await _refChecker.DepartmentExistsInTenantAsync(tenantId, dto.DomainCode, dto.Department))
|
|
|
+ return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
|
|
|
+ $"部门编码 '{dto.Department}' 不存在于当前租户部门主数据");
|
|
|
|
|
|
var entity = MapToEntity(dto);
|
|
|
+ entity.TenantId = tenantId;
|
|
|
entity.CreateTime = DateTime.Now;
|
|
|
await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
|
|
|
return Ok(entity);
|
|
|
@@ -93,46 +89,20 @@ public class AdoS0EmployeesController : ControllerBase
|
|
|
[HttpPut("{id:long}")]
|
|
|
public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0EmployeeUpsertDto dto)
|
|
|
{
|
|
|
- var entity = await _rep.GetByIdAsync(id);
|
|
|
+ if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
|
|
|
+
|
|
|
+ var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
|
|
|
if (entity == null) return NotFound();
|
|
|
|
|
|
- if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.Employee == dto.Employee))
|
|
|
+ if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.Employee == dto.Employee))
|
|
|
return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "员工编码已存在");
|
|
|
|
|
|
- // B2 + 历史兼容降级(D-03 严格条件,参 AdoS0MfgRoutingOpDetailsController)
|
|
|
- var deptResult = await _refChecker.DepartmentExistsInScopeAsync(dto.Department, dto.CompanyRefId, dto.FactoryRefId);
|
|
|
- if (deptResult == MaterialScopeCheck.NotFound)
|
|
|
+ if (!string.IsNullOrWhiteSpace(dto.Department)
|
|
|
+ && !await _refChecker.DepartmentExistsInTenantAsync(tenantId, dto.DomainCode, dto.Department))
|
|
|
return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
|
|
|
- $"部门编码 '{dto.Department}' 不存在于部门主数据");
|
|
|
-
|
|
|
- if (deptResult == MaterialScopeCheck.ScopeMiss)
|
|
|
- {
|
|
|
- // 降级条件(全部满足才允许):
|
|
|
- // 1. 是 Update(已满足)
|
|
|
- // 2. DB 原记录 scope 为 0/0
|
|
|
- // 3. 本次未修改 Department 引用值
|
|
|
- var originScopeEmpty = entity.CompanyRefId == 0 && entity.FactoryRefId == 0;
|
|
|
- var departmentUnchanged = string.Equals(entity.Department, dto.Department?.Trim(), StringComparison.Ordinal);
|
|
|
-
|
|
|
- if (originScopeEmpty && departmentUnchanged)
|
|
|
- {
|
|
|
- _logger.LogWarning(
|
|
|
- "[B2 Downgrade] Table={TableName} PrimaryKey={PrimaryKey} ReferenceField={ReferenceField} " +
|
|
|
- "OldValue={OldValue} NewValue={NewValue} CompanyRefId={CompanyRefId} FactoryRefId={FactoryRefId} " +
|
|
|
- "DowngradeReason={DowngradeReason}",
|
|
|
- "EmployeeMaster", entity.Id, "Department",
|
|
|
- entity.Department, dto.Department,
|
|
|
- dto.CompanyRefId, dto.FactoryRefId,
|
|
|
- "LegacyRecord_OriginScopeZero_DepartmentUnchanged");
|
|
|
- // 放行,继续执行写入
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.InvalidReferenceScope,
|
|
|
- $"部门编码 '{dto.Department}' 不属于当前公司/工厂 (CompanyRefId={dto.CompanyRefId}, FactoryRefId={dto.FactoryRefId})");
|
|
|
- }
|
|
|
- }
|
|
|
+ $"部门编码 '{dto.Department}' 不存在于当前租户部门主数据");
|
|
|
|
|
|
+ // entity.TenantId 保持不变(== tenantId);不接受前端修改 tenant
|
|
|
entity.CompanyRefId = dto.CompanyRefId;
|
|
|
entity.FactoryRefId = dto.FactoryRefId;
|
|
|
entity.DomainCode = dto.DomainCode ?? string.Empty;
|
|
|
@@ -162,10 +132,12 @@ public class AdoS0EmployeesController : ControllerBase
|
|
|
[HttpDelete("{id:long}")]
|
|
|
public async Task<IActionResult> DeleteAsync(long id)
|
|
|
{
|
|
|
- var item = await _rep.GetByIdAsync(id);
|
|
|
+ if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
|
|
|
+
|
|
|
+ var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
|
|
|
if (item == null) return NotFound();
|
|
|
|
|
|
- var refInfo = await _refChecker.EmployeeReferencesAsync(item.Employee);
|
|
|
+ var refInfo = await _refChecker.EmployeeReferencesAsync(tenantId, item.Employee);
|
|
|
if (refInfo is { } r)
|
|
|
return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DeleteBlocked,
|
|
|
$"存在 {r.Count} 条 {r.Table} 引用该雇员,无法删除");
|
|
|
@@ -198,13 +170,14 @@ public class AdoS0EmployeesController : ControllerBase
|
|
|
CreateUser = dto.CreateUser
|
|
|
};
|
|
|
|
|
|
- private async Task ApplyDeptDescrAsync(List<AdoS0EmployeeMaster> employees)
|
|
|
+ /// <summary>补显部门说明。关联 DepartmentMaster 限定当前租户,避免同编码部门跨租户补错描述。</summary>
|
|
|
+ private async Task ApplyDeptDescrAsync(List<AdoS0EmployeeMaster> employees, long tenantId)
|
|
|
{
|
|
|
if (employees.Count == 0) return;
|
|
|
|
|
|
var domainCodes = employees.Select(e => e.DomainCode).Distinct().ToList();
|
|
|
var depts = await _deptRep.AsQueryable()
|
|
|
- .Where(d => domainCodes.Contains(d.DomainCode))
|
|
|
+ .Where(d => d.TenantId == tenantId && domainCodes.Contains(d.DomainCode))
|
|
|
.ToListAsync();
|
|
|
|
|
|
foreach (var emp in employees)
|