功能: - 后端新增 KtpbCtgs / KtpbDcgs / KtpbZbzs 三张表、KtpbService 与 api/prod/ktpb 接口 (车头工、挡车工、值班长分页查询与维护,挡车工按车间+班次+机组整组替换保存) - 挡车数按 F9 口径自动重算:接壤车台各计 0.5,保存时服务端重新计算并校验车台范围 - 前端新增 /prod/qianxian/ktpb 看台排班页(三页签 + 车间/班次/机组过滤 + 批量编辑弹窗) - 新增通用职工选择弹窗 EmployeeSelect(单选/多选,按部门过滤) - 生产-前缫菜单下新增「看台排班」,权限 prod:ktpb:list 数据导入: - 重新生成 biz_prod_qs_jzct.json,新增 ktpb_ctg / ktpb_dcg / ktpb_zbz 三个内嵌资源 - F9 部门 Id 经 DeptData 映射到本系统部门;职工按原始雪花 Id 关联 - 剔除软删除/脏数据:机组车台 24 条、车头工 33 条、挡车工 202 条(34 组)、值班长 7 条 - 导入幂等:机组车台按车间+机组 upsert,排班按业务唯一键去重,已维护的组不覆盖 其他:同步工作进度-12 文档与开发数据库快照
1370 lines
70 KiB
C#
1370 lines
70 KiB
C#
using System.Data;
|
||
using System.Text;
|
||
using FreeSql;
|
||
using F9WebAiTest.Application.Interfaces.SnowflakeID;
|
||
using F9WebAiTest.Domain.Common;
|
||
using F9WebAiTest.Domain.Entities;
|
||
using F9WebAiTest.Domain.Entities.Base;
|
||
using F9WebAiTest.Domain.Entities.Notice;
|
||
using F9WebAiTest.Domain.Enums.SYS.RBAC;
|
||
using SysMenu = F9WebAiTest.Domain.Entities.SYS.RBAC.Menu;
|
||
using SysRole = F9WebAiTest.Domain.Entities.SYS.RBAC.Role;
|
||
using SysPermission = F9WebAiTest.Domain.Entities.SYS.RBAC.Permission;
|
||
using SysRolePermission = F9WebAiTest.Domain.Entities.SYS.RBAC.RolePermission;
|
||
using SysReport = F9WebAiTest.Domain.Entities.SYS.Reports.Report;
|
||
using SysReportFile = F9WebAiTest.Domain.Entities.SYS.Reports.ReportFile;
|
||
|
||
namespace F9WebAiTest.Infrastructure.Data;
|
||
|
||
public static class DbSeeder
|
||
{
|
||
public static void Seed(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
// 1. 建表(CodeFirst SyncStructure,增量同步,兼容旧库)
|
||
SyncStructure(fsql);
|
||
|
||
// 2. 默认角色
|
||
if (!fsql.Select<Role>().Any())
|
||
{
|
||
fsql.Insert(new Role { Name = "超级管理员", Code = "admin", Description = "拥有所有权限", Sort = 1 }).ExecuteAffrows();
|
||
fsql.Insert(new Role { Name = "普通用户", Code = "user", Description = "普通员工", Sort = 2 }).ExecuteAffrows();
|
||
}
|
||
|
||
// 3. 默认菜单(系统管理)
|
||
if (!fsql.Select<Menu>().Any())
|
||
{
|
||
var systemId = InsertAndGetId(fsql, new Menu { Name = "系统管理", Type = "directory", Path = "/system", Icon = "Setting", Sort = 1 });
|
||
var deptId = InsertAndGetId(fsql, new Menu { Name = "部门管理", Type = "menu", Path = "/system/department", Component = "system/department/index", Icon = "OfficeBuilding", Permission = "system:department:list", Sort = 1, ParentId = systemId });
|
||
var empId = InsertAndGetId(fsql, new Menu { Name = "职工管理", Type = "menu", Path = "/system/employee", Component = "system/employee/index", Icon = "User", Permission = "system:employee:list", Sort = 2, ParentId = systemId });
|
||
var roleId = InsertAndGetId(fsql, new Menu { Name = "角色管理", Type = "menu", Path = "/system/role", Component = "system/role/index", Icon = "UserFilled", Permission = "system:role:list", Sort = 3, ParentId = systemId });
|
||
var menuId = InsertAndGetId(fsql, new Menu { Name = "菜单管理", Type = "menu", Path = "/system/menu", Component = "system/menu/index", Icon = "Menu", Permission = "system:menu:list", Sort = 4, ParentId = systemId });
|
||
var logId = InsertAndGetId(fsql, new Menu { Name = "操作日志", Type = "menu", Path = "/system/log", Component = "system/log/index", Icon = "Document", Permission = "system:log:list", Sort = 5, ParentId = systemId });
|
||
fsql.Insert(new Menu[]
|
||
{
|
||
new Menu { Name = "部门操作", Type = "button", Permission = "system:department:btn", Sort = 1, ParentId = deptId },
|
||
new Menu { Name = "职工操作", Type = "button", Permission = "system:employee:btn", Sort = 1, ParentId = empId },
|
||
new Menu { Name = "角色操作", Type = "button", Permission = "system:role:btn", Sort = 1, ParentId = roleId },
|
||
new Menu { Name = "菜单操作", Type = "button", Permission = "system:menu:btn", Sort = 1, ParentId = menuId },
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
// 4. 考勤菜单(增量)
|
||
SeedAttendanceMenus(fsql);
|
||
|
||
// 5. 业务基础数据(部门树 + 机组车台 + 庄口 + 规格 + 定级指标 + 菜单)
|
||
BusinessSeedData.Seed(fsql);
|
||
|
||
// 6. 通知:消息类型种子 + 菜单
|
||
SeedNotificationData(fsql, snowflake);
|
||
|
||
// 6.5 数据迁移:旧 RBAC 表 → 新 DDD RBAC 表(先迁移,确保 admin 角色/用户已在新表)
|
||
MigrateRbacData(fsql);
|
||
|
||
// 6.6 F9 风格 RBAC 菜单(用户/角色/菜单/部门管理,写入新 sys_rbac_menus 表)
|
||
SeedSysRbacMenus(fsql, snowflake);
|
||
|
||
// 6.65 基础数据(工种 + 注册表配置 + 原料庄口 + 物资 + 往来单位 + 职工,F9 导出数据幂等导入)
|
||
WorkTypeSeedData.Seed(fsql, snowflake);
|
||
RegistrySeedData.Seed(fsql);
|
||
YuanliaoZhuangkouSeedData.Seed(fsql);
|
||
WuziSeedData.Seed(fsql);
|
||
GongyingshangSeedData.Seed(fsql);
|
||
EmployeeSeedData.Seed(fsql);
|
||
|
||
// 6.655 生产模块基础数据(机组车台 + 车间班次,F9 导出数据幂等导入)
|
||
ProdSeedData.Seed(fsql, snowflake);
|
||
|
||
// 6.66 基础数据菜单(工种管理、账期管理、注册表、原料庄口、物资目录、往来单位)
|
||
SeedWorkTypeMenus(fsql, snowflake);
|
||
SeedZhangqiMenus(fsql, snowflake);
|
||
SeedRegistryMenus(fsql, snowflake);
|
||
SeedYuanliaoZhuangkouMenus(fsql, snowflake);
|
||
SeedWuziMenus(fsql, snowflake);
|
||
SeedGongyingshangMenus(fsql, snowflake);
|
||
|
||
// 6.67 职工管理(一级菜单,与"基础数据"平级)
|
||
SeedEmployeeTopMenu(fsql, snowflake);
|
||
|
||
// 6.68 生产模块(按工序拆分:生产-前缫 / 生产-后缫)
|
||
SeedProdMenus(fsql, snowflake);
|
||
|
||
// 6.69 修正前缫部门的职责类型(车间班次菜单随"生产-前缫"一起处理)
|
||
FixQianxianDutyType(fsql);
|
||
|
||
// 6.695 质量模块(工艺执行书 / 样茧试缫)
|
||
SeedQcMenus(fsql, snowflake);
|
||
|
||
// 6.71 报表中心菜单(/report 目录 + 报表中心/报表管理两个页面)
|
||
SeedReportMenus(fsql, snowflake);
|
||
// 6.72 旧版内嵌模板(sys_reports.template_data)迁移为独立模板实例 ReportFile
|
||
MigrateReportTemplateToFile(fsql, snowflake);
|
||
|
||
// 6.72 报表中心示例报表(职工花名册)
|
||
SeedSampleReport(fsql, snowflake);
|
||
|
||
// 6.73 落丝记录报表(生产-前缫,DsName=RepDt_Prod_Lsjl)
|
||
SeedLsjlReport(fsql, snowflake);
|
||
|
||
// 6.7 为菜单创建权限单元(Permission),并修正旧菜单权限关联(统一权限模型)
|
||
FixMenuPermissions(fsql, snowflake);
|
||
|
||
// 7. 管理员账号(幂等)
|
||
var admin = fsql.Select<Employee>().Where(e => e.Username == "admin").First();
|
||
if (admin == null)
|
||
{
|
||
var dept = fsql.Select<Department>().Where(d => d.Name == "主干运维").First()
|
||
?? fsql.Select<Department>().First();
|
||
var adminId = InsertAndGetId(fsql, new Employee
|
||
{
|
||
Username = "admin",
|
||
Password = BCrypt.Net.BCrypt.HashPassword("123456"),
|
||
RealName = "系统管理员",
|
||
Nickname = "管理员",
|
||
DepartmentId = dept.Id,
|
||
Email = "admin@company.com",
|
||
Phone = "13800000000",
|
||
Status = 1
|
||
});
|
||
admin = fsql.Select<Employee>().Where(e => e.Id == adminId).First();
|
||
}
|
||
|
||
// 8. admin 关联 admin 角色
|
||
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
|
||
if (!fsql.Select<EmployeeRole>().Any(er => er.EmployeeId == admin.Id && er.RoleId == adminRole.Id))
|
||
{
|
||
fsql.Insert(new EmployeeRole { EmployeeId = admin.Id, RoleId = adminRole.Id }).ExecuteAffrows();
|
||
}
|
||
|
||
// 9. admin 角色分配所有菜单(幂等补充)
|
||
var allMenus = fsql.Select<Menu>().ToList();
|
||
foreach (var m in allMenus)
|
||
{
|
||
if (!fsql.Select<RoleMenu>().Any(rm => rm.RoleId == adminRole.Id && rm.MenuId == m.Id))
|
||
{
|
||
fsql.Insert(new RoleMenu { RoleId = adminRole.Id, MenuId = m.Id }).ExecuteAffrows();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>CodeFirst 建表(新表建表,旧表同步失败则跳过)+ 旧库加列兜底</summary>
|
||
private static void SyncStructure(IFreeSql fsql)
|
||
{
|
||
// 扫描 BaseEntity(旧)与 Entity(新 DDD 基类)两类实体的具体子类建表
|
||
var entityTypes = typeof(BaseEntity).Assembly.GetTypes()
|
||
.Where(t => t.IsClass && !t.IsAbstract &&
|
||
(t.IsSubclassOf(typeof(BaseEntity)) || t.IsSubclassOf(typeof(Entity))));
|
||
foreach (var t in entityTypes)
|
||
{
|
||
try
|
||
{
|
||
fsql.CodeFirst.SyncStructure(t);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 旧表同步失败(如唯一索引与历史数据冲突),跳过,保持旧库兼容
|
||
Console.WriteLine($"[DbSeeder] 表 {t.Name} 结构同步跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 旧库加列兜底(SyncStructure 不会为已有表加列)
|
||
EnsureColumn(fsql, "LeaveRequests", "CurrentOrderNo", "INTEGER NOT NULL DEFAULT 1");
|
||
EnsureColumn(fsql, "NotificationTypes", "Category", "INTEGER NOT NULL DEFAULT 0");
|
||
EnsureColumn(fsql, "NotificationTypes", "Icon", "TEXT NOT NULL DEFAULT ''");
|
||
EnsureColumn(fsql, "NotificationTypes", "RouteTemplate", "TEXT NOT NULL DEFAULT ''");
|
||
EnsureColumn(fsql, "NotificationMessages", "Category", "INTEGER NOT NULL DEFAULT 0");
|
||
EnsureColumn(fsql, "NotificationMessages", "RoutePath", "TEXT NOT NULL DEFAULT ''");
|
||
EnsureColumn(fsql, "sys_reports", "report_file_id", "INTEGER NOT NULL DEFAULT 0");
|
||
}
|
||
|
||
/// <summary>为已有表补加列(列已存在则忽略)</summary>
|
||
private static void EnsureColumn(IFreeSql fsql, string table, string column, string definition)
|
||
{
|
||
try
|
||
{
|
||
fsql.Ado.ExecuteNonQuery($"ALTER TABLE \"{table}\" ADD COLUMN \"{column}\" {definition};");
|
||
}
|
||
catch
|
||
{
|
||
// 列已存在,忽略
|
||
}
|
||
}
|
||
|
||
/// <summary>插入并返回自增主键 ID(SQLite 用 last_insert_rowid 可靠获取)</summary>
|
||
public static long InsertAndGetId<T>(IFreeSql fsql, T entity) where T : BaseEntity
|
||
{
|
||
fsql.Insert(entity).ExecuteAffrows();
|
||
return Convert.ToInt64(fsql.Ado.ExecuteScalar("SELECT last_insert_rowid();"));
|
||
}
|
||
|
||
/// <summary>通知种子:消息类型 + 消息中心菜单</summary>
|
||
private static void SeedNotificationData(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
// 消息类型:发现特野
|
||
if (!fsql.Select<NotificationType>().Any(t => t.Code == "TeyeFound"))
|
||
{
|
||
fsql.Insert(new NotificationType
|
||
{
|
||
Code = "TeyeFound", Name = "发现特野", Description = "纤度检验发现特野时自动推送提醒",
|
||
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 1,
|
||
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/xdjy"
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
// 消息类型:发现特大糙
|
||
if (!fsql.Select<NotificationType>().Any(t => t.Code == "TdcFound"))
|
||
{
|
||
fsql.Insert(new NotificationType
|
||
{
|
||
Code = "TdcFound", Name = "发现特大糙", Description = "黑板检验发现特大糙时自动推送提醒",
|
||
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 2,
|
||
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/hbjy"
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
// 消息类型:请假审批(订阅权限用独立码,避免与业务按钮权限冲突)
|
||
if (!fsql.Select<NotificationType>().Any(t => t.Code == "LeaveApproval"))
|
||
{
|
||
fsql.Insert(new NotificationType
|
||
{
|
||
Code = "LeaveApproval", Name = "请假审批", Description = "员工提交请假申请后,推送待办给审批人",
|
||
RequiredPermission = "notification:subscribe:leaveapproval", IsActive = true, CreatedBy = 0, Sort = 3,
|
||
Category = NotificationCategory.Todo, Icon = "Checked", RouteTemplate = "/attendance/approve?leaveId={businessId}"
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
// 消息类型:请假结果通知
|
||
if (!fsql.Select<NotificationType>().Any(t => t.Code == "LeaveResult"))
|
||
{
|
||
fsql.Insert(new NotificationType
|
||
{
|
||
Code = "LeaveResult", Name = "请假结果通知", Description = "请假审批完成后通知申请人结果",
|
||
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 4,
|
||
Category = NotificationCategory.Notification, Icon = "Message", RouteTemplate = "/attendance/my"
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
// 消息类型:广播通知(有权限的用户主动发送的广播,推送给所有订阅者)
|
||
if (!fsql.Select<NotificationType>().Any(t => t.Code == "Broadcast"))
|
||
{
|
||
fsql.Insert(new NotificationType
|
||
{
|
||
Code = "Broadcast", Name = "广播通知", Description = "用户发送的广播通知,推送给所有订阅该类型的用户",
|
||
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 5,
|
||
Category = NotificationCategory.Announcement, Icon = "Bell", RouteTemplate = ""
|
||
}).ExecuteAffrows();
|
||
}
|
||
|
||
// 广播发送权限单元(ResourceType=操作,Code=notification:broadcast,admin 自动拥有,普通用户可在角色管理分配)
|
||
var broadcastPerm = fsql.Select<SysPermission>()
|
||
.Where(p => p.Code == "notification:broadcast").First();
|
||
if (broadcastPerm == null)
|
||
{
|
||
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Enable, Resource_Type_Enum.操作, "broadcast", 0);
|
||
perm.SetCode("notification:broadcast");
|
||
fsql.Insert(perm).ExecuteAffrows();
|
||
}
|
||
|
||
// 菜单:消息中心
|
||
var notiDirId = fsql.Select<Menu>().Where(m => m.Path == "/notification").First()?.Id ?? 0;
|
||
if (notiDirId == 0)
|
||
{
|
||
notiDirId = InsertAndGetId(fsql, new Menu { Name = "消息中心", Type = "directory", Path = "/notification", Icon = "Bell", Sort = 5 });
|
||
}
|
||
|
||
AddNotificationMenu(fsql, notiDirId, "我的消息", "/notification/messages", "notification/messages/index", "Message", "notification:messages:list", 1);
|
||
AddNotificationMenu(fsql, notiDirId, "消息订阅", "/notification/subscribe", "notification/subscribe/index", "BellFilled", "notification:subscribe:list", 2);
|
||
AddNotificationMenu(fsql, notiDirId, "消息类型管理", "/notification/type", "notification/type/index", "SetUp", "notification:type:list", 3);
|
||
|
||
// 清理历史遗留:发现特野/发现特大糙 改为「所有人可订阅」
|
||
foreach (var (code, oldPerm) in new[] { ("TeyeFound", "notification:subscribe:foundteye"), ("TdcFound", "notification:subscribe:foundtedacao") })
|
||
{
|
||
var t = fsql.Select<NotificationType>().Where(x => x.Code == code).First();
|
||
if (t == null) continue;
|
||
|
||
// 1. RequiredPermission 改为空(所有人可订阅)
|
||
if (!string.IsNullOrEmpty(t.RequiredPermission))
|
||
{
|
||
t.RequiredPermission = "";
|
||
fsql.Update<NotificationType>().SetSource(t).ExecuteAffrows();
|
||
}
|
||
|
||
// 2. 删除订阅权限单元及其角色关联(新 Permission 表)
|
||
var perm = fsql.Select<SysPermission>()
|
||
.Where(p => p.ResourceType == Resource_Type_Enum.消息订阅 && p.ResourceId == t.Id).First();
|
||
if (perm != null)
|
||
{
|
||
fsql.Delete<SysRolePermission>().Where(rp => rp.PermissionId == perm.Id).ExecuteAffrows();
|
||
fsql.Delete<SysPermission>().Where(p => p.Id == perm.Id).ExecuteAffrows();
|
||
}
|
||
|
||
// 3. 删除历史遗留的旧"订阅按钮"菜单及其角色关联(新表 sys_rbac_menus + 旧表 Menus)
|
||
fsql.Ado.ExecuteNonQuery($@"
|
||
DELETE FROM sys_rbac_role_permissions WHERE permission_id IN
|
||
(SELECT id FROM sys_rbac_menus WHERE permission = '{oldPerm}');
|
||
DELETE FROM sys_rbac_menus WHERE permission = '{oldPerm}';
|
||
DELETE FROM RoleMenus WHERE MenuId IN (SELECT Id FROM Menus WHERE Permission = '{oldPerm}');
|
||
DELETE FROM Menus WHERE Permission = '{oldPerm}';");
|
||
}
|
||
|
||
// 清理所有旧的"订阅按钮"菜单(已被 Permission 表的消息订阅权限替代,避免与消息订阅权限重复冗余)
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
DELETE FROM sys_rbac_role_permissions WHERE permission_id IN
|
||
(SELECT id FROM sys_rbac_menus WHERE name LIKE '订阅:%' AND menu_type = 0);
|
||
DELETE FROM sys_rbac_menus WHERE name LIKE '订阅:%' AND menu_type = 0;
|
||
DELETE FROM RoleMenus WHERE MenuId IN (SELECT Id FROM Menus WHERE Name LIKE '订阅:%' AND Type = 'button');
|
||
DELETE FROM Menus WHERE Name LIKE '订阅:%' AND Type = 'button';");
|
||
|
||
// 清理 RequiredPermission 为空的消息类型的订阅权限(历史遗留,所有人可订阅无需权限单元)
|
||
foreach (var t in fsql.Select<NotificationType>().Where(x => x.RequiredPermission == "").ToList())
|
||
{
|
||
var perm = fsql.Select<SysPermission>()
|
||
.Where(p => p.ResourceType == Resource_Type_Enum.消息订阅 && p.ResourceId == t.Id).First();
|
||
if (perm != null)
|
||
{
|
||
fsql.Delete<SysRolePermission>().Where(rp => rp.PermissionId == perm.Id).ExecuteAffrows();
|
||
fsql.Delete<SysPermission>().Where(p => p.Id == perm.Id).ExecuteAffrows();
|
||
}
|
||
}
|
||
|
||
// 同步订阅权限到 RBAC 权限单元(ResourceType=消息订阅,Code=RequiredPermission)
|
||
foreach (var t in fsql.Select<NotificationType>().ToList())
|
||
{
|
||
if (string.IsNullOrEmpty(t.RequiredPermission)) continue;
|
||
var existPerm = fsql.Select<SysPermission>()
|
||
.Where(p => p.ResourceType == Resource_Type_Enum.消息订阅 && p.ResourceId == t.Id).First();
|
||
if (existPerm == null)
|
||
{
|
||
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.消息订阅, t.Code, t.Id);
|
||
perm.SetCode(t.RequiredPermission);
|
||
fsql.Insert(perm).ExecuteAffrows();
|
||
}
|
||
else if (existPerm.Code != t.RequiredPermission)
|
||
{
|
||
// 修正 Code 为业务权限码(覆盖旧的自动生成码)
|
||
existPerm.SetCode(t.RequiredPermission);
|
||
fsql.Update<SysPermission>().SetSource(existPerm).ExecuteAffrows();
|
||
}
|
||
}
|
||
|
||
// admin 角色分配消息中心菜单权限
|
||
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
|
||
if (adminRole != null)
|
||
{
|
||
var notiMenus = fsql.Select<Menu>().Where(m => m.Path.StartsWith("/notification")).ToList();
|
||
foreach (var m in notiMenus)
|
||
{
|
||
if (!fsql.Select<RoleMenu>().Any(rm => rm.RoleId == adminRole.Id && rm.MenuId == m.Id))
|
||
fsql.Insert(new RoleMenu { RoleId = adminRole.Id, MenuId = m.Id }).ExecuteAffrows();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void AddNotificationMenu(IFreeSql fsql, long parentId, string name, string path, string component, string icon, string permission, int sort)
|
||
{
|
||
if (!fsql.Select<Menu>().Any(m => m.Path == path))
|
||
{
|
||
fsql.Insert(new Menu { ParentId = parentId, Name = name, Type = "menu", Path = path, Component = component, Icon = icon, Permission = permission, Sort = sort }).ExecuteAffrows();
|
||
}
|
||
}
|
||
|
||
private static void SeedAttendanceMenus(IFreeSql fsql)
|
||
{
|
||
var attDirId = fsql.Select<Menu>().Where(m => m.Path == "/attendance").First()?.Id ?? 0;
|
||
if (attDirId == 0)
|
||
{
|
||
attDirId = InsertAndGetId(fsql, new Menu { Name = "考勤管理", Type = "directory", Path = "/attendance", Icon = "Clock", Sort = 2 });
|
||
}
|
||
|
||
void AddMenu(string name, string path, string component, string icon, string permission, int sort)
|
||
{
|
||
if (!fsql.Select<Menu>().Any(m => m.Path == path))
|
||
{
|
||
fsql.Insert(new Menu { Name = name, Type = "menu", Path = path, Component = component, Icon = icon, Permission = permission, Sort = sort, ParentId = attDirId }).ExecuteAffrows();
|
||
}
|
||
}
|
||
AddMenu("考勤打卡", "/attendance/punch", "attendance/punch/index", "AlarmClock", "attendance:punch:list", 1);
|
||
AddMenu("我的考勤", "/attendance/my", "attendance/my/index", "Calendar", "attendance:my:list", 2);
|
||
AddMenu("请假申请", "/attendance/leave", "attendance/leave/index", "Suitcase", "attendance:leave:list", 3);
|
||
AddMenu("考勤记录", "/attendance/records", "attendance/records/index", "List", "attendance:records:list", 4);
|
||
AddMenu("请假审批", "/attendance/approve", "attendance/approve/index", "Checked", "attendance:approve:list", 5);
|
||
|
||
void AddButton(string menuPath, string name, string permission)
|
||
{
|
||
var menu = fsql.Select<Menu>().Where(m => m.Path == menuPath).First();
|
||
if (menu != null && !fsql.Select<Menu>().Any(m => m.Permission == permission))
|
||
{
|
||
fsql.Insert(new Menu { Name = name, Type = "button", Permission = permission, Sort = 1, ParentId = menu.Id }).ExecuteAffrows();
|
||
}
|
||
}
|
||
AddButton("/attendance/punch", "打卡操作", "attendance:punch:btn");
|
||
AddButton("/attendance/leave", "请假操作", "attendance:leave:btn");
|
||
AddButton("/attendance/approve", "审批操作", "attendance:approve:btn");
|
||
|
||
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
|
||
if (adminRole != null)
|
||
{
|
||
var attMenus = fsql.Select<Menu>().Where(m => m.Path.StartsWith("/attendance") || m.Permission.StartsWith("attendance:")).ToList();
|
||
foreach (var m in attMenus)
|
||
{
|
||
if (!fsql.Select<RoleMenu>().Any(rm => rm.RoleId == adminRole.Id && rm.MenuId == m.Id))
|
||
fsql.Insert(new RoleMenu { RoleId = adminRole.Id, MenuId = m.Id }).ExecuteAffrows();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>数据迁移:旧 RBAC 表 → 新 DDD RBAC 表(幂等,仅迁移尚不存在的记录)</summary>
|
||
private static void MigrateRbacData(IFreeSql fsql)
|
||
{
|
||
try
|
||
{
|
||
// 1. 部门:Departments → sys_departments
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
INSERT INTO sys_departments (id, code, name, parent_id, manage_type, duty_type, description, is_active, sort_order, created_by, created_at, update_by, updated_at, is_deleted)
|
||
SELECT Id, COALESCE(Code, ''), Name, COALESCE(ParentId, 0), 10, 5, '', Status, Sort, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
|
||
FROM Departments WHERE Id NOT IN (SELECT id FROM sys_departments);");
|
||
|
||
// 2. 角色:Roles → sys_rbac_roles
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
INSERT INTO sys_rbac_roles (id, name, code, sort_order, description, is_active, created_by, created_at, update_by, updated_at, is_deleted)
|
||
SELECT Id, Name, Code, Sort, COALESCE(Description, ''), Status, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
|
||
FROM Roles WHERE Id NOT IN (SELECT id FROM sys_rbac_roles);");
|
||
|
||
// 3. 菜单:Menus → sys_rbac_menus
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
INSERT INTO sys_rbac_menus (id, icon_svg, code, name, menu_type, assembly_name, type_full_name, parent_id, level, is_active, sort_order, path, component, permission, created_by, created_at, update_by, updated_at, is_deleted)
|
||
SELECT Id, COALESCE(Icon, ''), '', Name, CASE WHEN Type = 'directory' THEN 1 WHEN Type = 'menu' THEN 2 ELSE 0 END, '', '', COALESCE(ParentId, 0), 1, Status, Sort, COALESCE(Path, ''), COALESCE(Component, ''), COALESCE(Permission, ''), '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
|
||
FROM Menus WHERE Id NOT IN (SELECT id FROM sys_rbac_menus);");
|
||
|
||
// 4. 用户:Employees → sys_rbac_users
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
INSERT INTO sys_rbac_users (id, department_id, user_name, phone_number, login_name, password_hash, is_active, sort_order, lastlogin_at, nickname, avatar, email, gender, created_by, created_at, update_by, updated_at, is_deleted)
|
||
SELECT Id, DepartmentId, RealName, COALESCE(Phone, ''), Username, Password, Status, 0, NULL, COALESCE(Nickname, ''), COALESCE(Avatar, ''), COALESCE(Email, ''), COALESCE(Gender, '男'), '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
|
||
FROM Employees WHERE Id NOT IN (SELECT id FROM sys_rbac_users);");
|
||
|
||
// 5. 用户角色:EmployeeRoles → sys_rbac_user_roles
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
INSERT INTO sys_rbac_user_roles (id, user_id, role_id, created_by, created_at, update_by, updated_at, is_deleted)
|
||
SELECT Id, EmployeeId, RoleId, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
|
||
FROM EmployeeRoles WHERE Id NOT IN (SELECT id FROM sys_rbac_user_roles);");
|
||
|
||
// 6. 角色权限:RoleMenus → sys_rbac_role_permissions
|
||
fsql.Ado.ExecuteNonQuery(@"
|
||
INSERT INTO sys_rbac_role_permissions (id, role_id, permission_id, created_by, created_at, update_by, updated_at, is_deleted)
|
||
SELECT Id, RoleId, MenuId, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
|
||
FROM RoleMenus WHERE Id NOT IN (SELECT id FROM sys_rbac_role_permissions);");
|
||
|
||
Console.WriteLine("[DbSeeder] RBAC 数据迁移完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] RBAC 数据迁移跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>F9 风格 RBAC 前端菜单种子(写入新 sys_rbac_menus 表,驱动前端导航)</summary>
|
||
private static void SeedSysRbacMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
// 删除旧"系统管理"菜单(/system/*,对接已下线的旧接口),仅保留新的 /sys 管理菜单
|
||
fsql.Ado.ExecuteNonQuery("DELETE FROM sys_rbac_role_permissions WHERE permission_id IN (SELECT id FROM sys_rbac_menus WHERE path LIKE '/system%')");
|
||
fsql.Ado.ExecuteNonQuery("DELETE FROM sys_rbac_menus WHERE path LIKE '/system%'");
|
||
|
||
// sys 目录
|
||
long sysDirId;
|
||
var sysDir = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_menus WHERE path = '/sys'");
|
||
if (sysDir.Rows.Count == 0)
|
||
{
|
||
sysDirId = snowflake.CreateId();
|
||
fsql.Ado.ExecuteNonQuery($"INSERT INTO sys_rbac_menus (id, icon_svg, name, menu_type, parent_id, level, is_active, sort_order, path, component, permission, created_at, is_deleted) VALUES ({sysDirId}, 'UserFilled', '系统管理(RBAC)', 1, 0, 1, 0, 6, '/sys', '', '', datetime('now'), 0)");
|
||
}
|
||
else
|
||
{
|
||
sysDirId = Convert.ToInt64(sysDir.Rows[0]["id"]);
|
||
}
|
||
|
||
void AddMenu(string name, string path, string component, string icon, string permission, int sort)
|
||
{
|
||
var exist = fsql.Ado.ExecuteDataTable($"SELECT id FROM sys_rbac_menus WHERE path = '{path}'");
|
||
if (exist.Rows.Count > 0) return;
|
||
var id = snowflake.CreateId();
|
||
fsql.Ado.ExecuteNonQuery($"INSERT INTO sys_rbac_menus (id, icon_svg, name, menu_type, parent_id, level, is_active, sort_order, path, component, permission, created_at, is_deleted) VALUES ({id}, '{icon}', '{name}', 2, {sysDirId}, 2, 0, {sort}, '{path}', '{component}', '{permission}', datetime('now'), 0)");
|
||
}
|
||
|
||
AddMenu("用户管理", "/sys/user", "sys/user/index", "User", "sys:user:list", 1);
|
||
AddMenu("角色管理", "/sys/role", "sys/role/index", "UserFilled", "sys:role:list", 2);
|
||
AddMenu("菜单管理", "/sys/menu", "sys/menu/index", "Menu", "sys:menu:list", 3);
|
||
AddMenu("部门管理", "/sys/department", "sys/department/index", "OfficeBuilding", "sys:department:list", 4);
|
||
|
||
// admin 角色分配 sys 菜单权限
|
||
var adminRole = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_roles WHERE code = 'admin'");
|
||
if (adminRole.Rows.Count > 0)
|
||
{
|
||
var roleId = Convert.ToInt64(adminRole.Rows[0]["id"]);
|
||
var menus = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_menus WHERE path LIKE '/sys%'");
|
||
foreach (DataRow row in menus.Rows)
|
||
{
|
||
var menuId = Convert.ToInt64(row["id"]);
|
||
var exist = fsql.Ado.ExecuteDataTable($"SELECT id FROM sys_rbac_role_permissions WHERE role_id = {roleId} AND permission_id = {menuId}");
|
||
if (exist.Rows.Count == 0)
|
||
{
|
||
fsql.Ado.ExecuteNonQuery($"INSERT INTO sys_rbac_role_permissions (id, role_id, permission_id, created_at, is_deleted) VALUES ({snowflake.CreateId()}, {roleId}, {menuId})");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] RBAC 菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>工种管理菜单(挂在"基础数据"目录下)</summary>
|
||
private static void SeedWorkTypeMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureBaseDataMenu(fsql, snowflake, "/base/worktype", "工种管理", "base/worktype/index", "base:worktype:list", "Postcard", 5);
|
||
Console.WriteLine("[DbSeeder] 工种管理菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 工种菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>账期管理菜单(挂在"基础数据"目录下)</summary>
|
||
private static void SeedZhangqiMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureBaseDataMenu(fsql, snowflake, "/base/zhangqi", "账期管理", "base/zhangqi/index", "base:zhangqi:list", "Tickets", 6);
|
||
Console.WriteLine("[DbSeeder] 账期管理菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 账期菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>注册表菜单(挂在"基础数据"目录下)</summary>
|
||
private static void SeedRegistryMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureBaseDataMenu(fsql, snowflake, "/base/registry", "注册表", "base/registry/index", "base:registry:list", "Setting", 7);
|
||
Console.WriteLine("[DbSeeder] 注册表菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 注册表菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>原料庄口菜单(挂在"基础数据"目录下)</summary>
|
||
private static void SeedYuanliaoZhuangkouMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureBaseDataMenu(fsql, snowflake, "/base/yuanliaozhuangkou", "原料庄口", "base/yuanliaozhuangkou/index", "base:yuanliaozhuangkou:list", "Box", 8);
|
||
Console.WriteLine("[DbSeeder] 原料庄口菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 原料庄口菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>物资目录菜单(挂在"基础数据"目录下)</summary>
|
||
private static void SeedWuziMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureBaseDataMenu(fsql, snowflake, "/base/wuzimulu", "物资目录", "base/wuzimulu/index", "base:wuzimulu:list", "Goods", 9);
|
||
Console.WriteLine("[DbSeeder] 物资目录菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 物资目录菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>往来单位菜单(挂在"基础数据"目录下)</summary>
|
||
private static void SeedGongyingshangMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureBaseDataMenu(fsql, snowflake, "/base/gongyingshang", "往来单位", "base/gongyingshang/index", "base:gongyingshang:list", "Connection", 10);
|
||
Console.WriteLine("[DbSeeder] 往来单位菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 往来单位菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 职工管理菜单:独立的一级菜单(原作为"基础数据"的子菜单)。
|
||
/// 会清理历史遗留的 /base/employee 子菜单,并规范一级菜单排序,避免同一页面出现两个入口。
|
||
/// </summary>
|
||
private static void SeedEmployeeTopMenu(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
const string menuPath = "/employee";
|
||
const string menuIcon = "Avatar";
|
||
|
||
// 1) 清理历史:曾是"基础数据"下的子菜单
|
||
var legacy = fsql.Select<SysMenu>().Where(m => m.Path == "/base/employee").First();
|
||
if (legacy != null)
|
||
{
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacy.Id}");
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacy.Id}");
|
||
Console.WriteLine("[DbSeeder] 已清理历史子菜单 /base/employee");
|
||
}
|
||
|
||
// 2) 创建/校正一级菜单
|
||
var menu = fsql.Select<SysMenu>().Where(m => m.Path == menuPath).First();
|
||
if (menu == null)
|
||
{
|
||
menu = new SysMenu(snowflake.CreateId(), "职工管理", menuIcon, MenuType_Enum.页面, null, null, 0, 4, 0);
|
||
menu.SetWebInfo(menuPath, "base/employee/index", "base:employee:list");
|
||
fsql.Insert(menu).ExecuteAffrows();
|
||
}
|
||
else if (!menu.IsActive || menu.IconSvg != menuIcon || menu.ParentId != 0)
|
||
{
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.IconSvg, menuIcon)
|
||
.Set(x => x.IsActive, true)
|
||
.Set(x => x.ParentId, 0)
|
||
.Set(x => x.Level, 1)
|
||
.Where(x => x.Id == menu.Id)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
// 3) 规范一级菜单顺序:考勤管理 / 基础数据 / 职工管理 / 生产管理 / 消息中心 / 系统管理
|
||
foreach (var (path, sort) in new (string Path, int Sort)[]
|
||
{
|
||
("/attendance", 2), ("/base", 3), ("/employee", 4),
|
||
("/prod", 5), ("/notification", 6), ("/sys", 7)
|
||
})
|
||
{
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.SortOrder, sort)
|
||
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
// 4) admin 角色授权(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)
|
||
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
|
||
if (adminRole != null)
|
||
{
|
||
var any = fsql.Select<SysRolePermission>()
|
||
.Where(x => x.RoleId == adminRole.Id && x.PermissionId == menu.Id)
|
||
.First();
|
||
if (any == null)
|
||
{
|
||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, menu.Id)).ExecuteAffrows();
|
||
}
|
||
}
|
||
|
||
Console.WriteLine("[DbSeeder] 职工管理一级菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 职工管理菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生产模块菜单:按缫丝工序拆分为「生产-前缫」(落丝记录)与「生产-后缫」(纤度检验、黑板检验)两个一级菜单。
|
||
/// 会清理旧的「生产管理」(/prod) 目录及其子菜单;页面组件与权限标识保持不变。
|
||
/// </summary>
|
||
private static void SeedProdMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
// 1) 清理旧「生产管理」结构(精确匹配历史路径)
|
||
foreach (var legacyPath in new[] { "/prod", "/prod/lsjl", "/prod/xdjy", "/prod/hbjy" })
|
||
{
|
||
var legacy = fsql.Select<SysMenu>().Where(m => m.Path == legacyPath).First();
|
||
if (legacy == null) continue;
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacy.Id}");
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacy.Id}");
|
||
Console.WriteLine($"[DbSeeder] 已清理旧生产菜单 {legacyPath}");
|
||
}
|
||
|
||
// 2) 两个一级目录 + 各自页面菜单
|
||
// 车间班次原先挂在"基础数据"目录下,现归入"生产-前缫"
|
||
var legacyChejianbanci = fsql.Select<SysMenu>().Where(m => m.Path == "/base/chejianbanci").First();
|
||
if (legacyChejianbanci != null)
|
||
{
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacyChejianbanci.Id}");
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacyChejianbanci.Id}");
|
||
Console.WriteLine("[DbSeeder] 已清理旧菜单 /base/chejianbanci(车间班次移入生产-前缫)");
|
||
}
|
||
|
||
// 一并清理旧菜单遗留的权限单元(权限标识已从 base:chejianbanci:list 改为 prod:chejianbanci:list)
|
||
var legacyPerms = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_permissions WHERE code = 'base:chejianbanci:list'");
|
||
foreach (DataRow permRow in legacyPerms.Rows)
|
||
{
|
||
var permId = Convert.ToInt64(permRow["id"]);
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {permId}");
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_permissions WHERE id = {permId}");
|
||
Console.WriteLine("[DbSeeder] 已清理旧权限单元 base:chejianbanci:list");
|
||
}
|
||
|
||
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/qianxian", "生产-前缫", "Cpu", 5,
|
||
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
|
||
{
|
||
("落丝记录", "/prod/qianxian/lsjl", "prod/lsjl/index", "prod:lsjl:list", "Document", 1),
|
||
("车间班次", "/prod/qianxian/chejianbanci", "base/chejianbanci/index", "prod:chejianbanci:list", "Timer", 2),
|
||
("看台排班", "/prod/qianxian/ktpb", "prod/ktpb/index", "prod:ktpb:list", "Grid", 3)
|
||
});
|
||
|
||
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/houxian", "生产-后缫", "DataLine", 6,
|
||
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
|
||
{
|
||
("纤度检验", "/prod/houxian/xdjy", "prod/xdjy/index", "prod:xdjy:list", "EditPen", 1),
|
||
("黑板检验", "/prod/houxian/hbjy", "prod/hbjy/index", "prod:hbjy:list", "Notebook", 2)
|
||
});
|
||
|
||
// 3) 规范一级菜单顺序
|
||
foreach (var (path, sort) in new (string Path, int Sort)[]
|
||
{
|
||
("/attendance", 2), ("/base", 3), ("/employee", 4),
|
||
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/notification", 7), ("/sys", 8)
|
||
})
|
||
{
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.SortOrder, sort)
|
||
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
// 4) 消息跳转路径同步到新路由
|
||
fsql.Ado.ExecuteNonQuery("UPDATE NotificationTypes SET RouteTemplate = '/prod/houxian/xdjy' WHERE Code = 'TeyeFound'");
|
||
fsql.Ado.ExecuteNonQuery("UPDATE NotificationTypes SET RouteTemplate = '/prod/houxian/hbjy' WHERE Code = 'TdcFound'");
|
||
|
||
Console.WriteLine("[DbSeeder] 生产模块菜单已就绪(生产-前缫 / 生产-后缫)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 生产菜单种子跳过:{ex}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 质量模块菜单:一级目录「质量」+ 工艺执行书 / 样茧试缫 两个页面。
|
||
/// 模块落在「生产-后缫」之后、消息中心之前。
|
||
/// </summary>
|
||
private static void SeedQcMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureTopMenuWithChildren(fsql, snowflake, "/qc", "质量", "Medal", 7,
|
||
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
|
||
{
|
||
("工艺执行书", "/qc/gyzxs", "qc/gyzxs/index", "qc:gyzxs:list", "Document", 1),
|
||
("样茧试缫", "/qc/yjss", "qc/yjss/index", "qc:yjss:list", "DataAnalysis", 2)
|
||
});
|
||
|
||
foreach (var (path, sort) in new (string Path, int Sort)[]
|
||
{
|
||
("/attendance", 2), ("/base", 3), ("/employee", 4),
|
||
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/qc", 7),
|
||
("/notification", 8), ("/sys", 9), ("/report", 10)
|
||
})
|
||
{
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.SortOrder, sort)
|
||
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
Console.WriteLine("[DbSeeder] 质量模块菜单已就绪(工艺执行书 / 样茧试缫)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 质量模块菜单种子跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保一个一级目录菜单及其下属页面菜单存在(含父级、图标、启用状态校正),并给 admin 角色授权。
|
||
/// 注意:图标名必须是 Element Plus 图标组件名。
|
||
/// </summary>
|
||
private static void EnsureTopMenuWithChildren(IFreeSql fsql, ISnowflakeIdGenerator snowflake,
|
||
string dirPath, string dirName, string dirIcon, int dirSort,
|
||
(string Name, string Path, string Component, string Permission, string Icon, int Sort)[] children)
|
||
{
|
||
var dir = fsql.Select<SysMenu>().Where(m => m.Path == dirPath).First();
|
||
if (dir == null)
|
||
{
|
||
dir = new SysMenu(snowflake.CreateId(), dirName, dirIcon, MenuType_Enum.分组, null, null, 0, dirSort, 0);
|
||
dir.SetWebInfo(dirPath, "", "");
|
||
fsql.Insert(dir).ExecuteAffrows();
|
||
}
|
||
else if (!dir.IsActive || dir.IconSvg != dirIcon || dir.Name != dirName || dir.ParentId != 0)
|
||
{
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.Name, dirName)
|
||
.Set(x => x.IconSvg, dirIcon)
|
||
.Set(x => x.IsActive, true)
|
||
.Set(x => x.ParentId, 0)
|
||
.Set(x => x.Level, 1)
|
||
.Where(x => x.Id == dir.Id)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
var adminRoleId = fsql.Select<SysRole>().Where(r => r.Code == "admin").First()?.Id ?? 0;
|
||
EnsureMenuPermission(fsql, snowflake, adminRoleId, dir.Id);
|
||
|
||
foreach (var (name, path, component, permission, icon, sort) in children)
|
||
{
|
||
var child = fsql.Select<SysMenu>().Where(m => m.Path == path).First();
|
||
if (child == null)
|
||
{
|
||
child = new SysMenu(snowflake.CreateId(), name, icon, MenuType_Enum.页面, null, null, dir.Id, sort, 1);
|
||
child.SetWebInfo(path, component, permission);
|
||
fsql.Insert(child).ExecuteAffrows();
|
||
}
|
||
else if (!child.IsActive || child.IconSvg != icon || child.ParentId != dir.Id)
|
||
{
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.IconSvg, icon)
|
||
.Set(x => x.IsActive, true)
|
||
.Set(x => x.ParentId, dir.Id)
|
||
.Set(x => x.Level, 2)
|
||
.Where(x => x.Id == child.Id)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
EnsureMenuPermission(fsql, snowflake, adminRoleId, child.Id);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 修正前缫部门的职责类型:历史数据迁移时 duty_type 统一写成了"其他"(5),
|
||
/// 而车间班次等模块需要按职责类型(前缫=25)筛选部门。
|
||
/// </summary>
|
||
private static void FixQianxianDutyType(IFreeSql fsql)
|
||
{
|
||
try
|
||
{
|
||
var rows = fsql.Ado.ExecuteNonQuery(
|
||
"UPDATE sys_departments SET duty_type = 25 WHERE is_deleted = 0 AND name LIKE '前缫%' AND duty_type <> 25");
|
||
if (rows > 0) Console.WriteLine($"[DbSeeder] 已修正 {rows} 个前缫部门的职责类型");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 部门职责类型修正跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>确保指定菜单已授权给角色(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)</summary>
|
||
private static void EnsureMenuPermission(IFreeSql fsql, ISnowflakeIdGenerator snowflake, long roleId, long menuId)
|
||
{
|
||
if (roleId <= 0 || menuId <= 0) return;
|
||
var any = fsql.Select<SysRolePermission>()
|
||
.Where(x => x.RoleId == roleId && x.PermissionId == menuId)
|
||
.First();
|
||
if (any == null)
|
||
{
|
||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), roleId, menuId)).ExecuteAffrows();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保"基础数据"目录下的菜单存在(含启用状态与图标校正),并给 admin 角色授权。
|
||
/// 注意:图标名必须是 Element Plus 图标组件名,前端按图标名渲染菜单图标。
|
||
/// </summary>
|
||
private static void EnsureBaseDataMenu(IFreeSql fsql, ISnowflakeIdGenerator snowflake,
|
||
string path, string name, string component, string permission, string icon, int sort)
|
||
{
|
||
// 基础数据目录:不存在则创建,已存在则复用
|
||
var baseDir = fsql.Select<SysMenu>().Where(m => m.Path == "/base").First();
|
||
if (baseDir == null)
|
||
{
|
||
baseDir = new SysMenu(snowflake.CreateId(), "基础数据", "Files", MenuType_Enum.分组, null, null, 0, 3, 0);
|
||
baseDir.SetWebInfo("/base", "", "");
|
||
fsql.Insert(baseDir).ExecuteAffrows();
|
||
}
|
||
|
||
var menu = fsql.Select<SysMenu>().Where(m => m.Path == path).First();
|
||
if (menu == null)
|
||
{
|
||
menu = new SysMenu(snowflake.CreateId(), name, icon, MenuType_Enum.页面, null, null, baseDir.Id, sort, 1);
|
||
menu.SetWebInfo(path, component, permission);
|
||
fsql.Insert(menu).ExecuteAffrows();
|
||
}
|
||
else if (!menu.IsActive || menu.IconSvg != icon)
|
||
{
|
||
// 兼容历史数据:补齐启用状态与图标
|
||
fsql.Update<SysMenu>()
|
||
.Set(x => x.IconSvg, icon)
|
||
.Set(x => x.IsActive, true)
|
||
.Where(x => x.Id == menu.Id)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
// admin 角色授权(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)
|
||
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
|
||
if (adminRole != null)
|
||
{
|
||
var any = fsql.Select<SysRolePermission>()
|
||
.Where(x => x.RoleId == adminRole.Id && x.PermissionId == menu.Id)
|
||
.First();
|
||
if (any == null)
|
||
{
|
||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, menu.Id)).ExecuteAffrows();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>为菜单创建权限单元(Permission),并修正旧菜单权限关联(RolePermission.PermissionId 从 Menu.Id 改为 Permission.Id)</summary>
|
||
private static void FixMenuPermissions(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
foreach (var menu in fsql.Select<SysMenu>().ToList())
|
||
{
|
||
// 1. 查或创建菜单权限单元(ResourceType=菜单,ResourceId=Menu.Id)
|
||
var perm = fsql.Select<SysPermission>()
|
||
.Where(p => p.ResourceType == Resource_Type_Enum.菜单 && p.ResourceId == menu.Id).First();
|
||
if (perm == null)
|
||
{
|
||
var resourceIdentity = string.IsNullOrEmpty(menu.Code) ? menu.Id.ToString() : menu.Code;
|
||
perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.菜单, resourceIdentity, menu.Id);
|
||
if (!string.IsNullOrEmpty(menu.Permission))
|
||
perm.SetCode(menu.Permission);
|
||
fsql.Insert(perm).ExecuteAffrows();
|
||
}
|
||
else if (!string.IsNullOrEmpty(menu.Permission) && perm.Code != menu.Permission)
|
||
{
|
||
perm.SetCode(menu.Permission);
|
||
fsql.Update<SysPermission>().SetSource(perm).ExecuteAffrows();
|
||
}
|
||
|
||
// 2. 修正旧菜单权限关联(permission_id 旧值 = Menu.Id,改为 Permission.Id)
|
||
fsql.Ado.ExecuteNonQuery($"UPDATE sys_rbac_role_permissions SET permission_id = {perm.Id} WHERE permission_id = {menu.Id};");
|
||
}
|
||
|
||
// 3. 清理失效的菜单权限单元:ResourceId 不对应任何菜单
|
||
// (前端曾用 Number() 传雪花 Id,超出 2^53 后精度丢失,写入的 resource_id 在菜单表里查不到,
|
||
// 表现为「勾选保存后重新打开仍是未勾选、该角色登录也看不到菜单」)
|
||
var menuIds = fsql.Select<SysMenu>().ToList().Select(m => m.Id).ToHashSet();
|
||
var orphanPerms = fsql.Select<SysPermission>()
|
||
.Where(p => p.ResourceType == Resource_Type_Enum.菜单).ToList()
|
||
.Where(p => !menuIds.Contains(p.ResourceId)).ToList();
|
||
foreach (var bad in orphanPerms)
|
||
{
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {bad.Id};");
|
||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_permissions WHERE id = {bad.Id};");
|
||
}
|
||
if (orphanPerms.Count > 0)
|
||
Console.WriteLine($"[DbSeeder] 已清理 {orphanPerms.Count} 条失效菜单权限单元(资源 Id 不存在)");
|
||
|
||
Console.WriteLine("[DbSeeder] 菜单权限单元同步完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 菜单权限单元同步跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>报表中心菜单(/report 目录 + 报表中心 / 报表管理 两个页面菜单,admin 授权)</summary>
|
||
private static void SeedReportMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
EnsureTopMenuWithChildren(fsql, snowflake, "/report", "报表中心", "DataAnalysis", 9,
|
||
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
|
||
{
|
||
("报表中心", "/report/center", "report/center/index", "report:center:list", "PieChart", 1),
|
||
("报表管理", "/report/manage", "report/manage/index", "report:manage:list", "Document", 2)
|
||
});
|
||
Console.WriteLine("[DbSeeder] 报表中心菜单已就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 报表中心菜单种子跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 旧版把模板二进制直接存在 sys_reports.template_data,现模板是独立实体 ReportFile(DesignContent 文本),
|
||
/// Report 通过 ReportFileId 关联。启动时把旧数据迁移过去,迁移完成后清空旧字段。
|
||
/// </summary>
|
||
private static void MigrateReportTemplateToFile(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
var legacy = fsql.Select<SysReport>()
|
||
.Where(r => !r.IsDeleted && r.ReportFileId == 0 && r.TemplateData != null && r.TemplateData.Length > 0)
|
||
.ToList();
|
||
foreach (var report in legacy)
|
||
{
|
||
var text = Encoding.UTF8.GetString(report.TemplateData!);
|
||
if (text.Length > 0 && text[0] == '\uFEFF') text = text.Substring(1);
|
||
|
||
var file = new SysReportFile(snowflake.CreateId(), report.Id, text);
|
||
fsql.Insert(file).ExecuteAffrows();
|
||
report.BindReportFile(file.Id);
|
||
report.ClearLegacyTemplate();
|
||
fsql.Update<SysReport>().SetSource(report).ExecuteAffrows();
|
||
Console.WriteLine($"[DbSeeder] 报表「{report.Name}」模板已迁移到 ReportFile#{file.Id}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 报表模板迁移跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>报表中心示例报表:职工花名册(模板为独立 ReportFile 实例 + F9 ReportConfig 查询配置)</summary>
|
||
private static void SeedSampleReport(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
const string code = "employee_directory";
|
||
const string name = "职工花名册";
|
||
const string sql = "SELECT login_name AS LoginName, user_name AS UserName, phone_number AS PhoneNumber, email AS Email FROM sys_rbac_users WHERE is_deleted = 0 ORDER BY id LIMIT 1000";
|
||
// F9 ReportConfig:DsName 指向后端查询提供者;Parms 定义动态查询控件
|
||
const string config = "{\"DsName\":\"RepDt_Sys_Users\",\"ViewerType\":0,\"Parms\":[" +
|
||
"{\"Name\":\"q_dep\",\"Label\":\"部门\",\"Prompt\":\"含子部门,留空为全部\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"AntdUI.Select\",\"Ds_Static\":null,\"Ds_Service\":\"/sys/department\",\"DefaultValue\":\"\"}," +
|
||
"{\"Name\":\"q_like\",\"Label\":\"关键字\",\"Prompt\":\"按工号 / 姓名模糊查询\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"AntdUI.Input\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}]}";
|
||
|
||
var exist = fsql.Select<SysReport>().Where(r => r.Code == code && !r.IsDeleted).First();
|
||
var templateJson = LoadEmbeddedTemplateJson("employee_directory.grf");
|
||
|
||
// 老版本种子生成的是手写 6.6.8.1 模板(设计器 6.8 无法打开),这里统一替换为 6.8.5.0 官方结构模板
|
||
bool needReplaceTemplate = false;
|
||
if (exist != null)
|
||
{
|
||
var file = fsql.Select<SysReportFile>().Where(f => f.ReportId == exist.Id && !f.IsDeleted).First();
|
||
needReplaceTemplate = file == null || !file.DesignContent.Contains("6.8.5.0");
|
||
}
|
||
|
||
if (exist == null)
|
||
{
|
||
var report = new SysReport(snowflake.CreateId(), code, name, "职工管理", "示例报表:按部门 / 关键字查询职工基本信息。", sql, "[]", 1, config);
|
||
|
||
// 模板实例:ReportFile(对齐 F9 Report.ReportFileId)
|
||
if (templateJson != null)
|
||
{
|
||
var file = new SysReportFile(snowflake.CreateId(), report.Id, templateJson);
|
||
fsql.Insert(file).ExecuteAffrows();
|
||
report.BindReportFile(file.Id);
|
||
}
|
||
fsql.Insert(report).ExecuteAffrows();
|
||
|
||
// 创建对应权限单元
|
||
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.报表, code, report.Id);
|
||
perm.SetCode($"report:{code}:visble");
|
||
fsql.Insert(perm).ExecuteAffrows();
|
||
|
||
// 授权给 admin 角色
|
||
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
|
||
if (adminRole != null)
|
||
{
|
||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, perm.Id)).ExecuteAffrows();
|
||
}
|
||
Console.WriteLine("[DbSeeder] 示例报表「职工花名册」已创建");
|
||
}
|
||
else
|
||
{
|
||
// 已存在:替换旧模板 / 补全缺失的模板与查询配置
|
||
bool changed = false;
|
||
if (needReplaceTemplate && templateJson != null)
|
||
{
|
||
var file = fsql.Select<SysReportFile>().Where(f => f.ReportId == exist.Id && !f.IsDeleted).First();
|
||
if (file != null)
|
||
{
|
||
file.UpdateContent(templateJson);
|
||
fsql.Update<SysReportFile>().SetSource(file).ExecuteAffrows();
|
||
}
|
||
else
|
||
{
|
||
file = new SysReportFile(snowflake.CreateId(), exist.Id, templateJson);
|
||
fsql.Insert(file).ExecuteAffrows();
|
||
exist.BindReportFile(file.Id);
|
||
}
|
||
changed = true;
|
||
}
|
||
if (string.IsNullOrEmpty(exist.Config) || !exist.Config.Contains("q_dep"))
|
||
{
|
||
exist.UpdateBasic(exist.Code, exist.Name, exist.Category, exist.Description, exist.DataSql, exist.DataParams, exist.SortOrder, config);
|
||
changed = true;
|
||
}
|
||
if (changed)
|
||
{
|
||
fsql.Update<SysReport>().SetSource(exist).ExecuteAffrows();
|
||
Console.WriteLine("[DbSeeder] 已更新示例报表模板/查询配置");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 示例报表种子跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 落丝记录报表(生产-前缫):DsName=RepDt_Prod_Lsjl,参数为日期范围 / 车间 / 机组 / 庄口 / 关键字。
|
||
/// 已存在的报表只补齐查询配置,不覆盖用户在设计器里维护的 .grf 模板。
|
||
/// </summary>
|
||
private static void SeedLsjlReport(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||
{
|
||
try
|
||
{
|
||
const string code = "PROD_QS_1";
|
||
const string name = "落丝记录";
|
||
const string category = "生产.前缫";
|
||
const string dsName = "RepDt_Prod_Lsjl";
|
||
|
||
// 日期范围参数默认值用「不限」:避免 paramInitialValue 把空值默认成今天,导致只查当天
|
||
const string config = "{\"DsName\":\"RepDt_Prod_Lsjl\",\"ViewerType\":0,\"Parms\":[" +
|
||
"{\"Name\":\"q_date_start\",\"Label\":\"开始日期\",\"Prompt\":\"留空则不限开始日期\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"AntdUI.DatePicker\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"不限\"}," +
|
||
"{\"Name\":\"q_date_end\",\"Label\":\"结束日期\",\"Prompt\":\"留空则不限结束日期\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"AntdUI.DatePicker\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"不限\"}," +
|
||
"{\"Name\":\"q_dep\",\"Label\":\"车间\",\"Prompt\":\"含子车间,留空为全部\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"F9-DEP-QS-SELECT\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}," +
|
||
"{\"Name\":\"q_jizu\",\"Label\":\"机组\",\"Prompt\":\"留空为全部\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"AntdUI.Input\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}," +
|
||
"{\"Name\":\"q_zk\",\"Label\":\"庄口\",\"Prompt\":\"点击选择庄口\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"F9-GYZK-SELECT\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}," +
|
||
"{\"Name\":\"q_like\",\"Label\":\"关键字\",\"Prompt\":\"庄口 / 规格 / 颜色\",\"IsRequired\":false," +
|
||
"\"ControlType\":\"AntdUI.Input\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}]}";
|
||
|
||
const string description = "生产-前缫:按日期范围 / 车间 / 机组 / 庄口查询落丝记录明细。";
|
||
|
||
var exist = fsql.Select<SysReport>().Where(r => r.Code == code && !r.IsDeleted).First();
|
||
|
||
if (exist == null)
|
||
{
|
||
var report = new SysReport(snowflake.CreateId(), code, name, category, description, "", "[]", 2, config);
|
||
|
||
var newTemplate = LoadEmbeddedTemplateJson("lsjl.grf");
|
||
if (newTemplate != null)
|
||
{
|
||
var newFile = new SysReportFile(snowflake.CreateId(), report.Id, newTemplate);
|
||
fsql.Insert(newFile).ExecuteAffrows();
|
||
report.BindReportFile(newFile.Id);
|
||
}
|
||
fsql.Insert(report).ExecuteAffrows();
|
||
|
||
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.报表, code, report.Id);
|
||
perm.SetCode($"report:{code}:visble");
|
||
fsql.Insert(perm).ExecuteAffrows();
|
||
|
||
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
|
||
if (adminRole != null)
|
||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, perm.Id)).ExecuteAffrows();
|
||
|
||
Console.WriteLine("[DbSeeder] 落丝记录报表已创建(DsName=RepDt_Prod_Lsjl)");
|
||
return;
|
||
}
|
||
|
||
// 已存在:DsName 未指向该 Provider、或参数标签全为空(程序生成的占位配置)时补齐查询配置
|
||
// (数据 SQL 清空,避免与 DsName 混淆)
|
||
var changed = false;
|
||
if (string.IsNullOrEmpty(exist.Config) || !exist.Config.Contains(dsName)
|
||
|| ConfigParamsMissingLabel(exist.Config))
|
||
{
|
||
exist.UpdateBasic(exist.Code, exist.Name,
|
||
string.IsNullOrWhiteSpace(exist.Category) ? category : exist.Category,
|
||
string.IsNullOrWhiteSpace(exist.Description) ? description : exist.Description,
|
||
"", exist.DataParams, exist.SortOrder, config);
|
||
changed = true;
|
||
Console.WriteLine("[DbSeeder] 落丝记录报表查询配置已更新(DsName=RepDt_Prod_Lsjl)");
|
||
}
|
||
|
||
// 模板是占位模板(无标题 / 无参数声明,多为早期上传测试产物)时,替换为内置规范模板;
|
||
// 旧版内置模板有两类问题一并升级:
|
||
// 1) 用 Parameter 属性绑定报表参数(GR 推荐 [#{参数名}#] 文本标记)
|
||
// 2) 把「查询条件」(q_*) 误当成模板参数(那是查询面板的东西,不是报表参数)
|
||
var templateJson = LoadEmbeddedTemplateJson("lsjl.grf");
|
||
var file = fsql.Select<SysReportFile>().Where(f => f.ReportId == exist.Id && !f.IsDeleted).First();
|
||
var oursTemplate = file != null && file.DesignContent.Contains("\"Author\": \"F9 报表中心\"");
|
||
var legacyParamBinding = oursTemplate
|
||
&& (file!.DesignContent.Contains("\"Parameter\": \"开始日期\"")
|
||
|| TemplateHasQueryParams(file.DesignContent)
|
||
|| TemplateHasStaticBoxParamBinding(file.DesignContent));
|
||
if (templateJson != null && (file == null || legacyParamBinding || IsPlaceholderReportTemplate(file.DesignContent)))
|
||
{
|
||
if (file == null)
|
||
{
|
||
file = new SysReportFile(snowflake.CreateId(), exist.Id, templateJson);
|
||
fsql.Insert(file).ExecuteAffrows();
|
||
}
|
||
else
|
||
{
|
||
file.UpdateContent(templateJson);
|
||
fsql.Update<SysReportFile>().SetSource(file).ExecuteAffrows();
|
||
}
|
||
exist.BindReportFile(file.Id);
|
||
changed = true;
|
||
Console.WriteLine("[DbSeeder] 落丝记录报表模板已更新为内置模板(标题 + 查询参数区)");
|
||
}
|
||
|
||
if (changed) fsql.Update<SysReport>().SetSource(exist).ExecuteAffrows();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 落丝记录报表种子跳过:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断查询条件配置是否缺少标签(Parms 为空,或所有 Label 都为空 → 程序生成的占位配置)。
|
||
/// 用户手工配过标签的配置不会被种子覆盖。
|
||
/// </summary>
|
||
private static bool ConfigParamsMissingLabel(string config)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(config)) return true;
|
||
try
|
||
{
|
||
using var doc = System.Text.Json.JsonDocument.Parse(config);
|
||
if (!doc.RootElement.TryGetProperty("Parms", out var ps)
|
||
|| ps.ValueKind != System.Text.Json.JsonValueKind.Array
|
||
|| ps.GetArrayLength() == 0)
|
||
return true;
|
||
|
||
foreach (var p in ps.EnumerateArray())
|
||
{
|
||
if (p.ValueKind != System.Text.Json.JsonValueKind.Object) continue;
|
||
var label = p.TryGetProperty("Label", out var l) && l.ValueKind == System.Text.Json.JsonValueKind.String
|
||
? l.GetString() ?? ""
|
||
: "";
|
||
if (!string.IsNullOrWhiteSpace(label)) return false;
|
||
}
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断模板是否把报表参数绑在 StaticBox 上(实测 [#{参数}#] 只在 MemoBox 生效,StaticBox 原样输出)。
|
||
/// </summary>
|
||
private static bool TemplateHasStaticBoxParamBinding(string content)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith("_WR", StringComparison.Ordinal)) return false;
|
||
try
|
||
{
|
||
using var doc = System.Text.Json.JsonDocument.Parse(content);
|
||
return ContainsStaticBoxParamBinding(doc.RootElement);
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private static bool ContainsStaticBoxParamBinding(System.Text.Json.JsonElement el)
|
||
{
|
||
if (el.ValueKind == System.Text.Json.JsonValueKind.Object)
|
||
{
|
||
var type = el.TryGetProperty("Type", out var t) && t.ValueKind == System.Text.Json.JsonValueKind.String
|
||
? t.GetString() ?? ""
|
||
: "";
|
||
var text = el.TryGetProperty("Text", out var x) && x.ValueKind == System.Text.Json.JsonValueKind.String
|
||
? x.GetString() ?? ""
|
||
: "";
|
||
if (type == "StaticBox" && text.Contains("[#{")) return true;
|
||
|
||
foreach (var prop in el.EnumerateObject())
|
||
if (ContainsStaticBoxParamBinding(prop.Value)) return true;
|
||
}
|
||
else if (el.ValueKind == System.Text.Json.JsonValueKind.Array)
|
||
{
|
||
foreach (var item in el.EnumerateArray())
|
||
if (ContainsStaticBoxParamBinding(item)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断模板是否把「查询条件」(q_* 参数) 误写成了模板参数。
|
||
/// 模板参数应来自后端 Parameter_Data(如 车间 / 记录数 / 查询时间),不是查询面板的 q_*。
|
||
/// </summary>
|
||
private static bool TemplateHasQueryParams(string content)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith("_WR", StringComparison.Ordinal)) return false;
|
||
try
|
||
{
|
||
using var doc = System.Text.Json.JsonDocument.Parse(content);
|
||
if (!doc.RootElement.TryGetProperty("Parameter", out var ps)
|
||
|| ps.ValueKind != System.Text.Json.JsonValueKind.Array)
|
||
return false;
|
||
|
||
foreach (var p in ps.EnumerateArray())
|
||
{
|
||
if (p.ValueKind != System.Text.Json.JsonValueKind.Object) continue;
|
||
if (!p.TryGetProperty("Name", out var n) || n.ValueKind != System.Text.Json.JsonValueKind.String) continue;
|
||
if ((n.GetString() ?? "").StartsWith("q_", StringComparison.Ordinal)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断模板是否为占位模板:JSON 格式、Title 为空、且没有声明任何 Parameter。
|
||
/// GR7 设计器保存的 _WR 压缩模板无法判断,一律视为正常模板(不覆盖)。
|
||
/// </summary>
|
||
private static bool IsPlaceholderReportTemplate(string content)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith("_WR", StringComparison.Ordinal)) return false;
|
||
try
|
||
{
|
||
using var doc = System.Text.Json.JsonDocument.Parse(content);
|
||
var root = doc.RootElement;
|
||
var title = root.TryGetProperty("Title", out var t) && t.ValueKind == System.Text.Json.JsonValueKind.String
|
||
? t.GetString() ?? ""
|
||
: "";
|
||
var hasParamDecl = root.TryGetProperty("Parameter", out var p)
|
||
&& p.ValueKind == System.Text.Json.JsonValueKind.Array
|
||
&& p.GetArrayLength() > 0;
|
||
return string.IsNullOrWhiteSpace(title) && !hasParamDecl;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>从嵌入资源读取报表 .grf 模板 JSON 文本(.grf 为 UTF-8 JSON,GR7 向下兼容 GR6 格式)</summary>
|
||
private static string? LoadEmbeddedTemplateJson(string fileName)
|
||
{
|
||
try
|
||
{
|
||
var asm = typeof(DbSeeder).Assembly;
|
||
var resName = asm.GetManifestResourceNames()
|
||
.FirstOrDefault(n => n.EndsWith("." + fileName, StringComparison.OrdinalIgnoreCase));
|
||
if (string.IsNullOrEmpty(resName)) return null;
|
||
|
||
using var stream = asm.GetManifestResourceStream(resName);
|
||
if (stream == null) return null;
|
||
using var reader = new StreamReader(stream, new UTF8Encoding(false));
|
||
var json = reader.ReadToEnd();
|
||
|
||
// 统一去 BOM 后保存(ReportFile.DesignContent 存纯文本,输出 .grf 时再补 BOM)
|
||
return json.Length > 0 && json[0] == '\uFEFF' ? json.Substring(1) : json;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[DbSeeder] 读取嵌入模板失败:{ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
} |