diff --git a/antis-ncc-admin/src/views/attendance-record/components/record-detail-dialog.vue b/antis-ncc-admin/src/views/attendance-record/components/record-detail-dialog.vue index 0986bbc..899c402 100644 --- a/antis-ncc-admin/src/views/attendance-record/components/record-detail-dialog.vue +++ b/antis-ncc-admin/src/views/attendance-record/components/record-detail-dialog.vue @@ -8,7 +8,7 @@
{{ detail.employeeName || '无' }}
{{ detail.statusText || '无' - }} + }}
{{ detail.attendanceDate || '无' }} @@ -144,33 +144,27 @@
- +
- 请假/病假流程 + 关联流程 + 确认销假
-
-
- - {{ detail.leaveApply.leaveType || '无' }} -
-
- - {{ detail.leaveApply.billNo || '无' }} -
-
- - - 查看申请信息 - -
-
- - 确认销假 +
+
+ + {{ wf.type }} + + {{ wf.leaveType }} + {{ wf.billNo }} + {{ wf.time }} + 查看申请
+
+ 当前为{{ detail.statusText }}状态,暂无关联流程记录 +
@@ -219,6 +213,12 @@ export default { default: return 'danger' } + }, + hasRelatedWorkflows() { + return this.detail && Array.isArray(this.detail.relatedWorkflows) && this.detail.relatedWorkflows.length > 0 + }, + isLeaveOrSickStatus() { + return this.detail && (this.detail.status === 4 || this.detail.status === 5) } }, methods: { @@ -265,33 +265,33 @@ export default { await this.loadDetail() this.$emit('refresh') }, - goToLeaveApply() { - if (!this.detail || !this.detail.leaveApply || !this.detail.leaveApply.id) return - const leaveType = this.detail.leaveApply.leaveType || '' - const isPersonal = leaveType === '事假' || leaveType === '病假' + workflowTagType(type) { + if (type === '请假') return 'danger' + if (type === '补卡') return 'warning' + return 'info' + }, + goToLeaveApply(wf) { + if (!wf || !wf.id) return + const lt = wf.leaveType || '' + const isPersonal = lt === '事假' || lt === '病假' const path = isPersonal ? '/workFlow/personal-leave-apply' : '/workFlow/paid-leave-apply' - this.$router.push({ - path, - query: { id: this.detail.leaveApply.id } - }) + this.$router.push({ path, query: { id: wf.id } }) }, async cancelLeave() { if (!this.detail || !this.detail.id) return - if (!(this.detail.status === 4 || this.detail.status === 5)) { + if (!this.isLeaveOrSickStatus) { this.$message.warning('当前考勤记录不是请假/病假状态,无法销假') return } this.cancelLoading = true try { - await this.$confirm('确定要对该考勤记录销假吗?', '提示', { type: 'warning' }) - await cancelAttendanceLeave({ - id: this.detail.id - }) + await this.$confirm('确定要对该考勤记录销假吗?销假后将根据打卡记录重新判定考勤状态。', '确认销假', { type: 'warning' }) + await cancelAttendanceLeave({ id: this.detail.id }) this.$message.success('销假成功') await this.loadDetail() this.$emit('refresh') } catch (e) { - // eslint-disable-next-line no-empty + // user cancelled or api error } finally { this.cancelLoading = false } @@ -496,6 +496,53 @@ export default { grid-column: 1 / -1; } +.workflow-card { + margin-top: 16px; +} + +.workflow-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.workflow-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border-radius: 8px; + background: #f8f9fb; + font-size: 13px; +} + +.workflow-item__tag { + flex-shrink: 0; +} + +.workflow-item__leave-type { + color: #606266; + font-weight: 500; +} + +.workflow-item__bill { + color: #409eff; + font-family: monospace; +} + +.workflow-item__time { + color: #909399; + font-size: 12px; + margin-left: auto; +} + +.workflow-empty { + color: #909399; + font-size: 13px; + text-align: center; + padding: 12px 0; +} + @media (max-width: 960px) { .overview-grid, diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAttendanceRecord/RelatedWorkflowItem.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAttendanceRecord/RelatedWorkflowItem.cs new file mode 100644 index 0000000..836e615 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAttendanceRecord/RelatedWorkflowItem.cs @@ -0,0 +1,38 @@ +namespace NCC.Extend.Entitys.Dto.LqAttendanceRecord +{ + /// + /// 考勤记录关联流程项(存储在 F_RelatedWorkflowsJson 字段中的数组元素) + /// + public class RelatedWorkflowItem + { + /// + /// 流程类型:请假 | 补卡 + /// + public string type { get; set; } + + /// + /// 流程/表单主键(WFORM_LEAVEAPPLY.F_Id 等) + /// + public string id { get; set; } + + /// + /// 单号(如 YGQJ-202604020008) + /// + public string billNo { get; set; } + + /// + /// 假种(仅请假类型有值,如"年假"/"事假"/"病假") + /// + public string leaveType { get; set; } + + /// + /// 备注 + /// + public string remark { get; set; } + + /// + /// 写入时间 + /// + public string time { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_attendance_record/LqAttendanceRecordEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_attendance_record/LqAttendanceRecordEntity.cs index b70135b..286b984 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_attendance_record/LqAttendanceRecordEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_attendance_record/LqAttendanceRecordEntity.cs @@ -240,6 +240,12 @@ namespace NCC.Extend.Entitys.lq_attendance_record public DateTime? UpdateTime { get; set; } /// + /// 关联流程JSON(所有与该考勤记录关联的流程信息,含请假/补卡等) + /// + [SugarColumn(ColumnName = "F_RelatedWorkflowsJson", ColumnDataType = "longtext")] + public string RelatedWorkflowsJson { get; set; } + + /// /// 是否有效(1-有效,0-无效) /// [SugarColumn(ColumnName = "F_IsEffective")] diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqAttendanceRecordService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqAttendanceRecordService.cs index 115f912..6afb2fd 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqAttendanceRecordService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqAttendanceRecordService.cs @@ -25,6 +25,7 @@ using NCC.Extend.Entitys.lq_attendance_missing_card_rule; using NCC.Extend.Entitys.lq_attendance_record; using NCC.Extend.Entitys.lq_attendance_setting; using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Entitys.wform_leaveapply; using NCC.Extend.Interfaces.LqAttendanceRecord; using NCC.FriendlyException; using Newtonsoft.Json; @@ -361,24 +362,41 @@ namespace NCC.Extend record = recalculated.FirstOrDefault() ?? record; var group = await GetAttendanceGroupAsync(record.AttendanceGroupId, false); - // 如果当前状态来自“请假/病假同步写入”,则尝试从 Remark 中解析单号并反查请假单信息,便于在考勤详情中跳转流程/操作销假 - string leaveApplyId = null; - string leaveApplyBillNo = null; - string leaveApplyType = null; + var relatedWorkflows = ParseRelatedWorkflows(record.RelatedWorkflowsJson); - if (TryParseLeaveApplyFromSyncRemark(record.Remark, out var parsedLeaveType, out var parsedBillNo) + var isLeaveStatus = record.Status == (int)AttendanceRecordStatusEnum.请假 + || record.Status == (int)AttendanceRecordStatusEnum.病假; + if (!relatedWorkflows.Any(x => x.type == "请假") && isLeaveStatus + && TryParseLeaveApplyFromSyncRemark(record.Remark, out var parsedLeaveType, out var parsedBillNo) && !string.IsNullOrWhiteSpace(parsedBillNo)) { - leaveApplyBillNo = parsedBillNo; - leaveApplyType = parsedLeaveType; - var leaveApply = await _db.Queryable() + var leaveApply = await _db.Queryable() .Where(x => x.BillNo == parsedBillNo) .WhereIF(!string.IsNullOrWhiteSpace(parsedLeaveType), x => x.LeaveType == parsedLeaveType) .FirstAsync(); - leaveApplyId = leaveApply?.Id; + + relatedWorkflows.Add(new RelatedWorkflowItem + { + type = "请假", + id = leaveApply?.Id, + billNo = parsedBillNo, + leaveType = parsedLeaveType, + remark = record.Remark + }); + } + + if (!string.IsNullOrWhiteSpace(record.SupplementWorkflowId) && !relatedWorkflows.Any(x => x.type == "补卡")) + { + relatedWorkflows.Add(new RelatedWorkflowItem + { + type = "补卡", + id = record.SupplementWorkflowId, + remark = record.SupplementRemark, + time = record.SupplementTime?.ToString("yyyy-MM-dd HH:mm:ss") + }); } - return BuildDetailResult(record, group, leaveApplyId, leaveApplyBillNo, leaveApplyType); + return BuildDetailResult(record, group, relatedWorkflows); } /// @@ -411,13 +429,8 @@ namespace NCC.Extend throw NCCException.Oh("当前考勤记录不是请假/病假状态,无法销假"); } - // 通过 Remark 解析请假单信息(用于展示与记录销假备注) - if (!TryParseLeaveApplyFromSyncRemark(record.Remark, out var leaveType, out var billNo)) - { - throw NCCException.Oh("请假备注格式不正确,无法解析销假所需信息"); - } + TryParseLeaveApplyFromSyncRemark(record.Remark, out var leaveType, out var billNo); - // 撤销对该日期的请假影响:根据假期/免考勤/打卡情况,把该日期恢复为“休息/正常/缺卡”等对应考勤状态 var attendanceDate = record.AttendanceDate.Date; var isHoliday = await IsHolidayAsync(attendanceDate); var isExempt = await IsExemptAsync(record.UserId, attendanceDate); @@ -445,13 +458,12 @@ namespace NCC.Extend record.LateMinutes = 0; record.EarlyLeaveMinutes = 0; record.IsManual = 1; - var cancelText = string.IsNullOrWhiteSpace(input.OperatorOpinion) - ? "已销假" - : input.OperatorOpinion.Trim(); - // 保留原 Remark 前缀,便于在考勤详情中仍能反查到对应请假单信息 - record.Remark = $"请假审批通过:{leaveType}(单号 {billNo});销假:{cancelText}"; - - // 置空快照,确保 RecalculateRecordStatusesAsync 会重新生成规则快照 + var cancelText = string.IsNullOrWhiteSpace(input.OperatorOpinion) ? "已销假" : input.OperatorOpinion.Trim(); + var hasLeaveInfo = !string.IsNullOrWhiteSpace(leaveType) && !string.IsNullOrWhiteSpace(billNo); + record.Remark = hasLeaveInfo + ? $"请假审批通过:{leaveType}(单号 {billNo});销假:{cancelText}" + : $"销假:{cancelText}"; + record.RuleSnapshotJson = null; record.RuleSnapshotJson = null; record.AllRuleSnapshotJson = null; @@ -616,6 +628,17 @@ namespace NCC.Extend record.UpdateUserId = operatorUserId; record.UpdateTime = record.SupplementTime; + if (!string.IsNullOrWhiteSpace(input.WorkflowId)) + { + record.RelatedWorkflowsJson = AppendRelatedWorkflow(record.RelatedWorkflowsJson, new RelatedWorkflowItem + { + type = "补卡", + id = input.WorkflowId.Trim(), + remark = input.Remark?.Trim(), + time = record.SupplementTime?.ToString("yyyy-MM-dd HH:mm:ss") + }); + } + await _db.Updateable(record).ExecuteCommandAsync(); return new @@ -1418,10 +1441,9 @@ namespace NCC.Extend private static dynamic BuildDetailResult( LqAttendanceRecordEntity record, LqAttendanceGroupEntity group, - string leaveApplyId = null, - string leaveApplyBillNo = null, - string leaveApplyType = null) + List relatedWorkflows = null) { + var workflows = relatedWorkflows ?? new List(); return new { id = record.Id, @@ -1472,14 +1494,15 @@ namespace NCC.Extend operateTime = record.SupplementTime?.ToString("yyyy-MM-dd HH:mm:ss"), workflowId = record.SupplementWorkflowId }, - leaveApply = string.IsNullOrWhiteSpace(leaveApplyId) - ? null - : new - { - id = leaveApplyId, - billNo = leaveApplyBillNo, - leaveType = leaveApplyType - }, + relatedWorkflows = workflows.Select(w => new + { + w.type, + w.id, + w.billNo, + w.leaveType, + w.remark, + w.time + }).ToList(), ruleSnapshotJson = record.RuleSnapshotJson, allRuleSnapshotJson = record.AllRuleSnapshotJson }; @@ -2263,6 +2286,35 @@ namespace NCC.Extend return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } + private static List ParseRelatedWorkflows(string json) + { + if (string.IsNullOrWhiteSpace(json)) + { + return new List(); + } + + try + { + return JsonConvert.DeserializeObject>(json) ?? new List(); + } + catch + { + return new List(); + } + } + + private static string AppendRelatedWorkflow(string existingJson, RelatedWorkflowItem item) + { + var list = ParseRelatedWorkflows(existingJson); + if (!string.IsNullOrWhiteSpace(item.id)) + { + list.RemoveAll(x => x.id == item.id && x.type == item.type); + } + + list.Add(item); + return JsonConvert.SerializeObject(list); + } + private sealed class AttendanceStatusContext { public int Status { get; set; } diff --git a/netcore/src/Modularity/WorkFlow/NCC.WorkFlow/WorkFlowForm/LeaveApplyService.cs b/netcore/src/Modularity/WorkFlow/NCC.WorkFlow/WorkFlowForm/LeaveApplyService.cs index 04840f8..d49d435 100644 --- a/netcore/src/Modularity/WorkFlow/NCC.WorkFlow/WorkFlowForm/LeaveApplyService.cs +++ b/netcore/src/Modularity/WorkFlow/NCC.WorkFlow/WorkFlowForm/LeaveApplyService.cs @@ -16,6 +16,7 @@ using NCC.Extend.Entitys.lq_mdxx; using NCC.FriendlyException; using NCC.JsonSerialization; using NCC.System.Entitys.Permission; +using Newtonsoft.Json; using NCC.System.Interfaces.System; using NCC.WorkFlow.Entitys; using NCC.WorkFlow.Entitys.Dto.WorkFlowForm.LeaveApply; @@ -883,6 +884,36 @@ LIMIT 1"; } return count; } + + /// + /// 将新的请假流程项追加到已有的 RelatedWorkflowsJson(去重后返回新 JSON) + /// + private static string AppendLeaveWorkflow(string existingJson, string newItemArrayJson) + { + List list; + try + { + list = string.IsNullOrWhiteSpace(existingJson) + ? new List() + : JsonConvert.DeserializeObject>(existingJson) ?? new List(); + } + catch + { + list = new List(); + } + + try + { + var newItems = JsonConvert.DeserializeObject>(newItemArrayJson) ?? new List(); + list.AddRange(newItems); + } + catch + { + // ignore + } + + return JsonConvert.SerializeObject(list); + } #endregion #region PublicMethod @@ -987,6 +1018,11 @@ LIMIT 1"; .ToListAsync(); var existing = existingList.FirstOrDefault(); + var workflowItemJson = JsonConvert.SerializeObject(new[] + { + new { type = "请假", id = leave.Id, billNo = leave.BillNo ?? "", leaveType, remark, time = now.ToString("yyyy-MM-dd HH:mm:ss") } + }); + if (existing != null) { existing.Status = status; @@ -1001,6 +1037,7 @@ LIMIT 1"; existing.AttendanceGroupName = group?.GroupName; existing.RuleSnapshotJson = "{}"; existing.AllRuleSnapshotJson = "{}"; + existing.RelatedWorkflowsJson = AppendLeaveWorkflow(existing.RelatedWorkflowsJson, workflowItemJson); existing.UpdateUserId = operatorId; existing.UpdateTime = now; await _sqlSugarRepository.Context.Updateable(existing).ExecuteCommandAsync(); @@ -1024,6 +1061,7 @@ LIMIT 1"; Remark = remark, RuleSnapshotJson = "{}", AllRuleSnapshotJson = "{}", + RelatedWorkflowsJson = workflowItemJson, CreateUserId = operatorId, CreateTime = now, UpdateUserId = operatorId, diff --git a/项目文档相关/docs/数据库说明.md b/项目文档相关/docs/数据库说明.md index 5bd9425..264879f 100644 --- a/项目文档相关/docs/数据库说明.md +++ b/项目文档相关/docs/数据库说明.md @@ -168,6 +168,7 @@ - `F_SupplementRemark`: 补卡备注 - `F_SupplementOperatorId` / `F_SupplementOperatorName` / `F_SupplementTime`: 补卡操作信息 - `F_SupplementWorkflowId`: 关联补卡流程ID(可空) + - `F_RelatedWorkflowsJson`: 关联流程 JSON(数组,记录与该日考勤相关的请假/补卡等流程,含类型、表单主键、单号、假种等,便于详情展示与追溯) - **索引说明**: - `uk_attendance_record_user_date`: 保证同一员工同一天只有一条有效记录 - `idx_attendance_record_month_group`: 支持按月份、分组快速查询月度矩阵 diff --git a/项目文档相关/sql/2026-3-23/创建考勤设置相关表.sql b/项目文档相关/sql/2026-3-23/创建考勤设置相关表.sql index 6f625c4..3ecc7df 100644 --- a/项目文档相关/sql/2026-3-23/创建考勤设置相关表.sql +++ b/项目文档相关/sql/2026-3-23/创建考勤设置相关表.sql @@ -225,6 +225,7 @@ CREATE TABLE IF NOT EXISTS `lq_attendance_record` ( `F_SupplementOperatorName` varchar(100) DEFAULT NULL COMMENT '补卡操作人姓名', `F_SupplementTime` datetime DEFAULT NULL COMMENT '补卡操作时间', `F_SupplementWorkflowId` varchar(50) DEFAULT NULL COMMENT '关联补卡流程ID', + `F_RelatedWorkflowsJson` longtext COMMENT '关联流程JSON(所有与该考勤记录关联的流程信息,含请假/补卡等)', `F_CreateUserId` varchar(50) DEFAULT NULL COMMENT '创建人ID', `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间', `F_UpdateUserId` varchar(50) DEFAULT NULL COMMENT '更新人ID', diff --git a/项目文档相关/sql/2026-3-25/考勤相关表全量重建.sql b/项目文档相关/sql/2026-3-25/考勤相关表全量重建.sql index 163b9a9..83c5b55 100644 --- a/项目文档相关/sql/2026-3-25/考勤相关表全量重建.sql +++ b/项目文档相关/sql/2026-3-25/考勤相关表全量重建.sql @@ -325,6 +325,7 @@ CREATE TABLE `lq_attendance_record` ( `F_SupplementOperatorName` varchar(100) DEFAULT NULL COMMENT '补卡操作人姓名', `F_SupplementTime` datetime DEFAULT NULL COMMENT '补卡操作时间', `F_SupplementWorkflowId` varchar(50) DEFAULT NULL COMMENT '关联补卡流程ID', + `F_RelatedWorkflowsJson` longtext COMMENT '关联流程JSON(所有与该考勤记录关联的流程信息,含请假/补卡等)', `F_CreateUserId` varchar(50) DEFAULT NULL COMMENT '创建人ID', `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间', `F_UpdateUserId` varchar(50) DEFAULT NULL COMMENT '更新人ID', diff --git a/项目文档相关/sql/2026-4-2/考勤记录_新增关联流程JSON字段.sql b/项目文档相关/sql/2026-4-2/考勤记录_新增关联流程JSON字段.sql new file mode 100644 index 0000000..8b7881f --- /dev/null +++ b/项目文档相关/sql/2026-4-2/考勤记录_新增关联流程JSON字段.sql @@ -0,0 +1,6 @@ +-- 考勤记录新增关联流程JSON字段:存储所有与该考勤记录关联的流程信息(请假/补卡等) +-- 执行时间:2026-04-02 +-- 影响表:lq_attendance_record + +ALTER TABLE lq_attendance_record +ADD COLUMN F_RelatedWorkflowsJson LONGTEXT NULL COMMENT '关联流程JSON(所有与该考勤记录关联的流程信息,含请假/补卡等)';