Bladeren bron

feat(s8): add business dimension metadata API

YY968XX 1 week geleden
bovenliggende
commit
ea5601a1ea

+ 26 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8DimensionController.cs

@@ -0,0 +1,26 @@
+using Admin.NET.Plugin.AiDOP.Service.S8;
+
+namespace Admin.NET.Plugin.AiDOP.Controllers.S8;
+
+[ApiController]
+[Route("api/aidop/s8/config")]
+[NonUnify]
+public class AdoS8DimensionController : ControllerBase
+{
+    private readonly S8DimensionService _svc;
+
+    public AdoS8DimensionController(S8DimensionService svc) => _svc = svc;
+
+    [HttpGet("dimensions")]
+    public async Task<IActionResult> GetDimensionsAsync(
+        [FromQuery] long tenantId = 1,
+        [FromQuery] long factoryId = 1) =>
+        Ok(await _svc.GetDimensionsAsync(tenantId, factoryId));
+
+    [HttpGet("dimension-nodes")]
+    public async Task<IActionResult> GetDimensionNodesAsync(
+        [FromQuery] long tenantId = 1,
+        [FromQuery] long factoryId = 1,
+        [FromQuery] string? dimensionCode = null) =>
+        Ok(await _svc.GetDimensionNodesAsync(tenantId, factoryId, dimensionCode));
+}

+ 28 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs

@@ -398,3 +398,31 @@ public class AdoS8SceneTreeNodeDto
     public bool Disabled { get; set; }
     public List<AdoS8SceneTreeNodeDto> Children { get; set; } = new();
 }
+
+// TASK-002-RESET-DIMENSION-MODEL-DEV-1: S8 业务维度元数据 DTO
+public class AdoS8DimensionDto
+{
+    public string DimensionCode { get; set; } = string.Empty;
+    public string DimensionName { get; set; } = string.Empty;
+    public bool Enabled { get; set; }
+    public int SortNo { get; set; }
+    public string? Remark { get; set; }
+}
+
+public class AdoS8DimensionNodeDto
+{
+    public long Id { get; set; }
+    public string DimensionCode { get; set; } = string.Empty;
+    public string NodeCode { get; set; } = string.Empty;
+    public string NodeName { get; set; } = string.Empty;
+    public long? ParentId { get; set; }
+    public int Level { get; set; }
+    public string Path { get; set; } = string.Empty;
+    public bool IsSelectable { get; set; }
+    /// <summary>= !IsSelectable,供前端树选择器直接消费</summary>
+    public bool Disabled { get; set; }
+    public bool Enabled { get; set; }
+    public int SortNo { get; set; }
+    public string? Remark { get; set; }
+    public List<AdoS8DimensionNodeDto> Children { get; set; } = new();
+}

+ 35 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8Dimension.cs

@@ -0,0 +1,35 @@
+namespace Admin.NET.Plugin.AiDOP.Entity.S8;
+
+[SugarTable("ado_s8_dimension", "S8 业务维度元数据")]
+public class AdoS8Dimension
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
+    public long Id { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", ColumnDataType = "bigint")]
+    public long TenantId { get; set; }
+
+    [SugarColumn(ColumnName = "factory_id", ColumnDataType = "bigint")]
+    public long FactoryId { get; set; }
+
+    [SugarColumn(ColumnName = "dimension_code", Length = 32)]
+    public string DimensionCode { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "dimension_name", Length = 128)]
+    public string DimensionName { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "enabled", ColumnDataType = "boolean")]
+    public bool Enabled { get; set; } = true;
+
+    [SugarColumn(ColumnName = "sort_no")]
+    public int SortNo { get; set; }
+
+    [SugarColumn(ColumnName = "remark", Length = 512, IsNullable = true)]
+    public string? Remark { get; set; }
+
+    [SugarColumn(ColumnName = "created_at")]
+    public DateTime CreatedAt { get; set; } = DateTime.Now;
+
+    [SugarColumn(ColumnName = "updated_at", IsNullable = true)]
+    public DateTime? UpdatedAt { get; set; }
+}

+ 50 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8DimensionNode.cs

