소스 검색

feat(s8): add S8 scene tree entity, read-only API and baseline seed (TASK-002-A)

YY968XX 5 일 전
부모
커밋
d7b2dcdcb6

+ 19 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8SceneTreeController.cs

@@ -0,0 +1,19 @@
+using Admin.NET.Plugin.AiDOP.Service.S8;
+
+namespace Admin.NET.Plugin.AiDOP.Controllers.S8;
+
+[ApiController]
+[Route("api/aidop/s8/config/scene-tree")]
+[NonUnify]
+public class AdoS8SceneTreeController : ControllerBase
+{
+    private readonly S8SceneTreeService _svc;
+
+    public AdoS8SceneTreeController(S8SceneTreeService svc) => _svc = svc;
+
+    [HttpGet]
+    public async Task<IActionResult> GetAsync(
+        [FromQuery] long tenantId = 1,
+        [FromQuery] long factoryId = 1) =>
+        Ok(await _svc.GetSceneTreeAsync(tenantId, factoryId));
+}

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

@@ -382,3 +382,19 @@ public class AdoS8RejectVerificationDto
 {
     public string Remark { get; set; } = string.Empty;
 }
+
+// TASK-002-A: S8 场景树只读节点 DTO
+public class AdoS8SceneTreeNodeDto
+{
+    public long Id { get; set; }
+    public long? ParentId { get; set; }
+    public string NodeCode { get; set; } = string.Empty;
+    public string NodeName { get; set; } = string.Empty;
+    public int Level { get; set; }
+    public string Path { get; set; } = string.Empty;
+    public string SModule { get; set; } = string.Empty;
+    public bool IsLeaf { get; set; }
+    /// <summary>非叶子节点标记为 Disabled,前端树选择器用于灰显</summary>
+    public bool Disabled { get; set; }
+    public List<AdoS8SceneTreeNodeDto> Children { get; set; } = new();
+}

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

@@ -0,0 +1,50 @@
+namespace Admin.NET.Plugin.AiDOP.Entity.S8;
+
+[SugarTable("ado_s8_scene_tree", "S8 场景树节点")]
+public class AdoS8SceneTree
+{
+    [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 = "parent_id", IsNullable = true, ColumnDataType = "bigint")]
+    public long? ParentId { get; set; }
+
+    [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 = "level")]
+    public int Level { get; set; }
+
+    [SugarColumn(ColumnName = "path", Length = 512)]
+    public string Path { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "s_module", Length = 16)]
+    public string SModule { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "is_leaf", ColumnDataType = "boolean")]
+    public bool IsLeaf { get; set; }
+
+    [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; }
+}

+ 85 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopSceneTreeSeed.cs

