| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse;
- /// <summary>
- /// S7 FQC 检规结构化判定维护(Phase 1B,语义纠偏版)。
- ///
- /// 检规模板 qms_ccjygfzb 维护每个检验项目的"录入类型 + 数值判定条件":
- /// lrlx=NUMERIC 数值型:维护 UpperToleranceLimit(上限)/LowerToleranceLimit(下限)
- /// → 生成检验单时快照进 inspbillst.sx/xx,检验员按样本录 j1..j50,未来 xx≤jN≤sx 自动判定;
- /// lrlx=NON_NUMERIC 非数值型:不维护上下限,检验时只录"本次实际合格数/不合格数"
- /// → 落 inspbillst.NonNumericalTypeOK/NG(检验结果数量,非检规模板字段,本服务绝不写)。
- /// jsyq(技术要求) 为人读原文,不解析、不覆盖。
- ///
- /// 【语义纠偏】NonNumericalTypeOK/NG = 检验单本次实际合格/不合格数量,属 inspbillst,绝不放检规模板;
- /// 本服务不再维护任何"非数值规则编码"。检规模板的非数值项目仅由 lrlx 标识类型。
- /// 硬边界:不新建规则表、不从 jsyq 猜类型、不批量填限值、不改历史数据;tenant 走 ResolveTenantOrThrow;
- /// 上下限长度 ≤255(保证快照进 inspbillst.sx/xx varchar(255) 无截断)。
- /// </summary>
- [ApiDescriptionSettings(Order = 341, Description = "FQC检规判定维护")]
- [Route("api/S7FqcSpecRule")]
- [NonUnify]
- public class FqcSpecRuleService : IDynamicApiController, ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly UserManager _userManager;
- public FqcSpecRuleService(ISqlSugarClient db, UserManager userManager)
- {
- _db = db;
- _userManager = userManager;
- }
- private long ResolveTenantOrThrow() => AidopTenantScope.ResolveOrThrow(_userManager);
- /// <summary>快照承载长度上界(inspbillst.sx/xx varchar(255))。</summary>
- private const int LimitMaxLen = 255;
- private const string TypeNumeric = "NUMERIC";
- private const string TypeNonNumeric = "NON_NUMERIC";
- /// <summary>检规主表列表(维护入口第一层:选检规)。</summary>
- [DisplayName("检规主表列表")]
- [HttpGet("spec-page")]
- public async Task<object> SpecPage([FromQuery] FqcSpecRulePageInput input)
- {
- var tenantId = ResolveTenantOrThrow();
- var page = input.Page <= 0 ? 1 : input.Page;
- var pageSize = input.PageSize <= 0 ? 20 : (input.PageSize > 200 ? 200 : input.PageSize);
- var where = new List<string> { "tenant_id=@t" };
- var pars = new List<SugarParameter> { new("@t", tenantId) };
- if (!string.IsNullOrWhiteSpace(input.Wjbh)) { where.Add("wjbh LIKE @w"); pars.Add(new("@w", $"%{input.Wjbh.Trim()}%")); }
- if (!string.IsNullOrWhiteSpace(input.Cpmc)) { where.Add("cpmc LIKE @c"); pars.Add(new("@c", $"%{input.Cpmc.Trim()}%")); }
- var whereSql = string.Join(" AND ", where);
- var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM qms_ccjygf WHERE {whereSql}", pars);
- var list = await _db.Ado.SqlQueryAsync<FqcSpecHeadRow>(
- $@"SELECT id AS Id, wjbh AS Wjbh, bb AS Bb, cpmc AS Cpmc, cpxh AS Cpxh, sxrq AS Sxrq
- FROM qms_ccjygf WHERE {whereSql} ORDER BY wjbh, bb DESC LIMIT {pageSize} OFFSET {(page - 1) * pageSize}", pars);
- return new { total, page, pageSize, list };
- }
- /// <summary>某检规的检验项目明细 + 录入类型 + 数值上下限(维护第二层)。</summary>
- [DisplayName("检规明细判定条件")]
- [HttpGet("detail-list")]
- public async Task<object> DetailList([FromQuery] long jygfid)
- {
- var tenantId = ResolveTenantOrThrow();
- if (jygfid <= 0) throw Oops.Oh("检规 id 非法");
- var rows = await _db.Ado.SqlQueryAsync<FqcSpecRuleRow>(
- @"SELECT z.id AS Id, z.xh AS Xh, z.jyxm AS Jyxm, z.jsyq AS Jsyq, z.jsbz AS Jsbz, z.cyfa AS Cyfa,
- z.lrlx AS Lrlx, z.UpperToleranceLimit AS UpperLimit, z.LowerToleranceLimit AS LowerLimit
- FROM qms_ccjygfzb z
- JOIN qms_ccjygf g ON g.id = z.glid AND g.tenant_id = z.tenant_id
- WHERE z.glid = @j AND z.tenant_id = @t
- ORDER BY z.xh, z.id",
- new SugarParameter("@j", jygfid), new SugarParameter("@t", tenantId));
- foreach (var r in rows) r.JudgmentType = DeriveJudgmentType(r.Lrlx, r.UpperLimit, r.LowerLimit);
- return new { jygfid, list = rows };
- }
- /// <summary>保存单条检验项目的录入类型 + 数值上下限(后端全量兜底校验)。</summary>
- [DisplayName("保存检规判定条件")]
- [HttpPost("save-rule")]
- public async Task<object> SaveRule([FromBody] FqcSpecRuleSaveInput input)
- {
- var tenantId = ResolveTenantOrThrow();
- if (input == null || input.Id <= 0) throw Oops.Oh("明细 id 非法");
- var lrlx = NormalizeType(input.Lrlx);
- var upper = Trim(input.UpperLimit);
- var lower = Trim(input.LowerLimit);
- // 1) 明细存在且属当前租户
- var exist = await _db.Ado.SqlQuerySingleAsync<long>(
- "SELECT COUNT(1) FROM qms_ccjygfzb WHERE id=@id AND tenant_id=@t",
- new SugarParameter("@id", input.Id), new SugarParameter("@t", tenantId));
- if (exist == 0) throw Oops.Oh("检规明细不存在或不属于当前租户");
- // 2) 非数值型不维护上下限:切换到 NON_NUMERIC 时清空上下限(避免残留脏数据)
- if (lrlx == TypeNonNumeric)
- {
- upper = string.Empty;
- lower = string.Empty;
- }
- // 3) 数值上下限校验(仅当有值时);NUMERIC 但暂无限值(未维护)允许
- var hasLimit = !string.IsNullOrEmpty(upper) || !string.IsNullOrEmpty(lower);
- if (hasLimit)
- {
- if (lrlx == TypeNonNumeric) throw Oops.Oh("非数值型项目不能配置上下限");
- if (upper.Length > LimitMaxLen || lower.Length > LimitMaxLen)
- throw Oops.Oh($"上/下限长度不能超过 {LimitMaxLen}(快照承载限制)");
- decimal? u = ParseDec(upper), l = ParseDec(lower);
- if (!string.IsNullOrEmpty(upper) && u == null) throw Oops.Oh("上限必须是数值");
- if (!string.IsNullOrEmpty(lower) && l == null) throw Oops.Oh("下限必须是数值");
- if (u.HasValue && l.HasValue && l.Value > u.Value) throw Oops.Oh("下限不能大于上限");
- }
- // 4) 写回
- var n = await _db.Ado.ExecuteCommandAsync(
- @"UPDATE qms_ccjygfzb
- SET lrlx=@lrlx, UpperToleranceLimit=@upper, LowerToleranceLimit=@lower
- WHERE id=@id AND tenant_id=@t",
- new SugarParameter("@lrlx", (object?)lrlx ?? DBNull.Value),
- new SugarParameter("@upper", (object?)NullIfEmpty(upper) ?? DBNull.Value),
- new SugarParameter("@lower", (object?)NullIfEmpty(lower) ?? DBNull.Value),
- new SugarParameter("@id", input.Id),
- new SugarParameter("@t", tenantId));
- return new { ok = n > 0, judgmentType = DeriveJudgmentType(lrlx, upper, lower) };
- }
- /// <summary>清空某明细的录入类型与上下限(→ 未配置)。</summary>
- [DisplayName("清空检规判定条件")]
- [HttpPost("clear-rule")]
- public async Task<object> ClearRule([FromBody] FqcSpecRuleClearInput input)
- {
- var tenantId = ResolveTenantOrThrow();
- if (input == null || input.Id <= 0) throw Oops.Oh("明细 id 非法");
- var n = await _db.Ado.ExecuteCommandAsync(
- @"UPDATE qms_ccjygfzb SET lrlx=NULL, UpperToleranceLimit=NULL, LowerToleranceLimit=NULL
- WHERE id=@id AND tenant_id=@t",
- new SugarParameter("@id", input.Id), new SugarParameter("@t", tenantId));
- return new { ok = n > 0, judgmentType = "NONE" };
- }
- // ===== 辅助 =====
- /// <summary>判定方式推导:显式 lrlx 优先;未设置时有上下限视为 NUMERIC,否则 NONE。
- /// (不从"上下限为空"反推 NON_NUMERIC,非数值型必须显式 lrlx=NON_NUMERIC 标注。)</summary>
- private static string DeriveJudgmentType(string? lrlx, string? upper, string? lower)
- {
- if (lrlx == TypeNumeric) return TypeNumeric;
- if (lrlx == TypeNonNumeric) return TypeNonNumeric;
- var hasLimit = !string.IsNullOrWhiteSpace(upper) || !string.IsNullOrWhiteSpace(lower);
- return hasLimit ? TypeNumeric : "NONE";
- }
- private static string? NormalizeType(string? t)
- {
- if (string.IsNullOrWhiteSpace(t)) return null;
- var v = t.Trim().ToUpperInvariant();
- if (v == TypeNumeric || v == TypeNonNumeric) return v;
- throw Oops.Oh("录入类型仅支持 NUMERIC / NON_NUMERIC");
- }
- private static string Trim(string? s) => (s ?? string.Empty).Trim();
- private static string? NullIfEmpty(string s) => string.IsNullOrEmpty(s) ? null : s;
- private static decimal? ParseDec(string s) => decimal.TryParse(s, out var d) ? d : null;
- }
- public class FqcSpecRulePageInput
- {
- public string? Wjbh { get; set; }
- public string? Cpmc { get; set; }
- public int Page { get; set; } = 1;
- public int PageSize { get; set; } = 20;
- }
- public class FqcSpecHeadRow
- {
- public long Id { get; set; }
- public string? Wjbh { get; set; }
- public string? Bb { get; set; }
- public string? Cpmc { get; set; }
- public string? Cpxh { get; set; }
- public string? Sxrq { get; set; }
- }
- public class FqcSpecRuleRow
- {
- public long Id { get; set; }
- public string? Xh { get; set; }
- public string? Jyxm { get; set; }
- public string? Jsyq { get; set; }
- public string? Jsbz { get; set; }
- public string? Cyfa { get; set; }
- /// <summary>录入类型 NUMERIC / NON_NUMERIC / null</summary>
- public string? Lrlx { get; set; }
- public string? UpperLimit { get; set; }
- public string? LowerLimit { get; set; }
- /// <summary>推导判定方式 NUMERIC / NON_NUMERIC / NONE</summary>
- public string? JudgmentType { get; set; }
- }
- public class FqcSpecRuleSaveInput
- {
- public long Id { get; set; }
- /// <summary>录入类型 NUMERIC / NON_NUMERIC(可空=未设置)</summary>
- public string? Lrlx { get; set; }
- public string? UpperLimit { get; set; }
- public string? LowerLimit { get; set; }
- }
- public class FqcSpecRuleClearInput
- {
- public long Id { get; set; }
- }
|