574 lines
23 KiB
C#
574 lines
23 KiB
C#
using System.Text.Json;
|
||
using F9WebAiTest.Application.Common;
|
||
using F9WebAiTest.Application.Dtos;
|
||
using F9WebAiTest.Application.Dtos.Attendance;
|
||
using F9WebAiTest.Application.Services.Notice;
|
||
using F9WebAiTest.Domain.Common;
|
||
using F9WebAiTest.Domain.Entities;
|
||
using F9WebAiTest.Domain.Entities.Attendance;
|
||
using F9WebAiTest.Domain.Entities.Notice;
|
||
using RBAC = F9WebAiTest.Domain.Entities.SYS.RBAC;
|
||
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
|
||
using MediatR;
|
||
|
||
namespace F9WebAiTest.Application.Services.Attendance;
|
||
|
||
public class AttendanceService : IAttendanceService
|
||
{
|
||
private readonly IRepository<AttendanceRecord> _attRepo;
|
||
private readonly IRepository<LeaveRequest> _leaveRepo;
|
||
private readonly IRepository<LeaveApprover> _approverRepo;
|
||
private readonly IRepository<RBAC.User> _empRepo;
|
||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||
private readonly IRepository<RBAC.Role> _roleRepo;
|
||
private readonly IRepository<RBAC.UserRole> _empRoleRepo;
|
||
private readonly IRepository<NotificationMessage> _msgRepo;
|
||
private readonly IRepository<NotificationReceiver> _receiverRepo;
|
||
private readonly IRepository<TodoItem> _todoRepo;
|
||
private readonly IRepository<NotificationType> _typeRepo;
|
||
private readonly IMediator _mediator;
|
||
|
||
// 上下班时间(可配置,这里简化固定)
|
||
private static readonly TimeSpan WorkStart = new(9, 0, 0);
|
||
private static readonly TimeSpan WorkEnd = new(18, 0, 0);
|
||
|
||
public AttendanceService(
|
||
IRepository<AttendanceRecord> attRepo,
|
||
IRepository<LeaveRequest> leaveRepo,
|
||
IRepository<LeaveApprover> approverRepo,
|
||
IRepository<RBAC.User> empRepo,
|
||
IRepository<SysDept.Department> deptRepo,
|
||
IRepository<RBAC.Role> roleRepo,
|
||
IRepository<RBAC.UserRole> empRoleRepo,
|
||
IRepository<NotificationMessage> msgRepo,
|
||
IRepository<NotificationReceiver> receiverRepo,
|
||
IRepository<TodoItem> todoRepo,
|
||
IRepository<NotificationType> typeRepo,
|
||
IMediator mediator)
|
||
{
|
||
_attRepo = attRepo;
|
||
_leaveRepo = leaveRepo;
|
||
_approverRepo = approverRepo;
|
||
_empRepo = empRepo;
|
||
_deptRepo = deptRepo;
|
||
_roleRepo = roleRepo;
|
||
_empRoleRepo = empRoleRepo;
|
||
_msgRepo = msgRepo;
|
||
_receiverRepo = receiverRepo;
|
||
_todoRepo = todoRepo;
|
||
_typeRepo = typeRepo;
|
||
_mediator = mediator;
|
||
}
|
||
|
||
public async Task<PunchResultDto> PunchAsync(long employeeId)
|
||
{
|
||
var now = DateTime.Now;
|
||
var today = now.Date;
|
||
|
||
var record = await _attRepo.FirstOrDefaultAsync(a => a.EmployeeId == employeeId && a.WorkDate == today);
|
||
|
||
// 签到
|
||
if (record == null)
|
||
{
|
||
var status = now.TimeOfDay <= WorkStart.Add(TimeSpan.FromMinutes(10)) ? "正常" : "迟到";
|
||
var newRecord = new AttendanceRecord
|
||
{
|
||
EmployeeId = employeeId,
|
||
WorkDate = today,
|
||
SignInTime = now,
|
||
SignInStatus = status,
|
||
SignOutStatus = "未签退"
|
||
};
|
||
await _attRepo.AddAsync(newRecord);
|
||
await _attRepo.SaveChangesAsync();
|
||
|
||
return new PunchResultDto
|
||
{
|
||
Type = "signIn",
|
||
Time = now,
|
||
Status = status,
|
||
Message = status == "正常" ? "签到成功,辛苦了!" : "签到成功(已迟到)"
|
||
};
|
||
}
|
||
|
||
// 已签到,进行签退
|
||
if (record.SignOutTime == null)
|
||
{
|
||
var status = now.TimeOfDay >= WorkEnd ? "正常" : "早退";
|
||
record.SignOutTime = now;
|
||
record.SignOutStatus = status;
|
||
await _attRepo.UpdateAsync(record);
|
||
await _attRepo.SaveChangesAsync();
|
||
|
||
return new PunchResultDto
|
||
{
|
||
Type = "signOut",
|
||
Time = now,
|
||
Status = status,
|
||
Message = status == "正常" ? "签退成功,明天见!" : "签退成功(早退)"
|
||
};
|
||
}
|
||
|
||
// 已签退
|
||
return new PunchResultDto
|
||
{
|
||
Type = "done",
|
||
Time = now,
|
||
Status = "已完成",
|
||
Message = "今日已签到和签退,无需重复打卡"
|
||
};
|
||
}
|
||
|
||
public async Task<PageResult<AttendanceDto>> GetMyRecordsAsync(long employeeId, int pageIndex, int pageSize)
|
||
{
|
||
var records = (await _attRepo.FindAsync(a => a.EmployeeId == employeeId))
|
||
.OrderByDescending(a => a.WorkDate)
|
||
.ToList();
|
||
var total = records.Count;
|
||
var items = records.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||
|
||
var emp = await _empRepo.GetByIdAsync(employeeId);
|
||
var dept = emp != null ? await _deptRepo.GetByIdAsync(emp.DepartmentId) : null;
|
||
|
||
return new PageResult<AttendanceDto>
|
||
{
|
||
Items = items.Select(a => new AttendanceDto
|
||
{
|
||
Id = a.Id,
|
||
EmployeeId = a.EmployeeId,
|
||
EmployeeName = emp?.UserName ?? "",
|
||
DepartmentName = dept?.Name ?? "",
|
||
WorkDate = a.WorkDate,
|
||
SignInTime = a.SignInTime,
|
||
SignOutTime = a.SignOutTime,
|
||
SignInStatus = a.SignInStatus,
|
||
SignOutStatus = a.SignOutStatus,
|
||
Remark = a.Remark
|
||
}).ToList(),
|
||
Total = total,
|
||
PageIndex = pageIndex,
|
||
PageSize = pageSize
|
||
};
|
||
}
|
||
|
||
public async Task<AttendanceSummaryDto> GetMySummaryAsync(long employeeId, string month)
|
||
{
|
||
// month 格式 yyyy-MM,默认当前月
|
||
if (string.IsNullOrWhiteSpace(month))
|
||
{
|
||
month = DateTime.Now.ToString("yyyy-MM");
|
||
}
|
||
var start = DateTime.Parse(month + "-01");
|
||
var end = start.AddMonths(1);
|
||
|
||
var records = await _attRepo.FindAsync(a =>
|
||
a.EmployeeId == employeeId && a.WorkDate >= start && a.WorkDate < end);
|
||
|
||
var summary = new AttendanceSummaryDto();
|
||
foreach (var r in records)
|
||
{
|
||
summary.TotalDays++;
|
||
if (r.SignInStatus == "迟到") summary.LateDays++;
|
||
if (r.SignOutStatus == "早退") summary.EarlyLeaveDays++;
|
||
if (r.SignInTime == null) summary.AbsentDays++;
|
||
}
|
||
summary.NormalDays = summary.TotalDays - summary.LateDays - summary.EarlyLeaveDays - summary.AbsentDays;
|
||
|
||
// 请假天数
|
||
var leaves = await _leaveRepo.FindAsync(l =>
|
||
l.EmployeeId == employeeId && l.Status == "已批准" && l.StartTime < end && l.EndTime >= start);
|
||
summary.LeaveDays = leaves.Sum(l => (l.EndTime - l.StartTime).Days + 1);
|
||
|
||
return summary;
|
||
}
|
||
|
||
public async Task<PageResult<AttendanceDto>> GetAllRecordsAsync(AttendanceQueryDto query)
|
||
{
|
||
var records = await _attRepo.GetAllAsync();
|
||
var filtered = records.AsQueryable();
|
||
|
||
if (!string.IsNullOrWhiteSpace(query.Date))
|
||
{
|
||
var date = DateTime.Parse(query.Date).Date;
|
||
filtered = filtered.Where(a => a.WorkDate == date);
|
||
}
|
||
|
||
var emps = await _empRepo.GetAllAsync();
|
||
var depts = await _deptRepo.GetAllAsync();
|
||
var empDict = emps.ToDictionary(e => e.Id, e => e);
|
||
var deptDict = depts.ToDictionary(d => d.Id, d => d.Name);
|
||
|
||
if (query.DepartmentId.HasValue)
|
||
{
|
||
var deptEmpIds = emps.Where(e => e.DepartmentId == query.DepartmentId.Value).Select(e => e.Id).ToHashSet();
|
||
filtered = filtered.Where(a => deptEmpIds.Contains(a.EmployeeId));
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(query.Keyword))
|
||
{
|
||
var kwEmpIds = emps.Where(e => e.UserName.Contains(query.Keyword) || e.LoginName.Contains(query.Keyword))
|
||
.Select(e => e.Id).ToHashSet();
|
||
filtered = filtered.Where(a => kwEmpIds.Contains(a.EmployeeId));
|
||
}
|
||
|
||
var total = filtered.LongCount();
|
||
var items = filtered.OrderByDescending(a => a.WorkDate).ThenByDescending(a => a.SignInTime)
|
||
.Skip((query.PageIndex - 1) * query.PageSize).Take(query.PageSize).ToList();
|
||
|
||
return new PageResult<AttendanceDto>
|
||
{
|
||
Items = items.Select(a =>
|
||
{
|
||
var emp = empDict.GetValueOrDefault(a.EmployeeId);
|
||
return new AttendanceDto
|
||
{
|
||
Id = a.Id,
|
||
EmployeeId = a.EmployeeId,
|
||
EmployeeName = emp?.UserName ?? "",
|
||
DepartmentName = emp != null ? deptDict.GetValueOrDefault(emp.DepartmentId, "") : "",
|
||
WorkDate = a.WorkDate,
|
||
SignInTime = a.SignInTime,
|
||
SignOutTime = a.SignOutTime,
|
||
SignInStatus = a.SignInStatus,
|
||
SignOutStatus = a.SignOutStatus,
|
||
Remark = a.Remark
|
||
};
|
||
}).ToList(),
|
||
Total = total,
|
||
PageIndex = query.PageIndex,
|
||
PageSize = query.PageSize
|
||
};
|
||
}
|
||
|
||
public async Task<LeaveRequestDto> ApplyLeaveAsync(long employeeId, CreateLeaveDto dto)
|
||
{
|
||
if (dto.EndTime < dto.StartTime)
|
||
throw new ServiceException("结束时间不能早于开始时间");
|
||
|
||
// 确定审批人:优先使用前端选择的审批人,为空则回退到管理员
|
||
var approverIds = (dto.ApproverIds ?? new List<long>()).Where(id => id > 0).Distinct().ToList();
|
||
if (approverIds.Count == 0)
|
||
approverIds = await GetApproverIdsAsync();
|
||
if (approverIds.Count == 0)
|
||
throw new ServiceException("没有可用的审批人,请联系管理员");
|
||
|
||
var firstApprover = await _empRepo.GetByIdAsync(approverIds[0]);
|
||
var leave = new LeaveRequest
|
||
{
|
||
EmployeeId = employeeId,
|
||
Type = dto.Type,
|
||
StartTime = dto.StartTime,
|
||
EndTime = dto.EndTime,
|
||
Reason = dto.Reason ?? "",
|
||
CurrentOrderNo = 1,
|
||
ApproverId = approverIds[0],
|
||
ApproverName = firstApprover?.UserName ?? ""
|
||
};
|
||
await _leaveRepo.AddAsync(leave);
|
||
await _leaveRepo.SaveChangesAsync();
|
||
|
||
// 创建审批节点(序号 = 审批顺序)
|
||
for (var i = 0; i < approverIds.Count; i++)
|
||
{
|
||
var emp = await _empRepo.GetByIdAsync(approverIds[i]);
|
||
await _approverRepo.AddAsync(new LeaveApprover
|
||
{
|
||
LeaveId = leave.Id,
|
||
ApproverId = approverIds[i],
|
||
ApproverName = emp?.UserName ?? "",
|
||
OrderNo = i + 1
|
||
});
|
||
}
|
||
await _approverRepo.SaveChangesAsync();
|
||
|
||
// 只给第一个审批人发待办(顺序审批,后续审批人待前一节点通过后才收到)
|
||
await SendLeaveApprovalAsync(leave, approverIds[0], 1, approverIds.Count);
|
||
|
||
// 业务单据已创建,推送事件让各端刷新(多端同步)
|
||
await _mediator.Publish(new BusinessChangedEvent("LeaveApproval", leave.Id.ToString(), "Created"));
|
||
|
||
return await ToLeaveDto(leave);
|
||
}
|
||
|
||
public async Task<List<LeaveRequestDto>> GetMyLeavesAsync(long employeeId)
|
||
{
|
||
var leaves = await _leaveRepo.FindAsync(l => l.EmployeeId == employeeId);
|
||
var result = new List<LeaveRequestDto>();
|
||
foreach (var l in leaves.OrderByDescending(l => l.CreatedAt))
|
||
{
|
||
result.Add(await ToLeaveDto(l));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
public async Task<List<LeaveRequestDto>> GetAllLeavesAsync(string status)
|
||
{
|
||
var leaves = await _leaveRepo.GetAllAsync();
|
||
if (!string.IsNullOrWhiteSpace(status) && status != "全部")
|
||
{
|
||
leaves = leaves.Where(l => l.Status == status).ToList();
|
||
}
|
||
var result = new List<LeaveRequestDto>();
|
||
foreach (var l in leaves.OrderByDescending(l => l.CreatedAt))
|
||
{
|
||
result.Add(await ToLeaveDto(l));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
public async Task<LeaveRequestDto> ApproveLeaveAsync(long leaveId, long approverId, string approverName, ApproveLeaveDto dto)
|
||
{
|
||
var leave = await _leaveRepo.GetByIdAsync(leaveId);
|
||
if (leave == null) throw new ServiceException("请假申请不存在", 404);
|
||
if (leave.Status != "待审批") throw new ServiceException("该申请已处理");
|
||
|
||
var approvers = (await _approverRepo.FindAsync(a => a.LeaveId == leaveId))
|
||
.OrderBy(a => a.OrderNo).ToList();
|
||
|
||
// 校验当前审批节点
|
||
var current = approvers.FirstOrDefault(a => a.OrderNo == leave.CurrentOrderNo);
|
||
if (current == null) throw new ServiceException("审批节点不存在", 404);
|
||
if (current.ApproverId != approverId)
|
||
throw new ServiceException($"当前应由「{current.ApproverName}」审批,您无权操作", 403);
|
||
if (current.Status != "待审批") throw new ServiceException("该节点已处理");
|
||
|
||
// 更新当前节点
|
||
var handleResult = dto.Approved ? "已批准" : "已驳回";
|
||
current.Status = handleResult;
|
||
current.ApproveRemark = dto.Remark ?? "";
|
||
current.ApprovedAt = DateTime.Now;
|
||
await _approverRepo.UpdateAsync(current);
|
||
|
||
if (!dto.Approved)
|
||
{
|
||
// 驳回:整体驳回,结束流程
|
||
leave.Status = "已驳回";
|
||
leave.ApproverId = approverId;
|
||
leave.ApproverName = approverName;
|
||
leave.ApproveRemark = dto.Remark ?? "";
|
||
leave.ApprovedAt = DateTime.Now;
|
||
await _leaveRepo.UpdateAsync(leave);
|
||
await _leaveRepo.SaveChangesAsync();
|
||
await _approverRepo.SaveChangesAsync();
|
||
|
||
// 流程终结:完成该单所有待办 + 标记消息已读,避免残留未读红点
|
||
await FinalizeLeaveApprovalAsync(leaveId, handleResult);
|
||
await NotifyLeaveResultAsync(leave, false, dto.Remark);
|
||
await _mediator.Publish(new BusinessChangedEvent("LeaveApproval", leaveId.ToString(), "StatusChanged"));
|
||
return await ToLeaveDto(leave);
|
||
}
|
||
|
||
// 批准:判断是否还有下一级审批人
|
||
var next = approvers.FirstOrDefault(a => a.OrderNo == leave.CurrentOrderNo + 1);
|
||
if (next != null)
|
||
{
|
||
// 流转到下一级审批人
|
||
leave.CurrentOrderNo = next.OrderNo;
|
||
leave.ApproverId = next.ApproverId;
|
||
leave.ApproverName = next.ApproverName;
|
||
await _leaveRepo.UpdateAsync(leave);
|
||
await _leaveRepo.SaveChangesAsync();
|
||
await _approverRepo.SaveChangesAsync();
|
||
|
||
// 完成当前审批人待办并标记其消息已读,再把同一条消息流转给下一审批人
|
||
await CompleteCurrentApproverAsync(leaveId, approverId, handleResult);
|
||
await SendLeaveApprovalAsync(leave, next.ApproverId, next.OrderNo, approvers.Count);
|
||
}
|
||
else
|
||
{
|
||
// 最后一级审批人批准:整体批准
|
||
leave.Status = "已批准";
|
||
leave.ApproverId = approverId;
|
||
leave.ApproverName = approverName;
|
||
leave.ApproveRemark = dto.Remark ?? "";
|
||
leave.ApprovedAt = DateTime.Now;
|
||
await _leaveRepo.UpdateAsync(leave);
|
||
await _leaveRepo.SaveChangesAsync();
|
||
await _approverRepo.SaveChangesAsync();
|
||
|
||
// 流程终结
|
||
await FinalizeLeaveApprovalAsync(leaveId, handleResult);
|
||
await NotifyLeaveResultAsync(leave, true, dto.Remark);
|
||
}
|
||
|
||
// 业务状态已变更,推送事件让各端刷新(多端同步)
|
||
await _mediator.Publish(new BusinessChangedEvent("LeaveApproval", leaveId.ToString(), "StatusChanged"));
|
||
return await ToLeaveDto(leave);
|
||
}
|
||
|
||
/// <summary>查询审批人(admin 角色用户)ID 列表(未指定审批人时的兜底)</summary>
|
||
private async Task<List<long>> GetApproverIdsAsync()
|
||
{
|
||
var adminRole = await _roleRepo.FirstOrDefaultAsync(r => r.Code == "admin");
|
||
if (adminRole == null) return new List<long>();
|
||
var relations = await _empRoleRepo.FindAsync(ur => ur.RoleId == adminRole.Id);
|
||
return relations.Select(ur => ur.UserId).Distinct().ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 请假审批待办消息:一张请假单仅保留一条,随审批流转复用。
|
||
/// 首次创建,流转时更新内容与当前审批人并重置为待投递(投递服务幂等,只推新审批人)。
|
||
/// </summary>
|
||
private async Task SendLeaveApprovalAsync(LeaveRequest leave, long targetApproverId, int currentLevel, int totalLevel)
|
||
{
|
||
var businessType = NotificationTypeEnum.LeaveApproval.ToString();
|
||
var businessId = leave.Id.ToString();
|
||
var content = $"类型:{leave.Type},时间:{leave.StartTime:MM-dd HH:mm} ~ {leave.EndTime:MM-dd HH:mm},事由:{leave.Reason}(第 {currentLevel}/{totalLevel} 级审批)";
|
||
|
||
var msg = await _msgRepo.FirstOrDefaultAsync(m =>
|
||
m.BusinessType == businessType && m.BusinessId == businessId);
|
||
|
||
if (msg == null)
|
||
{
|
||
var type = await _typeRepo.FirstOrDefaultAsync(t => t.Code == businessType);
|
||
var applicant = await _empRepo.GetByIdAsync(leave.EmployeeId);
|
||
msg = new NotificationMessage
|
||
{
|
||
NotificationTypeId = type?.Id ?? 0,
|
||
MessageTypeCode = businessType,
|
||
BusinessType = businessType,
|
||
BusinessId = businessId,
|
||
Title = $"{applicant?.UserName ?? "员工"} 提交了请假申请",
|
||
Content = content,
|
||
Priority = NotificationPriority.Important,
|
||
Status = NotificationStatus.Ready,
|
||
ForceReceivers = JsonSerializer.Serialize(new List<long> { targetApproverId }),
|
||
CreatorId = leave.EmployeeId,
|
||
CreatorName = applicant?.UserName ?? "",
|
||
Category = NotificationCategory.Todo,
|
||
RoutePath = !string.IsNullOrWhiteSpace(type?.RouteTemplate)
|
||
? type.RouteTemplate.Replace("{businessId}", businessId)
|
||
: $"/attendance/approve?leaveId={businessId}"
|
||
};
|
||
await _msgRepo.AddAsync(msg);
|
||
await _msgRepo.SaveChangesAsync();
|
||
}
|
||
else
|
||
{
|
||
// 复用:更新内容与当前审批人,重置为待投递,投递服务幂等只会推送给新审批人
|
||
msg.Content = content;
|
||
msg.ForceReceivers = JsonSerializer.Serialize(new List<long> { targetApproverId });
|
||
msg.Status = NotificationStatus.Ready;
|
||
await _msgRepo.UpdateAsync(msg);
|
||
await _msgRepo.SaveChangesAsync();
|
||
}
|
||
|
||
// 当前审批人的待办(幂等:已存在则跳过)
|
||
var existTodo = await _todoRepo.FirstOrDefaultAsync(t => t.MessageId == msg.Id && t.AssigneeId == targetApproverId);
|
||
if (existTodo == null)
|
||
{
|
||
await _todoRepo.AddAsync(new TodoItem
|
||
{
|
||
MessageId = msg.Id,
|
||
AssigneeId = targetApproverId,
|
||
TodoStatus = TodoItemStatus.Pending
|
||
});
|
||
await _todoRepo.SaveChangesAsync();
|
||
}
|
||
}
|
||
|
||
/// <summary>完成当前审批人的待办,并标记其消息已读(流转时调用,避免已处理人残留未读红点)</summary>
|
||
private async Task CompleteCurrentApproverAsync(long leaveId, long approverId, string handleResult)
|
||
{
|
||
var msgs = await _msgRepo.FindAsync(m =>
|
||
m.BusinessType == NotificationTypeEnum.LeaveApproval.ToString() && m.BusinessId == leaveId.ToString());
|
||
var msgIds = msgs.Select(m => m.Id).ToList();
|
||
if (msgIds.Count == 0) return;
|
||
|
||
var todos = await _todoRepo.FindAsync(t =>
|
||
msgIds.Contains(t.MessageId) && t.AssigneeId == approverId && t.TodoStatus == TodoItemStatus.Pending);
|
||
foreach (var t in todos)
|
||
{
|
||
t.TodoStatus = TodoItemStatus.Completed;
|
||
t.CompletedTime = DateTime.Now;
|
||
t.HandleResult = handleResult;
|
||
await _todoRepo.UpdateAsync(t);
|
||
}
|
||
|
||
var receivers = await _receiverRepo.FindAsync(r =>
|
||
msgIds.Contains(r.MessageId) && r.UserId == approverId && r.ReadStatus == NotificationReadStatus.Unread);
|
||
foreach (var r in receivers)
|
||
{
|
||
r.ReadStatus = NotificationReadStatus.Read;
|
||
r.ReadTime = DateTime.Now;
|
||
await _receiverRepo.UpdateAsync(r);
|
||
}
|
||
}
|
||
|
||
/// <summary>流程终结:完成该单所有待处理待办,并标记所有未读消息为已读</summary>
|
||
private async Task FinalizeLeaveApprovalAsync(long leaveId, string handleResult)
|
||
{
|
||
var msgs = await _msgRepo.FindAsync(m =>
|
||
m.BusinessType == NotificationTypeEnum.LeaveApproval.ToString() && m.BusinessId == leaveId.ToString());
|
||
var msgIds = msgs.Select(m => m.Id).ToList();
|
||
if (msgIds.Count == 0) return;
|
||
|
||
var todos = await _todoRepo.FindAsync(t => msgIds.Contains(t.MessageId) && t.TodoStatus == TodoItemStatus.Pending);
|
||
foreach (var t in todos)
|
||
{
|
||
t.TodoStatus = TodoItemStatus.Completed;
|
||
t.CompletedTime = DateTime.Now;
|
||
t.HandleResult = handleResult;
|
||
await _todoRepo.UpdateAsync(t);
|
||
}
|
||
|
||
var receivers = await _receiverRepo.FindAsync(r => msgIds.Contains(r.MessageId) && r.ReadStatus == NotificationReadStatus.Unread);
|
||
foreach (var r in receivers)
|
||
{
|
||
r.ReadStatus = NotificationReadStatus.Read;
|
||
r.ReadTime = DateTime.Now;
|
||
await _receiverRepo.UpdateAsync(r);
|
||
}
|
||
}
|
||
|
||
/// <summary>通知申请人审批结果</summary>
|
||
private async Task NotifyLeaveResultAsync(LeaveRequest leave, bool approved, string remark)
|
||
{
|
||
await _mediator.Send(new SendNotificationCommand(
|
||
NotificationTypeEnum.LeaveResult,
|
||
BusinessId: leave.Id.ToString(),
|
||
Title: approved ? "请假申请已批准" : "请假申请已驳回",
|
||
Content: $"您的请假({leave.Type},{leave.StartTime:MM-dd HH:mm} ~ {leave.EndTime:MM-dd HH:mm})已{(approved ? "批准" : "驳回")}。审批意见:{remark}",
|
||
ForceReceivers: new List<long> { leave.EmployeeId }
|
||
));
|
||
}
|
||
|
||
private async Task<LeaveRequestDto> ToLeaveDto(LeaveRequest l)
|
||
{
|
||
var emp = await _empRepo.GetByIdAsync(l.EmployeeId);
|
||
var dept = emp != null ? await _deptRepo.GetByIdAsync(emp.DepartmentId) : null;
|
||
|
||
var approvers = (await _approverRepo.FindAsync(a => a.LeaveId == l.Id))
|
||
.OrderBy(a => a.OrderNo)
|
||
.Select(a => new LeaveApproverDto
|
||
{
|
||
Id = a.Id,
|
||
ApproverId = a.ApproverId,
|
||
ApproverName = a.ApproverName,
|
||
OrderNo = a.OrderNo,
|
||
Status = a.Status,
|
||
ApproveRemark = a.ApproveRemark,
|
||
ApprovedAt = a.ApprovedAt
|
||
})
|
||
.ToList();
|
||
|
||
return new LeaveRequestDto
|
||
{
|
||
Id = l.Id,
|
||
EmployeeId = l.EmployeeId,
|
||
EmployeeName = emp?.UserName ?? "",
|
||
DepartmentName = dept?.Name ?? "",
|
||
Type = l.Type,
|
||
StartTime = l.StartTime,
|
||
EndTime = l.EndTime,
|
||
Reason = l.Reason,
|
||
Status = l.Status,
|
||
CurrentOrderNo = l.CurrentOrderNo,
|
||
ApproverName = l.ApproverName,
|
||
ApproveRemark = l.ApproveRemark,
|
||
ApprovedAt = l.ApprovedAt,
|
||
CreatedAt = l.CreatedAt,
|
||
Approvers = approvers
|
||
};
|
||
}
|
||
}
|