@@ -0,0 +1,85 @@
+using System.Diagnostics;
+using Admin.NET.Plugin.AiDOP.Entity.S8;
+using SqlSugar;
+
+namespace Admin.NET.Plugin.AiDOP.Infrastructure;
+
+/// <summary>
+/// S8 场景树基线种子。
+/// 首次启动空表时灌入 S1-S7 三层结构;表已有数据则跳过(幂等)。
+/// </summary>
+public static class AidopSceneTreeSeed
+{
+    public static void EnsureSeed(ISqlSugarClient db)
+    {
+        try
+        {
+            if (db.Queryable<AdoS8SceneTree>().Any())
+                return;
+
+            var ct = DateTime.Parse("2026-05-07 00:00:00");
+            const long b = 1329910000000L;
+
+            var nodes = new List<AdoS8SceneTree>
+            {
+                // S1 产销协同
+                N(b+1,  null,  "S1",                "S1 产销协同",        1, "S1",                                              "S1", false, 10, ct),
+                N(b+2,  b+1,   "S1_ORDER_REVIEW",   "订单评审",           2, "S1/ORDER_REVIEW",                                 "S1", false, 11, ct),
+                N(b+3,  b+2,   "S1_LEGAL_REVIEW",   "法务评审",           3, "S1/ORDER_REVIEW/LEGAL_REVIEW",                    "S1", true,  12, ct),
+                N(b+4,  b+2,   "S1_TECH_REVIEW",    "技术评审",           3, "S1/ORDER_REVIEW/TECH_REVIEW",                     "S1", true,  13, ct),
+                N(b+5,  b+2,   "S1_GENERAL_REVIEW", "综合评审",           3, "S1/ORDER_REVIEW/GENERAL_REVIEW",                  "S1", true,  14, ct),
+                N(b+6,  b+2,   "S1_LAB_REVIEW",     "实验室评审",         3, "S1/ORDER_REVIEW/LAB_REVIEW",                      "S1", true,  15, ct),
+                // S2 生产准备
+                N(b+10, null,  "S2",                "S2 生产准备",        1, "S2",                                              "S2", false, 20, ct),
+                N(b+11, b+10,  "S2_WORK_ORDER_PREP","工单准备",           2, "S2/WORK_ORDER_PREP",                              "S2", false, 21, ct),
+                N(b+12, b+11,  "S2_MATERIAL_KITTING","物料齐套确认",      3, "S2/WORK_ORDER_PREP/MATERIAL_KITTING",             "S2", true,  22, ct),
+                // S3 采购协同
+                N(b+20, null,  "S3",                "S3 采购协同",        1, "S3",                                              "S3", false, 30, ct),
+                N(b+21, b+20,  "S3_PURCHASE_EXEC",  "采购执行",           2, "S3/PURCHASE_EXEC",                                "S3", false, 31, ct),
+                N(b+22, b+21,  "S3_ARRIVAL_CONFIRM","到货确认",           3, "S3/PURCHASE_EXEC/ARRIVAL_CONFIRM",                "S3", true,  32, ct),
+                // S4 质量检验
+                N(b+30, null,  "S4",                "S4 质量检验",        1, "S4",                                              "S4", false, 40, ct),
+                N(b+31, b+30,  "S4_QUALITY_INSPECT","来料检验",           2, "S4/QUALITY_INSPECT",                              "S4", false, 41, ct),
+                N(b+32, b+31,  "S4_IQC_INSPECT",    "IQC 检验",           3, "S4/QUALITY_INSPECT/IQC_INSPECT",                  "S4", true,  42, ct),
+                // S5 仓储供应
+                N(b+40, null,  "S5",                "S5 仓储供应",        1, "S5",                                              "S5", false, 50, ct),
+                N(b+41, b+40,  "S5_INVENTORY_ASSURANCE","库存保障",       2, "S5/INVENTORY_ASSURANCE",                          "S5", false, 51, ct),
+                N(b+42, b+41,  "S5_STOCK_ALERT_HANDLING","库存预警处理",  3, "S5/INVENTORY_ASSURANCE/STOCK_ALERT_HANDLING",     "S5", true,  52, ct),
+                // S6 生产制造
+                N(b+50, null,  "S6",                "S6 生产制造",        1, "S6",                                              "S6", false, 60, ct),
+                N(b+51, b+50,  "S6_PRODUCTION_EXEC","生产执行",           2, "S6/PRODUCTION_EXEC",                              "S6", false, 61, ct),
+                N(b+52, b+51,  "S6_PROCESS_COMPLETION","工序完工确认",    3, "S6/PRODUCTION_EXEC/PROCESS_COMPLETION",           "S6", true,  62, ct),
+                // S7 订单交付
+                N(b+60, null,  "S7",                "S7 订单交付",        1, "S7",                                              "S7", false, 70, ct),
+                N(b+61, b+60,  "S7_DELIVERY_EXEC",  "交付执行",           2, "S7/DELIVERY_EXEC",                                "S7", false, 71, ct),
+                N(b+62, b+61,  "S7_SHIPMENT_CONFIRM","发货确认",          3, "S7/DELIVERY_EXEC/SHIPMENT_CONFIRM",               "S7", true,  72, ct),
+            };
+
+            db.Insertable(nodes).ExecuteCommand();
+        }
+        catch (Exception ex)
+        {
+            Trace.TraceWarning("AidopSceneTreeSeed: " + ex);
+        }
+    }
+
+    private static AdoS8SceneTree N(
+        long id, long? parentId, string nodeCode, string nodeName,
+        int level, string path, string sModule, bool isLeaf, int sortNo, DateTime ct) =>
+        new()
+        {
+            Id = id,
+            TenantId = 0,
+            FactoryId = 0,
+            ParentId = parentId,
+            NodeCode = nodeCode,
+            NodeName = nodeName,
+            Level = level,
+            Path = path,
+            SModule = sModule,
+            IsLeaf = isLeaf,
+            Enabled = true,
+            SortNo = sortNo,
+            CreatedAt = ct,
+        };
+}

