新增工资/仓储/生产日报等模块,移除旧考勤模块

- 新增 PayRoll(工资)、Wms(仓储)、Prod(车速/缫丝/考勤/丝片检测/丝片台账/纤检日报/质量日报) 模块
- 新增对应 Domain 实体、Application DTO/Service、Api Controller 与前端页面
- 移除旧 Attendance 考勤模块(Controller/Service/Entity/DTO 及 5 个前端页面),由 Prod/Kaoqin 取代
- 调整路由、仪表盘、Lsjl 流丝记录实体与服务
- 更新业务种子数据与 DbSeeder
- 补充工作进度文档 14-27
This commit is contained in:
yanpeng200212
2026-10-27 09:09:57 +08:00
parent 69c4052a64
commit 1f1b184411
119 changed files with 21576 additions and 1668 deletions
@@ -23,6 +23,9 @@ public static class DbSeeder
// 1. 建表(CodeFirst SyncStructure,增量同步,兼容旧库)
SyncStructure(fsql);
// 1.1 仓储单据采用物理删除:清理历史软删残留,避免唯一索引阻止重建同号单据
CleanWmsSoftDeletedRows(fsql);
// 2. 默认角色
if (!fsql.Select<Role>().Any())
{
@@ -48,8 +51,8 @@ public static class DbSeeder
}).ExecuteAffrows();
}
// 4. 考勤菜单(增量)
SeedAttendanceMenus(fsql);
// 4. 旧「考勤管理」模块已移除,替换为「生产-职工考勤」(CQDJ/CQTJ)。
// 不再创建 /attendance 菜单;库中历史记录需在「系统管理-菜单管理」删除。
// 5. 业务基础数据(部门树 + 机组车台 + 庄口 + 规格 + 定级指标 + 菜单)
BusinessSeedData.Seed(fsql);
@@ -94,6 +97,12 @@ public static class DbSeeder
// 6.695 质量模块(工艺执行书 / 样茧试缫)
SeedQcMenus(fsql, snowflake);
// 6.696 薪资模块(挡车/车头产量计算 + 其依赖配置)
SeedPayRollMenus(fsql, snowflake);
// 6.697 仓储模块(原料 / 副产品 / 在制品 / 生丝 / 物资)
SeedWmsMenus(fsql, snowflake);
// 6.71 报表中心菜单(/report 目录 + 报表中心/报表管理两个页面)
SeedReportMenus(fsql, snowflake);
// 6.72 旧版内嵌模板(sys_reports.template_data)迁移为独立模板实例 ReportFile
@@ -174,6 +183,39 @@ public static class DbSeeder
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");
// 落丝记录补「落丝桶数 / 每桶茧量」:生产-后缫「秤大丝」算茧量、缫折、台产需要
EnsureColumn(fsql, "LsjlRecords", "Tongshu", "INTEGER NOT NULL DEFAULT 0");
EnsureColumn(fsql, "LsjlRecords", "Meitongjianliang", "INTEGER NOT NULL DEFAULT 0");
// 生丝:出入库按批次/包件关联(对齐 F9 SS_Rk_Item.PiciIds / SS_Ck_Item.PcAndBjIds)
EnsureColumn(fsql, "WmsRukuDetails", "PiciIds", "TEXT NOT NULL DEFAULT ''");
EnsureColumn(fsql, "WmsChukuDetails", "PcAndBjIds", "TEXT NOT NULL DEFAULT ''");
// 生丝批次:出库汇总字段(对齐 F9 SS_Pici.CK_LastDate / Ck_Baoshu / Ck_Jingzhong / Kc_Baoshu / Rk_Date)
EnsureColumn(fsql, "WmsPicis", "RkDate", "TEXT NULL");
EnsureColumn(fsql, "WmsPicis", "CkLastDate", "TEXT NULL");
EnsureColumn(fsql, "WmsPicis", "CkBaoshu", "INTEGER NOT NULL DEFAULT 0");
EnsureColumn(fsql, "WmsPicis", "CkJingzhong", "REAL NOT NULL DEFAULT 0");
EnsureColumn(fsql, "WmsPicis", "KcBaoshu", "INTEGER NOT NULL DEFAULT 0");
// 生丝包件:出库标记(对齐 F9 SS_Pici_Baojian.Ck_Isck / Ck_Item_Id / Ck_date / Ck_Jingzhong)
EnsureColumn(fsql, "WmsPiciBaojians", "CkIsck", "INTEGER NOT NULL DEFAULT 0");
EnsureColumn(fsql, "WmsPiciBaojians", "CkItemId", "INTEGER NOT NULL DEFAULT 0");
EnsureColumn(fsql, "WmsPiciBaojians", "CkDate", "TEXT NULL");
EnsureColumn(fsql, "WmsPiciBaojians", "CkJingzhong", "REAL NOT NULL DEFAULT 0");
}
/// <summary>仓储单据(入库 / 出库)改用物理删除,这里清理历史软删残留行</summary>
private static void CleanWmsSoftDeletedRows(IFreeSql fsql)
{
try
{
fsql.Ado.ExecuteNonQuery("DELETE FROM WmsRukus WHERE IsDeleted = 1;");
fsql.Ado.ExecuteNonQuery("DELETE FROM WmsChukus WHERE IsDeleted = 1;");
fsql.Ado.ExecuteNonQuery("DELETE FROM WmsPicis WHERE IsDeleted = 1;");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 仓储软删残留清理跳过:{ex.Message}");
}
}
/// <summary>为已有表补加列(列已存在则忽略)</summary>
@@ -731,6 +773,27 @@ public static class DbSeeder
Console.WriteLine("[DbSeeder] 已清理旧权限单元 base:jizuchetai:list");
}
// 1.9) 清理旧「考勤管理」(/attendance):前端页面组件已随「生产-职工考勤」移植删除,
// 菜单留着会出现「点了打不开(空白页)」,这里按路径整体移除。
var legacyAtt = fsql.Select<SysMenu>().Where(m => m.Path.StartsWith("/attendance")).ToList();
foreach (var m in legacyAtt)
{
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {m.Id}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {m.Id}");
}
if (legacyAtt.Count > 0)
Console.WriteLine($"[DbSeeder] 已清理旧考勤菜单 {legacyAtt.Count} 条(页面组件已移除)");
var legacyAttPerms = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_permissions WHERE code LIKE 'attendance:%'");
foreach (DataRow permRow in legacyAttPerms.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}");
}
if (legacyAttPerms.Rows.Count > 0)
Console.WriteLine($"[DbSeeder] 已清理旧考勤权限单元 {legacyAttPerms.Rows.Count} 条");
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/qianxian", "生产-前缫", "Cpu", 5,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
@@ -738,7 +801,8 @@ public static class DbSeeder
("落丝记录", "/prod/qianxian/lsjl", "prod/lsjl/index", "prod:lsjl:list", "Document", 2),
("车间班次", "/prod/qianxian/chejianbanci", "base/chejianbanci/index", "prod:chejianbanci:list", "Timer", 3),
("看台排班", "/prod/qianxian/ktpb", "prod/ktpb/index", "prod:ktpb:list", "Grid", 4),
("看台记录", "/prod/qianxian/ktjl", "prod/ktjl/index", "prod:ktjl:list", "Tickets", 5)
("看台记录", "/prod/qianxian/ktjl", "prod/ktjl/index", "prod:ktjl:list", "Tickets", 5),
("煮茧日报", "/prod/qianxian/zjrb", "prod/zjrb/index", "prod:zjrb:list", "Notebook", 6)
});
// 子菜单顺序归一(EnsureTopMenuWithChildren 只补新菜单,不调整已存在菜单的排序)
@@ -746,7 +810,7 @@ public static class DbSeeder
{
("/prod/qianxian/jizuchetai", 1), ("/prod/qianxian/lsjl", 2),
("/prod/qianxian/chejianbanci", 3), ("/prod/qianxian/ktpb", 4),
("/prod/qianxian/ktjl", 5)
("/prod/qianxian/ktjl", 5), ("/prod/qianxian/zjrb", 6)
})
{
fsql.Update<SysMenu>()
@@ -759,9 +823,68 @@ public static class DbSeeder
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)
("黑板检验", "/prod/houxian/hbjy", "prod/hbjy/index", "prod:hbjy:list", "Notebook", 2),
("秤大丝", "/prod/houxian/cds", "prod/cds/index", "prod:cds:list", "Box", 3),
("公量纤度", "/prod/houxian/glxd", "prod/glxd/index", "prod:glxd:list", "TrendCharts", 4),
("丝片检查", "/prod/houxian/spjc", "prod/spjc/index", "prod:spjc:list", "Document", 5),
("丝片台帐", "/prod/houxian/sptz", "prod/sptz/index", "prod:sptz:list", "Files", 6)
});
// 生产-选剥茧(对齐 F9 WinClient\F9.Business.Prod\XBJ):选茧日报 / 选茧计划 / 选茧班组
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/xuanjian", "生产-选剥茧", "Grid", 7,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("选茧日报", "/prod/xuanjian/rb", "prod/xuanjian/index", "prod:xuanjian:list", "Document", 1),
("选茧计划", "/prod/xuanjian/plan", "prod/xuanjian/index", "prod:xuanjian:plan:list", "Calendar", 2),
("选茧班组", "/prod/xuanjian/banzu", "prod/xuanjian/index", "prod:xuanjian:banzu:list", "User", 3)
});
// 生产-考勤(对齐 F9 WinClient\F9.Business.Prod\KQ):考勤登记
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/kaoqin", "生产-考勤", "Clock", 8,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("考勤登记", "/prod/kaoqin/cqdj", "prod/kaoqin/index", "prod:kaoqin:cqdj:list", "EditPen", 1),
("考勤统计", "/prod/kaoqin/cqtj", "prod/kaoqin/cqtj/index", "prod:kaoqin:cqtj:list", "DataAnalysis", 2)
});
// 子菜单顺序归一
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/prod/kaoqin/cqdj", 1), ("/prod/kaoqin/cqtj", 2)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 子菜单顺序归一
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/prod/xuanjian/rb", 1), ("/prod/xuanjian/plan", 2), ("/prod/xuanjian/banzu", 3)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 子菜单顺序归一
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/prod/houxian/xdjy", 1), ("/prod/houxian/hbjy", 2),
("/prod/houxian/cds", 3), ("/prod/houxian/glxd", 4),
("/prod/houxian/spjc", 5), ("/prod/houxian/sptz", 6)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 3) 规范一级菜单顺序
foreach (var (path, sort) in new (string Path, int Sort)[]
{
@@ -791,6 +914,87 @@ public static class DbSeeder
/// 质量模块菜单:一级目录「质量」+ 工艺执行书 / 样茧试缫 两个页面。
/// 模块落在「生产-后缫」之后、消息中心之前。
/// </summary>
private static void SeedPayRollMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureTopMenuWithChildren(fsql, snowflake, "/payroll", "薪资", "Money", 8,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("挡车车头产量计算", "/payroll/calcldcct", "payroll/calcldcct/index", "payroll:calcldcct:list", "Coin", 1),
("工艺庄口薪资配置", "/payroll/confgyzk", "payroll/confgyzk/index", "payroll:confgyzk:list", "Setting", 2),
("工种薪资配置", "/payroll/confgz", "payroll/confgz/index", "payroll:confgz:list", "Setting", 3),
("挡车岗位薪资配置", "/payroll/confdcg", "payroll/confdcg/index", "payroll:confdcg:list", "Setting", 4),
("挡车纤度奖扣", "/payroll/dcxdjk", "payroll/dcxdjk/index", "payroll:dcxdjk:list", "Setting", 5),
("薪资价格配置", "/payroll/xzjg", "payroll/xzjg/index", "payroll:xzjg:list", "Setting", 6),
// 考勤统计依赖的三套配置(CONFZQ / CONFEMPLOYEE / 岗位胜任考核)
("账期薪资配置", "/payroll/confzq", "payroll/confzq/index", "payroll:confzq:list", "Calendar", 7),
("职工薪资配置", "/payroll/confemployee", "payroll/confemployee/index", "payroll:confemployee:list", "User", 8),
("岗位胜任考核", "/payroll/gwsrkh", "payroll/gwsrkh/index", "payroll:gwsrkh:list", "Trophy", 9)
});
// 一级菜单顺序归一:质量(7) < 薪资(8) < 通知(9) < 系统(10) < 报表(11)
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/attendance", 2), ("/base", 3), ("/employee", 4),
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/qc", 7),
("/payroll", 8), ("/notification", 9), ("/sys", 10), ("/report", 11)
})
{
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>
/// 仓储模块菜单:一级目录「仓储」+ 五个模块页面(原料 / 副产品 / 在制品 / 生丝 / 物资)。
/// 对齐 F9 WinClient\F9.Business.WMS 的五个子模块,落在一级菜单「薪资」之后。
/// </summary>
private static void SeedWmsMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureTopMenuWithChildren(fsql, snowflake, "/wms", "仓储", "Box", 9,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("原料", "/wms/yuanliao", "wms/yuanliao/index", "wms:yuanliao:list", "Coin", 1),
("副产品", "/wms/fuchanpin", "wms/fuchanpin/index", "wms:fuchanpin:list", "Goods", 2),
("中间品", "/wms/zjp", "wms/zjp/index", "wms:zjp:list", "Grid", 3),
("生丝", "/wms/ss", "wms/ss/index", "wms:ss:list", "ShoppingCart", 4),
("物资", "/wms/wj", "wms/wj/index", "wms:wj:list", "Tools", 5)
});
// 一级菜单顺序归一:质量(7) < 薪资(8) < 仓储(9) < 通知(10) < 系统(11) < 报表(12)
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/attendance", 2), ("/base", 3), ("/employee", 4),
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/qc", 7),
("/payroll", 8), ("/wms", 9), ("/notification", 10), ("/sys", 11), ("/report", 12)
})
{
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}");
}
}
private static void SeedQcMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
@@ -862,9 +1066,10 @@ public static class DbSeeder
child.SetWebInfo(path, component, permission);
fsql.Insert(child).ExecuteAffrows();
}
else if (!child.IsActive || child.IconSvg != icon || child.ParentId != dir.Id)
else if (!child.IsActive || child.IconSvg != icon || child.ParentId != dir.Id || child.Name != name)
{
fsql.Update<SysMenu>()
.Set(x => x.Name, name)
.Set(x => x.IconSvg, icon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, dir.Id)