initial commit

This commit is contained in:
2026-07-21 14:08:10 +07:00
commit f7cf118326
533 changed files with 41762 additions and 0 deletions
@@ -0,0 +1,54 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class CreateEmployeeDeductionRequest
{
public string? EmployeeId { get; set; }
public string? DeductionId { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
}
public class CreateEmployeeDeductionResponse
{
public string? Id { get; set; }
}
public record CreateEmployeeDeductionCommand(CreateEmployeeDeductionRequest Data) : IRequest<CreateEmployeeDeductionResponse>;
public class CreateEmployeeDeductionHandler : IRequestHandler<CreateEmployeeDeductionCommand, CreateEmployeeDeductionResponse>
{
private readonly AppDbContext _context;
public CreateEmployeeDeductionHandler(AppDbContext context) => _context = context;
public async Task<CreateEmployeeDeductionResponse> Handle(CreateEmployeeDeductionCommand request, CancellationToken cancellationToken)
{
var entityName = nameof(Data.Entities.EmployeeDeduction);
var autoNo = await _context.GenerateAutoNumberAsync(
entityName: entityName,
prefixTemplate: $"EDED/{{Year}}/",
ct: cancellationToken
);
var entity = new Data.Entities.EmployeeDeduction
{
AutoNumber = autoNo,
EmployeeId = request.Data.EmployeeId,
DeductionId = request.Data.DeductionId,
Amount = request.Data.Amount,
Description = request.Data.Description,
IsActive = request.Data.IsActive
};
_context.EmployeeDeduction.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateEmployeeDeductionResponse { Id = entity.Id };
}
}
@@ -0,0 +1,15 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class CreateEmployeeDeductionValidator : AbstractValidator<CreateEmployeeDeductionRequest>
{
public CreateEmployeeDeductionValidator()
{
RuleFor(x => x.EmployeeId).NotEmpty().WithMessage("Employee is required");
RuleFor(x => x.DeductionId).NotEmpty().WithMessage("Deduction component is required");
RuleFor(x => x.Amount).GreaterThanOrEqualTo(0).WithMessage("Amount must be 0 or greater");
RuleFor(x => x.Description).MaximumLength(GlobalConsts.StringLengthShort);
}
}
@@ -0,0 +1,128 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class CreateEmployeeRequest
{
public string Code { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string MiddleName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string JobDescription { get; set; } = string.Empty;
public string GradeId { get; set; } = string.Empty;
public decimal BasicSalary { get; set; }
public string SalaryBankName { get; set; } = string.Empty;
public string SalaryBankAccountName { get; set; } = string.Empty;
public string SalaryBankAccountNumber { get; set; } = string.Empty;
public string PlaceOfBirth { get; set; } = string.Empty;
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; } = string.Empty;
public string MaritalStatus { get; set; } = string.Empty;
public string Religion { get; set; } = string.Empty;
public string BloodType { get; set; } = string.Empty;
public string IdentityNumber { get; set; } = string.Empty;
public string TaxNumber { get; set; } = string.Empty;
public string LastEducation { get; set; } = string.Empty;
public DateTime? JoinedDate { get; set; }
public DateTime? ResignedDate { get; set; }
public string EmployeeStatus { get; set; } = string.Empty;
public string EmploymentType { get; set; } = string.Empty;
public string StreetAddress { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string StateProvince { get; set; } = string.Empty;
public string ZipCode { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string BranchId { get; set; } = string.Empty;
public string DepartmentId { get; set; } = string.Empty;
public string DesignationId { get; set; } = string.Empty;
public string SocialMediaLinkedIn { get; set; } = string.Empty;
public string SocialMediaX { get; set; } = string.Empty;
public string SocialMediaFacebook { get; set; } = string.Empty;
public string SocialMediaInstagram { get; set; } = string.Empty;
public string SocialMediaTikTok { get; set; } = string.Empty;
public string OtherInformation1 { get; set; } = string.Empty;
public string OtherInformation2 { get; set; } = string.Empty;
public string OtherInformation3 { get; set; } = string.Empty;
}
public class CreateEmployeeResponse
{
public string? Id { get; set; }
public string? Code { get; set; }
}
public record CreateEmployeeCommand(CreateEmployeeRequest Data) : IRequest<CreateEmployeeResponse>;
public class CreateEmployeeHandler : IRequestHandler<CreateEmployeeCommand, CreateEmployeeResponse>
{
private readonly AppDbContext _context;
public CreateEmployeeHandler(AppDbContext context) => _context = context;
public async Task<CreateEmployeeResponse> Handle(CreateEmployeeCommand request, CancellationToken cancellationToken)
{
var isExists = await _context.Employee.AnyAsync(x => x.Code == request.Data.Code, cancellationToken);
if (isExists) throw new AlreadyExistsException("Employee", request.Data.Code);
var entityName = nameof(Data.Entities.Employee);
var autoNo = await _context.GenerateAutoNumberAsync(
entityName: entityName,
prefixTemplate: $"EMP/{{Year}}/",
ct: cancellationToken
);
var entity = new Data.Entities.Employee
{
AutoNumber = autoNo,
Code = request.Data.Code,
FirstName = request.Data.FirstName,
MiddleName = request.Data.MiddleName,
LastName = request.Data.LastName,
JobDescription = request.Data.JobDescription,
GradeId = request.Data.GradeId,
BasicSalary = request.Data.BasicSalary,
SalaryBankName = request.Data.SalaryBankName,
SalaryBankAccountName = request.Data.SalaryBankAccountName,
SalaryBankAccountNumber = request.Data.SalaryBankAccountNumber,
PlaceOfBirth = request.Data.PlaceOfBirth,
DateOfBirth = request.Data.DateOfBirth,
Gender = request.Data.Gender,
MaritalStatus = request.Data.MaritalStatus,
Religion = request.Data.Religion,
BloodType = request.Data.BloodType,
IdentityNumber = request.Data.IdentityNumber,
TaxNumber = request.Data.TaxNumber,
LastEducation = request.Data.LastEducation,
JoinedDate = request.Data.JoinedDate,
ResignedDate = request.Data.ResignedDate,
EmployeeStatus = request.Data.EmployeeStatus,
EmploymentType = request.Data.EmploymentType,
StreetAddress = request.Data.StreetAddress,
City = request.Data.City,
StateProvince = request.Data.StateProvince,
ZipCode = request.Data.ZipCode,
Phone = request.Data.Phone,
Email = request.Data.Email,
BranchId = request.Data.BranchId,
DepartmentId = request.Data.DepartmentId,
DesignationId = request.Data.DesignationId,
SocialMediaLinkedIn = request.Data.SocialMediaLinkedIn,
SocialMediaX = request.Data.SocialMediaX,
SocialMediaFacebook = request.Data.SocialMediaFacebook,
SocialMediaInstagram = request.Data.SocialMediaInstagram,
SocialMediaTikTok = request.Data.SocialMediaTikTok,
OtherInformation1 = request.Data.OtherInformation1,
OtherInformation2 = request.Data.OtherInformation2,
OtherInformation3 = request.Data.OtherInformation3
};
_context.Employee.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateEmployeeResponse { Id = entity.Id, Code = entity.Code };
}
}
@@ -0,0 +1,54 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class CreateEmployeeIncomeRequest
{
public string? EmployeeId { get; set; }
public string? IncomeId { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
}
public class CreateEmployeeIncomeResponse
{
public string? Id { get; set; }
}
public record CreateEmployeeIncomeCommand(CreateEmployeeIncomeRequest Data) : IRequest<CreateEmployeeIncomeResponse>;
public class CreateEmployeeIncomeHandler : IRequestHandler<CreateEmployeeIncomeCommand, CreateEmployeeIncomeResponse>
{
private readonly AppDbContext _context;
public CreateEmployeeIncomeHandler(AppDbContext context) => _context = context;
public async Task<CreateEmployeeIncomeResponse> Handle(CreateEmployeeIncomeCommand request, CancellationToken cancellationToken)
{
var entityName = nameof(Data.Entities.EmployeeIncome);
var autoNo = await _context.GenerateAutoNumberAsync(
entityName: entityName,
prefixTemplate: $"EINC/{{Year}}/",
ct: cancellationToken
);
var entity = new Data.Entities.EmployeeIncome
{
AutoNumber = autoNo,
EmployeeId = request.Data.EmployeeId,
IncomeId = request.Data.IncomeId,
Amount = request.Data.Amount,
Description = request.Data.Description,
IsActive = request.Data.IsActive
};
_context.EmployeeIncome.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateEmployeeIncomeResponse { Id = entity.Id };
}
}
@@ -0,0 +1,15 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class CreateEmployeeIncomeValidator : AbstractValidator<CreateEmployeeIncomeRequest>
{
public CreateEmployeeIncomeValidator()
{
RuleFor(x => x.EmployeeId).NotEmpty().WithMessage("Employee is required");
RuleFor(x => x.IncomeId).NotEmpty().WithMessage("Income component is required");
RuleFor(x => x.Amount).GreaterThanOrEqualTo(0).WithMessage("Amount must be 0 or greater");
RuleFor(x => x.Description).MaximumLength(GlobalConsts.StringLengthShort);
}
}
@@ -0,0 +1,58 @@
using FluentValidation;
using Indotalent.Infrastructure.Database;
using Indotalent.Shared.Consts;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class CreateEmployeeValidator : AbstractValidator<CreateEmployeeRequest>
{
public CreateEmployeeValidator()
{
RuleFor(x => x.Code)
.NotEmpty().WithMessage("Employee Code is required")
.MaximumLength(GlobalConsts.StringLengthShort).WithMessage($"Code cannot exceed {GlobalConsts.StringLengthShort} characters");
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("First Name is required")
.MaximumLength(GlobalConsts.StringLengthShort).WithMessage($"First Name cannot exceed {GlobalConsts.StringLengthShort} characters");
RuleFor(x => x.LastName)
.NotEmpty().WithMessage("Last Name is required")
.MaximumLength(GlobalConsts.StringLengthShort).WithMessage($"Last Name cannot exceed {GlobalConsts.StringLengthShort} characters");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format");
RuleFor(x => x.IdentityNumber)
.NotEmpty().WithMessage("Identity Number is required");
RuleFor(x => x.JoinedDate)
.NotEmpty().WithMessage("Joined Date is required");
RuleFor(x => x.BranchId)
.NotEmpty().WithMessage("Please select a Branch");
RuleFor(x => x.DepartmentId)
.NotEmpty().WithMessage("Please select a Department");
RuleFor(x => x.DesignationId)
.NotEmpty().WithMessage("Please select a Designation");
RuleFor(x => x.GradeId)
.NotEmpty().WithMessage("Please select a Salary Grade");
RuleFor(x => x.Gender)
.NotEmpty().WithMessage("Gender is required");
RuleFor(x => x.Phone)
.NotEmpty().WithMessage("Phone number is required");
RuleFor(x => x.BasicSalary)
.GreaterThan(0).WithMessage("Basic Salary must be greater than 0.");
}
}
@@ -0,0 +1,27 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public record DeleteEmployeeByIdRequest(string Id);
public record DeleteEmployeeByIdCommand(DeleteEmployeeByIdRequest Data) : IRequest<bool>;
public class DeleteEmployeeByIdHandler : IRequestHandler<DeleteEmployeeByIdCommand, bool>
{
private readonly AppDbContext _context;
public DeleteEmployeeByIdHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteEmployeeByIdCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Employee
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) return false;
_context.Employee.Remove(entity);
return await _context.SaveChangesAsync(cancellationToken) > 0;
}
}
@@ -0,0 +1,27 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public record DeleteEmployeeDeductionByIdRequest(string Id);
public record DeleteEmployeeDeductionByIdCommand(DeleteEmployeeDeductionByIdRequest Data) : IRequest<bool>;
public class DeleteEmployeeDeductionByIdHandler : IRequestHandler<DeleteEmployeeDeductionByIdCommand, bool>
{
private readonly AppDbContext _context;
public DeleteEmployeeDeductionByIdHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteEmployeeDeductionByIdCommand request, CancellationToken cancellationToken)
{
var entity = await _context.EmployeeDeduction
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) return false;
_context.EmployeeDeduction.Remove(entity);
return await _context.SaveChangesAsync(cancellationToken) > 0;
}
}
@@ -0,0 +1,27 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public record DeleteEmployeeIncomeByIdRequest(string Id);
public record DeleteEmployeeIncomeByIdCommand(DeleteEmployeeIncomeByIdRequest Data) : IRequest<bool>;
public class DeleteEmployeeIncomeByIdHandler : IRequestHandler<DeleteEmployeeIncomeByIdCommand, bool>
{
private readonly AppDbContext _context;
public DeleteEmployeeIncomeByIdHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteEmployeeIncomeByIdCommand request, CancellationToken cancellationToken)
{
var entity = await _context.EmployeeIncome
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) return false;
_context.EmployeeIncome.Remove(entity);
return await _context.SaveChangesAsync(cancellationToken) > 0;
}
}
@@ -0,0 +1,120 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class GetEmployeeByIdResponse
{
public string Id { get; set; } = string.Empty;
public string? AutoNumber { get; set; }
public string Code { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string MiddleName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string JobDescription { get; set; } = string.Empty;
public string? GradeId { get; set; }
public decimal BasicSalary { get; set; }
public string SalaryBankName { get; set; } = string.Empty;
public string SalaryBankAccountName { get; set; } = string.Empty;
public string SalaryBankAccountNumber { get; set; } = string.Empty;
public string PlaceOfBirth { get; set; } = string.Empty;
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; } = string.Empty;
public string MaritalStatus { get; set; } = string.Empty;
public string Religion { get; set; } = string.Empty;
public string BloodType { get; set; } = string.Empty;
public string IdentityNumber { get; set; } = string.Empty;
public string TaxNumber { get; set; } = string.Empty;
public string LastEducation { get; set; } = string.Empty;
public DateTime? JoinedDate { get; set; }
public DateTime? ResignedDate { get; set; }
public string EmployeeStatus { get; set; } = string.Empty;
public string EmploymentType { get; set; } = string.Empty;
public string StreetAddress { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string StateProvince { get; set; } = string.Empty;
public string ZipCode { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string BranchId { get; set; } = string.Empty;
public string DepartmentId { get; set; } = string.Empty;
public string DesignationId { get; set; } = string.Empty;
public string SocialMediaLinkedIn { get; set; } = string.Empty;
public string SocialMediaX { get; set; } = string.Empty;
public string SocialMediaFacebook { get; set; } = string.Empty;
public string SocialMediaInstagram { get; set; } = string.Empty;
public string SocialMediaTikTok { get; set; } = string.Empty;
public string OtherInformation1 { get; set; } = string.Empty;
public string OtherInformation2 { get; set; } = string.Empty;
public string OtherInformation3 { get; set; } = string.Empty;
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
public record GetEmployeeByIdQuery(string Id) : IRequest<GetEmployeeByIdResponse?>;
public class GetEmployeeByIdHandler : IRequestHandler<GetEmployeeByIdQuery, GetEmployeeByIdResponse?>
{
private readonly AppDbContext _context;
public GetEmployeeByIdHandler(AppDbContext context) => _context = context;
public async Task<GetEmployeeByIdResponse?> Handle(GetEmployeeByIdQuery request, CancellationToken cancellationToken)
{
return await _context.Employee
.AsNoTracking()
.Where(x => x.Id == request.Id)
.Select(x => new GetEmployeeByIdResponse
{
Id = x.Id,
AutoNumber = x.AutoNumber,
Code = x.Code,
FirstName = x.FirstName,
MiddleName = x.MiddleName,
LastName = x.LastName,
JobDescription = x.JobDescription,
GradeId = x.GradeId,
BasicSalary = x.BasicSalary,
SalaryBankName = x.SalaryBankName,
SalaryBankAccountName = x.SalaryBankAccountName,
SalaryBankAccountNumber = x.SalaryBankAccountNumber,
PlaceOfBirth = x.PlaceOfBirth,
DateOfBirth = x.DateOfBirth,
Gender = x.Gender,
MaritalStatus = x.MaritalStatus,
Religion = x.Religion,
BloodType = x.BloodType,
IdentityNumber = x.IdentityNumber,
TaxNumber = x.TaxNumber,
LastEducation = x.LastEducation,
JoinedDate = x.JoinedDate,
ResignedDate = x.ResignedDate,
EmployeeStatus = x.EmployeeStatus,
EmploymentType = x.EmploymentType,
StreetAddress = x.StreetAddress,
City = x.City,
StateProvince = x.StateProvince,
ZipCode = x.ZipCode,
Phone = x.Phone,
Email = x.Email,
BranchId = x.BranchId,
DepartmentId = x.DepartmentId,
DesignationId = x.DesignationId,
SocialMediaLinkedIn = x.SocialMediaLinkedIn,
SocialMediaX = x.SocialMediaX,
SocialMediaFacebook = x.SocialMediaFacebook,
SocialMediaInstagram = x.SocialMediaInstagram,
SocialMediaTikTok = x.SocialMediaTikTok,
OtherInformation1 = x.OtherInformation1,
OtherInformation2 = x.OtherInformation2,
OtherInformation3 = x.OtherInformation3,
CreatedAt = x.CreatedAt,
CreatedBy = x.CreatedBy,
UpdatedAt = x.UpdatedAt,
UpdatedBy = x.UpdatedBy
})
.FirstOrDefaultAsync(cancellationToken);
}
}
@@ -0,0 +1,41 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class GetEmployeeDeductionByIdResponse
{
public string? Id { get; set; }
public string? DeductionId { get; set; }
public string? DeductionName { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
public record GetEmployeeDeductionByIdQuery(string Id) : IRequest<GetEmployeeDeductionByIdResponse>;
public class GetEmployeeDeductionByIdHandler : IRequestHandler<GetEmployeeDeductionByIdQuery, GetEmployeeDeductionByIdResponse>
{
private readonly AppDbContext _context;
public GetEmployeeDeductionByIdHandler(AppDbContext context) => _context = context;
public async Task<GetEmployeeDeductionByIdResponse> Handle(GetEmployeeDeductionByIdQuery request, CancellationToken cancellationToken)
{
var data = await _context.EmployeeDeduction
.Where(x => x.Id == request.Id)
.Select(x => new GetEmployeeDeductionByIdResponse
{
Id = x.Id,
DeductionId = x.DeductionId,
DeductionName = x.Deduction != null ? x.Deduction.Name : string.Empty,
Amount = x.Amount,
Description = x.Description,
IsActive = x.IsActive
})
.FirstOrDefaultAsync(cancellationToken);
return data!;
}
}
@@ -0,0 +1,46 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class GetEmployeeDeductionListResponse
{
public string? Id { get; set; }
public string? DeductionId { get; set; }
public string? EmployeeCode { get; set; }
public string? DeductionName { get; set; }
public string? DeductionCategory { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
public record GetEmployeeDeductionListByEmployeeIdQuery(string EmployeeId) : IRequest<List<GetEmployeeDeductionListResponse>>;
public class GetEmployeeDeductionListByEmployeeIdHandler : IRequestHandler<GetEmployeeDeductionListByEmployeeIdQuery, List<GetEmployeeDeductionListResponse>>
{
private readonly AppDbContext _context;
public GetEmployeeDeductionListByEmployeeIdHandler(AppDbContext context) => _context = context;
public async Task<List<GetEmployeeDeductionListResponse>> Handle(GetEmployeeDeductionListByEmployeeIdQuery request, CancellationToken cancellationToken)
{
var data = await _context.EmployeeDeduction
.Where(x => x.EmployeeId == request.EmployeeId)
.Select(x => new GetEmployeeDeductionListResponse
{
Id = x.Id,
DeductionId = x.DeductionId,
EmployeeCode = x.Employee != null ? x.Employee.Code : string.Empty,
DeductionName = x.Deduction != null ? x.Deduction.Name : string.Empty,
DeductionCategory = x.Deduction != null ? x.Deduction.Category : string.Empty,
Amount = x.Amount,
Description = x.Description,
IsActive = x.IsActive
})
.OrderBy(x => x.DeductionName)
.ToListAsync(cancellationToken);
return data;
}
}
@@ -0,0 +1,41 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class GetEmployeeIncomeByIdResponse
{
public string? Id { get; set; }
public string? IncomeId { get; set; }
public string? IncomeName { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
public record GetEmployeeIncomeByIdQuery(string Id) : IRequest<GetEmployeeIncomeByIdResponse>;
public class GetEmployeeIncomeByIdHandler : IRequestHandler<GetEmployeeIncomeByIdQuery, GetEmployeeIncomeByIdResponse>
{
private readonly AppDbContext _context;
public GetEmployeeIncomeByIdHandler(AppDbContext context) => _context = context;
public async Task<GetEmployeeIncomeByIdResponse> Handle(GetEmployeeIncomeByIdQuery request, CancellationToken cancellationToken)
{
var data = await _context.EmployeeIncome
.Where(x => x.Id == request.Id)
.Select(x => new GetEmployeeIncomeByIdResponse
{
Id = x.Id,
IncomeId = x.IncomeId,
IncomeName = x.Income != null ? x.Income.Name : string.Empty,
Amount = x.Amount,
Description = x.Description,
IsActive = x.IsActive
})
.FirstOrDefaultAsync(cancellationToken);
return data!;
}
}
@@ -0,0 +1,46 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class GetEmployeeIncomeListResponse
{
public string? Id { get; set; }
public string? IncomeId { get; set; }
public string? EmployeeCode { get; set; }
public string? IncomeName { get; set; }
public string? IncomeType { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
public record GetEmployeeIncomeListByEmployeeIdQuery(string EmployeeId) : IRequest<List<GetEmployeeIncomeListResponse>>;
public class GetEmployeeIncomeListByEmployeeIdHandler : IRequestHandler<GetEmployeeIncomeListByEmployeeIdQuery, List<GetEmployeeIncomeListResponse>>
{
private readonly AppDbContext _context;
public GetEmployeeIncomeListByEmployeeIdHandler(AppDbContext context) => _context = context;
public async Task<List<GetEmployeeIncomeListResponse>> Handle(GetEmployeeIncomeListByEmployeeIdQuery request, CancellationToken cancellationToken)
{
var data = await _context.EmployeeIncome
.Where(x => x.EmployeeId == request.EmployeeId)
.Select(x => new GetEmployeeIncomeListResponse
{
Id = x.Id,
IncomeId = x.IncomeId,
EmployeeCode = x.Employee != null ? x.Employee.Code : string.Empty,
IncomeName = x.Income != null ? x.Income.Name : string.Empty,
IncomeType = x.Income != null ? x.Income.Type : string.Empty,
Amount = x.Amount,
Description = x.Description,
IsActive = x.IsActive
})
.OrderBy(x => x.IncomeName)
.ToListAsync(cancellationToken);
return data;
}
}
@@ -0,0 +1,53 @@
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class GetEmployeeListResponse
{
public string Id { get; set; } = string.Empty;
public string? AutoNumber { get; set; }
public string Code { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public string DesignationName { get; set; } = string.Empty;
public string DepartmentName { get; set; } = string.Empty;
public string BranchName { get; set; } = string.Empty;
public string EmployeeStatus { get; set; } = string.Empty;
public string? GradeName { get; set; }
public decimal? BasicSalary { get; set; }
}
public record GetEmployeeListQuery() : IRequest<List<GetEmployeeListResponse>>;
public class GetEmployeeListHandler : IRequestHandler<GetEmployeeListQuery, List<GetEmployeeListResponse>>
{
private readonly AppDbContext _context;
public GetEmployeeListHandler(AppDbContext context) => _context = context;
public async Task<List<GetEmployeeListResponse>> Handle(GetEmployeeListQuery request, CancellationToken cancellationToken)
{
var data = await _context.Employee
.Include(x => x.Grade)
.Include(x => x.Branch)
.Include(x => x.Department)
.Include(x => x.Designation)
.AsNoTracking()
.Select(x => new GetEmployeeListResponse
{
Id = x.Id,
Code = x.Code,
FullName = $"{x.FirstName} {x.MiddleName} {x.LastName}".Replace(" ", " "),
DesignationName = x.Designation!.Name,
DepartmentName = x.Department!.Name,
BranchName = x.Branch!.Name,
GradeName = x.Grade != null ? x.Grade.Name : string.Empty,
BasicSalary = x.BasicSalary
})
.ToListAsync(cancellationToken);
return data;
}
}
@@ -0,0 +1,78 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class LookupResponse
{
public List<LookupItem> Branches { get; set; } = new();
public List<LookupItem> Departments { get; set; } = new();
public List<LookupItem> Designations { get; set; } = new();
public List<LookupItem> Grades { get; set; } = new();
public List<LookupItem> Incomes { get; set; } = new();
public List<LookupItem> Deductions { get; set; } = new();
}
public class LookupItem
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Code { get; set; }
public decimal SalaryFrom { get; set; }
public decimal SalaryTo { get; set; }
}
public record LookupQuery() : IRequest<LookupResponse>;
public class LookupHandler : IRequestHandler<LookupQuery, LookupResponse>
{
private readonly AppDbContext _context;
public LookupHandler(AppDbContext context) => _context = context;
public async Task<LookupResponse> Handle(LookupQuery request, CancellationToken cancellationToken)
{
var response = new LookupResponse();
response.Branches = await _context.Branch
.AsNoTracking()
.Select(x => new LookupItem { Id = x.Id, Name = x.Name, Code = x.Code })
.ToListAsync(cancellationToken);
response.Departments = await _context.Department
.AsNoTracking()
.Select(x => new LookupItem { Id = x.Id, Name = x.Name, Code = x.CostCenter })
.ToListAsync(cancellationToken);
response.Designations = await _context.Designation
.AsNoTracking()
.Select(x => new LookupItem { Id = x.Id, Name = x.Name, Code = x.Code })
.ToListAsync(cancellationToken);
response.Grades = await _context.Grade
.AsNoTracking()
.Select(x => new LookupItem
{
Id = x.Id,
Name = x.Name,
Code = x.Code,
SalaryFrom = x.SalaryFrom,
SalaryTo = x.SalaryTo
})
.ToListAsync(cancellationToken);
response.Incomes = await _context.Income
.AsNoTracking()
.Where(x => x.Status == "Active")
.Select(x => new LookupItem { Id = x.Id, Name = x.Name, Code = x.Code })
.ToListAsync(cancellationToken);
response.Deductions = await _context.Deduction
.AsNoTracking()
.Where(x => x.Status == "Active")
.Select(x => new LookupItem { Id = x.Id, Name = x.Name, Code = x.Code })
.ToListAsync(cancellationToken);
return response;
}
}
@@ -0,0 +1,45 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class UpdateEmployeeDeductionRequest
{
public string? Id { get; set; }
public string? DeductionId { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
public class UpdateEmployeeDeductionResponse
{
public string? Id { get; set; }
}
public record UpdateEmployeeDeductionCommand(UpdateEmployeeDeductionRequest Data) : IRequest<UpdateEmployeeDeductionResponse>;
public class UpdateEmployeeDeductionHandler : IRequestHandler<UpdateEmployeeDeductionCommand, UpdateEmployeeDeductionResponse>
{
private readonly AppDbContext _context;
public UpdateEmployeeDeductionHandler(AppDbContext context) => _context = context;
public async Task<UpdateEmployeeDeductionResponse> Handle(UpdateEmployeeDeductionCommand request, CancellationToken cancellationToken)
{
var entity = await _context.EmployeeDeduction
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) throw new NotFoundException("EmployeeDeduction", request.Data.Id ?? string.Empty);
entity.DeductionId = request.Data.DeductionId;
entity.Amount = request.Data.Amount;
entity.Description = request.Data.Description;
entity.IsActive = request.Data.IsActive;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateEmployeeDeductionResponse { Id = entity.Id };
}
}
@@ -0,0 +1,15 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class UpdateEmployeeDeductionValidator : AbstractValidator<UpdateEmployeeDeductionRequest>
{
public UpdateEmployeeDeductionValidator()
{
RuleFor(x => x.Id).NotEmpty().WithMessage("ID is required");
RuleFor(x => x.DeductionId).NotEmpty().WithMessage("Deduction component is required");
RuleFor(x => x.Amount).GreaterThanOrEqualTo(0).WithMessage("Amount must be 0 or greater");
RuleFor(x => x.Description).MaximumLength(GlobalConsts.StringLengthShort);
}
}
@@ -0,0 +1,82 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class UpdateEmployeeRequest : CreateEmployeeRequest
{
public string Id { get; set; } = string.Empty;
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
public class UpdateEmployeeResponse
{
public string? Id { get; set; }
public bool Success { get; set; }
}
public record UpdateEmployeeCommand(UpdateEmployeeRequest Data) : IRequest<UpdateEmployeeResponse>;
public class UpdateEmployeeHandler : IRequestHandler<UpdateEmployeeCommand, UpdateEmployeeResponse>
{
private readonly AppDbContext _context;
public UpdateEmployeeHandler(AppDbContext context) => _context = context;
public async Task<UpdateEmployeeResponse> Handle(UpdateEmployeeCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Employee.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) return new UpdateEmployeeResponse { Id = request.Data.Id, Success = false };
var isExists = await _context.Employee.AnyAsync(x => x.Code == request.Data.Code && x.Id != request.Data.Id, cancellationToken);
if (isExists) throw new AlreadyExistsException("Employee", request.Data.Code);
entity.Code = request.Data.Code;
entity.FirstName = request.Data.FirstName;
entity.MiddleName = request.Data.MiddleName;
entity.LastName = request.Data.LastName;
entity.JobDescription = request.Data.JobDescription;
entity.GradeId = request.Data.GradeId;
entity.BasicSalary = request.Data.BasicSalary;
entity.SalaryBankName = request.Data.SalaryBankName;
entity.SalaryBankAccountName = request.Data.SalaryBankAccountName;
entity.SalaryBankAccountNumber = request.Data.SalaryBankAccountNumber;
entity.PlaceOfBirth = request.Data.PlaceOfBirth;
entity.DateOfBirth = request.Data.DateOfBirth;
entity.Gender = request.Data.Gender;
entity.MaritalStatus = request.Data.MaritalStatus;
entity.Religion = request.Data.Religion;
entity.BloodType = request.Data.BloodType;
entity.IdentityNumber = request.Data.IdentityNumber;
entity.TaxNumber = request.Data.TaxNumber;
entity.LastEducation = request.Data.LastEducation;
entity.JoinedDate = request.Data.JoinedDate;
entity.ResignedDate = request.Data.ResignedDate;
entity.EmployeeStatus = request.Data.EmployeeStatus;
entity.EmploymentType = request.Data.EmploymentType;
entity.StreetAddress = request.Data.StreetAddress;
entity.City = request.Data.City;
entity.StateProvince = request.Data.StateProvince;
entity.ZipCode = request.Data.ZipCode;
entity.Phone = request.Data.Phone;
entity.Email = request.Data.Email;
entity.BranchId = request.Data.BranchId;
entity.DepartmentId = request.Data.DepartmentId;
entity.DesignationId = request.Data.DesignationId;
entity.SocialMediaLinkedIn = request.Data.SocialMediaLinkedIn;
entity.SocialMediaX = request.Data.SocialMediaX;
entity.SocialMediaFacebook = request.Data.SocialMediaFacebook;
entity.SocialMediaInstagram = request.Data.SocialMediaInstagram;
entity.SocialMediaTikTok = request.Data.SocialMediaTikTok;
entity.OtherInformation1 = request.Data.OtherInformation1;
entity.OtherInformation2 = request.Data.OtherInformation2;
entity.OtherInformation3 = request.Data.OtherInformation3;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateEmployeeResponse { Id = entity.Id, Success = true };
}
}
@@ -0,0 +1,45 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class UpdateEmployeeIncomeRequest
{
public string? Id { get; set; }
public string? IncomeId { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
public class UpdateEmployeeIncomeResponse
{
public string? Id { get; set; }
}
public record UpdateEmployeeIncomeCommand(UpdateEmployeeIncomeRequest Data) : IRequest<UpdateEmployeeIncomeResponse>;
public class UpdateEmployeeIncomeHandler : IRequestHandler<UpdateEmployeeIncomeCommand, UpdateEmployeeIncomeResponse>
{
private readonly AppDbContext _context;
public UpdateEmployeeIncomeHandler(AppDbContext context) => _context = context;
public async Task<UpdateEmployeeIncomeResponse> Handle(UpdateEmployeeIncomeCommand request, CancellationToken cancellationToken)
{
var entity = await _context.EmployeeIncome
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) throw new NotFoundException("EmployeeIncome", request.Data.Id ?? string.Empty);
entity.IncomeId = request.Data.IncomeId;
entity.Amount = request.Data.Amount;
entity.Description = request.Data.Description;
entity.IsActive = request.Data.IsActive;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateEmployeeIncomeResponse { Id = entity.Id };
}
}
@@ -0,0 +1,15 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class UpdateEmployeeIncomeValidator : AbstractValidator<UpdateEmployeeIncomeRequest>
{
public UpdateEmployeeIncomeValidator()
{
RuleFor(x => x.Id).NotEmpty().WithMessage("ID is required");
RuleFor(x => x.IncomeId).NotEmpty().WithMessage("Income component is required");
RuleFor(x => x.Amount).GreaterThanOrEqualTo(0).WithMessage("Amount must be 0 or greater");
RuleFor(x => x.Description).MaximumLength(GlobalConsts.StringLengthShort);
}
}
@@ -0,0 +1,44 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Organization.Employee.Cqrs;
public class UpdateEmployeeValidator : AbstractValidator<UpdateEmployeeRequest>
{
public UpdateEmployeeValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Employee ID is required for update");
RuleFor(x => x.Code)
.NotEmpty().WithMessage("Employee Code is required")
.MaximumLength(GlobalConsts.StringLengthShort).WithMessage($"Code cannot exceed {GlobalConsts.StringLengthShort} characters");
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("First Name is required")
.MaximumLength(GlobalConsts.StringLengthShort).WithMessage($"First Name cannot exceed {GlobalConsts.StringLengthShort} characters");
RuleFor(x => x.LastName)
.NotEmpty().WithMessage("Last Name is required")
.MaximumLength(GlobalConsts.StringLengthShort).WithMessage($"Last Name cannot exceed {GlobalConsts.StringLengthShort} characters");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format");
RuleFor(x => x.BranchId)
.NotEmpty().WithMessage("Please select a Branch");
RuleFor(x => x.DepartmentId)
.NotEmpty().WithMessage("Please select a Department");
RuleFor(x => x.DesignationId)
.NotEmpty().WithMessage("Please select a Designation");
RuleFor(x => x.GradeId)
.NotEmpty().WithMessage("Please select a Salary Grade");
RuleFor(x => x.BasicSalary)
.GreaterThan(0).WithMessage("Basic Salary must be greater than 0");
}
}