+ 77 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/S8SceneTreeSeedData.cs

@@ -0,0 +1,77 @@
+namespace Admin.NET.Plugin.AiDOP;
+
+/// <summary>
+/// S8 场景树基线种子(3 层结构:S 模块 → 流程 → 动作)。
+/// TenantId=0 / FactoryId=0 表示全局基线,服务层用 OR 逻辑兼容所有租户/工厂。
+/// S1 含 4 条叶子节点;S2-S7 各含 1 条 3 层叶子路径。
+/// </summary>
+[IncreSeed]
+public class S8SceneTreeSeedData : ISqlSugarEntitySeedData<Entity.S8.AdoS8SceneTree>
+{
+    public IEnumerable<Entity.S8.AdoS8SceneTree> HasData()
+    {
+        const long b = 1329910000000L;
+        var ct = DateTime.Parse("2026-05-07 00:00:00");
+
+        return new[]
+        {
+            // ── S1 产销协同 ──
+            N(b+1,  null,  "S1",               "S1 产销协同",        1, "S1",                    "S1",           false, 10, ct),
+            N(b+2,  b+1,   "S1_ORDER_REVIEW",  "订单评审",           2, "S1/ORDER_REVIEW",        "S1",           false, 11, ct),
+            N(b+3,  b+2,   "S1_LEGAL_REVIEW",  "法务评审",           3, "S1/ORDER_REVIEW/LEGAL_REVIEW",    "S1", true,  12, ct),
+            N(b+4,  b+2,   "S1_TECH_REVIEW",   "技术评审",           3, "S1/ORDER_REVIEW/TECH_REVIEW",     "S1", true,  13, ct),
+            N(b+5,  b+2,   "S1_GENERAL_REVIEW","综合评审",           3, "S1/ORDER_REVIEW/GENERAL_REVIEW",  "S1", true,  14, ct),
+            N(b+6,  b+2,   "S1_LAB_REVIEW",    "实验室评审",         3, "S1/ORDER_REVIEW/LAB_REVIEW",      "S1", true,  15, ct),
+
+            // ── S2 生产准备 ──
+            N(b+10, null,  "S2",               "S2 生产准备",        1, "S2",                    "S2",           false, 20, ct),
+            N(b+11, b+10,  "S2_WORK_ORDER_PREP","工单准备",          2, "S2/WORK_ORDER_PREP",    "S2",           false, 21, ct),
+            N(b+12, b+11,  "S2_MATERIAL_KITTING","物料齐套确认",     3, "S2/WORK_ORDER_PREP/MATERIAL_KITTING", "S2", true, 22, ct),
+
+            // ── S3 采购协同 ──
+            N(b+20, null,  "S3",               "S3 采购协同",        1, "S3",                    "S3",           false, 30, ct),
+            N(b+21, b+20,  "S3_PURCHASE_EXEC", "采购执行",           2, "S3/PURCHASE_EXEC",      "S3",           false, 31, ct),
+            N(b+22, b+21,  "S3_ARRIVAL_CONFIRM","到货确认",          3, "S3/PURCHASE_EXEC/ARRIVAL_CONFIRM",   "S3", true, 32, ct),
+
+            // ── S4 质量检验 ──
+            N(b+30, null,  "S4",               "S4 质量检验",        1, "S4",                    "S4",           false, 40, ct),
+            N(b+31, b+30,  "S4_QUALITY_INSPECT","来料检验",          2, "S4/QUALITY_INSPECT",    "S4",           false, 41, ct),
+            N(b+32, b+31,  "S4_IQC_INSPECT",   "IQC 检验",           3, "S4/QUALITY_INSPECT/IQC_INSPECT",     "S4", true, 42, ct),
+
+            // ── S5 仓储供应 ──
+            N(b+40, null,  "S5",               "S5 仓储供应",        1, "S5",                    "S5",           false, 50, ct),
+            N(b+41, b+40,  "S5_INVENTORY_ASSURANCE","库存保障",      2, "S5/INVENTORY_ASSURANCE","S5",           false, 51, ct),
+            N(b+42, b+41,  "S5_STOCK_ALERT_HANDLING","库存预警处理", 3, "S5/INVENTORY_ASSURANCE/STOCK_ALERT_HANDLING", "S5", true, 52, ct),
+
+            // ── S6 生产制造 ──
+            N(b+50, null,  "S6",               "S6 生产制造",        1, "S6",                    "S6",           false, 60, ct),
+            N(b+51, b+50,  "S6_PRODUCTION_EXEC","生产执行",          2, "S6/PRODUCTION_EXEC",    "S6",           false, 61, ct),
+            N(b+52, b+51,  "S6_PROCESS_COMPLETION","工序完工确认",   3, "S6/PRODUCTION_EXEC/PROCESS_COMPLETION", "S6", true, 62, ct),
+
+            // ── S7 订单交付 ──
+            N(b+60, null,  "S7",               "S7 订单交付",        1, "S7",                    "S7",           false, 70, ct),
+            N(b+61, b+60,  "S7_DELIVERY_EXEC", "交付执行",           2, "S7/DELIVERY_EXEC",      "S7",           false, 71, ct),
+            N(b+62, b+61,  "S7_SHIPMENT_CONFIRM","发货确认",         3, "S7/DELIVERY_EXEC/SHIPMENT_CONFIRM",   "S7", true, 72, ct),
+        };
+    }
+
+    private static Entity.S8.AdoS8SceneTree N(
+        long id, long? parentId, string nodeCode, string nodeName,
+        int level, string path, string sModule, bool isLeaf, int sortNo, DateTime ct) =>
+        new()
+        {
+            Id = id,
+            TenantId = 0,
+            FactoryId = 0,
+            ParentId = parentId,
+            NodeCode = nodeCode,
+            NodeName = nodeName,
+            Level = level,
+            Path = path,
+            SModule = sModule,
+            IsLeaf = isLeaf,
+            Enabled = true,
+            SortNo = sortNo,
+            CreatedAt = ct,
+        };
+}

