Skip to content

Commit

Permalink
Logger, Email Sender, Leave Request (All Functionalities), Models Added
Browse files Browse the repository at this point in the history
  • Loading branch information
rahiyansafin committed May 4, 2023
1 parent 19b01d4 commit deadf6f
Show file tree
Hide file tree
Showing 47 changed files with 793 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using HR.LeaveManagement.Application.Models.Email;

namespace HR.LeaveManagement.Application.Contracts.Email;
public interface IEmailSender
{
Task<bool> SendEmail(EmailMessage email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using HR.LeaveManagement.Application.Models.Identity;

namespace HR.LeaveManagement.Application.Contracts.Identity;
public interface IAuthService
{
Task<AuthResponse> Login(AuthRequest request);
Task<RegistrationResponse> Register(RegistrationRequest request);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using HR.LeaveManagement.Application.Models.Identity;

namespace HR.LeaveManagement.Application.Contracts.Identity;
public interface IUserService
{
Task<List<Employee>> GetEmployees();
Task<Employee> GetEmployee(string userId);
public string UserId { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace HR.LeaveManagement.Application.Contracts.Logging;
public interface IAppLogger<T>
{
void LogInformation(string message, params object[] args);
void LogWarning(string message, params object[] args);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ namespace HR.LeaveManagement.Application.Contracts.Persistence;

public interface ILeaveAllocationRepository : IGenericRepository<LeaveAllocation>
{

Task<LeaveAllocation> GetLeaveAllocationWithDetails(int id);
Task<List<LeaveAllocation>> GetLeaveAllocationsWithDetails();
Task<List<LeaveAllocation>> GetLeaveAllocationsWithDetails(string userId);
Task<bool> AllocationExists(string userId, int leaveTypeId, int period);
Task AddAllocations(List<LeaveAllocation> allocations);
Task<LeaveAllocation> GetUserAllocations(string userId, int leaveTypeId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ namespace HR.LeaveManagement.Application.Contracts.Persistence;

public interface ILeaveRequestRepository : IGenericRepository<LeaveRequest>
{

Task<LeaveRequest> GetLeaveRequestWithDetails(int id);
Task<List<LeaveRequest>> GetLeaveRequestsWithDetails();
Task<List<LeaveRequest>> GetLeaveRequestsWithDetails(string userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ public BadRequestException(string message) : base(message)
}

public BadRequestException(string message, ValidationResult validationResult) : base(message)
{
Errors = new();
foreach (var error in validationResult.Errors)
Errors.Add(error.ErrorMessage);
}
=> ValidationErrors = validationResult.ToDictionary();

public List<string> Errors { get; set; }
public IDictionary<string, string[]> ValidationErrors { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.CancelLeaveRequest;
public class CancelLeaveRequestCommand : IRequest<Unit>
{
public int Id { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using HR.LeaveManagement.Application.Contracts.Email;
using HR.LeaveManagement.Application.Contracts.Logging;
using HR.LeaveManagement.Application.Contracts.Persistence;
using HR.LeaveManagement.Application.Exceptions;
using HR.LeaveManagement.Application.Models.Email;

using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.CancelLeaveRequest;
public class CancelLeaveRequestCommandHandler : IRequestHandler<CancelLeaveRequestCommand, Unit>
{
private readonly ILeaveRequestRepository _leaveRequestRepository;
private readonly ILeaveAllocationRepository _leaveAllocationRepository;
private readonly IEmailSender _emailSender;
private readonly IAppLogger<CancelLeaveRequestCommandHandler> _appLogger;

public CancelLeaveRequestCommandHandler(ILeaveRequestRepository leaveRequestRepository,
ILeaveAllocationRepository leaveAllocationRepository,
IEmailSender emailSender, IAppLogger<CancelLeaveRequestCommandHandler> appLogger)
{
_leaveRequestRepository = leaveRequestRepository;
_leaveAllocationRepository = leaveAllocationRepository;
_emailSender = emailSender;
_appLogger = appLogger;
}

public async Task<Unit> Handle(CancelLeaveRequestCommand request, CancellationToken cancellationToken)
{
var leaveRequest = await _leaveRequestRepository.GetAsync(request.Id);

if (leaveRequest is null)
throw new NotFoundException(nameof(leaveRequest), request.Id);

leaveRequest.Cancelled = true;
await _leaveRequestRepository.UpdateAsync(leaveRequest);

// if already approved, re-evaluate the employee's allocations for the leave type
if (leaveRequest.Approved == true)
{
int daysRequested = (int)(leaveRequest.EndDate - leaveRequest.StartDate).TotalDays;
var allocation = await _leaveAllocationRepository.GetUserAllocations(leaveRequest.RequestingEmployeeId, leaveRequest.LeaveTypeId);
allocation.NumberOfDays += daysRequested;

await _leaveAllocationRepository.UpdateAsync(allocation);
}

// send confirmation email
try
{
var email = new EmailMessage
{
To = string.Empty, /* Get email from employee record */
Body = $"Your leave request for {leaveRequest.StartDate:D} to {leaveRequest.EndDate:D} has been cancelled successfully.",
Subject = "Leave Request Cancelled"
};

await _emailSender.SendEmail(email);
}
catch (Exception ex)
{
_appLogger.LogWarning(ex.Message);
}

return Unit.Value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.ChangeLeaveRequestApproval;
public class ChangeLeaveRequestApprovalCommand : IRequest<Unit>
{
public int Id { get; set; }
public bool Approved { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using AutoMapper;

using HR.LeaveManagement.Application.Contracts.Email;
using HR.LeaveManagement.Application.Contracts.Logging;
using HR.LeaveManagement.Application.Contracts.Persistence;
using HR.LeaveManagement.Application.Exceptions;
using HR.LeaveManagement.Application.Models.Email;

using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.ChangeLeaveRequestApproval;
public class ChangeLeaveRequestApprovalCommandHandler : IRequestHandler<ChangeLeaveRequestApprovalCommand, Unit>
{
private readonly IMapper _mapper;
private readonly IEmailSender _emailSender;
private readonly IAppLogger<ChangeLeaveRequestApprovalCommandHandler> _appLogger;
private readonly ILeaveRequestRepository _leaveRequestRepository;
private readonly ILeaveTypeRepository _leaveTypeRepository;
private readonly ILeaveAllocationRepository _leaveAllocationRepository;

public ChangeLeaveRequestApprovalCommandHandler(
ILeaveRequestRepository leaveRequestRepository,
ILeaveTypeRepository leaveTypeRepository,
ILeaveAllocationRepository leaveAllocationRepository,
IMapper mapper,
IEmailSender emailSender,
IAppLogger<ChangeLeaveRequestApprovalCommandHandler> appLogger)
{
_leaveRequestRepository = leaveRequestRepository;
_leaveTypeRepository = leaveTypeRepository;
_leaveAllocationRepository = leaveAllocationRepository;
_mapper = mapper;
_emailSender = emailSender;
_appLogger = appLogger;
}

public async Task<Unit> Handle(ChangeLeaveRequestApprovalCommand request, CancellationToken cancellationToken)
{
var leaveRequest = await _leaveRequestRepository.GetAsync(request.Id) ?? throw new NotFoundException(nameof(LeaveRequest), request.Id);

leaveRequest.Approved = request.Approved;
await _leaveRequestRepository.UpdateAsync(leaveRequest);

// if request is approved, get and update the employee's allocations
if (request.Approved)
{
int daysRequested = (int)(leaveRequest.EndDate - leaveRequest.StartDate).TotalDays;
var allocation = await _leaveAllocationRepository.GetUserAllocations(leaveRequest.RequestingEmployeeId, leaveRequest.LeaveTypeId);
allocation.NumberOfDays -= daysRequested;

await _leaveAllocationRepository.UpdateAsync(allocation);
}

// send confirmation email
try
{
var email = new EmailMessage
{
To = string.Empty, /* Get email from employee record */
Body = $"The approval status for your leave request for {leaveRequest.StartDate:D} to {leaveRequest.EndDate:D} has been updated.",
Subject = "Leave Request Approval Status Updated"
};
await _emailSender.SendEmail(email);
}
catch (Exception ex)
{
_appLogger.LogWarning(ex.Message);
}

return Unit.Value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FluentValidation;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.ChangeLeaveRequestApproval;
public class ChangeLeaveRequestApprovalCommandValidator : AbstractValidator<ChangeLeaveRequestApprovalCommand>
{
public ChangeLeaveRequestApprovalCommandValidator()
{
RuleFor(p => p.Approved)
.NotNull()
.WithMessage("Approval status cannot be null");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using HR.LeaveManagement.Application.Features.LeaveRequest.Shared;

using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.CreateLeaveRequest;
public class CreateLeaveRequestCommand : BaseLeaveRequest, IRequest<Unit>
{
public string RequestComments { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using AutoMapper;

using HR.LeaveManagement.Application.Contracts.Email;
using HR.LeaveManagement.Application.Contracts.Identity;
using HR.LeaveManagement.Application.Contracts.Logging;
using HR.LeaveManagement.Application.Contracts.Persistence;
using HR.LeaveManagement.Application.Exceptions;
using HR.LeaveManagement.Application.Models.Email;

using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.CreateLeaveRequest;
public class CreateLeaveRequestCommandHandler : IRequestHandler<CreateLeaveRequestCommand, Unit>
{
private readonly IEmailSender _emailSender;
private readonly IMapper _mapper;
private readonly ILeaveTypeRepository _leaveTypeRepository;
private readonly ILeaveRequestRepository _leaveRequestRepository;
private readonly ILeaveAllocationRepository _leaveAllocationRepository;
private readonly IUserService _userService;
private readonly IAppLogger<CreateLeaveRequestCommandHandler> _appLogger;

public CreateLeaveRequestCommandHandler(IEmailSender emailSender,
IMapper mapper, ILeaveTypeRepository leaveTypeRepository, ILeaveRequestRepository leaveRequestRepository, ILeaveAllocationRepository leaveAllocationRepository, IUserService userService,
IAppLogger<CreateLeaveRequestCommandHandler> appLogger)
{
_emailSender = emailSender;
_mapper = mapper;
_leaveTypeRepository = leaveTypeRepository;
_leaveRequestRepository = leaveRequestRepository;
_leaveAllocationRepository = leaveAllocationRepository;
_userService = userService;
_appLogger = appLogger;
}

public async Task<Unit> Handle(CreateLeaveRequestCommand request, CancellationToken cancellationToken)
{
var validator = new CreateLeaveRequestCommandValidator(_leaveTypeRepository);
var validationResult = await validator.ValidateAsync(request, cancellationToken);

if (validationResult.Errors.Any())
throw new BadRequestException("Invalid Leave Request", validationResult);

// Get requesting employee's id
var employeeId = _userService.UserId;

// Check on employee's allocation
var allocation = await _leaveAllocationRepository.GetUserAllocations(employeeId, request.LeaveTypeId);

// if allocations aren't enough, return validation error with message
if (allocation is null)
{
validationResult.Errors.Add(new FluentValidation.Results.ValidationFailure(nameof(request.LeaveTypeId),
"You do not have any allocations for this leave type."));
throw new BadRequestException("Invalid Leave Request", validationResult);
}

int daysRequested = (int)(request.EndDate - request.StartDate).TotalDays;
if (daysRequested > allocation.NumberOfDays)
{
validationResult.Errors.Add(new FluentValidation.Results.ValidationFailure(
nameof(request.EndDate), "You do not have enough days for this request"));
throw new BadRequestException("Invalid Leave Request", validationResult);
}

// Create leave request
var leaveRequest = _mapper.Map<Domain.LeaveRequest>(request);
leaveRequest.RequestingEmployeeId = employeeId;
leaveRequest.DateRequested = DateTime.Now;
await _leaveRequestRepository.CreateAsync(leaveRequest);

// send confirmation email
try
{
var email = new EmailMessage
{
To = string.Empty, /* Get email from employee record */
Body = $"Your leave request for {request.StartDate:D} to {request.EndDate:D} " +
$"has been submitted successfully.",
Subject = "Leave Request Submitted"
};

await _emailSender.SendEmail(email);
}
catch (Exception ex)
{
_appLogger.LogWarning(ex.Message);
}

return Unit.Value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FluentValidation;

using HR.LeaveManagement.Application.Contracts.Persistence;
using HR.LeaveManagement.Application.Features.LeaveRequest.Shared;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.CreateLeaveRequest;
public class CreateLeaveRequestCommandValidator : AbstractValidator<CreateLeaveRequestCommand>
{
private readonly ILeaveTypeRepository _leaveTypeRepository;

public CreateLeaveRequestCommandValidator(ILeaveTypeRepository leaveTypeRepository)
{
_leaveTypeRepository = leaveTypeRepository;
Include(new BaseLeaveRequestValidator(_leaveTypeRepository));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace HR.LeaveManagement.Application.Features.LeaveRequest.Commands.DeleteLeaveRequest;
public class DeleteLeaveRequestCommand : IRequest
{
public int Id { get; set; }
}
Loading

0 comments on commit deadf6f

Please sign in to comment.