| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- using Admin.NET.Plugin.AiDOP.Dto.S0.Manufacturing;
- using Admin.NET.Plugin.AiDOP.Dto.S0.Sales;
- using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
- [ApiController]
- [Route("api/s0/manufacturing/line-posts")]
- [NonUnify]
- public class AdoS0MfgLinePostsController : ControllerBase
- {
- private readonly SqlSugarRepository<AdoS0LineSkillMaster> _postRep;
- private readonly SqlSugarRepository<AdoS0LineSkillDetail> _skillRep;
- private readonly SqlSugarRepository<AdoS0MfgPersonSkill> _personSkillRep;
- private readonly SqlSugarRepository<AdoS0LineMaster> _lineMasterRep;
- public AdoS0MfgLinePostsController(
- SqlSugarRepository<AdoS0LineSkillMaster> postRep,
- SqlSugarRepository<AdoS0LineSkillDetail> skillRep,
- SqlSugarRepository<AdoS0MfgPersonSkill> personSkillRep,
- SqlSugarRepository<AdoS0LineMaster> lineMasterRep)
- {
- _postRep = postRep;
- _skillRep = skillRep;
- _personSkillRep = personSkillRep;
- _lineMasterRep = lineMasterRep;
- }
- [HttpGet]
- public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0LineSkillMasterQueryDto q)
- {
- if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
- var page = q.Page;
- var pageSize = q.PageSize;
- (page, pageSize) = PagingGuard.Normalize(page, pageSize);
- var domain = q.Domain?.Trim();
- var query = _postRep.ScopedTo(tenantId)
- .WhereIF(!string.IsNullOrWhiteSpace(domain), x => x.Domain == domain)
- .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
- x => x.ProdLine.Contains(q.Keyword!) || x.JOBNo.Contains(q.Keyword!) || (x.JOBDescr != null && x.JOBDescr.Contains(q.Keyword!)));
- var total = await query.CountAsync();
- var list = await query
- .OrderByDescending(x => x.UpdateTime ?? x.CreateTime)
- .Skip((page - 1) * pageSize)
- .Take(pageSize)
- .ToListAsync();
- // 补展示字段:按 (Domain, ProdLine) 元组反查 LineMaster.Describe,避免跨域错配
- var domains = list.Select(x => x.Domain).Distinct().ToList();
- var lineCodes = list.Select(x => x.ProdLine).Distinct().ToList();
- var lineMap = await _lineMasterRep.ScopedTo(tenantId)
- .Where(lm => domains.Contains(lm.Domain) && lineCodes.Contains(lm.Line))
- .Select(lm => new { lm.Domain, lm.Line, lm.Describe })
- .ToListAsync();
- var describeByKey = lineMap
- .GroupBy(x => (x.Domain, x.Line))
- .ToDictionary(g => g.Key, g => g.First().Describe ?? string.Empty);
- var shaped = list.Select(x => new
- {
- id = x.Id,
- domain = x.Domain,
- prodLine = x.ProdLine,
- prodLineDisplay = $"{x.ProdLine} {(describeByKey.TryGetValue((x.Domain, x.ProdLine), out var d) ? d : string.Empty)}".Trim(),
- jobNo = x.JOBNo,
- jobDescr = x.JOBDescr,
- site = x.Site,
- orderby = x.Orderby,
- wType = x.WType,
- runCrew = x.RunCrew,
- createUser = x.CreateUser,
- createTime = x.CreateTime,
- updateUser = x.UpdateUser,
- updateTime = x.UpdateTime
- }).ToList();
- return Ok(new { total, page, pageSize, list = shaped });
- }
- [HttpGet("{id:long}")]
- public async Task<IActionResult> GetDetailAsync(long id)
- {
- if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
- var header = await _postRep.ByIdScopedAsync(id, tenantId);
- if (header == null) return NotFound();
- var skills = await _skillRep.ScopedTo(tenantId)
- .Where(x => x.LineSkillMasterId == id)
- .OrderBy(x => x.Id)
- .ToListAsync();
- // 补显人员技能的业务编码/名称:一次批量查询 + 字典映射,杜绝 N+1;
- // 显式限定当前租户,不跨租户取名。
- await FillPersonSkillDisplayAsync(skills, tenantId);
- return Ok(new { header, linePostSkills = skills });
- }
- [HttpPost]
- public async Task<IActionResult> CreateAsync([FromBody] AdoS0LineSkillMasterUpsertDto dto)
- {
- if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
- var (fkErr, skillMap) = await ResolvePersonSkillCodesAsync(dto, tenantId);
- if (fkErr != null) return BadRequest(new { message = fkErr });
- var db = _postRep.Context;
- await db.Ado.BeginTranAsync();
- try
- {
- var now = DateTime.Now;
- var post = new AdoS0LineSkillMaster
- {
- TenantId = tenantId,
- Domain = dto.Domain.Trim(),
- ProdLine = dto.ProdLine.Trim(),
- JOBNo = dto.JOBNo.Trim(),
- JOBDescr = dto.JOBDescr,
- Site = dto.Site,
- Orderby = dto.Orderby,
- WType = dto.WType,
- RunCrew = dto.RunCrew,
- CreateUser = dto.CreateUser ?? dto.UpdateUser,
- CreateTime = dto.CreateTime ?? dto.UpdateTime ?? now,
- UpdateUser = dto.UpdateUser,
- UpdateTime = dto.UpdateTime
- };
- post = await _postRep.AsInsertable(post).ExecuteReturnEntityAsync();
- foreach (var s in dto.Skills)
- {
- var row = MapSkill(post, s, tenantId, skillMap);
- await _skillRep.AsInsertable(row).ExecuteCommandAsync();
- }
- await db.Ado.CommitTranAsync();
- return await GetDetailAsync(post.Id);
- }
- catch
- {
- await db.Ado.RollbackTranAsync();
- throw;
- }
- }
- [HttpPut("{id:long}")]
- public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0LineSkillMasterUpsertDto dto)
- {
- if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
- var header = await _postRep.ByIdScopedAsync(id, tenantId);
- if (header == null) return NotFound();
- var (fkErr, skillMap) = await ResolvePersonSkillCodesAsync(dto, tenantId);
- if (fkErr != null) return BadRequest(new { message = fkErr });
- var db = _postRep.Context;
- await db.Ado.BeginTranAsync();
- try
- {
- header.Domain = dto.Domain.Trim();
- header.ProdLine = dto.ProdLine.Trim();
- header.JOBNo = dto.JOBNo.Trim();
- header.JOBDescr = dto.JOBDescr;
- header.Site = dto.Site;
- header.Orderby = dto.Orderby;
- header.WType = dto.WType;
- header.RunCrew = dto.RunCrew;
- header.UpdateUser = dto.UpdateUser;
- header.UpdateTime = dto.UpdateTime ?? DateTime.Now;
- await _postRep.AsUpdateable(header).ExecuteCommandAsync();
- await _skillRep.AsDeleteable().Where(x => x.LineSkillMasterId == id && x.TenantId == tenantId).ExecuteCommandAsync();
- foreach (var s in dto.Skills)
- {
- var row = MapSkill(header, s, tenantId, skillMap);
- await _skillRep.AsInsertable(row).ExecuteCommandAsync();
- }
- await db.Ado.CommitTranAsync();
- return await GetDetailAsync(id);
- }
- catch
- {
- await db.Ado.RollbackTranAsync();
- throw;
- }
- }
- [HttpDelete("{id:long}")]
- public async Task<IActionResult> DeleteAsync(long id)
- {
- if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
- var header = await _postRep.ByIdScopedAsync(id, tenantId);
- if (header == null) return NotFound();
- var db = _postRep.Context;
- await db.Ado.BeginTranAsync();
- try
- {
- await _skillRep.AsDeleteable().Where(x => x.LineSkillMasterId == id && x.TenantId == tenantId).ExecuteCommandAsync();
- await _postRep.DeleteAsync(header);
- await db.Ado.CommitTranAsync();
- return Ok(new { message = "删除成功" });
- }
- catch
- {
- await db.Ado.RollbackTranAsync();
- throw;
- }
- }
- /// <summary>
- /// 把提交的人员技能【业务编码】解析为当前租户下的 PersonSkill.Id。
- ///
- /// 作用域依据:uk_PersonSkill_code = UNIQUE (tenant_id, CompanyRefId, FactoryRefId, Code)。
- /// PersonSkill 表**没有 Domain 列**,故不按 Domain 收窄;产线岗位主表也不带
- /// CompanyRefId / FactoryRefId,无法据此再收窄,因此按 (租户, Code) 解析并对
- /// 多义(同租户下跨公司/工厂重码)显式报错,而不是随便取一条。
- ///
- /// 返回 error 时不写库;成功时返回 code -> id 映射。
- /// </summary>
- private async Task<(string? Error, Dictionary<string, long> Map)> ResolvePersonSkillCodesAsync(
- AdoS0LineSkillMasterUpsertDto dto, long tenantId)
- {
- var map = new Dictionary<string, long>(StringComparer.Ordinal);
- if (dto.Skills == null || dto.Skills.Count == 0)
- return (null, map);
- var codes = dto.Skills
- .Select(s => (s.PersonSkillCode ?? string.Empty).Trim())
- .Where(c => c.Length > 0)
- .Distinct(StringComparer.Ordinal)
- .ToList();
- if (codes.Count == 0)
- return ("人员技能不能为空", map);
- var rows = await _personSkillRep.ScopedTo(tenantId)
- .Where(p => codes.Contains(p.Code))
- .Select(p => new { p.Id, p.Code })
- .ToListAsync();
- foreach (var code in codes)
- {
- var hits = rows.Where(r => string.Equals(r.Code, code, StringComparison.Ordinal)).ToList();
- if (hits.Count == 0)
- return ($"人员技能 {code} 不存在或不属于当前作用域", map);
- if (hits.Count > 1)
- return ($"人员技能编码 {code} 在当前租户下存在 {hits.Count} 条同码主数据,无法唯一确定,请先处理主数据重码", map);
- map[code] = hits[0].Id;
- }
- return (null, map);
- }
- /// <summary>批量补显 PersonSkill 的 Code / Name(单次查询 + 字典映射,无 N+1)。</summary>
- private async Task FillPersonSkillDisplayAsync(List<AdoS0LineSkillDetail> rows, long tenantId)
- {
- if (rows.Count == 0) return;
- var ids = rows.Select(r => r.PersonSkillId).Distinct().ToList();
- var skills = await _personSkillRep.ScopedTo(tenantId)
- .Where(p => ids.Contains(p.Id))
- .Select(p => new { p.Id, p.Code, p.Name })
- .ToListAsync();
- var map = skills.GroupBy(x => x.Id).ToDictionary(g => g.Key, g => g.First());
- foreach (var r in rows)
- {
- if (map.TryGetValue(r.PersonSkillId, out var p))
- {
- r.PersonSkillCode = p.Code;
- r.PersonSkillName = p.Name;
- }
- }
- }
- /// <summary>
- /// 明细行映射。除新体系字段外,同时回填旧系统遗留业务键
- /// (Domain / Site / ProdLine / JOBNo 取自主表,SkillNo 取自提交的技能业务编码),
- /// 避免 Update 的「删除+重插」把这些列写成 NULL。
- /// </summary>
- private static AdoS0LineSkillDetail MapSkill(AdoS0LineSkillMaster master, AdoS0LineSkillDetailUpsertDto s,
- long tenantId, IReadOnlyDictionary<string, long> skillMap)
- {
- var code = (s.PersonSkillCode ?? string.Empty).Trim();
- return new AdoS0LineSkillDetail
- {
- TenantId = tenantId,
- LineSkillMasterId = master.Id,
- PersonSkillId = skillMap[code],
- Domain = master.Domain,
- Site = master.Site,
- ProdLine = master.ProdLine,
- JOBNo = master.JOBNo,
- SkillNo = code,
- RequiredLevel = s.RequiredLevel,
- Remark = s.Remark,
- EffectiveDate = s.EffectiveDate,
- IsEnabled = s.IsEnabled,
- CreateTime = DateTime.Now
- };
- }
- }
|