+ 42 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8SceneTreeService.cs

@@ -0,0 +1,42 @@
+using Admin.NET.Plugin.AiDOP.Dto.S8;
+using Admin.NET.Plugin.AiDOP.Entity.S8;
+
+namespace Admin.NET.Plugin.AiDOP.Service.S8;
+
+public class S8SceneTreeService : ITransient
+{
+    private readonly SqlSugarRepository<AdoS8SceneTree> _rep;
+
+    public S8SceneTreeService(SqlSugarRepository<AdoS8SceneTree> rep) => _rep = rep;
+
+    public async Task<List<AdoS8SceneTreeNodeDto>> GetSceneTreeAsync(long tenantId, long factoryId)
+    {
+        var nodes = await _rep.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 BuildTree(nodes, null);
+    }
+
+    private static List<AdoS8SceneTreeNodeDto> BuildTree(List<AdoS8SceneTree> nodes, long? parentId) =>
+        nodes
+            .Where(n => n.ParentId == parentId)
+            .Select(n => new AdoS8SceneTreeNodeDto
+            {
+                Id = n.Id,
+                ParentId = n.ParentId,
+                NodeCode = n.NodeCode,
+                NodeName = n.NodeName,
+                Level = n.Level,
+                Path = n.Path,
+                SModule = n.SModule,
+                IsLeaf = n.IsLeaf,
+                Disabled = !n.IsLeaf,
+                Children = BuildTree(nodes, n.Id),
+            })
+            .ToList();
+}

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

@@ -137,12 +137,14 @@ public class Startup : AppStartup
                 typeof(AdoS8ExceptionType),
                 typeof(AdoS8DetectionLog),
                 typeof(AdoS8RuleDetectionState),
+                typeof(AdoS8SceneTree),
                 typeof(ContractReview),
                 typeof(ContractReviewFlow),
                 typeof(ProductDesign),
                 typeof(ProductDesignBom),
                 typeof(ProductDesignRouting)
             );
+            AidopSceneTreeSeed.EnsureSeed(db);
         }
         catch (Exception ex)
         {