using Admin.NET.Plugin.AiDOP.Dto.S0.Manufacturing; using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing; namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing; /// /// 工单控制参数设置(WorkOrdControl):单记录配置表单。 /// /// 业务模型(S0-WORKORD-CONTROL-TENANT-UX-FIX):**每个租户一套配置**。 /// - tenant_id 是唯一隔离边界;Domain 只是历史兼容业务字段,不承担可见性/安全职责。 /// - GET 不要求传 Domain:不传即返回当前租户的配置,页面打开即可自动回填。 /// - Upsert 按 Id 定位时必须先 tenant scope;Id 属于其他租户一律 404,绝不更新。 /// - 单配置约束:未命中 Id 时按租户定位既有记录,不会因 Domain 不同而无限新建。 /// [ApiController] [Route("api/s0/manufacturing/work-ord-control")] [NonUnify] public class AdoS0WorkOrdControlController : ControllerBase { private readonly SqlSugarRepository _rep; public AdoS0WorkOrdControlController(SqlSugarRepository rep) { _rep = rep; } [HttpGet] public async Task GetOneAsync([FromQuery] AdoS0WorkOrdControlGetDto q) { if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!; // 租户优先:Domain 仅作可选业务筛选,留空即返回当前租户配置(页面无需先手输工厂域)。 var domain = q.Domain?.Trim(); var item = await _rep.ScopedTo(tenantId) .WhereIF(!string.IsNullOrWhiteSpace(domain), x => x.Domain == domain) .OrderBy(x => x.Id) .FirstAsync(); return Ok(item); } [HttpPost("upsert")] public async Task UpsertAsync([FromBody] AdoS0WorkOrdControlUpsertDto dto) { if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!; var now = DateTime.Now; var domain = dto.Domain?.Trim(); AdoS0WorkOrdControl? entity = null; if (dto.Id is > 0) { // P0:先 tenant scope 再按 Id 命中。Id 属于其他租户时查不到 → 404,绝不落到 Update 分支。 entity = await _rep.ScopedTo(tenantId) .Where(x => x.Id == dto.Id!.Value) .FirstAsync(); if (entity == null) return NotFound(); } // 单配置约束:一个租户最多一条当前配置,不因 Domain 不同而无限新建。 entity ??= await _rep.ScopedTo(tenantId) .OrderBy(x => x.Id) .FirstAsync(); if (entity == null) { entity = new AdoS0WorkOrdControl { TenantId = tenantId, // Domain 可空:留空存空串(列非空),不猜 8010 / S0-F001 等历史值。 Domain = domain ?? string.Empty, EnteringPer = dto.EnteringPer, PaintingPer = dto.PaintingPer, IssuedInterval = dto.IssuedInterval, PostVarsatSFC = dto.PostVarsatSFC, IsIssued = dto.IsIssued, IsPrintRejectBarcode = dto.IsPrintRejectBarcode, IsPrintScrapBarcode = dto.IsPrintScrapBarcode, IsQualityConfirm = dto.IsQualityConfirm, IsActive = dto.IsActive, IsConfirm = dto.IsConfirm, CreateUser = dto.CreateUser ?? dto.UpdateUser, CreateTime = dto.CreateTime ?? dto.UpdateTime ?? now, UpdateUser = dto.UpdateUser, UpdateTime = dto.UpdateTime ?? now }; await _rep.AsInsertable(entity).ExecuteReturnEntityAsync(); return Ok(entity); } // 保值:dto 未传 Domain 时不得抹掉既有历史值。 if (!string.IsNullOrWhiteSpace(domain)) entity.Domain = domain; entity.EnteringPer = dto.EnteringPer; entity.PaintingPer = dto.PaintingPer; entity.IssuedInterval = dto.IssuedInterval; entity.PostVarsatSFC = dto.PostVarsatSFC; entity.IsIssued = dto.IsIssued; entity.IsPrintRejectBarcode = dto.IsPrintRejectBarcode; entity.IsPrintScrapBarcode = dto.IsPrintScrapBarcode; entity.IsQualityConfirm = dto.IsQualityConfirm; entity.IsActive = dto.IsActive; entity.IsConfirm = dto.IsConfirm; entity.UpdateUser = dto.UpdateUser; entity.UpdateTime = dto.UpdateTime ?? now; await _rep.AsUpdateable(entity).ExecuteCommandAsync(); return Ok(entity); } }