@@ -0,0 +1,50 @@
+namespace Admin.NET.Plugin.AiDOP.Entity.S8;
+
+[SugarTable("ado_s8_dimension_node", "S8 业务维度节点")]
+public class AdoS8DimensionNode
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
+    public long Id { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", ColumnDataType = "bigint")]
+    public long TenantId { get; set; }
+
+    [SugarColumn(ColumnName = "factory_id", ColumnDataType = "bigint")]
+    public long FactoryId { get; set; }
+
+    [SugarColumn(ColumnName = "dimension_code", Length = 32)]
+    public string DimensionCode { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "node_code", Length = 64)]
+    public string NodeCode { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "node_name", Length = 128)]
+    public string NodeName { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "parent_id", IsNullable = true, ColumnDataType = "bigint")]
+    public long? ParentId { get; set; }
+
+    [SugarColumn(ColumnName = "level")]
+    public int Level { get; set; } = 1;
+
+    [SugarColumn(ColumnName = "path", Length = 512)]
+    public string Path { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "is_selectable", ColumnDataType = "boolean")]
+    public bool IsSelectable { get; set; } = true;
+
+    [SugarColumn(ColumnName = "enabled", ColumnDataType = "boolean")]
+    public bool Enabled { get; set; } = true;
+
+    [SugarColumn(ColumnName = "sort_no")]
+    public int SortNo { get; set; }
+
+    [SugarColumn(ColumnName = "remark", Length = 512, IsNullable = true)]
+    public string? Remark { get; set; }
+
+    [SugarColumn(ColumnName = "created_at")]
+    public DateTime CreatedAt { get; set; } = DateTime.Now;
+
+    [SugarColumn(ColumnName = "updated_at", IsNullable = true)]
+    public DateTime? UpdatedAt { get; set; }
+}

+ 86 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopDimensionSeed.cs

@@ -0,0 +1,86 @@
+using System.Diagnostics;
+using Admin.NET.Plugin.AiDOP.Entity.S8;
+using SqlSugar;
+
+namespace Admin.NET.Plugin.AiDOP.Infrastructure;
+
+/// <summary>
+/// S8 业务维度元数据基线种子(S_STAGE / ORDER_FLOW)。
+/// 幂等:按 dimension_code / (dimension_code, node_code) 逐条 Any() 判断后插入;
+/// 不删除任何已有数据,不修改 scene_tree / scene_config。
+/// </summary>
+public static class AidopDimensionSeed
+{
+    public static void EnsureSeed(ISqlSugarClient db)
+    {
+        try
+        {
+            var ct = DateTime.Parse("2026-05-07 00:00:00");
+
+            EnsureDimension(db, "S_STAGE", "S1-S7 阶段维度", 10, ct);
+            EnsureDimension(db, "ORDER_FLOW", "订单全流程维度", 20, ct);
+
+            // S_STAGE 节点(顶层叶节点,is_selectable=true)
+            EnsureNode(db, "S_STAGE", "S1", "S1 产销协同", "/S_STAGE/S1", 10, ct);
+            EnsureNode(db, "S_STAGE", "S2", "S2 生产准备", "/S_STAGE/S2", 20, ct);
+            EnsureNode(db, "S_STAGE", "S3", "S3 供应协同", "/S_STAGE/S3", 30, ct);
+            EnsureNode(db, "S_STAGE", "S4", "S4 质量检验", "/S_STAGE/S4", 40, ct);
+            EnsureNode(db, "S_STAGE", "S5", "S5 仓储供应", "/S_STAGE/S5", 50, ct);
+            EnsureNode(db, "S_STAGE", "S6", "S6 本体生产", "/S_STAGE/S6", 60, ct);
+            EnsureNode(db, "S_STAGE", "S7", "S7 总装发货", "/S_STAGE/S7", 70, ct);
+
+            // ORDER_FLOW 节点(顶层叶节点,is_selectable=true)
+            EnsureNode(db, "ORDER_FLOW", "ORDER_REVIEW_PLAN_CALC", "评审/排产/测算", "/ORDER_FLOW/ORDER_REVIEW_PLAN_CALC", 10, ct);
+            EnsureNode(db, "ORDER_FLOW", "PRODUCT_DESIGN", "产品设计", "/ORDER_FLOW/PRODUCT_DESIGN", 20, ct);
+            EnsureNode(db, "ORDER_FLOW", "MATERIAL_PURCHASE", "材料采购", "/ORDER_FLOW/MATERIAL_PURCHASE", 30, ct);
+            EnsureNode(db, "ORDER_FLOW", "BODY_PRODUCTION", "本体生产", "/ORDER_FLOW/BODY_PRODUCTION", 40, ct);
+            EnsureNode(db, "ORDER_FLOW", "FINAL_ASSEMBLY_DELIVERY", "总装发货", "/ORDER_FLOW/FINAL_ASSEMBLY_DELIVERY", 50, ct);
+        }
+        catch (Exception ex)
+        {
+            Trace.TraceWarning("AidopDimensionSeed: " + ex);
+        }
+    }
+
+    private static void EnsureDimension(ISqlSugarClient db, string code, string name, int sortNo, DateTime ct)
+    {
+        var exists = db.Queryable<AdoS8Dimension>()
+            .Any(x => x.TenantId == 0 && x.FactoryId == 0 && x.DimensionCode == code);
+        if (exists) return;
+
+        db.Insertable(new AdoS8Dimension
+        {
+            TenantId = 0,
+            FactoryId = 0,
+            DimensionCode = code,
+            DimensionName = name,
+            Enabled = true,
+            SortNo = sortNo,
+            CreatedAt = ct,
+        }).ExecuteCommand();
+    }
+
+    private static void EnsureNode(ISqlSugarClient db, string dimensionCode, string nodeCode, string nodeName, string path, int sortNo, DateTime ct)
+    {
+        var exists = db.Queryable<AdoS8DimensionNode>()
+            .Any(x => x.TenantId == 0 && x.FactoryId == 0
+                   && x.DimensionCode == dimensionCode && x.NodeCode == nodeCode);
+        if (exists) return;
+
+        db.Insertable(new AdoS8DimensionNode
+        {
+            TenantId = 0,
+            FactoryId = 0,
+            DimensionCode = dimensionCode,
+            NodeCode = nodeCode,
+            NodeName = nodeName,
+            ParentId = null,
+            Level = 1,
+            Path = path,
+            IsSelectable = true,
+            Enabled = true,
+            SortNo = sortNo,
+            CreatedAt = ct,
+        }).ExecuteCommand();
+    }
+}

