initial commit

This commit is contained in:
2026-07-21 14:22:06 +07:00
commit 2d7959f202
572 changed files with 45295 additions and 0 deletions
@@ -0,0 +1,142 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.Payrolls.Cqrs;
public class CreatePayrollProcessRequest
{
public string? PeriodName { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
public string? Description { get; set; }
}
public class CreatePayrollProcessResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
}
public record CreatePayrollProcessCommand(CreatePayrollProcessRequest Data) : IRequest<CreatePayrollProcessResponse>;
public class CreatePayrollProcessHandler : IRequestHandler<CreatePayrollProcessCommand, CreatePayrollProcessResponse>
{
private readonly AppDbContext _context;
public CreatePayrollProcessHandler(AppDbContext context) => _context = context;
public async Task<CreatePayrollProcessResponse> Handle(CreatePayrollProcessCommand request, CancellationToken cancellationToken)
{
var isExists = await _context.PayrollProcess
.AnyAsync(x => x.PeriodName == request.Data.PeriodName, cancellationToken);
if (isExists)
{
throw new AlreadyExistsException("Payroll Period", request.Data.PeriodName ?? string.Empty);
}
var entityName = nameof(Data.Entities.PayrollProcess);
var autoNo = await _context.GenerateAutoNumberAsync(
entityName: entityName,
prefixTemplate: $"PAY/{request.Data.FromDate?.ToString("yyyyMM") ?? DateTime.Now.ToString("yyyyMM")}/",
ct: cancellationToken
);
var payrollProcess = new Data.Entities.PayrollProcess
{
AutoNumber = autoNo,
PeriodName = request.Data.PeriodName,
FromDate = request.Data.FromDate ?? DateTime.Now,
ToDate = request.Data.ToDate ?? DateTime.Now,
Description = request.Data.Description,
Status = "Draft",
ProcessedBy = "System Admin",
ProcessedAt = DateTime.Now
};
var employees = await _context.Employee
.Include(x => x.EmployeeIncome)
.Include(x => x.EmployeeDeduction)
.Where(x => x.EmployeeStatus == "Active")
.ToListAsync(cancellationToken);
decimal grandBasicSalary = 0;
decimal grandTotalIncome = 0;
decimal grandTotalDeduction = 0;
foreach (var emp in employees)
{
var totalIncome = emp.EmployeeIncome?.Sum(x => x.Amount) ?? 0m;
var totalDeduction = emp.EmployeeDeduction?.Sum(x => x.Amount) ?? 0m;
var takeHomePay = emp.BasicSalary + totalIncome - totalDeduction;
var detail = new Data.Entities.PayrollDetail
{
PayrollProcess = payrollProcess,
EmployeeId = emp.Id,
EmployeeCode = emp.Code,
EmployeeName = $"{emp.FirstName} {emp.LastName}",
BasicSalary = emp.BasicSalary,
TotalIncome = totalIncome,
TotalDeduction = totalDeduction,
TakeHomePay = takeHomePay
};
if (emp.EmployeeIncome != null)
{
foreach (var inc in emp.EmployeeIncome)
{
detail.Components.Add(new Data.Entities.PayrollComponent
{
PayrollDetail = detail,
ComponentName = "Income Component",
ComponentType = "Income",
Amount = inc.Amount,
Description = inc.Description
});
}
}
if (emp.EmployeeDeduction != null)
{
foreach (var ded in emp.EmployeeDeduction)
{
detail.Components.Add(new Data.Entities.PayrollComponent
{
PayrollDetail = detail,
ComponentName = "Deduction Component",
ComponentType = "Deduction",
Amount = ded.Amount,
Description = ded.Description
});
}
}
grandBasicSalary += detail.BasicSalary;
grandTotalIncome += totalIncome;
grandTotalDeduction += totalDeduction;
_context.PayrollDetail.Add(detail);
}
payrollProcess.TotalEmployees = employees.Count;
payrollProcess.TotalBasicSalary = grandBasicSalary;
payrollProcess.TotalIncome = grandTotalIncome;
payrollProcess.TotalDeduction = grandTotalDeduction;
payrollProcess.TotalGross = grandBasicSalary + grandTotalIncome;
payrollProcess.TotalTakeHomePay = payrollProcess.TotalGross - grandTotalDeduction;
_context.PayrollProcess.Add(payrollProcess);
await _context.SaveChangesAsync(cancellationToken);
return new CreatePayrollProcessResponse
{
Id = payrollProcess.Id,
AutoNumber = payrollProcess.AutoNumber
};
}
}
@@ -0,0 +1,44 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.Payrolls.Cqrs;
public class DeletePayrollProcessByIdRequest
{
public string? Id { get; set; }
}
public class DeletePayrollProcessByIdResponse
{
public string? Id { get; set; }
}
public record DeletePayrollProcessByIdCommand(DeletePayrollProcessByIdRequest Data) : IRequest<DeletePayrollProcessByIdResponse>;
public class DeletePayrollProcessByIdHandler : IRequestHandler<DeletePayrollProcessByIdCommand, DeletePayrollProcessByIdResponse>
{
private readonly AppDbContext _context;
public DeletePayrollProcessByIdHandler(AppDbContext context) => _context = context;
public async Task<DeletePayrollProcessByIdResponse> Handle(DeletePayrollProcessByIdCommand request, CancellationToken cancellationToken)
{
var entity = await _context.PayrollProcess
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
throw new NotFoundException("Payroll Process", request.Data.Id ?? string.Empty);
}
_context.PayrollProcess.Remove(entity);
await _context.SaveChangesAsync(cancellationToken);
return new DeletePayrollProcessByIdResponse
{
Id = entity.Id
};
}
}
@@ -0,0 +1,76 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.PayrollDetails.Cqrs;
public class GetPayrollDetailByIdRequest
{
public string? Id { get; set; }
}
public class GetPayrollDetailByIdResponse
{
public string? Id { get; set; }
public string? PayrollProcessId { get; set; }
public string? EmployeeId { get; set; }
public string? EmployeeCode { get; set; }
public string? EmployeeName { get; set; }
public decimal BasicSalary { get; set; }
public decimal TotalIncome { get; set; }
public decimal TotalDeduction { get; set; }
public decimal TakeHomePay { get; set; }
public List<PayrollDetailComponentResponse> Components { get; set; } = new();
}
public class PayrollDetailComponentResponse
{
public string? ComponentName { get; set; }
public string? ComponentType { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set; }
}
public record GetPayrollDetailByIdQuery(GetPayrollDetailByIdRequest Data) : IRequest<GetPayrollDetailByIdResponse>;
public class GetPayrollDetailByIdHandler : IRequestHandler<GetPayrollDetailByIdQuery, GetPayrollDetailByIdResponse>
{
private readonly AppDbContext _context;
public GetPayrollDetailByIdHandler(AppDbContext context) => _context = context;
public async Task<GetPayrollDetailByIdResponse> Handle(GetPayrollDetailByIdQuery request, CancellationToken cancellationToken)
{
var entity = await _context.PayrollDetail
.Include(x => x.Components)
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
throw new NotFoundException("Payroll Detail", request.Data.Id ?? string.Empty);
}
var response = new GetPayrollDetailByIdResponse
{
Id = entity.Id,
PayrollProcessId = entity.PayrollProcessId,
EmployeeId = entity.EmployeeId,
EmployeeCode = entity.EmployeeCode,
EmployeeName = entity.EmployeeName,
BasicSalary = entity.BasicSalary,
TotalIncome = entity.TotalIncome,
TotalDeduction = entity.TotalDeduction,
TakeHomePay = entity.TakeHomePay,
Components = entity.Components.Select(c => new PayrollDetailComponentResponse
{
ComponentName = c.ComponentName,
ComponentType = c.ComponentType,
Amount = c.Amount,
Description = c.Description
}).ToList()
};
return response;
}
}
@@ -0,0 +1,51 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.PayrollDetails.Cqrs;
public class GetPayrollDetailListByProcessIdRequest
{
public string? PayrollProcessId { get; set; }
}
public class GetPayrollDetailListByProcessIdResponse
{
public string? Id { get; set; }
public string? EmployeeId { get; set; }
public string? EmployeeCode { get; set; }
public string? EmployeeName { get; set; }
public decimal BasicSalary { get; set; }
public decimal TotalIncome { get; set; }
public decimal TotalDeduction { get; set; }
public decimal TakeHomePay { get; set; }
}
public record GetPayrollDetailListByProcessIdQuery(GetPayrollDetailListByProcessIdRequest Data) : IRequest<List<GetPayrollDetailListByProcessIdResponse>>;
public class GetPayrollDetailListByProcessIdHandler : IRequestHandler<GetPayrollDetailListByProcessIdQuery, List<GetPayrollDetailListByProcessIdResponse>>
{
private readonly AppDbContext _context;
public GetPayrollDetailListByProcessIdHandler(AppDbContext context) => _context = context;
public async Task<List<GetPayrollDetailListByProcessIdResponse>> Handle(GetPayrollDetailListByProcessIdQuery request, CancellationToken cancellationToken)
{
var data = await _context.PayrollDetail
.Where(x => x.PayrollProcessId == request.Data.PayrollProcessId)
.Select(x => new GetPayrollDetailListByProcessIdResponse
{
Id = x.Id,
EmployeeId = x.EmployeeId,
EmployeeCode = x.EmployeeCode,
EmployeeName = x.EmployeeName,
BasicSalary = x.BasicSalary,
TotalIncome = x.TotalIncome,
TotalDeduction = x.TotalDeduction,
TakeHomePay = x.TakeHomePay
})
.ToListAsync(cancellationToken);
return data;
}
}
@@ -0,0 +1,71 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.Payrolls.Cqrs;
public class GetPayrollProcessByIdRequest
{
public string? Id { get; set; }
}
public class GetPayrollProcessByIdResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public string? PeriodName { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public int TotalEmployees { get; set; }
public decimal TotalBasicSalary { get; set; }
public decimal TotalIncome { get; set; }
public decimal TotalDeduction { get; set; }
public decimal TotalGross { get; set; }
public decimal TotalTakeHomePay { get; set; }
public string? Status { get; set; }
public string? ProcessedBy { get; set; }
public DateTime ProcessedAt { get; set; }
public string? Description { get; set; }
}
public record GetPayrollProcessByIdQuery(GetPayrollProcessByIdRequest Data) : IRequest<GetPayrollProcessByIdResponse>;
public class GetPayrollProcessByIdHandler : IRequestHandler<GetPayrollProcessByIdQuery, GetPayrollProcessByIdResponse>
{
private readonly AppDbContext _context;
public GetPayrollProcessByIdHandler(AppDbContext context) => _context = context;
public async Task<GetPayrollProcessByIdResponse> Handle(GetPayrollProcessByIdQuery request, CancellationToken cancellationToken)
{
var entity = await _context.PayrollProcess
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
throw new NotFoundException("Payroll Process", request.Data.Id ?? string.Empty);
}
var response = new GetPayrollProcessByIdResponse
{
Id = entity.Id,
AutoNumber = entity.AutoNumber,
PeriodName = entity.PeriodName,
FromDate = entity.FromDate,
ToDate = entity.ToDate,
TotalEmployees = entity.TotalEmployees,
TotalBasicSalary = entity.TotalBasicSalary,
TotalIncome = entity.TotalIncome,
TotalDeduction = entity.TotalDeduction,
TotalGross = entity.TotalGross,
TotalTakeHomePay = entity.TotalTakeHomePay,
Status = entity.Status,
ProcessedBy = entity.ProcessedBy,
ProcessedAt = entity.ProcessedAt,
Description = entity.Description
};
return response;
}
}
@@ -0,0 +1,64 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.Payrolls.Cqrs;
public class GetPayrollProcessListRequest
{
}
public class GetPayrollProcessListResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public string? PeriodName { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public int TotalEmployees { get; set; }
public decimal TotalBasicSalary { get; set; }
public decimal TotalIncome { get; set; }
public decimal TotalDeduction { get; set; }
public decimal TotalGross { get; set; }
public decimal TotalTakeHomePay { get; set; }
public string? Status { get; set; }
public string? ProcessedBy { get; set; }
public DateTime ProcessedAt { get; set; }
public string? Description { get; set; }
}
public record GetPayrollProcessListQuery(GetPayrollProcessListRequest Data) : IRequest<List<GetPayrollProcessListResponse>>;
public class GetPayrollProcessListHandler : IRequestHandler<GetPayrollProcessListQuery, List<GetPayrollProcessListResponse>>
{
private readonly AppDbContext _context;
public GetPayrollProcessListHandler(AppDbContext context) => _context = context;
public async Task<List<GetPayrollProcessListResponse>> Handle(GetPayrollProcessListQuery request, CancellationToken cancellationToken)
{
var data = await _context.PayrollProcess
.OrderByDescending(x => x.FromDate)
.Select(x => new GetPayrollProcessListResponse
{
Id = x.Id,
AutoNumber = x.AutoNumber,
PeriodName = x.PeriodName,
FromDate = x.FromDate,
ToDate = x.ToDate,
TotalEmployees = x.TotalEmployees,
TotalBasicSalary = x.TotalBasicSalary,
TotalIncome = x.TotalIncome,
TotalDeduction = x.TotalDeduction,
TotalGross = x.TotalGross,
TotalTakeHomePay = x.TotalTakeHomePay,
Status = x.Status,
ProcessedBy = x.ProcessedBy,
ProcessedAt = x.ProcessedAt,
Description = x.Description
})
.ToListAsync(cancellationToken);
return data;
}
}
@@ -0,0 +1,93 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.PayrollDetails.Cqrs;
public class GetPayrollSlipByIdRequest
{
public string? Id { get; set; }
}
public class GetPayrollSlipByIdResponse
{
public string? Id { get; set; }
public string? EmployeeName { get; set; }
public string? EmployeeCode { get; set; }
public string? DesignationName { get; set; }
public string? DepartmentName { get; set; }
public string? PeriodName { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public decimal BasicSalary { get; set; }
public decimal TotalIncome { get; set; }
public decimal TotalDeduction { get; set; }
public decimal TakeHomePay { get; set; }
public List<PayrollSlipComponentResponse> Incomes { get; set; } = new();
public List<PayrollSlipComponentResponse> Deductions { get; set; } = new();
}
public class PayrollSlipComponentResponse
{
public string? ComponentName { get; set; }
public decimal Amount { get; set; }
}
public record GetPayrollSlipByIdQuery(GetPayrollSlipByIdRequest Data) : IRequest<GetPayrollSlipByIdResponse>;
public class GetPayrollSlipByIdHandler : IRequestHandler<GetPayrollSlipByIdQuery, GetPayrollSlipByIdResponse>
{
private readonly AppDbContext _context;
public GetPayrollSlipByIdHandler(AppDbContext context) => _context = context;
public async Task<GetPayrollSlipByIdResponse> Handle(GetPayrollSlipByIdQuery request, CancellationToken cancellationToken)
{
var data = await _context.PayrollDetail
.Include(x => x.PayrollProcess)
.Include(x => x.Components)
.Include(x => x.Employee)
.ThenInclude(e => e!.Designation)
.Include(x => x.Employee)
.ThenInclude(e => e!.Department)
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (data == null)
{
throw new NotFoundException("Payroll Detail", request.Data.Id ?? string.Empty);
}
var response = new GetPayrollSlipByIdResponse
{
Id = data.Id,
EmployeeName = data.EmployeeName,
EmployeeCode = data.EmployeeCode,
DesignationName = data.Employee?.Designation?.Name,
DepartmentName = data.Employee?.Department?.Name,
PeriodName = data.PayrollProcess?.PeriodName,
FromDate = data.PayrollProcess?.FromDate ?? DateTime.MinValue,
ToDate = data.PayrollProcess?.ToDate ?? DateTime.MinValue,
BasicSalary = data.BasicSalary,
TotalIncome = data.TotalIncome,
TotalDeduction = data.TotalDeduction,
TakeHomePay = data.TakeHomePay,
Incomes = data.Components
.Where(c => c.ComponentType == "Income")
.Select(c => new PayrollSlipComponentResponse
{
ComponentName = c.ComponentName,
Amount = c.Amount
}).ToList(),
Deductions = data.Components
.Where(c => c.ComponentType == "Deduction")
.Select(c => new PayrollSlipComponentResponse
{
ComponentName = c.ComponentName,
Amount = c.Amount
}).ToList()
};
return response;
}
}
@@ -0,0 +1,52 @@
using Indotalent.ConfigBackEnd.Exceptions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Payroll.Payrolls.Cqrs;
public class UpdatePayrollProcessRequest
{
public string? Id { get; set; }
public string? Status { get; set; }
public string? Description { get; set; }
public string? PeriodName { get; set; }
}
public class UpdatePayrollProcessResponse
{
public string? Id { get; set; }
public string? Status { get; set; }
}
public record UpdatePayrollProcessCommand(UpdatePayrollProcessRequest Data) : IRequest<UpdatePayrollProcessResponse>;
public class UpdatePayrollProcessHandler : IRequestHandler<UpdatePayrollProcessCommand, UpdatePayrollProcessResponse>
{
private readonly AppDbContext _context;
public UpdatePayrollProcessHandler(AppDbContext context) => _context = context;
public async Task<UpdatePayrollProcessResponse> Handle(UpdatePayrollProcessCommand request, CancellationToken cancellationToken)
{
var entity = await _context.PayrollProcess
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
throw new NotFoundException("Payroll Process", request.Data.Id ?? string.Empty);
}
entity.Status = request.Data.Status;
entity.Description = request.Data.Description;
entity.PeriodName = request.Data.PeriodName;
await _context.SaveChangesAsync(cancellationToken);
return new UpdatePayrollProcessResponse
{
Id = entity.Id,
Status = entity.Status
};
}
}