| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- using Admin.NET.Plugin.AiDOP.Dto.S0.Warehouse;
- using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Warehouse;
- /// <summary>
- /// S0 物料职责维护(EmpWorkDutyMaster 语义)。员工 + 库位关联。
- /// 多租户隔离:所有读写显式限定当前请求租户;员工/库位存在性与补显均限定当前租户,杜绝跨租户绑定;不依赖全局 AOP。
- /// </summary>
- [ApiController]
- [Route("api/s0/warehouse/emp-work-duties")]
- [NonUnify]
- public class AdoS0EmpWorkDutiesController : ControllerBase
- {
- private readonly SqlSugarRepository<AdoS0EmpWorkDutyMaster> _rep;
- private readonly SqlSugarRepository<AdoS0EmployeeMaster> _empRep;
- private readonly SqlSugarRepository<AdoS0LocationMaster> _locRep;
- private readonly AdoS0ReferenceChecker _refChecker;
- public AdoS0EmpWorkDutiesController(
- SqlSugarRepository<AdoS0EmpWorkDutyMaster> rep,
- SqlSugarRepository<AdoS0EmployeeMaster> empRep,
- SqlSugarRepository<AdoS0LocationMaster> locRep,
- AdoS0ReferenceChecker refChecker)
- {
- _rep = rep;
- _empRep = empRep;
- _locRep = locRep;
- _refChecker = refChecker;
- }
- [HttpGet]
- public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0EmpWorkDutyQueryDto 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)
- .WhereIF(!string.IsNullOrWhiteSpace(q.Employee), x => x.Employee.Contains(q.Employee!))
- .WhereIF(!string.IsNullOrWhiteSpace(q.Location), x => x.Location == q.Location)
- .WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum),
- x => (x.ItemNum != null && x.ItemNum.Contains(q.ItemNum!))
- || (x.ItemNum1 != null && x.ItemNum1.Contains(q.ItemNum!))
- || (x.ItemNum2 != null && x.ItemNum2.Contains(q.ItemNum!)))
- .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
- x => x.Employee.Contains(q.Keyword!)
- || (x.ItemNum != null && x.ItemNum.Contains(q.Keyword!))
- || (x.ItemNum1 != null && x.ItemNum1.Contains(q.Keyword!))
- || (x.ItemNum2 != null && x.ItemNum2.Contains(q.Keyword!)));
- var total = await query.CountAsync();
- var list = await query
- .OrderBy(x => x.Employee)
- .Skip((q.Page - 1) * q.PageSize)
- .Take(q.PageSize)
- .ToListAsync();
- await ApplyDisplayFieldsAsync(list, tenantId);
- return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
- }
- [HttpGet("{id:long}")]
- public async Task<IActionResult> GetAsync(long 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 ApplyDisplayFieldsAsync(new List<AdoS0EmpWorkDutyMaster> { item }, tenantId);
- return Ok(item);
- }
- [HttpPost]
- public async Task<IActionResult> CreateAsync([FromBody] AdoS0EmpWorkDutyUpsertDto dto)
- {
- if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
- var scopeError = ValidateMaterialScope(dto, out var itemNum, out var itemNum1, out var itemNum2);
- if (scopeError != null) return AdoS0ApiErrors.InvalidRequest(scopeError);
- var relError = await ValidateRelationsInTenantAsync(tenantId, dto);
- if (relError != null) return relError;
- var entity = new AdoS0EmpWorkDutyMaster
- {
- TenantId = tenantId,
- CompanyRefId = dto.CompanyRefId,
- FactoryRefId = dto.FactoryRefId,
- DomainCode = dto.DomainCode ?? string.Empty,
- Employee = dto.Employee,
- ItemNum = itemNum,
- ItemNum1 = itemNum1,
- ItemNum2 = itemNum2,
- Location = dto.Location,
- ProdLine = dto.ProdLine,
- Duty = dto.Duty,
- EmpType = dto.EmpType,
- Ufld2 = dto.Ufld2,
- CreateUser = dto.CreateUser,
- CreateTime = DateTime.Now
- };
- await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
- return Ok(entity);
- }
- [HttpPut("{id:long}")]
- public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0EmpWorkDutyUpsertDto dto)
- {
- 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();
- var scopeError = ValidateMaterialScope(dto, out var itemNum, out var itemNum1, out var itemNum2);
- if (scopeError != null) return AdoS0ApiErrors.InvalidRequest(scopeError);
- var relError = await ValidateRelationsInTenantAsync(tenantId, dto);
- if (relError != null) return relError;
- // entity.TenantId 保持不变(== tenantId);不接受前端修改 tenant
- entity.CompanyRefId = dto.CompanyRefId;
- entity.FactoryRefId = dto.FactoryRefId;
- entity.DomainCode = dto.DomainCode ?? string.Empty;
- entity.Employee = dto.Employee;
- entity.ItemNum = itemNum;
- entity.ItemNum1 = itemNum1;
- entity.ItemNum2 = itemNum2;
- entity.Location = dto.Location;
- entity.ProdLine = dto.ProdLine;
- entity.Duty = dto.Duty;
- entity.EmpType = dto.EmpType;
- entity.Ufld2 = dto.Ufld2;
- entity.UpdateUser = dto.UpdateUser;
- entity.UpdateTime = DateTime.Now;
- await _rep.AsUpdateable(entity).ExecuteCommandAsync();
- return Ok(entity);
- }
- [HttpDelete("{id:long}")]
- public async Task<IActionResult> DeleteAsync(long 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 _rep.DeleteAsync(item);
- return Ok(new { message = "删除成功" });
- }
- /// <summary>员工与库位必须属于当前租户(同 domain),杜绝为他租户员工分配职责/绑定他租户库位。</summary>
- private async Task<IActionResult?> ValidateRelationsInTenantAsync(long tenantId, AdoS0EmpWorkDutyUpsertDto dto)
- {
- if (!string.IsNullOrWhiteSpace(dto.Employee)
- && !await _refChecker.EmployeeExistsInTenantAsync(tenantId, dto.DomainCode, dto.Employee))
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
- $"员工编码 '{dto.Employee}' 不存在于当前租户雇员主数据");
- if (!string.IsNullOrWhiteSpace(dto.Location)
- && !await _refChecker.LocationExistsInTenantAsync(tenantId, dto.DomainCode, dto.Location))
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
- $"库位编码 '{dto.Location}' 不存在于当前租户库位主数据");
- return null;
- }
- private static readonly Dictionary<string, string> DutyMap = new(StringComparer.OrdinalIgnoreCase)
- {
- ["iss-po"] = "采购收货",
- ["iss-so"] = "销售发货",
- ["iss-wo"] = "发料",
- ["packing"] = "退料",
- ["rct-unp"] = "计划外入库",
- ["rct-wo"] = "入库",
- ["Up-Shelf"] = "上架"
- };
- /// <summary>补显员工名 / 库位名。关联 EmployeeMaster、LocationMaster 均限定当前租户,避免跨租户补错。</summary>
- private async Task ApplyDisplayFieldsAsync(List<AdoS0EmpWorkDutyMaster> duties, long tenantId)
- {
- if (duties.Count == 0) return;
- var domainCodes = duties.Select(d => d.DomainCode).Distinct().ToList();
- var employees = await _empRep.AsQueryable()
- .Where(e => e.TenantId == tenantId && domainCodes.Contains(e.DomainCode))
- .ToListAsync();
- var locations = await _locRep.AsQueryable()
- .Where(l => l.TenantId == tenantId && domainCodes.Contains(l.DomainCode))
- .ToListAsync();
- foreach (var duty in duties)
- {
- var emp = employees.Find(e =>
- string.Equals(e.DomainCode, duty.DomainCode, StringComparison.OrdinalIgnoreCase)
- && string.Equals(e.Employee, duty.Employee, StringComparison.OrdinalIgnoreCase));
- duty.EmployeeName = emp?.Name;
- if (!string.IsNullOrWhiteSpace(duty.Location))
- {
- var loc = locations.Find(l =>
- string.Equals(l.DomainCode, duty.DomainCode, StringComparison.OrdinalIgnoreCase)
- && string.Equals(l.Location, duty.Location, StringComparison.OrdinalIgnoreCase));
- duty.LocationName = $"{duty.Location} {loc?.Descr ?? ""}".Trim();
- }
- // Duty 翻译(原始多值逗号/空格拼接场景兼容)
- if (!string.IsNullOrWhiteSpace(duty.Duty))
- {
- var parts = duty.Duty.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
- duty.DutyDisplay = string.Join("、", parts.Select(p => DutyMap.TryGetValue(p.Trim(), out var cn) ? cn : p.Trim()));
- }
- duty.ItemNumDisplay = !string.IsNullOrWhiteSpace(duty.ItemNum)
- ? duty.ItemNum
- : $"{duty.ItemNum1 ?? ""} ~ {duty.ItemNum2 ?? ""}".Trim(' ', '~');
- }
- }
- private static string? ValidateMaterialScope(
- AdoS0EmpWorkDutyUpsertDto dto,
- out string? itemNum,
- out string? itemNum1,
- out string? itemNum2)
- {
- itemNum = string.IsNullOrWhiteSpace(dto.ItemNum) ? null : dto.ItemNum.Trim();
- itemNum1 = string.IsNullOrWhiteSpace(dto.ItemNum1) ? null : dto.ItemNum1.Trim();
- itemNum2 = string.IsNullOrWhiteSpace(dto.ItemNum2) ? null : dto.ItemNum2.Trim();
- if (!string.IsNullOrWhiteSpace(itemNum))
- {
- itemNum1 = null;
- itemNum2 = null;
- return null;
- }
- if (string.IsNullOrWhiteSpace(itemNum1) || string.IsNullOrWhiteSpace(itemNum2))
- return "请填写单物料编码,或完整填写物料编码起/止区间";
- if (string.Compare(itemNum1, itemNum2, StringComparison.OrdinalIgnoreCase) > 0)
- return "物料编码起不能大于物料编码止";
- return null;
- }
- }
|