using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NCC.Common.Core.Manager; using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqAttendanceSetting; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_attendance_absenteeism_rule; using NCC.Extend.Entitys.lq_attendance_annual_leave_rule; using NCC.Extend.Entitys.lq_attendance_config_history; using NCC.Extend.Entitys.lq_attendance_exempt_user; using NCC.Extend.Entitys.lq_attendance_extra_leave; using NCC.Extend.Entitys.lq_attendance_funeral_leave_rule; using NCC.Extend.Entitys.lq_attendance_group; using NCC.Extend.Entitys.lq_attendance_holiday; using NCC.Extend.Entitys.lq_attendance_late_rule; using NCC.Extend.Entitys.lq_attendance_marriage_leave_rule; using NCC.Extend.Entitys.lq_attendance_maternity_leave_rule; using NCC.Extend.Entitys.lq_attendance_missing_card_rule; using NCC.Extend.Entitys.lq_attendance_setting; using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Interfaces.LqAttendanceSetting; using NCC.FriendlyException; using NCC.System.Entitys.Permission; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SqlSugar; using Yitter.IdGenerator; namespace NCC.Extend.LqAttendanceSetting { /// /// 考勤设置服务 /// [ApiDescriptionSettings(Tag = "绿纤考勤设置服务", Name = "LqAttendanceSetting", Order = 200)] [Route("api/Extend/[controller]")] [ApiController] public class LqAttendanceSettingService : ILqAttendanceSettingService, IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; private readonly IUserManager _userManager; /// /// 初始化一个类型的新实例 /// public LqAttendanceSettingService(ISqlSugarClient db, IUserManager userManager) { _db = db; _userManager = userManager; } /// /// 获取考勤设置聚合信息(兼容旧页面) /// [HttpGet("Info")] public async Task GetInfo() { var baseInfo = await BuildBaseSettingOutputAsync(); var holidays = await GetHolidayQuery().OrderBy(x => x.HolidayDate).OrderBy(x => x.SortCode).ToListAsync(); var groups = await GetGroupQuery().OrderBy(x => x.SortCode).ToListAsync(); var exemptUsers = await GetExemptUserQuery().OrderBy(x => x.SortCode).ToListAsync(); var extraLeaves = await GetExtraLeaveQuery().OrderBy(x => x.GrantYear, OrderByType.Desc).OrderBy(x => x.SortCode).ToListAsync(); var marriageRules = await GetMarriageRuleQuery().OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToListAsync(); var funeralRules = await GetFuneralRuleQuery().OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToListAsync(); var annualRules = await GetAnnualRuleQuery().OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToListAsync(); var maternityRules = await GetMaternityRuleQuery().OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToListAsync(); var lateRules = await GetLateRuleQuery().OrderBy(x => x.MinMinutes).OrderBy(x => x.SortCode).ToListAsync(); var missingCardRules = await GetMissingCardRuleQuery().OrderBy(x => x.MinCount).OrderBy(x => x.SortCode).ToListAsync(); var absenteeismRules = await GetAbsenteeismRuleQuery().OrderBy(x => x.MinDays).OrderBy(x => x.SortCode).ToListAsync(); return new LqAttendanceSettingInfoOutput { leaveDeductDailySalaryRate = baseInfo.leaveDeductDailySalaryRate, sickLeaveDeductDailySalaryRate = baseInfo.sickLeaveDeductDailySalaryRate, remark = baseInfo.remark, holidays = holidays.Select(MapHolidayEntity).ToList(), groups = groups.Select(MapGroupEntity).ToList(), exemptUsers = exemptUsers.Select(MapExemptEntity).ToList(), extraLeaves = extraLeaves.Select(MapExtraLeaveEntity).ToList(), marriageLeaveRules = marriageRules.Select(MapMarriageEntity).ToList(), funeralLeaveRules = funeralRules.Select(MapFuneralEntity).ToList(), annualLeaveRules = annualRules.Select(MapAnnualEntity).ToList(), maternityLeaveRules = maternityRules.Select(MapMaternityEntity).ToList(), lateRules = lateRules.Select(MapLateEntity).ToList(), missingCardRules = missingCardRules.Select(MapMissingCardEntity).ToList(), absenteeismRules = absenteeismRules.Select(MapAbsenteeismEntity).ToList() }; } /// /// 获取考勤设置概览 /// [HttpGet("Overview")] public async Task GetOverview() { var historyLatest = await _db.Queryable() .OrderBy(x => x.OperateTime, OrderByType.Desc) .FirstAsync(); return new AttendanceSettingOverviewOutput { holidayCount = await GetHolidayQuery().CountAsync(), groupCount = await GetGroupQuery().CountAsync(), enabledGroupCount = await GetGroupQuery().Where(x => x.IsEnabled == 1).CountAsync(), leaveRuleCount = await GetMarriageRuleQuery().CountAsync() + await GetFuneralRuleQuery().CountAsync() + await GetAnnualRuleQuery().CountAsync() + await GetMaternityRuleQuery().CountAsync() + await GetExtraLeaveQuery().CountAsync(), deductRuleCount = await GetLateRuleQuery().CountAsync() + await GetMissingCardRuleQuery().CountAsync() + await GetAbsenteeismRuleQuery().CountAsync(), specialCount = await GetExemptUserQuery().CountAsync() + await GetExtraLeaveQuery().CountAsync(), lastModifyTime = historyLatest?.OperateTime.ToString("yyyy-MM-dd HH:mm:ss") }; } /// /// 获取基础扣款设置 /// [HttpGet("BaseInfo")] public async Task GetBaseInfo() { return await BuildBaseSettingOutputAsync(); } /// /// 保存基础扣款设置 /// [HttpPost("SaveBaseInfo")] public async Task SaveBaseInfo([FromBody] AttendanceBaseSettingSaveInput input) { input ??= new AttendanceBaseSettingSaveInput(); if (input.leaveDeductDailySalaryRate < 0) { throw NCCException.Oh("请假扣款倍率不能小于0"); } if (input.sickLeaveDeductDailySalaryRate < 0) { throw NCCException.Oh("病假扣款倍率不能小于0"); } var currentUser = await GetOperatorAsync(); var now = DateTime.Now; var entity = await GetBaseSettingQuery().OrderBy(x => x.CreateTime, OrderByType.Desc).FirstAsync(); var snapshot = new { leaveDeductDailySalaryRate = input.leaveDeductDailySalaryRate, sickLeaveDeductDailySalaryRate = input.sickLeaveDeductDailySalaryRate, remark = TrimToNull(input.remark) }; if (entity == null) { entity = new LqAttendanceSettingEntity { Id = string.IsNullOrWhiteSpace(input.id) ? YitIdHelper.NextId().ToString() : input.id, LeaveDeductDailySalaryRate = input.leaveDeductDailySalaryRate, SickLeaveDeductDailySalaryRate = input.sickLeaveDeductDailySalaryRate, Remark = TrimToNull(input.remark), CreateTime = now, CreateUserId = currentUser.UserId, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.BaseSetting, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Create, "基础扣款设置", snapshot, input.changeReason, currentUser, now); } else { var beforeSnapshot = new { leaveDeductDailySalaryRate = entity.LeaveDeductDailySalaryRate, sickLeaveDeductDailySalaryRate = entity.SickLeaveDeductDailySalaryRate, remark = TrimToNull(entity.Remark) }; if (IsSnapshotEqual(beforeSnapshot, snapshot)) { return new { changed = false, data = await BuildBaseSettingOutputAsync(entity) }; } entity.LeaveDeductDailySalaryRate = input.leaveDeductDailySalaryRate; entity.SickLeaveDeductDailySalaryRate = input.sickLeaveDeductDailySalaryRate; entity.Remark = TrimToNull(input.remark); entity.VersionNo = (entity.VersionNo ?? 0) + 1; entity.LastModifyTime = now; entity.LastModifyUserId = currentUser.UserId; entity.LastModifyUserName = currentUser.UserName; await _db.Updateable(entity).ExecuteCommandAsync(); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.BaseSetting, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, "基础扣款设置", snapshot, input.changeReason, currentUser, now); } return new { changed = true, data = await BuildBaseSettingOutputAsync(entity) }; } /// /// 获取指定模块配置列表 /// [HttpGet("ConfigList")] public async Task GetConfigList([FromQuery] AttendanceConfigListQueryInput input) { input ??= new AttendanceConfigListQueryInput(); input.currentPage = input.currentPage <= 0 ? 1 : input.currentPage; input.pageSize = input.pageSize <= 0 ? 20 : input.pageSize; var moduleType = ParseModuleType(input.moduleType); var keyword = input.keyword?.Trim(); switch (moduleType) { case AttendanceConfigModuleTypeEnum.Holiday: return await GetHolidayListAsync(input.currentPage, input.pageSize, keyword, input.year); case AttendanceConfigModuleTypeEnum.Group: return await GetGroupListAsync(input.currentPage, input.pageSize, keyword); case AttendanceConfigModuleTypeEnum.MarriageLeaveRule: return await GetMarriageRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.FuneralLeaveRule: return await GetFuneralRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.AnnualLeaveRule: return await GetAnnualRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.MaternityLeaveRule: return await GetMaternityRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.LateRule: return await GetLateRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.MissingCardRule: return await GetMissingCardRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.AbsenteeismRule: return await GetAbsenteeismRuleListAsync(input.currentPage, input.pageSize); case AttendanceConfigModuleTypeEnum.ExemptUser: return await GetExemptUserListAsync(input.currentPage, input.pageSize, keyword); case AttendanceConfigModuleTypeEnum.ExtraLeave: return await GetExtraLeaveListAsync(input.currentPage, input.pageSize, keyword, input.year); default: throw NCCException.Oh("暂不支持的配置模块"); } } /// /// 获取指定模块历史记录 /// [HttpGet("ConfigHistory")] public async Task GetConfigHistory([FromQuery] AttendanceConfigHistoryQueryInput input) { input ??= new AttendanceConfigHistoryQueryInput(); input.currentPage = input.currentPage <= 0 ? 1 : input.currentPage; input.pageSize = input.pageSize <= 0 ? 20 : input.pageSize; if (string.IsNullOrWhiteSpace(input.bizId)) { throw NCCException.Oh("业务主键ID不能为空"); } var moduleType = ParseModuleType(input.moduleType); var page = await _db.Queryable() .Where(x => x.ModuleType == (int)moduleType && x.BizId == input.bizId) .OrderBy(x => x.VersionNo, OrderByType.Desc) .Select(x => new AttendanceConfigHistoryOutput { id = x.Id, bizId = x.BizId, versionNo = x.VersionNo, operateType = x.OperateType, operateTypeText = "", title = x.Title, snapshotJson = x.SnapshotJson, changeReason = x.ChangeReason, operateUserId = x.OperateUserId, operateUserName = x.OperateUserName, operateTime = "" }) .ToPagedListAsync(input.currentPage, input.pageSize); if (page.list != null && page.list.Any()) { var historyIds = page.list.Select(t => t.id).ToList(); var timeMap = await _db.Queryable() .Where(x => x.ModuleType == (int)moduleType && x.BizId == input.bizId) .Where(x => historyIds.Contains(x.Id)) .Select(x => new { x.Id, x.OperateTime }) .ToListAsync(); var timeLookup = timeMap.ToDictionary(x => x.Id, x => x.OperateTime); foreach (var item in page.list) { item.operateTypeText = GetOperateTypeText((AttendanceConfigOperateTypeEnum)item.operateType); if (timeLookup.TryGetValue(item.id, out var operateTime)) { item.operateTime = operateTime.ToString("yyyy-MM-dd HH:mm:ss"); } } } return PageResult.SqlSugarPageResult(page); } /// /// 保存指定模块配置 /// [HttpPost("ConfigSave")] public async Task SaveConfig([FromBody] AttendanceConfigSaveInput input) { input ??= new AttendanceConfigSaveInput(); if (input.data == null) { throw NCCException.Oh("配置数据不能为空"); } var moduleType = ParseModuleType(input.moduleType); switch (moduleType) { case AttendanceConfigModuleTypeEnum.Holiday: return await SaveHolidayAsync(input); case AttendanceConfigModuleTypeEnum.Group: return await SaveGroupAsync(input); case AttendanceConfigModuleTypeEnum.MarriageLeaveRule: return await SaveMarriageRuleAsync(input); case AttendanceConfigModuleTypeEnum.FuneralLeaveRule: return await SaveFuneralRuleAsync(input); case AttendanceConfigModuleTypeEnum.AnnualLeaveRule: return await SaveAnnualRuleAsync(input); case AttendanceConfigModuleTypeEnum.MaternityLeaveRule: return await SaveMaternityRuleAsync(input); case AttendanceConfigModuleTypeEnum.LateRule: return await SaveLateRuleAsync(input); case AttendanceConfigModuleTypeEnum.MissingCardRule: return await SaveMissingCardRuleAsync(input); case AttendanceConfigModuleTypeEnum.AbsenteeismRule: return await SaveAbsenteeismRuleAsync(input); case AttendanceConfigModuleTypeEnum.ExemptUser: return await SaveExemptUserAsync(input); case AttendanceConfigModuleTypeEnum.ExtraLeave: return await SaveExtraLeaveAsync(input); default: throw NCCException.Oh("暂不支持的配置模块"); } } /// /// 删除指定模块配置(逻辑删除) /// [HttpPost("ConfigDelete")] public async Task DeleteConfig([FromBody] AttendanceConfigDeleteInput input) { input ??= new AttendanceConfigDeleteInput(); if (string.IsNullOrWhiteSpace(input.id)) { throw NCCException.Oh("主键ID不能为空"); } var moduleType = ParseModuleType(input.moduleType); switch (moduleType) { case AttendanceConfigModuleTypeEnum.Holiday: await DeleteHolidayAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.Group: await DeleteGroupAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.MarriageLeaveRule: await DeleteMarriageRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.FuneralLeaveRule: await DeleteFuneralRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.AnnualLeaveRule: await DeleteAnnualRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.MaternityLeaveRule: await DeleteMaternityRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.LateRule: await DeleteLateRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.MissingCardRule: await DeleteMissingCardRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.AbsenteeismRule: await DeleteAbsenteeismRuleAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.ExemptUser: await DeleteExemptUserAsync(input.id, input.changeReason); break; case AttendanceConfigModuleTypeEnum.ExtraLeave: await DeleteExtraLeaveAsync(input.id, input.changeReason); break; default: throw NCCException.Oh("暂不支持的配置模块"); } } /// /// 获取考勤分组下拉框 /// [HttpGet("Selector")] public async Task GetSelector() { var list = await GetGroupQuery() .Where(x => x.IsEnabled == 1) .OrderBy(x => x.SortCode) .Select(x => new { id = x.Id, fullName = x.GroupName, workStartTime = x.WorkStartTime, workEndTime = x.WorkEndTime, monthlyRestDays = x.MonthlyRestDays, halfDaySplitRestDays = x.HalfDaySplitRestDays }) .ToListAsync(); return new { list }; } /// /// 获取考勤分组成员列表 /// [HttpGet("GroupUsers")] public async Task GetGroupUsers([FromQuery] AttendanceGroupUserQueryInput input) { input ??= new AttendanceGroupUserQueryInput(); if (string.IsNullOrWhiteSpace(input.GroupId)) { throw NCCException.Oh("考勤分组ID不能为空"); } input.currentPage = input.currentPage <= 0 ? 1 : input.currentPage; input.pageSize = input.pageSize <= 0 ? 20 : input.pageSize; var keyword = input.Keyword?.Trim(); var query = _db.Queryable((u, s) => new JoinQueryInfos( JoinType.Left, s.Id == u.Mdid)) .Where((u, s) => u.DeleteMark == null && u.AttendanceGroupId == input.GroupId) .WhereIF(input.OnJobStatus.HasValue, (u, s) => SqlFunc.IsNull(u.IsOnJob, 1) == input.OnJobStatus.Value) .WhereIF(!string.IsNullOrWhiteSpace(keyword), (u, s) => u.RealName.Contains(keyword) || u.Account.Contains(keyword) || u.MobilePhone.Contains(keyword)) .Select((u, s) => new AttendanceGroupUserOutput { id = u.Id, realName = u.RealName, account = u.Account, mobilePhone = u.MobilePhone, storeId = u.Mdid, storeName = s.Dm, gw = u.Gw, gwfl = u.Gwfl, entryDate = u.EntryDate, isOnJob = SqlFunc.IIF(SqlFunc.IsNull(u.IsOnJob, 1) == 1, 1, 0), isOnJobText = SqlFunc.IIF(SqlFunc.IsNull(u.IsOnJob, 1) == 1, "在职", "离职") }) .MergeTable() .OrderBy(x => x.isOnJob, OrderByType.Desc) .OrderBy(x => x.storeName) .OrderBy(x => x.realName); var pageData = await query.ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(pageData); } private async Task BuildBaseSettingOutputAsync(LqAttendanceSettingEntity entity = null) { entity ??= await GetBaseSettingQuery().OrderBy(x => x.CreateTime, OrderByType.Desc).FirstAsync(); return new AttendanceBaseSettingOutput { id = entity?.Id, leaveDeductDailySalaryRate = entity?.LeaveDeductDailySalaryRate ?? 1, sickLeaveDeductDailySalaryRate = entity?.SickLeaveDeductDailySalaryRate ?? 1, remark = entity?.Remark, versionNo = entity?.VersionNo ?? 0, lastModifyTime = entity?.LastModifyTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? entity?.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss"), lastModifyUserName = entity?.LastModifyUserName }; } private async Task GetHolidayListAsync(int currentPage, int pageSize, string keyword, int? year) { var query = GetHolidayQuery() .WhereIF(year.HasValue, x => x.HolidayDate >= new DateTime(year.Value, 1, 1) && x.HolidayDate < new DateTime(year.Value + 1, 1, 1)) .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.HolidayName.Contains(keyword) || x.Remark.Contains(keyword)); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.HolidayDate).OrderBy(x => x.SortCode) .ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapHolidayEntity).ToList(), total, currentPage, pageSize); } private async Task GetGroupListAsync(int currentPage, int pageSize, string keyword) { var query = GetGroupQuery() .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.GroupName.Contains(keyword) || x.Remark.Contains(keyword)); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapGroupEntity).ToList(), total, currentPage, pageSize); } private async Task GetMarriageRuleListAsync(int currentPage, int pageSize) { var query = GetMarriageRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapMarriageEntity).ToList(), total, currentPage, pageSize); } private async Task GetFuneralRuleListAsync(int currentPage, int pageSize) { var query = GetFuneralRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapFuneralEntity).ToList(), total, currentPage, pageSize); } private async Task GetAnnualRuleListAsync(int currentPage, int pageSize) { var query = GetAnnualRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapAnnualEntity).ToList(), total, currentPage, pageSize); } private async Task GetMaternityRuleListAsync(int currentPage, int pageSize) { var query = GetMaternityRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinYears).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapMaternityEntity).ToList(), total, currentPage, pageSize); } private async Task GetLateRuleListAsync(int currentPage, int pageSize) { var query = GetLateRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinMinutes).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapLateEntity).ToList(), total, currentPage, pageSize); } private async Task GetMissingCardRuleListAsync(int currentPage, int pageSize) { var query = GetMissingCardRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinCount).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapMissingCardEntity).ToList(), total, currentPage, pageSize); } private async Task GetAbsenteeismRuleListAsync(int currentPage, int pageSize) { var query = GetAbsenteeismRuleQuery(); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.MinDays).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapAbsenteeismEntity).ToList(), total, currentPage, pageSize); } private async Task GetExemptUserListAsync(int currentPage, int pageSize, string keyword) { var query = GetExemptUserQuery() .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.UserName.Contains(keyword) || x.Remark.Contains(keyword)); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapExemptEntity).ToList(), total, currentPage, pageSize); } private async Task GetExtraLeaveListAsync(int currentPage, int pageSize, string keyword, int? year) { var query = GetExtraLeaveQuery() .WhereIF(year.HasValue, x => x.GrantYear == year.Value) .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.UserName.Contains(keyword) || x.LeaveName.Contains(keyword) || x.Remark.Contains(keyword)); var total = await query.CountAsync(); var list = await query.OrderBy(x => x.GrantYear, OrderByType.Desc).OrderBy(x => x.SortCode).ToPageListAsync(currentPage, pageSize); return BuildListResult(list.Select(MapExtraLeaveEntity).ToList(), total, currentPage, pageSize); } private async Task SaveHolidayAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); var currentUser = await GetOperatorAsync(); model.holidayName = TrimToNull(model.holidayName); model.remark = TrimToNull(model.remark); var existingList = (await GetHolidayQuery().Where(x => x.Id != model.id).OrderBy(x => x.HolidayDate).ToListAsync()).Select(MapHolidayEntity).ToList(); existingList.Add(model); ValidateHolidays(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceHolidayEntity { Id = YitIdHelper.NextId().ToString(), HolidayDate = ParseDate(model.holidayDate, "公休日期"), HolidayName = model.holidayName, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetHolidayQuery(), x => (int?)x.SortCode), CreateTime = now, CreateUserId = currentUser.UserId, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapHolidayEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.Holiday, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildHolidayTitle(output), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetHolidayQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("公休配置不存在或已删除"); var before = MapHolidayEntity(dbEntity); var after = new AttendanceHolidayModel { id = dbEntity.Id, holidayDate = ParseDate(model.holidayDate, "公休日期").ToString("yyyy-MM-dd"), holidayName = model.holidayName, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.HolidayDate = ParseDate(model.holidayDate, "公休日期"); dbEntity.HolidayName = model.holidayName; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after.id = dbEntity.Id; after = MapHolidayEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.Holiday, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildHolidayTitle(after), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveGroupAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.groupName = TrimToNull(model.groupName); model.remark = TrimToNull(model.remark); var currentUser = await GetOperatorAsync(); var existingList = (await GetGroupQuery().Where(x => x.Id != model.id).OrderBy(x => x.SortCode).ToListAsync()).Select(MapGroupEntity).ToList(); existingList.Add(model); ValidateGroups(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceGroupEntity { Id = YitIdHelper.NextId().ToString(), GroupName = model.groupName, WorkStartTime = model.workStartTime, WorkEndTime = model.workEndTime, MonthlyRestDays = model.monthlyRestDays, HalfDaySplitRestDays = model.halfDaySplitRestDays, IsEnabled = model.isEnabled, Remark = model.remark, RestUnlockCycle = model.restUnlockCycle, LateToleranceMinutes = model.lateToleranceMinutes, EarlyLeaveToleranceMinutes = model.earlyLeaveToleranceMinutes, SortCode = await GetNextSortCodeAsync(GetGroupQuery(), x => (int?)x.SortCode), CreateTime = now, CreateUserId = currentUser.UserId, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapGroupEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.Group, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, output.groupName, output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetGroupQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("考勤分组不存在或已删除"); var before = MapGroupEntity(dbEntity); var after = new AttendanceGroupModel { id = dbEntity.Id, groupName = model.groupName, workStartTime = model.workStartTime, workEndTime = model.workEndTime, monthlyRestDays = model.monthlyRestDays, halfDaySplitRestDays = model.halfDaySplitRestDays, isEnabled = model.isEnabled, remark = model.remark, restUnlockCycle = model.restUnlockCycle, lateToleranceMinutes = model.lateToleranceMinutes, earlyLeaveToleranceMinutes = model.earlyLeaveToleranceMinutes }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.GroupName = model.groupName; dbEntity.WorkStartTime = model.workStartTime; dbEntity.WorkEndTime = model.workEndTime; dbEntity.MonthlyRestDays = model.monthlyRestDays; dbEntity.HalfDaySplitRestDays = model.halfDaySplitRestDays; dbEntity.IsEnabled = model.isEnabled; dbEntity.Remark = model.remark; dbEntity.RestUnlockCycle = model.restUnlockCycle; dbEntity.LateToleranceMinutes = model.lateToleranceMinutes; dbEntity.EarlyLeaveToleranceMinutes = model.earlyLeaveToleranceMinutes; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapGroupEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.Group, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, after.groupName, after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveMarriageRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); var currentUser = await GetOperatorAsync(); var existingList = (await GetMarriageRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinYears).ToListAsync()).Select(MapMarriageEntity).ToList(); existingList.Add(model); ValidateYearRangeRules(existingList, "婚假规则"); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceMarriageLeaveRuleEntity { Id = YitIdHelper.NextId().ToString(), MinYears = model.minYears, MaxYears = model.maxYears, LeaveDays = model.leaveDays, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetMarriageRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapMarriageEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MarriageLeaveRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildYearRangeTitle(output.minYears, output.maxYears, "婚假规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetMarriageRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("婚假规则不存在或已删除"); var before = MapMarriageEntity(dbEntity); var after = new AttendanceYearRangeRuleModel { id = dbEntity.Id, minYears = model.minYears, maxYears = model.maxYears, leaveDays = model.leaveDays, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinYears = model.minYears; dbEntity.MaxYears = model.maxYears; dbEntity.LeaveDays = model.leaveDays; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapMarriageEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MarriageLeaveRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildYearRangeTitle(after.minYears, after.maxYears, "婚假规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveAnnualRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); var currentUser = await GetOperatorAsync(); var existingList = (await GetAnnualRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinYears).ToListAsync()).Select(MapAnnualEntity).ToList(); existingList.Add(model); ValidateYearRangeRules(existingList, "年假规则"); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceAnnualLeaveRuleEntity { Id = YitIdHelper.NextId().ToString(), MinYears = model.minYears, MaxYears = model.maxYears, LeaveDays = model.leaveDays, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetAnnualRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapAnnualEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.AnnualLeaveRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildYearRangeTitle(output.minYears, output.maxYears, "年假规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetAnnualRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("年假规则不存在或已删除"); var before = MapAnnualEntity(dbEntity); var after = new AttendanceYearRangeRuleModel { id = dbEntity.Id, minYears = model.minYears, maxYears = model.maxYears, leaveDays = model.leaveDays, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinYears = model.minYears; dbEntity.MaxYears = model.maxYears; dbEntity.LeaveDays = model.leaveDays; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapAnnualEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.AnnualLeaveRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildYearRangeTitle(after.minYears, after.maxYears, "年假规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveMaternityRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); var currentUser = await GetOperatorAsync(); var existingList = (await GetMaternityRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinYears).ToListAsync()).Select(MapMaternityEntity).ToList(); existingList.Add(model); ValidateYearRangeRules(existingList, "产假规则"); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceMaternityLeaveRuleEntity { Id = YitIdHelper.NextId().ToString(), MinYears = model.minYears, MaxYears = model.maxYears, LeaveDays = model.leaveDays, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetMaternityRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapMaternityEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MaternityLeaveRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildYearRangeTitle(output.minYears, output.maxYears, "产假规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetMaternityRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("产假规则不存在或已删除"); var before = MapMaternityEntity(dbEntity); var after = new AttendanceYearRangeRuleModel { id = dbEntity.Id, minYears = model.minYears, maxYears = model.maxYears, leaveDays = model.leaveDays, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinYears = model.minYears; dbEntity.MaxYears = model.maxYears; dbEntity.LeaveDays = model.leaveDays; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapMaternityEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MaternityLeaveRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildYearRangeTitle(after.minYears, after.maxYears, "产假规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveFuneralRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); var currentUser = await GetOperatorAsync(); var existingList = (await GetFuneralRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinYears).ToListAsync()).Select(MapFuneralEntity).ToList(); existingList.Add(model); ValidateFuneralRules(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceFuneralLeaveRuleEntity { Id = YitIdHelper.NextId().ToString(), MinYears = model.minYears, MaxYears = model.maxYears, DirectRelativeDays = model.directRelativeDays, IndirectRelativeDays = model.indirectRelativeDays, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetFuneralRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapFuneralEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.FuneralLeaveRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildYearRangeTitle(output.minYears, output.maxYears, "丧假规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetFuneralRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("丧假规则不存在或已删除"); var before = MapFuneralEntity(dbEntity); var after = new AttendanceFuneralLeaveRuleModel { id = dbEntity.Id, minYears = model.minYears, maxYears = model.maxYears, directRelativeDays = model.directRelativeDays, indirectRelativeDays = model.indirectRelativeDays, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinYears = model.minYears; dbEntity.MaxYears = model.maxYears; dbEntity.DirectRelativeDays = model.directRelativeDays; dbEntity.IndirectRelativeDays = model.indirectRelativeDays; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapFuneralEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.FuneralLeaveRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildYearRangeTitle(after.minYears, after.maxYears, "丧假规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveLateRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); model.expressionText = TrimToNull(model.expressionText); var currentUser = await GetOperatorAsync(); var existingList = (await GetLateRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinMinutes).ToListAsync()).Select(MapLateEntity).ToList(); existingList.Add(model); ValidateLateRules(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceLateRuleEntity { Id = YitIdHelper.NextId().ToString(), MinMinutes = model.minMinutes, MaxMinutes = model.maxMinutes, DeductMode = model.deductMode, DeductValue = model.deductValue, ExpressionText = model.expressionText, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetLateRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapLateEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.LateRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildMinuteRangeTitle(output.minMinutes, output.maxMinutes, "迟到/早退规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetLateRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("迟到/早退规则不存在或已删除"); var before = MapLateEntity(dbEntity); var after = new AttendanceLateRuleModel { id = dbEntity.Id, minMinutes = model.minMinutes, maxMinutes = model.maxMinutes, deductMode = model.deductMode, deductValue = model.deductValue, expressionText = model.expressionText, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinMinutes = model.minMinutes; dbEntity.MaxMinutes = model.maxMinutes; dbEntity.DeductMode = model.deductMode; dbEntity.DeductValue = model.deductValue; dbEntity.ExpressionText = model.expressionText; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapLateEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.LateRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildMinuteRangeTitle(after.minMinutes, after.maxMinutes, "迟到/早退规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveMissingCardRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); model.expressionText = TrimToNull(model.expressionText); var currentUser = await GetOperatorAsync(); var existingList = (await GetMissingCardRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinCount).ToListAsync()).Select(MapMissingCardEntity).ToList(); existingList.Add(model); ValidateMissingCardRules(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceMissingCardRuleEntity { Id = YitIdHelper.NextId().ToString(), MinCount = model.minCount, MaxCount = model.maxCount, DeductPerTime = model.deductPerTime, ExpressionText = model.expressionText, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetMissingCardRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapMissingCardEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MissingCardRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildCountRangeTitle(output.minCount, output.maxCount, "缺卡规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetMissingCardRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("缺卡规则不存在或已删除"); var before = MapMissingCardEntity(dbEntity); var after = new AttendanceMissingCardRuleModel { id = dbEntity.Id, minCount = model.minCount, maxCount = model.maxCount, deductPerTime = model.deductPerTime, expressionText = model.expressionText, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinCount = model.minCount; dbEntity.MaxCount = model.maxCount; dbEntity.DeductPerTime = model.deductPerTime; dbEntity.ExpressionText = model.expressionText; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapMissingCardEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MissingCardRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildCountRangeTitle(after.minCount, after.maxCount, "缺卡规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveAbsenteeismRuleAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.remark = TrimToNull(model.remark); model.actionText = TrimToNull(model.actionText); var currentUser = await GetOperatorAsync(); var existingList = (await GetAbsenteeismRuleQuery().Where(x => x.Id != model.id).OrderBy(x => x.MinDays).ToListAsync()).Select(MapAbsenteeismEntity).ToList(); existingList.Add(model); ValidateAbsenteeismRules(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceAbsenteeismRuleEntity { Id = YitIdHelper.NextId().ToString(), MinDays = model.minDays, MaxDays = model.maxDays, DeductMode = model.deductMode, DeductValue = model.deductValue, ActionType = model.actionType, ActionText = model.actionText, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetAbsenteeismRuleQuery(), x => (int?)x.SortCode), CreateTime = now, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapAbsenteeismEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.AbsenteeismRule, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildDecimalRangeTitle(output.minDays, output.maxDays, "旷工规则"), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetAbsenteeismRuleQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("旷工规则不存在或已删除"); var before = MapAbsenteeismEntity(dbEntity); var after = new AttendanceAbsenteeismRuleModel { id = dbEntity.Id, minDays = model.minDays, maxDays = model.maxDays, deductMode = model.deductMode, deductValue = model.deductValue, actionType = model.actionType, actionText = model.actionText, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.MinDays = model.minDays; dbEntity.MaxDays = model.maxDays; dbEntity.DeductMode = model.deductMode; dbEntity.DeductValue = model.deductValue; dbEntity.ActionType = model.actionType; dbEntity.ActionText = model.actionText; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapAbsenteeismEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.AbsenteeismRule, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildDecimalRangeTitle(after.minDays, after.maxDays, "旷工规则"), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveExemptUserAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); var currentUser = await GetOperatorAsync(); model.userId = TrimToNull(model.userId); model.remark = TrimToNull(model.remark); model.userName = await GetRequiredUserNameAsync(model.userId); var existingList = (await GetExemptUserQuery().Where(x => x.Id != model.id).OrderBy(x => x.SortCode).ToListAsync()).Select(MapExemptEntity).ToList(); existingList.Add(model); ValidateExemptUsers(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceExemptUserEntity { Id = YitIdHelper.NextId().ToString(), UserId = model.userId, UserName = model.userName, StartDate = ParseNullableDate(model.startDate, "免考勤开始日期"), EndDate = ParseNullableDate(model.endDate, "免考勤结束日期"), IsEnabled = model.isEnabled, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetExemptUserQuery(), x => (int?)x.SortCode), CreateTime = now, CreateUserId = currentUser.UserId, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapExemptEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.ExemptUser, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, output.userName, output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetExemptUserQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("免考勤配置不存在或已删除"); var before = MapExemptEntity(dbEntity); var after = new AttendanceExemptUserModel { id = dbEntity.Id, userId = model.userId, userName = model.userName, startDate = ParseNullableDate(model.startDate, "免考勤开始日期")?.ToString("yyyy-MM-dd"), endDate = ParseNullableDate(model.endDate, "免考勤结束日期")?.ToString("yyyy-MM-dd"), isEnabled = model.isEnabled, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.UserId = model.userId; dbEntity.UserName = model.userName; dbEntity.StartDate = ParseNullableDate(model.startDate, "免考勤开始日期"); dbEntity.EndDate = ParseNullableDate(model.endDate, "免考勤结束日期"); dbEntity.IsEnabled = model.isEnabled; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapExemptEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.ExemptUser, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, after.userName, after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task SaveExtraLeaveAsync(AttendanceConfigSaveInput input) { var model = ParseModel(input.data); model.userId = TrimToNull(model.userId); model.leaveName = TrimToNull(model.leaveName); model.remark = TrimToNull(model.remark); var currentUser = await GetOperatorAsync(); model.userName = await GetRequiredUserNameAsync(model.userId); var existingList = (await GetExtraLeaveQuery().Where(x => x.Id != model.id).OrderBy(x => x.GrantYear, OrderByType.Desc).ToListAsync()).Select(MapExtraLeaveEntity).ToList(); existingList.Add(model); ValidateExtraLeaves(existingList); var now = DateTime.Now; if (string.IsNullOrWhiteSpace(model.id)) { var entity = new LqAttendanceExtraLeaveEntity { Id = YitIdHelper.NextId().ToString(), UserId = model.userId, UserName = model.userName, LeaveName = model.leaveName, GrantYear = model.grantYear, ExtraDays = model.extraDays, IsEnabled = model.isEnabled, Remark = model.remark, SortCode = await GetNextSortCodeAsync(GetExtraLeaveQuery(), x => (int?)x.SortCode), CreateTime = now, CreateUserId = currentUser.UserId, DeleteMark = 0, VersionNo = 1, LastModifyTime = now, LastModifyUserId = currentUser.UserId, LastModifyUserName = currentUser.UserName }; await _db.Insertable(entity).ExecuteCommandAsync(); var output = MapExtraLeaveEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.ExtraLeave, entity.Id, 1, AttendanceConfigOperateTypeEnum.Create, BuildExtraLeaveTitle(output), output, input.changeReason, currentUser, now); return new { changed = true, data = output }; } var dbEntity = await GetExtraLeaveQuery().Where(x => x.Id == model.id).FirstAsync(); _ = dbEntity ?? throw NCCException.Oh("额外假期配置不存在或已删除"); var before = MapExtraLeaveEntity(dbEntity); var after = new AttendanceExtraLeaveModel { id = dbEntity.Id, userId = model.userId, userName = model.userName, leaveName = model.leaveName, grantYear = model.grantYear, extraDays = model.extraDays, isEnabled = model.isEnabled, remark = model.remark }; if (IsSnapshotEqual(before, after)) { return new { changed = false, data = before }; } dbEntity.UserId = model.userId; dbEntity.UserName = model.userName; dbEntity.LeaveName = model.leaveName; dbEntity.GrantYear = model.grantYear; dbEntity.ExtraDays = model.extraDays; dbEntity.IsEnabled = model.isEnabled; dbEntity.Remark = model.remark; ApplyModifyMeta(dbEntity, currentUser, now); await _db.Updateable(dbEntity).ExecuteCommandAsync(); after = MapExtraLeaveEntity(dbEntity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.ExtraLeave, dbEntity.Id, dbEntity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Update, BuildExtraLeaveTitle(after), after, input.changeReason, currentUser, now); return new { changed = true, data = after }; } private async Task DeleteHolidayAsync(string id, string changeReason) { var entity = await GetHolidayQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("公休配置不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapHolidayEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.Holiday, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildHolidayTitle(snapshot), snapshot, changeReason, currentUser, now); } private async Task DeleteGroupAsync(string id, string changeReason) { var entity = await GetGroupQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("考勤分组不存在或已删除"); var bindCount = await _db.Queryable().Where(x => x.DeleteMark == null && x.AttendanceGroupId == id).CountAsync(); if (bindCount > 0) { throw NCCException.Oh("当前考勤分组下仍有成员绑定,请先解除绑定后再删除"); } var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapGroupEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.Group, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, snapshot.groupName, snapshot, changeReason, currentUser, now); } private async Task DeleteMarriageRuleAsync(string id, string changeReason) { var entity = await GetMarriageRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("婚假规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapMarriageEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MarriageLeaveRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildYearRangeTitle(snapshot.minYears, snapshot.maxYears, "婚假规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteAnnualRuleAsync(string id, string changeReason) { var entity = await GetAnnualRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("年假规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapAnnualEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.AnnualLeaveRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildYearRangeTitle(snapshot.minYears, snapshot.maxYears, "年假规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteFuneralRuleAsync(string id, string changeReason) { var entity = await GetFuneralRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("丧假规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapFuneralEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.FuneralLeaveRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildYearRangeTitle(snapshot.minYears, snapshot.maxYears, "丧假规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteMaternityRuleAsync(string id, string changeReason) { var entity = await GetMaternityRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("产假规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapMaternityEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MaternityLeaveRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildYearRangeTitle(snapshot.minYears, snapshot.maxYears, "产假规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteLateRuleAsync(string id, string changeReason) { var entity = await GetLateRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("迟到/早退规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapLateEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.LateRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildMinuteRangeTitle(snapshot.minMinutes, snapshot.maxMinutes, "迟到/早退规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteMissingCardRuleAsync(string id, string changeReason) { var entity = await GetMissingCardRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("缺卡规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapMissingCardEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.MissingCardRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildCountRangeTitle(snapshot.minCount, snapshot.maxCount, "缺卡规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteAbsenteeismRuleAsync(string id, string changeReason) { var entity = await GetAbsenteeismRuleQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("旷工规则不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapAbsenteeismEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.AbsenteeismRule, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildDecimalRangeTitle(snapshot.minDays, snapshot.maxDays, "旷工规则"), snapshot, changeReason, currentUser, now); } private async Task DeleteExemptUserAsync(string id, string changeReason) { var entity = await GetExemptUserQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("免考勤配置不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapExemptEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.ExemptUser, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, snapshot.userName, snapshot, changeReason, currentUser, now); } private async Task DeleteExtraLeaveAsync(string id, string changeReason) { var entity = await GetExtraLeaveQuery().Where(x => x.Id == id).FirstAsync(); _ = entity ?? throw NCCException.Oh("额外假期配置不存在或已删除"); var currentUser = await GetOperatorAsync(); var now = DateTime.Now; entity.DeleteMark = 1; ApplyModifyMeta(entity, currentUser, now); await _db.Updateable(entity).ExecuteCommandAsync(); var snapshot = MapExtraLeaveEntity(entity); await WriteHistoryAsync(AttendanceConfigModuleTypeEnum.ExtraLeave, entity.Id, entity.VersionNo ?? 1, AttendanceConfigOperateTypeEnum.Delete, BuildExtraLeaveTitle(snapshot), snapshot, changeReason, currentUser, now); } private async Task WriteHistoryAsync(AttendanceConfigModuleTypeEnum moduleType, string bizId, int versionNo, AttendanceConfigOperateTypeEnum operateType, string title, object snapshot, string changeReason, OperatorInfo currentUser, DateTime now) { var history = new LqAttendanceConfigHistoryEntity { Id = YitIdHelper.NextId().ToString(), ModuleType = (int)moduleType, BizId = bizId, VersionNo = versionNo, OperateType = (int)operateType, Title = TrimToNull(title), SnapshotJson = SerializeSnapshot(snapshot), ChangeReason = TrimToNull(changeReason), OperateUserId = currentUser.UserId, OperateUserName = currentUser.UserName, OperateTime = now }; await _db.Insertable(history).ExecuteCommandAsync(); } private async Task GetOperatorAsync() { var user = await _userManager.GetUserInfo(); return new OperatorInfo { UserId = user?.userId ?? _userManager?.UserId ?? "admin", UserName = user?.userName ?? "系统" }; } private async Task GetRequiredUserNameAsync(string userId) { if (string.IsNullOrWhiteSpace(userId)) { throw NCCException.Oh("员工不能为空"); } var user = await _db.Queryable() .Where(x => x.Id == userId && x.DeleteMark == null) .Select(x => new { x.Id, x.RealName }) .FirstAsync(); if (user == null) { throw NCCException.Oh("所选员工不存在或已删除"); } return user.RealName; } private ISugarQueryable GetBaseSettingQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetHolidayQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetGroupQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetExemptUserQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetExtraLeaveQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetMarriageRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetFuneralRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetAnnualRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetMaternityRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetLateRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetMissingCardRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private ISugarQueryable GetAbsenteeismRuleQuery() => _db.Queryable().Where(x => SqlFunc.IsNull(x.DeleteMark, 0) == 0); private static dynamic BuildListResult(object list, int total, int currentPage, int pageSize) => new { list, pagination = new { total, currentPage, pageSize } }; private static AttendanceHolidayModel MapHolidayEntity(LqAttendanceHolidayEntity entity) => new AttendanceHolidayModel { id = entity.Id, holidayDate = entity.HolidayDate.ToString("yyyy-MM-dd"), holidayName = entity.HolidayName, remark = entity.Remark }; private static AttendanceGroupModel MapGroupEntity(LqAttendanceGroupEntity entity) => new AttendanceGroupModel { id = entity.Id, groupName = entity.GroupName, workStartTime = entity.WorkStartTime, workEndTime = entity.WorkEndTime, monthlyRestDays = entity.MonthlyRestDays, halfDaySplitRestDays = entity.HalfDaySplitRestDays, isEnabled = entity.IsEnabled, remark = entity.Remark, restUnlockCycle = entity.RestUnlockCycle, lateToleranceMinutes = entity.LateToleranceMinutes, earlyLeaveToleranceMinutes = entity.EarlyLeaveToleranceMinutes }; private static AttendanceExemptUserModel MapExemptEntity(LqAttendanceExemptUserEntity entity) => new AttendanceExemptUserModel { id = entity.Id, userId = entity.UserId, userName = entity.UserName, startDate = entity.StartDate?.ToString("yyyy-MM-dd"), endDate = entity.EndDate?.ToString("yyyy-MM-dd"), isEnabled = entity.IsEnabled, remark = entity.Remark }; private static AttendanceExtraLeaveModel MapExtraLeaveEntity(LqAttendanceExtraLeaveEntity entity) => new AttendanceExtraLeaveModel { id = entity.Id, userId = entity.UserId, userName = entity.UserName, leaveName = entity.LeaveName, grantYear = entity.GrantYear, extraDays = entity.ExtraDays, isEnabled = entity.IsEnabled, remark = entity.Remark }; private static AttendanceYearRangeRuleModel MapMarriageEntity(LqAttendanceMarriageLeaveRuleEntity entity) => new AttendanceYearRangeRuleModel { id = entity.Id, minYears = entity.MinYears, maxYears = entity.MaxYears, leaveDays = entity.LeaveDays, remark = entity.Remark }; private static AttendanceFuneralLeaveRuleModel MapFuneralEntity(LqAttendanceFuneralLeaveRuleEntity entity) => new AttendanceFuneralLeaveRuleModel { id = entity.Id, minYears = entity.MinYears, maxYears = entity.MaxYears, directRelativeDays = entity.DirectRelativeDays, indirectRelativeDays = entity.IndirectRelativeDays, remark = entity.Remark }; private static AttendanceYearRangeRuleModel MapAnnualEntity(LqAttendanceAnnualLeaveRuleEntity entity) => new AttendanceYearRangeRuleModel { id = entity.Id, minYears = entity.MinYears, maxYears = entity.MaxYears, leaveDays = entity.LeaveDays, remark = entity.Remark }; private static AttendanceYearRangeRuleModel MapMaternityEntity(LqAttendanceMaternityLeaveRuleEntity entity) => new AttendanceYearRangeRuleModel { id = entity.Id, minYears = entity.MinYears, maxYears = entity.MaxYears, leaveDays = entity.LeaveDays, remark = entity.Remark }; private static AttendanceLateRuleModel MapLateEntity(LqAttendanceLateRuleEntity entity) => new AttendanceLateRuleModel { id = entity.Id, minMinutes = entity.MinMinutes, maxMinutes = entity.MaxMinutes, deductMode = entity.DeductMode, deductValue = entity.DeductValue, expressionText = entity.ExpressionText, remark = entity.Remark }; private static AttendanceMissingCardRuleModel MapMissingCardEntity(LqAttendanceMissingCardRuleEntity entity) => new AttendanceMissingCardRuleModel { id = entity.Id, minCount = entity.MinCount, maxCount = entity.MaxCount, deductPerTime = entity.DeductPerTime, expressionText = entity.ExpressionText, remark = entity.Remark }; private static AttendanceAbsenteeismRuleModel MapAbsenteeismEntity(LqAttendanceAbsenteeismRuleEntity entity) => new AttendanceAbsenteeismRuleModel { id = entity.Id, minDays = entity.MinDays, maxDays = entity.MaxDays, deductMode = entity.DeductMode, deductValue = entity.DeductValue, actionType = entity.ActionType, actionText = entity.ActionText, remark = entity.Remark }; private static AttendanceConfigModuleTypeEnum ParseModuleType(string moduleType) { switch ((moduleType ?? string.Empty).Trim().ToLowerInvariant()) { case "basesetting": case "base": return AttendanceConfigModuleTypeEnum.BaseSetting; case "holiday": return AttendanceConfigModuleTypeEnum.Holiday; case "group": return AttendanceConfigModuleTypeEnum.Group; case "marriagerule": case "marriageleaverule": return AttendanceConfigModuleTypeEnum.MarriageLeaveRule; case "funeralrule": case "funeralleaverule": return AttendanceConfigModuleTypeEnum.FuneralLeaveRule; case "annualrule": case "annualleaverule": return AttendanceConfigModuleTypeEnum.AnnualLeaveRule; case "maternityrule": case "maternityleaverule": return AttendanceConfigModuleTypeEnum.MaternityLeaveRule; case "laterule": return AttendanceConfigModuleTypeEnum.LateRule; case "missingcardrule": return AttendanceConfigModuleTypeEnum.MissingCardRule; case "absenteeismrule": return AttendanceConfigModuleTypeEnum.AbsenteeismRule; case "exemptuser": return AttendanceConfigModuleTypeEnum.ExemptUser; case "extraleave": return AttendanceConfigModuleTypeEnum.ExtraLeave; default: throw NCCException.Oh("配置模块类型不正确"); } } private static string GetOperateTypeText(AttendanceConfigOperateTypeEnum operateType) { switch (operateType) { case AttendanceConfigOperateTypeEnum.Create: return "新增"; case AttendanceConfigOperateTypeEnum.Update: return "编辑"; case AttendanceConfigOperateTypeEnum.Delete: return "删除"; default: return "未知"; } } private static string BuildHolidayTitle(AttendanceHolidayModel model) => $"{model.holidayDate}{(string.IsNullOrWhiteSpace(model.holidayName) ? string.Empty : $" · {model.holidayName}")}"; private static string BuildExtraLeaveTitle(AttendanceExtraLeaveModel model) => $"{model.userName} · {model.grantYear}年 · {model.leaveName}"; private static string BuildYearRangeTitle(int min, int? max, string prefix) => max.HasValue ? $"{prefix} {min}-{max}年" : $"{prefix} ≥{min}年"; private static string BuildMinuteRangeTitle(int min, int? max, string prefix) => max.HasValue ? $"{prefix} {min}-{max}分钟" : $"{prefix} ≥{min}分钟"; private static string BuildCountRangeTitle(int min, int? max, string prefix) => max.HasValue ? $"{prefix} {min}-{max}次" : $"{prefix} ≥{min}次"; private static string BuildDecimalRangeTitle(decimal min, decimal? max, string prefix) => max.HasValue ? $"{prefix} {min}-{max}天" : $"{prefix} ≥{min}天"; private static T ParseModel(JObject data) { var result = data.ToObject(); _ = result ?? throw NCCException.Oh("配置数据格式不正确"); return result; } private static string SerializeSnapshot(object snapshot) => JsonConvert.SerializeObject(snapshot, Formatting.None); private static bool IsSnapshotEqual(object source, object target) { var sourceToken = JToken.Parse(SerializeSnapshot(source)); var targetToken = JToken.Parse(SerializeSnapshot(target)); return JToken.DeepEquals(sourceToken, targetToken); } private static string TrimToNull(string value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); private static DateTime ParseDate(string value, string title) { if (!DateTime.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { throw NCCException.Oh($"{title}格式不正确,应为yyyy-MM-dd"); } return date; } private static DateTime? ParseNullableDate(string value, string title) { if (string.IsNullOrWhiteSpace(value)) { return null; } return ParseDate(value, title); } private static void ApplyModifyMeta(object entity, OperatorInfo currentUser, DateTime now) { SetProperty(entity, "VersionNo", GetIntProperty(entity, "VersionNo") + 1); SetProperty(entity, "LastModifyTime", now); SetProperty(entity, "LastModifyUserId", currentUser.UserId); SetProperty(entity, "LastModifyUserName", currentUser.UserName); } private static int GetIntProperty(object entity, string name) { var property = entity.GetType().GetProperty(name); if (property == null) { return 0; } var value = property.GetValue(entity); return value == null ? 0 : Convert.ToInt32(value); } private static void SetProperty(object entity, string name, object value) { var property = entity.GetType().GetProperty(name); if (property != null && property.CanWrite) { property.SetValue(entity, value); } } private async Task GetNextSortCodeAsync(ISugarQueryable query, Expression> sortExpression) where T : class, new() { var max = await query.MaxAsync(sortExpression); return (max ?? 0) + 1; } private static void ValidateHolidays(List holidays) { var parsedDates = new List(); for (var i = 0; i < holidays.Count; i++) { var item = holidays[i]; if (string.IsNullOrWhiteSpace(item.holidayDate)) { throw NCCException.Oh($"第{i + 1}条公休日期不能为空"); } var date = ParseDate(item.holidayDate, $"第{i + 1}条公休日期").Date; parsedDates.Add(date); } var duplicateDate = parsedDates.GroupBy(x => x).FirstOrDefault(x => x.Count() > 1); if (duplicateDate != null) { throw NCCException.Oh($"公休日期 {duplicateDate.Key:yyyy-MM-dd} 重复,请检查"); } } private static void ValidateGroups(List groups) { var groupNames = new HashSet(); for (var i = 0; i < groups.Count; i++) { var group = groups[i]; var groupName = group.groupName?.Trim(); if (string.IsNullOrWhiteSpace(groupName)) { throw NCCException.Oh($"第{i + 1}个考勤分组名称不能为空"); } if (!groupNames.Add(groupName)) { throw NCCException.Oh($"考勤分组名称【{groupName}】重复,请调整后重试"); } ValidateWorkTime(group.workStartTime, group.workEndTime, groupName); if (group.monthlyRestDays < 0) { throw NCCException.Oh($"考勤分组【{groupName}】的月应休天数不能小于0"); } if (group.halfDaySplitRestDays < 0) { throw NCCException.Oh($"考勤分组【{groupName}】的可拆分半天休假天数不能小于0"); } if (group.halfDaySplitRestDays > group.monthlyRestDays) { throw NCCException.Oh($"考勤分组【{groupName}】的可拆分半天休假天数不能大于月应休天数"); } } } private static void ValidateExemptUsers(List exemptUsers) { var userIds = new HashSet(); for (var i = 0; i < exemptUsers.Count; i++) { var item = exemptUsers[i]; if (string.IsNullOrWhiteSpace(item.userId)) { throw NCCException.Oh($"第{i + 1}条免考勤人员未选择员工"); } if (!userIds.Add(item.userId)) { throw NCCException.Oh($"免考勤人员【{item.userName}】重复,请检查"); } if (!string.IsNullOrWhiteSpace(item.startDate) && !string.IsNullOrWhiteSpace(item.endDate)) { var startDate = ParseDate(item.startDate, $"第{i + 1}条免考勤开始日期"); var endDate = ParseDate(item.endDate, $"第{i + 1}条免考勤结束日期"); if (endDate.Date < startDate.Date) { throw NCCException.Oh($"免考勤人员【{item.userName}】的结束日期不能早于开始日期"); } } } } private static void ValidateExtraLeaves(List extraLeaves) { var uniqueKeys = new HashSet(); for (var i = 0; i < extraLeaves.Count; i++) { var item = extraLeaves[i]; if (string.IsNullOrWhiteSpace(item.userId)) { throw NCCException.Oh($"第{i + 1}条额外假期未选择员工"); } var leaveName = item.leaveName?.Trim(); if (string.IsNullOrWhiteSpace(leaveName)) { throw NCCException.Oh($"第{i + 1}条额外假期名称不能为空"); } if (item.grantYear < 2000 || item.grantYear > 2100) { throw NCCException.Oh($"额外假期【{leaveName}】的归属年份不合法,请填写 2000 - 2100 之间的年份"); } if (item.extraDays <= 0) { throw NCCException.Oh($"额外假期【{leaveName}】的天数必须大于0"); } var uniqueKey = $"{item.userId}_{item.grantYear}_{leaveName}"; if (!uniqueKeys.Add(uniqueKey)) { throw NCCException.Oh($"员工【{item.userName}】在 {item.grantYear} 年的额外假期【{leaveName}】重复,请检查"); } } } private static void ValidateYearRangeRules(List rules, string ruleName) { ValidateRangeRules( rules.Select((x, index) => new RangeRuleItem { Title = $"{ruleName}第{index + 1}条", MinValue = x.minYears, MaxValue = x.maxYears, ExtraValidation = () => { if (x.leaveDays < 0) { throw NCCException.Oh($"{ruleName}第{index + 1}条的休假天数不能小于0"); } } }).ToList(), ruleName, "司龄"); } private static void ValidateFuneralRules(List rules) { ValidateRangeRules( rules.Select((x, index) => new RangeRuleItem { Title = $"丧假规则第{index + 1}条", MinValue = x.minYears, MaxValue = x.maxYears, ExtraValidation = () => { if (x.directRelativeDays < 0 || x.indirectRelativeDays < 0) { throw NCCException.Oh($"丧假规则第{index + 1}条的休假天数不能小于0"); } } }).ToList(), "丧假规则", "司龄"); } private static void ValidateLateRules(List rules) { ValidateRangeRules( rules.Select((x, index) => new RangeRuleItem { Title = $"迟到/早退规则第{index + 1}条", MinValue = x.minMinutes, MaxValue = x.maxMinutes, ExtraValidation = () => { ValidateDeductMode(x.deductMode, $"迟到/早退规则第{index + 1}条"); if (x.deductValue < 0) { throw NCCException.Oh($"迟到/早退规则第{index + 1}条的扣款值不能小于0"); } } }).ToList(), "迟到/早退规则", "迟到分钟"); } private static void ValidateMissingCardRules(List rules) { ValidateRangeRules( rules.Select((x, index) => new RangeRuleItem { Title = $"缺卡规则第{index + 1}条", MinValue = x.minCount, MaxValue = x.maxCount, MinLimit = 1, ExtraValidation = () => { if (x.deductPerTime < 0) { throw NCCException.Oh($"缺卡规则第{index + 1}条的每次扣款金额不能小于0"); } } }).ToList(), "缺卡规则", "缺卡次数"); } private static void ValidateAbsenteeismRules(List rules) { ValidateRangeRules( rules.Select((x, index) => new RangeRuleItem { Title = $"旷工规则第{index + 1}条", MinValue = x.minDays, MaxValue = x.maxDays, ExtraValidation = () => { if (!Enum.IsDefined(typeof(AttendanceAbsenteeismActionTypeEnum), x.actionType)) { throw NCCException.Oh($"旷工规则第{index + 1}条的处理方式无效"); } if (x.actionType == (int)AttendanceAbsenteeismActionTypeEnum.Deduct) { if (!x.deductMode.HasValue) { throw NCCException.Oh($"旷工规则第{index + 1}条请选择扣款方式"); } ValidateDeductMode(x.deductMode.Value, $"旷工规则第{index + 1}条"); if (!x.deductValue.HasValue || x.deductValue.Value < 0) { throw NCCException.Oh($"旷工规则第{index + 1}条的扣款值不能小于0"); } } } }).ToList(), "旷工规则", "旷工天数"); } private static void ValidateDeductMode(int deductMode, string ruleTitle) { if (!Enum.IsDefined(typeof(AttendanceDeductModeEnum), deductMode)) { throw NCCException.Oh($"{ruleTitle}的扣款方式无效"); } } private static void ValidateWorkTime(string workStartTime, string workEndTime, string groupName) { if (string.IsNullOrWhiteSpace(workStartTime) || string.IsNullOrWhiteSpace(workEndTime)) { throw NCCException.Oh($"考勤分组【{groupName}】的上下班时间不能为空"); } if (!TimeSpan.TryParseExact(workStartTime, @"hh\:mm", CultureInfo.InvariantCulture, out var startTime)) { throw NCCException.Oh($"考勤分组【{groupName}】的上班时间格式不正确,请使用HH:mm"); } if (!TimeSpan.TryParseExact(workEndTime, @"hh\:mm", CultureInfo.InvariantCulture, out var endTime)) { throw NCCException.Oh($"考勤分组【{groupName}】的下班时间格式不正确,请使用HH:mm"); } if (endTime <= startTime) { throw NCCException.Oh($"考勤分组【{groupName}】的下班时间必须晚于上班时间"); } } private static void ValidateRangeRules(List> rules, string ruleName, string dimensionName) where T : struct, IComparable { var orderedRules = rules.OrderBy(x => x.MinValue).ToList(); var emptyMaxCount = orderedRules.Count(x => !x.MaxValue.HasValue); if (emptyMaxCount > 1) { throw NCCException.Oh($"{ruleName}只能配置一条无上限规则"); } for (var i = 0; i < orderedRules.Count; i++) { var rule = orderedRules[i]; if (rule.MinValue.CompareTo(rule.MinLimit) < 0) { throw NCCException.Oh($"{rule.Title}的最小{dimensionName}不能小于{rule.MinLimit}"); } if (rule.MaxValue.HasValue && rule.MaxValue.Value.CompareTo(rule.MinValue) < 0) { throw NCCException.Oh($"{rule.Title}的最大{dimensionName}不能小于最小{dimensionName}"); } rule.ExtraValidation?.Invoke(); if (i == 0) { continue; } var previousRule = orderedRules[i - 1]; if (!previousRule.MaxValue.HasValue) { throw NCCException.Oh($"{ruleName}存在多条无上限规则,请检查"); } if (rule.MinValue.CompareTo(previousRule.MaxValue.Value) < 0) { throw NCCException.Oh($"{ruleName}区间存在重叠,请检查【{previousRule.Title}】和【{rule.Title}】"); } } } private sealed class RangeRuleItem where T : struct, IComparable { public string Title { get; set; } public T MinValue { get; set; } public T? MaxValue { get; set; } public T MinLimit { get; set; } = default; public Action ExtraValidation { get; set; } } private sealed class OperatorInfo { public string UserId { get; set; } public string UserName { get; set; } } } }