+ 80 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DimensionService.cs

@@ -0,0 +1,80 @@
+using Admin.NET.Plugin.AiDOP.Dto.S8;
+using Admin.NET.Plugin.AiDOP.Entity.S8;
+
+namespace Admin.NET.Plugin.AiDOP.Service.S8;
+
+/// <summary>
+/// S8 业务维度元数据只读服务(S_STAGE / ORDER_FLOW)。
+/// TenantId=0 / FactoryId=0 表示全局基线,与 tenant/factory 数据并存。
+/// </summary>
+public class S8DimensionService : ITransient
+{
+    private readonly SqlSugarRepository<AdoS8Dimension> _dimRep;
+    private readonly SqlSugarRepository<AdoS8DimensionNode> _nodeRep;
+
+    public S8DimensionService(
+        SqlSugarRepository<AdoS8Dimension> dimRep,
+        SqlSugarRepository<AdoS8DimensionNode> nodeRep)
+    {
+        _dimRep = dimRep;
+        _nodeRep = nodeRep;
+    }
+
+    public async Task<List<AdoS8DimensionDto>> GetDimensionsAsync(long tenantId, long factoryId)
+    {
+        var rows = await _dimRep.AsQueryable()
+            .Where(x => (x.TenantId == 0 || x.TenantId == tenantId)
+                     && (x.FactoryId == 0 || x.FactoryId == factoryId)
+                     && x.Enabled)
+            .OrderBy(x => x.SortNo)
+            .OrderBy(x => x.Id)
+            .ToListAsync();
+
+        return rows.Select(x => new AdoS8DimensionDto
+        {
+            DimensionCode = x.DimensionCode,
+            DimensionName = x.DimensionName,
+            Enabled = x.Enabled,
+            SortNo = x.SortNo,
+            Remark = x.Remark,
+        }).ToList();
+    }
+
+    public async Task<List<AdoS8DimensionNodeDto>> GetDimensionNodesAsync(long tenantId, long factoryId, string? dimensionCode)
+    {
+        if (string.IsNullOrWhiteSpace(dimensionCode))
+            return new List<AdoS8DimensionNodeDto>();
+
+        var nodes = await _nodeRep.AsQueryable()
+            .Where(x => (x.TenantId == 0 || x.TenantId == tenantId)
+                     && (x.FactoryId == 0 || x.FactoryId == factoryId)
+                     && x.DimensionCode == dimensionCode
+                     && x.Enabled)
+            .OrderBy(x => x.SortNo)
+            .OrderBy(x => x.Id)
+            .ToListAsync();
+
+        return BuildTree(nodes, null);
+    }
+
+    private static List<AdoS8DimensionNodeDto> BuildTree(List<AdoS8DimensionNode> nodes, long? parentId) =>
+        nodes
+            .Where(n => n.ParentId == parentId)
+            .Select(n => new AdoS8DimensionNodeDto
+            {
+                Id = n.Id,
+                DimensionCode = n.DimensionCode,
+                NodeCode = n.NodeCode,
+                NodeName = n.NodeName,
+                ParentId = n.ParentId,
+                Level = n.Level,
+                Path = n.Path,
+                IsSelectable = n.IsSelectable,
+                Disabled = !n.IsSelectable,
+                Enabled = n.Enabled,
+                SortNo = n.SortNo,
+                Remark = n.Remark,
+                Children = BuildTree(nodes, n.Id),
+            })
+            .ToList();
+}

+ 3 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs

@@ -138,6 +138,8 @@ public class Startup : AppStartup
                 typeof(AdoS8DetectionLog),
                 typeof(AdoS8RuleDetectionState),
                 typeof(AdoS8SceneTree),
+                typeof(AdoS8Dimension),
+                typeof(AdoS8DimensionNode),
                 typeof(ContractReview),
                 typeof(ContractReviewFlow),
                 typeof(ProductDesign),
@@ -145,6 +147,7 @@ public class Startup : AppStartup
                 typeof(ProductDesignRouting)
             );
             AidopSceneTreeSeed.EnsureSeed(db);
+            AidopDimensionSeed.EnsureSeed(db);
         }
         catch (Exception ex)
         {