生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 IIS 单站点部署
1. 生产模块按工序拆分为两个一级菜单 - 生产-前缫(落丝记录)、生产-后缫(纤度检验、黑板检验) - 旧的「生产管理」(/prod) 目录及子菜单由种子自动清理,页面组件与权限标识保持不变 - 消息中心跳转路径、首页快捷入口路径同步更新 2. 职工管理独立为一级菜单(原挂在「基础数据」目录下),并清理历史子菜单 3. 修复侧边栏菜单展开卡顿(基础数据等子项较多的目录尤其明显) - 去掉 Element Plus 的 height 过渡动画(会逐帧重排全部子项) - 收起时改用 height:0 + visibility:hidden 而非 display:none,子项提前完成布局 - 菜单区独立滚动并预留滚动条宽度(scrollbar-gutter),消除展开时的横向抖动 - 菜单项增加 contain 布局隔离,淡入动画改为容器级 4. 修复页面首次打开重复请求 - 分页组件挂载时会触发一次 current-change,页码未变化时不再重复加载(18 个页面) - 职工管理去掉 onMounted 与默认选中部门的重复加载 - 工种管理、原料庄口的注册表下拉由按 key 多次请求改为一次全量拉取 5. 后端增加前端静态托管与 SPA 路由回退,支持 IIS 单站点部署(前后端同源) 6. 新增部署脚本 deploy-iis.ps1(首次部署)与 update-iis.ps1(更新发布)
This commit is contained in:
@@ -227,15 +227,21 @@ public static class BusinessSeedData
|
||||
AddMenu(fsql, baseDirId, "生丝定级指标", "/base/dingjizhibiao", "base/dingjizhibiao/index", "TrendCharts", "base:dingjizhibiao:list", 3);
|
||||
AddMenu(fsql, baseDirId, "机组车台", "/base/jizuchetai", "base/jizuchetai/index", "SetUp", "base:jizuchetai:list", 4);
|
||||
|
||||
// 生产管理目录
|
||||
var prodDirId = fsql.Select<Menu>().Where(m => m.Path == "/prod").First()?.Id ?? 0;
|
||||
if (prodDirId == 0)
|
||||
// 生产模块按工序拆分为两个一级目录:生产-前缫(落丝)/ 生产-后缫(纤度、黑板检验)
|
||||
var qianxianDirId = fsql.Select<Menu>().Where(m => m.Path == "/prod/qianxian").First()?.Id ?? 0;
|
||||
if (qianxianDirId == 0)
|
||||
{
|
||||
prodDirId = DbSeeder.InsertAndGetId(fsql, new Menu { Name = "生产管理", Type = "directory", Path = "/prod", Icon = "Cpu", Sort = 4 });
|
||||
qianxianDirId = DbSeeder.InsertAndGetId(fsql, new Menu { Name = "生产-前缫", Type = "directory", Path = "/prod/qianxian", Icon = "Cpu", Sort = 5 });
|
||||
}
|
||||
AddMenu(fsql, prodDirId, "落丝记录", "/prod/lsjl", "prod/lsjl/index", "Document", "prod:lsjl:list", 1);
|
||||
AddMenu(fsql, prodDirId, "纤度检验", "/prod/xdjy", "prod/xdjy/index", "EditPen", "prod:xdjy:list", 2);
|
||||
AddMenu(fsql, prodDirId, "黑板检验", "/prod/hbjy", "prod/hbjy/index", "Notebook", "prod:hbjy:list", 3);
|
||||
AddMenu(fsql, qianxianDirId, "落丝记录", "/prod/qianxian/lsjl", "prod/lsjl/index", "Document", "prod:lsjl:list", 1);
|
||||
|
||||
var houxianDirId = fsql.Select<Menu>().Where(m => m.Path == "/prod/houxian").First()?.Id ?? 0;
|
||||
if (houxianDirId == 0)
|
||||
{
|
||||
houxianDirId = DbSeeder.InsertAndGetId(fsql, new Menu { Name = "生产-后缫", Type = "directory", Path = "/prod/houxian", Icon = "DataLine", Sort = 6 });
|
||||
}
|
||||
AddMenu(fsql, houxianDirId, "纤度检验", "/prod/houxian/xdjy", "prod/xdjy/index", "EditPen", "prod:xdjy:list", 1);
|
||||
AddMenu(fsql, houxianDirId, "黑板检验", "/prod/houxian/hbjy", "prod/hbjy/index", "Notebook", "prod:hbjy:list", 2);
|
||||
|
||||
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
|
||||
if (adminRole != null)
|
||||
|
||||
@@ -60,12 +60,13 @@ public static class DbSeeder
|
||||
// 6.6 F9 风格 RBAC 菜单(用户/角色/菜单/部门管理,写入新 sys_rbac_menus 表)
|
||||
SeedSysRbacMenus(fsql, snowflake);
|
||||
|
||||
// 6.65 基础数据(工种 + 注册表配置 + 原料庄口 + 物资 + 往来单位,F9 导出数据幂等导入)
|
||||
// 6.65 基础数据(工种 + 注册表配置 + 原料庄口 + 物资 + 往来单位 + 职工,F9 导出数据幂等导入)
|
||||
WorkTypeSeedData.Seed(fsql, snowflake);
|
||||
RegistrySeedData.Seed(fsql);
|
||||
YuanliaoZhuangkouSeedData.Seed(fsql);
|
||||
WuziSeedData.Seed(fsql);
|
||||
GongyingshangSeedData.Seed(fsql);
|
||||
EmployeeSeedData.Seed(fsql);
|
||||
|
||||
// 6.66 基础数据菜单(工种管理、账期管理、注册表、原料庄口、物资目录、往来单位)
|
||||
SeedWorkTypeMenus(fsql, snowflake);
|
||||
@@ -75,6 +76,12 @@ public static class DbSeeder
|
||||
SeedWuziMenus(fsql, snowflake);
|
||||
SeedGongyingshangMenus(fsql, snowflake);
|
||||
|
||||
// 6.67 职工管理(一级菜单,与"基础数据"平级)
|
||||
SeedEmployeeTopMenu(fsql, snowflake);
|
||||
|
||||
// 6.68 生产模块(按工序拆分:生产-前缫 / 生产-后缫)
|
||||
SeedProdMenus(fsql, snowflake);
|
||||
|
||||
// 6.7 为菜单创建权限单元(Permission),并修正旧菜单权限关联(统一权限模型)
|
||||
FixMenuPermissions(fsql, snowflake);
|
||||
|
||||
@@ -175,7 +182,7 @@ public static class DbSeeder
|
||||
{
|
||||
Code = "TeyeFound", Name = "发现特野", Description = "纤度检验发现特野时自动推送提醒",
|
||||
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 1,
|
||||
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/xdjy"
|
||||
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/xdjy"
|
||||
}).ExecuteAffrows();
|
||||
}
|
||||
|
||||
@@ -186,7 +193,7 @@ public static class DbSeeder
|
||||
{
|
||||
Code = "TdcFound", Name = "发现特大糙", Description = "黑板检验发现特大糙时自动推送提醒",
|
||||
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 2,
|
||||
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/hbjy"
|
||||
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/hbjy"
|
||||
}).ExecuteAffrows();
|
||||
}
|
||||
|
||||
@@ -572,6 +579,203 @@ public static class DbSeeder
|
||||
}
|
||||
}
|
||||
|
||||
/// <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) 两个一级目录 + 各自页面菜单
|
||||
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)
|
||||
});
|
||||
|
||||
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>
|
||||
/// 确保一个一级目录菜单及其下属页面菜单存在(含父级、图标、启用状态校正),并给 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>确保指定菜单已授权给角色(后续 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 图标组件名,前端按图标名渲染菜单图标。
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 职工数据导入:来源于 F9 系统 biz_base_employee / biz_base_employee_Change_record /
|
||||
/// biz_base_employee_tiaogang_record 三张表的导出数据。
|
||||
/// 幂等:按原始 Id 判重;F9 部门 Id 通过"部门名称"映射到本系统部门(工种 Id 与 F9 一致,直接沿用)。
|
||||
/// </summary>
|
||||
public static class EmployeeSeedData
|
||||
{
|
||||
private const string EmployeeResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee.json";
|
||||
private const string ChangeResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee_Change_record.json";
|
||||
private const string TiaogangResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee_tiaogang_record.json";
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
public static void Seed(IFreeSql fsql)
|
||||
{
|
||||
try
|
||||
{
|
||||
var depMap = BuildDepartmentIdMap(fsql);
|
||||
SeedEmployees(fsql, depMap);
|
||||
SeedChangeRecords(fsql);
|
||||
SeedTiaogangRecords(fsql, depMap);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[EmployeeSeed] 职工数据导入跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>导入职工档案</summary>
|
||||
private static void SeedEmployees(IFreeSql fsql, Dictionary<long, long> depMap)
|
||||
{
|
||||
var items = Load<EmployeeSeedItem>(EmployeeResource);
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var existIds = fsql.Select<Employee>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
|
||||
var inserts = new List<Employee>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Id <= 0 || existIds.Contains(item.Id)) continue;
|
||||
if (string.IsNullOrWhiteSpace(item.BaseName)) continue;
|
||||
|
||||
var depId = depMap.TryGetValue(item.BaseDepId, out var mapped) ? mapped : item.BaseDepId;
|
||||
|
||||
inserts.Add(new Employee(item.Id, depId, item.BaseWorktypeId,
|
||||
(Employee_WorkStatus_Enum)item.BaseWorkStatus, item.BaseCode ?? string.Empty, item.BaseName.Trim(),
|
||||
item.BaseGangwei ?? string.Empty, (item.BasePhone ?? string.Empty).Trim(),
|
||||
(item.BaseIdCard ?? string.Empty).Trim(), ParseLegacyDate(item.BaseBirnDate), item.BaseAddress ?? string.Empty,
|
||||
(item.BaseGender ?? string.Empty).Trim(), (item.BaseMinzu ?? string.Empty).Trim(),
|
||||
item.BaseHunyinStatus ?? string.Empty, item.BaseHuji ?? string.Empty, ParseLegacyDate(item.BaseRuzhiDate),
|
||||
ParseLegacyDate(item.BaseLizhiDate), ParseLegacyDate(item.BaseZhuanzhengDate), ParseBool(item.BaseIsZhuanzheng),
|
||||
item.BaseJinjiLianxiren ?? string.Empty, item.BaseJinjiLianxirenPhone ?? string.Empty,
|
||||
item.BaseHetongType ?? string.Empty, ParseLegacyDate(item.BaseLaodongHetongStartDate),
|
||||
ParseLegacyDate(item.BaseLaodongHetongEndDate), ParseLegacyDate(item.BaseTuixiuDate),
|
||||
ParseLegacyDate(item.BaseShebaoCanbaoDate), item.BaseShebaocanbaoDesc ?? string.Empty,
|
||||
(item.BaseBanckcardCode ?? string.Empty).Trim(), item.KzBiyeYuanxiao ?? string.Empty, item.KbZhuanye ?? string.Empty,
|
||||
item.KbXueli ?? string.Empty, item.ScBanci.HasValue ? (Prod_Banci_Enum)item.ScBanci.Value : null,
|
||||
item.ScWorkGroup ?? string.Empty, item.ScSort, item.NamePinyin ?? string.Empty, item.Memo ?? string.Empty,
|
||||
item.BaseLizhiYuanyin ?? string.Empty)
|
||||
{
|
||||
CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now,
|
||||
CreatedBy = item.CreatedBy ?? string.Empty,
|
||||
UpdatedAt = ParseLegacyDate(item.UpdatedAt),
|
||||
UpdateBy = item.UpdateBy ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows();
|
||||
Console.WriteLine($"[EmployeeSeed] 职工档案:来源 {items.Count} 条,新增 {inserts.Count} 条");
|
||||
}
|
||||
|
||||
/// <summary>导入变更记录</summary>
|
||||
private static void SeedChangeRecords(IFreeSql fsql)
|
||||
{
|
||||
var items = Load<ChangeRecordSeedItem>(ChangeResource);
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var existIds = fsql.Select<EmployeeChangeRecord>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
var employeeIds = fsql.Select<Employee>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
|
||||
var inserts = new List<EmployeeChangeRecord>();
|
||||
var orphan = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Id <= 0 || existIds.Contains(item.Id)) continue;
|
||||
if (!employeeIds.Contains(item.EmployeeId)) { orphan++; continue; }
|
||||
|
||||
inserts.Add(new EmployeeChangeRecord(item.Id, item.EmployeeId, ParseLegacyDate(item.Date) ?? DateTime.Now,
|
||||
(Employee_ChangeStatus_Enum)item.ChangeStatus, item.ChangeShuoming ?? string.Empty,
|
||||
item.ChangeYuanyin ?? string.Empty)
|
||||
{
|
||||
CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now,
|
||||
CreatedBy = item.CreatedBy ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows();
|
||||
Console.WriteLine($"[EmployeeSeed] 变更记录:来源 {items.Count} 条,新增 {inserts.Count} 条,孤儿 {orphan} 条");
|
||||
}
|
||||
|
||||
/// <summary>导入调岗记录</summary>
|
||||
private static void SeedTiaogangRecords(IFreeSql fsql, Dictionary<long, long> depMap)
|
||||
{
|
||||
var items = Load<TiaogangRecordSeedItem>(TiaogangResource);
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var existIds = fsql.Select<EmployeeTiaogangRecord>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
var employeeIds = fsql.Select<Employee>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
|
||||
var inserts = new List<EmployeeTiaogangRecord>();
|
||||
var orphan = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Id <= 0 || existIds.Contains(item.Id)) continue;
|
||||
if (!employeeIds.Contains(item.EmployeeId)) { orphan++; continue; }
|
||||
|
||||
inserts.Add(new EmployeeTiaogangRecord(item.Id, item.EmployeeId,
|
||||
depMap.TryGetValue(item.OldDepId, out var od) ? od : item.OldDepId, item.OldWorktypeId,
|
||||
depMap.TryGetValue(item.NewDepId, out var nd) ? nd : item.NewDepId, item.NewWorktypeId,
|
||||
ParseLegacyDate(item.TiaogangDate) ?? DateTime.Now)
|
||||
{
|
||||
CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now,
|
||||
CreatedBy = item.CreatedBy ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
if (inserts.Count > 0) fsql.Insert(inserts).ExecuteAffrows();
|
||||
Console.WriteLine($"[EmployeeSeed] 调岗记录:来源 {items.Count} 条,新增 {inserts.Count} 条,孤儿 {orphan} 条");
|
||||
}
|
||||
|
||||
/// <summary>F9 部门 Id → 本系统部门 Id(以部门名称为桥梁,同工种模块的处理方式)</summary>
|
||||
private static Dictionary<long, long> BuildDepartmentIdMap(IFreeSql fsql)
|
||||
{
|
||||
var map = new Dictionary<long, long>();
|
||||
|
||||
var byName = new Dictionary<string, long>();
|
||||
foreach (var dep in fsql.Select<Department>().ToList())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dep.Name)) continue;
|
||||
if (!byName.ContainsKey(dep.Name)) byName[dep.Name] = dep.Id;
|
||||
}
|
||||
|
||||
foreach (var (f9Id, name, _, _, _) in BusinessSeedData.DeptData)
|
||||
{
|
||||
if (byName.TryGetValue(name, out var localId)) map[f9Id] = localId;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static List<T> Load<T>(string resourceName)
|
||||
{
|
||||
var assembly = typeof(EmployeeSeedData).Assembly;
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null)
|
||||
{
|
||||
Console.WriteLine($"[EmployeeSeed] 未找到内嵌资源:{resourceName}");
|
||||
return new List<T>();
|
||||
}
|
||||
return JsonSerializer.Deserialize<List<T>>(stream, JsonOptions) ?? new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>兼容 "0"/"1" 字符串、数字与布尔写法</summary>
|
||||
private static bool ParseBool(JsonElement element)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
var text = element.GetString();
|
||||
return text == "1" || string.Equals(text, "true", StringComparison.OrdinalIgnoreCase);
|
||||
case JsonValueKind.Number:
|
||||
return element.GetInt32() != 0;
|
||||
case JsonValueKind.True:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] LegacyDateFormats =
|
||||
{
|
||||
"d/M/yyyy H:mm:ss.fff", "d/M/yyyy H:mm:ss", "d/M/yyyy H:mm", "d/M/yyyy",
|
||||
"M/d/yyyy H:mm:ss.fff", "M/d/yyyy H:mm:ss", "M/d/yyyy H:mm", "M/d/yyyy",
|
||||
"yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd", "yyyy/M/d"
|
||||
};
|
||||
|
||||
/// <summary>解析 F9 导出的日期,空值与 1970 占位值返回 null</summary>
|
||||
private static DateTime? ParseLegacyDate(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
|
||||
if (DateTime.TryParseExact(value, LegacyDateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact))
|
||||
return exact.Year <= 1900 ? null : exact;
|
||||
|
||||
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var any))
|
||||
return any.Year <= 1900 ? null : any;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private sealed class EmployeeSeedItem
|
||||
{
|
||||
[JsonPropertyName("id")] public long Id { get; set; }
|
||||
[JsonPropertyName("created_by")] public string CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_at")] public string CreatedAt { get; set; }
|
||||
[JsonPropertyName("update_by")] public string UpdateBy { get; set; }
|
||||
[JsonPropertyName("updated_at")] public string UpdatedAt { get; set; }
|
||||
[JsonPropertyName("base_depid")] public long BaseDepId { get; set; }
|
||||
[JsonPropertyName("base_worktypeid")] public long BaseWorktypeId { get; set; }
|
||||
[JsonPropertyName("base_workstatus")] public int BaseWorkStatus { get; set; }
|
||||
[JsonPropertyName("base_code")] public string BaseCode { get; set; }
|
||||
[JsonPropertyName("base_name")] public string BaseName { get; set; }
|
||||
[JsonPropertyName("base_gangwei")] public string BaseGangwei { get; set; }
|
||||
[JsonPropertyName("base_phone")] public string BasePhone { get; set; }
|
||||
[JsonPropertyName("base_idcard")] public string BaseIdCard { get; set; }
|
||||
[JsonPropertyName("base_birndate")] public string BaseBirnDate { get; set; }
|
||||
[JsonPropertyName("base_address")] public string BaseAddress { get; set; }
|
||||
[JsonPropertyName("base_gender")] public string BaseGender { get; set; }
|
||||
[JsonPropertyName("base_minzu")] public string BaseMinzu { get; set; }
|
||||
[JsonPropertyName("base_hunyinstatus")] public string BaseHunyinStatus { get; set; }
|
||||
[JsonPropertyName("base_huji")] public string BaseHuji { get; set; }
|
||||
[JsonPropertyName("base_ruzhidate")] public string BaseRuzhiDate { get; set; }
|
||||
[JsonPropertyName("base_lizhidate")] public string BaseLizhiDate { get; set; }
|
||||
[JsonPropertyName("base_zhuanzhengdate")] public string BaseZhuanzhengDate { get; set; }
|
||||
[JsonPropertyName("base_iszhuanzheng")] public JsonElement BaseIsZhuanzheng { get; set; }
|
||||
[JsonPropertyName("base_jinjilianxiren")] public string BaseJinjiLianxiren { get; set; }
|
||||
[JsonPropertyName("base_jinjilianxiren_phone")] public string BaseJinjiLianxirenPhone { get; set; }
|
||||
[JsonPropertyName("base_hetongtype")] public string BaseHetongType { get; set; }
|
||||
[JsonPropertyName("base_laodonghetong_startdate")] public string BaseLaodongHetongStartDate { get; set; }
|
||||
[JsonPropertyName("base_laodonghetong_enddate")] public string BaseLaodongHetongEndDate { get; set; }
|
||||
[JsonPropertyName("base_tuixiu_date")] public string BaseTuixiuDate { get; set; }
|
||||
[JsonPropertyName("base_shebaocanbao_date")] public string BaseShebaoCanbaoDate { get; set; }
|
||||
[JsonPropertyName("base_shebaocanbao_desc")] public string BaseShebaocanbaoDesc { get; set; }
|
||||
[JsonPropertyName("base_banckcardcode")] public string BaseBanckcardCode { get; set; }
|
||||
[JsonPropertyName("kz_biyeyuanxiao")] public string KzBiyeYuanxiao { get; set; }
|
||||
[JsonPropertyName("kb_zhuanye")] public string KbZhuanye { get; set; }
|
||||
[JsonPropertyName("kb_xueli")] public string KbXueli { get; set; }
|
||||
[JsonPropertyName("sc_banci")] public int? ScBanci { get; set; }
|
||||
[JsonPropertyName("sc_workgroup")] public string ScWorkGroup { get; set; }
|
||||
[JsonPropertyName("sc_sort")] public int ScSort { get; set; }
|
||||
[JsonPropertyName("name_pinyin")] public string NamePinyin { get; set; }
|
||||
[JsonPropertyName("memo")] public string Memo { get; set; }
|
||||
[JsonPropertyName("base_lizhiyuanyin")] public string BaseLizhiYuanyin { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ChangeRecordSeedItem
|
||||
{
|
||||
[JsonPropertyName("id")] public long Id { get; set; }
|
||||
[JsonPropertyName("created_by")] public string CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_at")] public string CreatedAt { get; set; }
|
||||
[JsonPropertyName("employee_id")] public long EmployeeId { get; set; }
|
||||
[JsonPropertyName("date")] public string Date { get; set; }
|
||||
[JsonPropertyName("change_status")] public int ChangeStatus { get; set; }
|
||||
[JsonPropertyName("change_shuoming")] public string ChangeShuoming { get; set; }
|
||||
[JsonPropertyName("change_yuanyin")] public string ChangeYuanyin { get; set; }
|
||||
}
|
||||
|
||||
private sealed class TiaogangRecordSeedItem
|
||||
{
|
||||
[JsonPropertyName("id")] public long Id { get; set; }
|
||||
[JsonPropertyName("created_by")] public string CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_at")] public string CreatedAt { get; set; }
|
||||
[JsonPropertyName("employee_id")] public long EmployeeId { get; set; }
|
||||
[JsonPropertyName("old_depid")] public long OldDepId { get; set; }
|
||||
[JsonPropertyName("old_worktypeid")] public long OldWorktypeId { get; set; }
|
||||
[JsonPropertyName("new_depid")] public long NewDepId { get; set; }
|
||||
[JsonPropertyName("new_worktypeid")] public long NewWorktypeId { get; set; }
|
||||
[JsonPropertyName("tiaogang_date")] public string TiaogangDate { get; set; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+13002
File diff suppressed because it is too large
Load Diff
+1542
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ using F9WebAiTest.Application.Interfaces.Repositories.SYS.RBAC.Users;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.WorkTypes;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Registries;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.YuanliaoZhuangkous;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Gongyingshangs;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.WuziMulus;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Zq;
|
||||
@@ -23,6 +24,7 @@ using F9WebAiTest.Domain.Interfaces.SYS.RBAC.Users;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.WorkTypes;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Registries;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.YuanliaoZhuangkous;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Gongyingshangs;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.WuziMulus;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Zq;
|
||||
@@ -35,6 +37,7 @@ using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.RBAC.Users;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.WorkTypes;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Registries;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.YuanliaoZhuangkous;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Gongyingshangs;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.WuziMulus;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Zq;
|
||||
@@ -97,6 +100,7 @@ public static class DependencyInjection
|
||||
services.AddScoped<IYuanliaoZhuangkouWriteRepository, YuanliaoZhuangkouWriteRepository>();
|
||||
services.AddScoped<IWuziGroupWriteRepository, WuziGroupWriteRepository>();
|
||||
services.AddScoped<IGongyingshangWriteRepository, GongyingshangWriteRepository>();
|
||||
services.AddScoped<IEmployeeWriteRepository, EmployeeWriteRepository>();
|
||||
services.AddScoped<IWuziMuluWriteRepository, WuziMuluWriteRepository>();
|
||||
|
||||
// 读仓储(Application 接口)
|
||||
@@ -110,6 +114,7 @@ public static class DependencyInjection
|
||||
services.AddScoped<IYuanliaoZhuangkouReadRepository, YuanliaoZhuangkouReadRepository>();
|
||||
services.AddScoped<IWuziGroupReadRepository, WuziGroupReadRepository>();
|
||||
services.AddScoped<IGongyingshangReadRepository, GongyingshangReadRepository>();
|
||||
services.AddScoped<IEmployeeReadRepository, EmployeeReadRepository>();
|
||||
services.AddScoped<IWuziMuluReadRepository, WuziMuluReadRepository>();
|
||||
|
||||
return services;
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Interfaces.SnowflakeID;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Entities.SYS.WorkTypes;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Employees;
|
||||
|
||||
internal class EmployeeWriteRepository : F9RepositoryBase, IEmployeeWriteRepository
|
||||
{
|
||||
public EmployeeWriteRepository(IFreeSql fsql) : base(fsql) { }
|
||||
|
||||
public async Task<Employee> GetByIdAsync(long id, CancellationToken cancellationToken)
|
||||
=> await Orm.Select<Employee>().Where(x => x.Id == id && x.DeletedAt == null).FirstAsync(cancellationToken);
|
||||
|
||||
public async Task<bool> AddAsync(Employee entity)
|
||||
{
|
||||
entity.CreatedAt = DateTime.Now;
|
||||
return await Orm.Insert(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Employee entity)
|
||||
{
|
||||
entity.UpdatedAt = DateTime.Now;
|
||||
return await Orm.Update<Employee>().SetSource(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> SoftDeleteAsync(long id)
|
||||
=> await Orm.Update<Employee>()
|
||||
.Set(x => x.IsDeleted, true)
|
||||
.Set(x => x.DeletedAt, DateTime.Now)
|
||||
.Where(x => x.Id == id)
|
||||
.ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
internal class EmployeeReadRepository : F9RepositoryBase, IEmployeeReadRepository
|
||||
{
|
||||
private readonly ISnowflakeIdGenerator _snowflakeIdGenerator;
|
||||
|
||||
public EmployeeReadRepository(IFreeSql fsql, ISnowflakeIdGenerator snowflakeIdGenerator) : base(fsql)
|
||||
=> _snowflakeIdGenerator = snowflakeIdGenerator;
|
||||
|
||||
public async Task<PageResult<EmployeeReadDto_View>> GetPagedAsync(long depId, long worktypeId,
|
||||
Employee_WorkStatus_Enum? workStatus, Prod_Banci_Enum? banci, string keyword, int pageIndex, int pageSize)
|
||||
{
|
||||
if (pageIndex <= 0) pageIndex = 1;
|
||||
if (pageSize <= 0) pageSize = 20;
|
||||
|
||||
var query = Orm.Select<Employee>().Where(x => x.DeletedAt == null);
|
||||
|
||||
if (depId > 0)
|
||||
{
|
||||
var depIds = await GetDepIdsWithChildrenAsync(depId);
|
||||
query = query.Where(x => depIds.Contains(x.DepId));
|
||||
}
|
||||
if (worktypeId > 0) query = query.Where(x => x.WorktypeId == worktypeId);
|
||||
if (workStatus.HasValue) query = query.Where(x => x.WorkStatus == workStatus.Value);
|
||||
if (banci.HasValue) query = query.Where(x => x.Banci == banci.Value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
var kw = keyword.Trim();
|
||||
query = query.Where(x => x.Name.Contains(kw) || x.Code.Contains(kw) || x.Phone.Contains(kw)
|
||||
|| x.IdCard.Contains(kw) || x.Gangwei.Contains(kw) || x.NamePinyin.Contains(kw));
|
||||
}
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderBy(x => x.DepId)
|
||||
.OrderBy(x => x.Sort)
|
||||
.OrderBy(x => x.Name)
|
||||
.Page(pageIndex, pageSize)
|
||||
.ToListAsync(x => new EmployeeReadDto_View(x.Id, x.DepId, "", x.WorktypeId, "", x.Banci, "", x.Gangwei,
|
||||
x.WorkStatus, x.IsZhuanzheng, x.Code, x.Name, x.Gender, 0, x.IdCard, x.Phone, x.BirthDate, x.RuzhiDate,
|
||||
x.ZhuanzhengDate, x.LizhiDate, x.LizhiYuanyin, x.WorkGroup, x.Sort, x.BankcardCode, x.HetongType,
|
||||
x.HetongStartDate, x.HetongEndDate, x.TuixiuDate, x.ShebaoDate, x.Minzu, x.HunyinStatus, x.Xueli, x.Huji,
|
||||
x.Address, x.JinjiLianxiren, x.JinjiLianxirenPhone, x.BiyeYuanxiao, x.Zhuanye, x.NamePinyin, x.Memo));
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return new PageResult<EmployeeReadDto_View> { Items = items, Total = total, PageIndex = pageIndex, PageSize = pageSize };
|
||||
}
|
||||
|
||||
var depNameMap = await GetDepNameMapAsync();
|
||||
var worktypeNameMap = await GetWorktypeNameMapAsync();
|
||||
|
||||
var list = items
|
||||
.Select(x => x with
|
||||
{
|
||||
DepName = depNameMap.TryGetValue(x.DepId, out var dn) ? dn : string.Empty,
|
||||
WorktypeName = worktypeNameMap.TryGetValue(x.WorktypeId, out var wn) ? wn : string.Empty,
|
||||
BanciName = x.Banci.HasValue ? x.Banci.Value.ToString() : string.Empty,
|
||||
Nianling = CalcAge(x.BirthDate, x.IdCard)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new PageResult<EmployeeReadDto_View> { Items = list, Total = total, PageIndex = pageIndex, PageSize = pageSize };
|
||||
}
|
||||
|
||||
public async Task<EmployeeReadDto_Edit> GetByIdForEditAsync(long id)
|
||||
=> await Orm.Select<Employee>()
|
||||
.Where(x => x.Id == id && x.DeletedAt == null)
|
||||
.FirstAsync(x => new EmployeeReadDto_Edit(x.Id, x.DepId, x.WorktypeId, x.WorkStatus, x.Banci, x.Code,
|
||||
x.Name, x.Gangwei, x.Phone, x.IdCard, x.BirthDate, x.Address, x.Gender, x.Minzu, x.HunyinStatus, x.Huji,
|
||||
x.RuzhiDate, x.LizhiDate, x.ZhuanzhengDate, x.IsZhuanzheng, x.JinjiLianxiren, x.JinjiLianxirenPhone,
|
||||
x.HetongType, x.HetongStartDate, x.HetongEndDate, x.TuixiuDate, x.ShebaoDate, x.ShebaoDesc,
|
||||
x.BankcardCode, x.BiyeYuanxiao, x.Zhuanye, x.Xueli, x.WorkGroup, x.Sort, x.NamePinyin, x.Memo,
|
||||
x.LizhiYuanyin));
|
||||
|
||||
public async Task<List<EmployeeChangeRecordDto>> GetChangeRecordsAsync(long employeeId)
|
||||
{
|
||||
var rows = await Orm.Select<EmployeeChangeRecord>()
|
||||
.Where(x => x.EmployeeId == employeeId)
|
||||
.OrderByDescending(x => x.Date)
|
||||
.ToListAsync(x => new EmployeeChangeRecordDto(x.Id, x.EmployeeId, x.Date, x.ChangeStatus, "",
|
||||
x.ChangeShuoming, x.ChangeYuanyin));
|
||||
|
||||
return rows.Select(x => x with { ChangeStatusName = x.ChangeStatus.ToString() }).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<EmployeeTiaogangRecordDto>> GetTiaogangRecordsAsync(long employeeId)
|
||||
{
|
||||
var rows = await Orm.Select<EmployeeTiaogangRecord>()
|
||||
.Where(x => x.EmployeeId == employeeId)
|
||||
.OrderByDescending(x => x.TiaogangDate)
|
||||
.ToListAsync(x => new EmployeeTiaogangRecordDto(x.Id, x.EmployeeId, x.OldDepId, "", x.OldWorktypeId, "",
|
||||
x.NewDepId, "", x.NewWorktypeId, "", x.TiaogangDate));
|
||||
|
||||
if (rows.Count == 0) return rows;
|
||||
|
||||
var depNameMap = await GetDepNameMapAsync();
|
||||
var worktypeNameMap = await GetWorktypeNameMapAsync();
|
||||
|
||||
return rows
|
||||
.Select(x => x with
|
||||
{
|
||||
OldDepName = depNameMap.TryGetValue(x.OldDepId, out var odn) ? odn : string.Empty,
|
||||
NewDepName = depNameMap.TryGetValue(x.NewDepId, out var ndn) ? ndn : string.Empty,
|
||||
OldWorktypeName = worktypeNameMap.TryGetValue(x.OldWorktypeId, out var own) ? own : string.Empty,
|
||||
NewWorktypeName = worktypeNameMap.TryGetValue(x.NewWorktypeId, out var nwn) ? nwn : string.Empty
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> AddChangeRecordAsync(EmployeeChangeRecordDto record)
|
||||
{
|
||||
var entity = new EmployeeChangeRecord(_snowflakeIdGenerator.CreateId(), record.EmployeeId,
|
||||
record.Date, record.ChangeStatus, record.ChangeShuoming ?? string.Empty, record.ChangeYuanyin ?? string.Empty)
|
||||
{
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
return await Orm.Insert(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> AddTiaogangRecordAsync(long employeeId, long oldDepId, long oldWorktypeId, long newDepId,
|
||||
long newWorktypeId, DateTime date)
|
||||
{
|
||||
var entity = new EmployeeTiaogangRecord(_snowflakeIdGenerator.CreateId(), employeeId, oldDepId,
|
||||
oldWorktypeId, newDepId, newWorktypeId, date)
|
||||
{
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
return await Orm.Insert(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<string> GetDepNameAsync(long depId)
|
||||
{
|
||||
if (depId <= 0) return string.Empty;
|
||||
var dep = await Orm.Select<Department>().Where(x => x.Id == depId).FirstAsync();
|
||||
return dep?.Name ?? string.Empty;
|
||||
}
|
||||
|
||||
public async Task<string> GetWorktypeNameAsync(long worktypeId)
|
||||
{
|
||||
if (worktypeId <= 0) return string.Empty;
|
||||
var worktype = await Orm.Select<WorkType>().Where(x => x.Id == worktypeId).FirstAsync();
|
||||
return worktype?.WorkName ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>取部门及其全部下级部门 Id</summary>
|
||||
private async Task<List<long>> GetDepIdsWithChildrenAsync(long rootDepId)
|
||||
{
|
||||
var all = await Orm.Select<Department>().Where(x => x.DeletedAt == null).ToListAsync();
|
||||
var result = new HashSet<long> { rootDepId };
|
||||
|
||||
var changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
foreach (var dep in all)
|
||||
{
|
||||
if (dep.ParentId != 0 && result.Contains(dep.ParentId) && result.Add(dep.Id)) changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
private async Task<Dictionary<long, string>> GetDepNameMapAsync()
|
||||
=> (await Orm.Select<Department>().Where(x => x.DeletedAt == null).ToListAsync())
|
||||
.ToDictionary(x => x.Id, x => x.Name);
|
||||
|
||||
private async Task<Dictionary<long, string>> GetWorktypeNameMapAsync()
|
||||
=> (await Orm.Select<WorkType>().Where(x => x.DeletedAt == null).ToListAsync())
|
||||
.ToDictionary(x => x.Id, x => x.WorkName);
|
||||
|
||||
/// <summary>年龄:优先出生日期,其次从身份证号推算</summary>
|
||||
private static int CalcAge(DateTime? birthDate, string idCard)
|
||||
{
|
||||
var birth = birthDate;
|
||||
|
||||
if (birth == null && !string.IsNullOrWhiteSpace(idCard))
|
||||
{
|
||||
var card = idCard.Trim();
|
||||
string? datePart = card.Length switch
|
||||
{
|
||||
18 => card.Substring(6, 8),
|
||||
15 => "19" + card.Substring(6, 6),
|
||||
_ => null
|
||||
};
|
||||
if (datePart != null
|
||||
&& DateTime.TryParseExact(datePart, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var parsed))
|
||||
{
|
||||
birth = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (birth == null) return 0;
|
||||
|
||||
var today = DateTime.Today;
|
||||
var age = today.Year - birth.Value.Year;
|
||||
if (birth.Value.Date > today.AddYears(-age)) age--;
|
||||
return age < 0 ? 0 : age;
|
||||
}
|
||||
}
|
||||
+13
-9
@@ -28,11 +28,14 @@ internal class WorkTypeReadRepository : F9RepositoryBase, IWorkTypeReadRepositor
|
||||
.OrderBy(x => x.SortOrder)
|
||||
.OrderBy(x => x.Id)
|
||||
.Page(pageIndex, pageSize)
|
||||
.ToListAsync(x => new WorkTypeReadDto_View(x.Id, x.WorkName, x.SortOrder, x.BizType, x.SccbFenlei, x.IsActive, ""));
|
||||
.ToListAsync(x => new WorkTypeReadDto_View(x.Id, x.WorkName, x.SortOrder, x.BizType, x.SccbFenlei, x.IsActive, "", null));
|
||||
|
||||
var nameMap = await GetDepartmentNameMapAsync(items.Select(x => x.Id).ToList());
|
||||
// 部门名称与 Id 一次性带出,前端无需再逐个工种查询关联部门
|
||||
var infoMap = await GetDepartmentInfoMapAsync(items.Select(x => x.Id).ToList());
|
||||
var list = items
|
||||
.Select(x => x with { DepartmentNames = nameMap.TryGetValue(x.Id, out var names) ? names : string.Empty })
|
||||
.Select(x => infoMap.TryGetValue(x.Id, out var info)
|
||||
? x with { DepartmentNames = info.Names, DepartmentIds = info.Ids }
|
||||
: x with { DepartmentNames = string.Empty, DepartmentIds = new List<long>() })
|
||||
.ToList();
|
||||
|
||||
return new PageResult<WorkTypeReadDto_View>
|
||||
@@ -61,10 +64,10 @@ internal class WorkTypeReadRepository : F9RepositoryBase, IWorkTypeReadRepositor
|
||||
.OrderBy(x => x.AssignedAt)
|
||||
.ToListAsync(x => new WorkTypeRoleReadDto_Cache(x.Id, x.WorkTypeId, x.RoleId));
|
||||
|
||||
/// <summary>工种 Id → 所属部门名称串(顿号分隔)</summary>
|
||||
private async Task<Dictionary<long, string>> GetDepartmentNameMapAsync(List<long> workTypeIds)
|
||||
/// <summary>工种 Id → 所属部门名称串(顿号分隔)与部门 Id 列表</summary>
|
||||
private async Task<Dictionary<long, (string Names, List<long> Ids)>> GetDepartmentInfoMapAsync(List<long> workTypeIds)
|
||||
{
|
||||
var map = new Dictionary<long, string>();
|
||||
var map = new Dictionary<long, (string, List<long>)>();
|
||||
if (workTypeIds == null || workTypeIds.Count == 0) return map;
|
||||
|
||||
var relations = await Orm.Select<WorkTypeDepartment>()
|
||||
@@ -78,11 +81,12 @@ internal class WorkTypeReadRepository : F9RepositoryBase, IWorkTypeReadRepositor
|
||||
|
||||
foreach (var group in relations.GroupBy(x => x.WorkTypeId))
|
||||
{
|
||||
var names = group
|
||||
.Select(r => depNameMap.TryGetValue(r.DepartmentId, out var name) ? name : null)
|
||||
var ids = group.Select(r => r.DepartmentId).Distinct().ToList();
|
||||
var names = ids
|
||||
.Select(id => depNameMap.TryGetValue(id, out var name) ? name : null)
|
||||
.Where(name => !string.IsNullOrEmpty(name))
|
||||
.ToList();
|
||||
map[group.Key] = string.Join("、", names);
|
||||
map[group.Key] = (string.Join("、", names), ids);
|
||||
}
|
||||
|
||||
return map;
|
||||
|
||||
Reference in New Issue
Block a user