develop full initial module

This commit is contained in:
Dhananjaya99
2026-07-23 19:54:56 +05:30
parent eacc21afad
commit 755df494fe
188 changed files with 13894 additions and 49 deletions
@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Hrm;
public sealed record AttendanceUploadBatchDto(
int AttendanceUploadBatchId, string DocNo, DateTime PeriodStart, DateTime PeriodEnd,
AttendanceSourceType SourceType, string? OriginalFileName, int UploadedBy, DateTime UploadedAt,
AttendanceBatchStatus Status, int? ConfirmedBy, DateTime? ConfirmedAt,
int RowCountTotal, int RowCountDuplicate, int RowCountError);
public sealed class UploadAttendanceBatchMetadata
{
[Required] public DateTime PeriodStart { get; set; }
[Required] public DateTime PeriodEnd { get; set; }
}
public sealed record AttendanceRecordDto(
int AttendanceRecordId, int? AttendanceUploadBatchId, int EmployeeId, string? EmployeeCode, string? EmployeeName,
DateTime AttendanceDate, TimeSpan? CheckIn, TimeSpan? CheckOut,
int WorkingMinutes, int LateMinutes, int EarlyLeaveMinutes, int OvertimeMinutes,
AttendanceStatus AttendanceStatus, RowValidationStatus RowValidationStatus,
int? DuplicateOfAttendanceRecordId, string? Notes);
public sealed class UpdateAttendanceRecordRequest
{
public TimeSpan? CheckIn { get; set; }
public TimeSpan? CheckOut { get; set; }
public AttendanceStatus? AttendanceStatus { get; set; }
[StringLength(1000)] public string? Notes { get; set; }
}
public sealed class ResolveDuplicateRequest
{
[Required] public int RecordId { get; set; }
/// <summary>"keep" discards the other duplicate row(s); "discard" removes this row; "supersede" (cross-batch-confirmed only) replaces the prior confirmed record.</summary>
[Required, RegularExpression("^(keep|discard|supersede)$")]
public string Action { get; set; } = string.Empty;
}
public sealed class UnlockAttendanceBatchRequest
{
[Required, StringLength(500)] public string Reason { get; set; } = string.Empty;
}
+46
View File
@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Hrm;
public sealed record HrDocumentTypeDto(
int HrDocumentTypeId, string Code, string Name, HrDocumentCategory Category,
bool RequiredAtOnboarding, bool ExpiryTracked, EntityStatus Status,
DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateHrDocumentTypeRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[Required, EnumDataType(typeof(HrDocumentCategory))] public HrDocumentCategory Category { get; set; }
public bool RequiredAtOnboarding { get; set; }
public bool ExpiryTracked { get; set; }
}
public sealed class UpdateHrDocumentTypeRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[Required, EnumDataType(typeof(HrDocumentCategory))] public HrDocumentCategory Category { get; set; }
public bool RequiredAtOnboarding { get; set; }
public bool ExpiryTracked { get; set; }
}
public sealed record EmployeeDocumentDto(
int EmployeeDocumentId, int EmployeeId, int HrDocumentTypeId, string? HrDocumentTypeName,
string OriginalFileName, string ContentType, long SizeBytes,
DateTime? IssueDate, DateTime? ExpiryDate, string? Notes,
int UploadedBy, DateTime UploadedAt, EmployeeDocumentStatus Status);
/// <summary>Metadata accompanying a multipart file upload (the file itself is bound separately).</summary>
public sealed class UploadEmployeeDocumentRequest
{
[Required] public int HrDocumentTypeId { get; set; }
public DateTime? IssueDate { get; set; }
public DateTime? ExpiryDate { get; set; }
[StringLength(1000)] public string? Notes { get; set; }
}
public sealed class UpdateEmployeeDocumentStatusRequest
{
[Required, EnumDataType(typeof(EmployeeDocumentStatus))] public EmployeeDocumentStatus Status { get; set; }
}
+117
View File
@@ -0,0 +1,117 @@
using System.ComponentModel.DataAnnotations;
using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Hrm;
public sealed record EmployeeListItemDto(
int EmployeeId, string EmployeeCode, string FullName, string? Email,
int DepartmentId, string? DepartmentName, int DesignationId, string? DesignationName,
int EmploymentTypeId, string? EmploymentTypeName, int? BranchId, string? BranchName,
EmployeeStatus Status, bool HasUserLink, DateTime HireDate);
public sealed record EmployeeDetailDto(
int EmployeeId, string EmployeeCode, string FullName, string? Nic, DateTime? DateOfBirth,
Gender? Gender, string? Nationality, string? ProfilePhotoPath,
string? Email, string? PersonalMobile, string? AddressLine1, string? AddressLine2,
string? City, string? PostalCode, string? Country,
string? EmergencyContactName, string? EmergencyContactRelationship, string? EmergencyContactPhone,
DateTime HireDate, DateTime? ConfirmationDate, DateTime? LastWorkingDate,
int DepartmentId, int DesignationId, int EmploymentTypeId, int? BranchId, int WorkShiftId,
int? ReportingManagerId, string? EpfNumber, string? EtfNumber, string? TaxIdentificationNumber,
int? UserId, EmployeeStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateEmployeeRequest
{
[Required, StringLength(30)] public string EmployeeCode { get; set; } = string.Empty;
[Required, StringLength(200)] public string FullName { get; set; } = string.Empty;
[StringLength(30)] public string? Nic { get; set; }
public DateTime? DateOfBirth { get; set; }
public Gender? Gender { get; set; }
[StringLength(100)] public string? Nationality { get; set; }
[EmailAddress, StringLength(320)] public string? Email { get; set; }
[StringLength(30)] public string? PersonalMobile { get; set; }
[StringLength(200)] public string? AddressLine1 { get; set; }
[StringLength(200)] public string? AddressLine2 { get; set; }
[StringLength(100)] public string? City { get; set; }
[StringLength(20)] public string? PostalCode { get; set; }
[StringLength(100)] public string? Country { get; set; }
[StringLength(200)] public string? EmergencyContactName { get; set; }
[StringLength(100)] public string? EmergencyContactRelationship { get; set; }
[StringLength(30)] public string? EmergencyContactPhone { get; set; }
[Required] public DateTime HireDate { get; set; }
[Required] public int DepartmentId { get; set; }
[Required] public int DesignationId { get; set; }
[Required] public int EmploymentTypeId { get; set; }
public int? BranchId { get; set; }
[Required] public int WorkShiftId { get; set; }
public int? ReportingManagerId { get; set; }
[StringLength(30)] public string? EpfNumber { get; set; }
[StringLength(30)] public string? EtfNumber { get; set; }
[StringLength(30)] public string? TaxIdentificationNumber { get; set; }
/// <summary>Explicit, human-confirmed link to an existing User found via email-lookup — never automatic.</summary>
public int? LinkUserId { get; set; }
}
public sealed class UpdateEmployeeRequest
{
[Required, StringLength(200)] public string FullName { get; set; } = string.Empty;
[StringLength(30)] public string? Nic { get; set; }
public DateTime? DateOfBirth { get; set; }
public Gender? Gender { get; set; }
[StringLength(100)] public string? Nationality { get; set; }
[EmailAddress, StringLength(320)] public string? Email { get; set; }
[StringLength(30)] public string? PersonalMobile { get; set; }
[StringLength(200)] public string? AddressLine1 { get; set; }
[StringLength(200)] public string? AddressLine2 { get; set; }
[StringLength(100)] public string? City { get; set; }
[StringLength(20)] public string? PostalCode { get; set; }
[StringLength(100)] public string? Country { get; set; }
[StringLength(200)] public string? EmergencyContactName { get; set; }
[StringLength(100)] public string? EmergencyContactRelationship { get; set; }
[StringLength(30)] public string? EmergencyContactPhone { get; set; }
public DateTime? ConfirmationDate { get; set; }
public DateTime? LastWorkingDate { get; set; }
[Required] public int DepartmentId { get; set; }
[Required] public int DesignationId { get; set; }
[Required] public int EmploymentTypeId { get; set; }
public int? BranchId { get; set; }
[Required] public int WorkShiftId { get; set; }
public int? ReportingManagerId { get; set; }
[StringLength(30)] public string? EpfNumber { get; set; }
[StringLength(30)] public string? EtfNumber { get; set; }
[StringLength(30)] public string? TaxIdentificationNumber { get; set; }
}
public sealed class UpdateEmployeeStatusRequest
{
[Required, EnumDataType(typeof(EmployeeStatus))] public EmployeeStatus Status { get; set; }
}
public sealed record EmployeeBankDetailDto(
int EmployeeBankDetailId, string BankName, string BranchName, string AccountNumber,
string AccountHolderName, string? SwiftCode, bool IsPrimary, EntityStatus Status);
public sealed class UpsertEmployeeBankDetailRequest
{
public int? EmployeeBankDetailId { get; set; }
[Required, StringLength(200)] public string BankName { get; set; } = string.Empty;
[Required, StringLength(200)] public string BranchName { get; set; } = string.Empty;
[Required, StringLength(50)] public string AccountNumber { get; set; } = string.Empty;
[Required, StringLength(200)] public string AccountHolderName { get; set; } = string.Empty;
[StringLength(20)] public string? SwiftCode { get; set; }
public bool IsPrimary { get; set; }
}
public sealed class ReplaceEmployeeBankDetailsRequest
{
[Required] public List<UpsertEmployeeBankDetailRequest> Items { get; set; } = new();
}
@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace ERPCore.Dtos.Hrm;
/// <summary>Advisory match surfaced by the reverse-direction email-lookup (docs/12-BACKEND-HRM.md A.5).</summary>
public sealed record EmployeeMatchDto(int EmployeeId, string EmployeeCode, string FullName, string Email);
public sealed record UserMatchDto(int UserId, string Username, string DisplayName, string Email);
public sealed record EmployeeMatchResponse(EmployeeMatchDto? Match);
public sealed record UserMatchResponse(UserMatchDto? Match);
public sealed class LinkUserRequest
{
[Required] public int UserId { get; set; }
}
+67
View File
@@ -0,0 +1,67 @@
using System.ComponentModel.DataAnnotations;
using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Hrm;
public sealed record LeaveTypeDto(
int LeaveTypeId, string Code, string Name, bool IsPaid, bool CountsAsNoPay, decimal AccrualPerYear,
bool CarryForwardAllowed, int? MaxCarryForwardDays, bool RequiresApproval, EntityStatus Status,
DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateLeaveTypeRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
public bool IsPaid { get; set; } = true;
public bool CountsAsNoPay { get; set; }
[Range(0, 365)] public decimal AccrualPerYear { get; set; }
public bool CarryForwardAllowed { get; set; }
public int? MaxCarryForwardDays { get; set; }
public bool RequiresApproval { get; set; } = true;
}
public sealed class UpdateLeaveTypeRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
public bool IsPaid { get; set; }
public bool CountsAsNoPay { get; set; }
[Range(0, 365)] public decimal AccrualPerYear { get; set; }
public bool CarryForwardAllowed { get; set; }
public int? MaxCarryForwardDays { get; set; }
public bool RequiresApproval { get; set; }
}
public sealed record LeaveRequestDto(
int LeaveRequestId, string DocNo, int EmployeeId, string? EmployeeName, int LeaveTypeId, string? LeaveTypeName,
DateTime StartDate, DateTime EndDate, decimal DaysCount, string? Reason,
LeaveRequestStatus Status, int? ApprovedBy, DateTime? ApprovedAt, string? RejectionReason, DateTime CreatedAt);
public sealed class CreateLeaveRequestRequest
{
[Required] public int EmployeeId { get; set; }
[Required] public int LeaveTypeId { get; set; }
[Required] public DateTime StartDate { get; set; }
[Required] public DateTime EndDate { get; set; }
[StringLength(1000)] public string? Reason { get; set; }
}
public sealed class RejectLeaveRequestRequest
{
[Required, StringLength(1000)] public string Reason { get; set; } = string.Empty;
}
public sealed record LeaveBalanceDto(
int LeaveBalanceId, int EmployeeId, int LeaveTypeId, string? LeaveTypeName, int Year,
decimal EntitledDays, decimal TakenDays, decimal CarriedForwardDays, decimal AdjustmentDays, decimal RemainingDays);
public sealed class LeaveBalanceAdjustmentItem
{
[Required] public int LeaveTypeId { get; set; }
public decimal AdjustmentDays { get; set; }
}
public sealed class UpdateLeaveBalancesRequest
{
[Required] public int Year { get; set; }
[Required] public List<LeaveBalanceAdjustmentItem> Items { get; set; } = new();
}
+111
View File
@@ -0,0 +1,111 @@
using System.ComponentModel.DataAnnotations;
using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Hrm;
// Narrow DTOs for the five HRM org masters — server-controlled fields (status,
// ids, timestamps) excluded from create/update requests (02-SECURITY B.6/C.1).
public sealed record BranchDto(
int BranchId, string Code, string Name, string? Address, EntityStatus Status,
DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateBranchRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[StringLength(500)] public string? Address { get; set; }
}
public sealed class UpdateBranchRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[StringLength(500)] public string? Address { get; set; }
}
public sealed record DepartmentDto(
int DepartmentId, string Code, string Name, int? ParentDepartmentId, int? HeadEmployeeId,
int? BranchId, EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateDepartmentRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
public int? ParentDepartmentId { get; set; }
public int? HeadEmployeeId { get; set; }
public int? BranchId { get; set; }
}
public sealed class UpdateDepartmentRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
public int? ParentDepartmentId { get; set; }
public int? HeadEmployeeId { get; set; }
public int? BranchId { get; set; }
}
public sealed record DesignationDto(
int DesignationId, string Code, string Name, EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateDesignationRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
}
public sealed class UpdateDesignationRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
}
public sealed record EmploymentTypeDto(
int EmploymentTypeId, string Code, string Name, EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateEmploymentTypeRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
}
public sealed class UpdateEmploymentTypeRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
}
public sealed record WorkShiftDto(
int WorkShiftId, string Code, string Name, TimeSpan StartTime, TimeSpan EndTime, bool IsOvernight,
int GraceMinutes, int BreakMinutes, int StandardWorkingMinutes, decimal OtMultiplier, int WorkingDaysMask,
EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateWorkShiftRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[Required] public TimeSpan StartTime { get; set; }
[Required] public TimeSpan EndTime { get; set; }
public bool IsOvernight { get; set; }
[Range(0, 240)] public int GraceMinutes { get; set; } = 15;
[Range(0, 240)] public int BreakMinutes { get; set; } = 60;
[Range(1, 1440)] public int StandardWorkingMinutes { get; set; } = 480;
[Range(1, 5)] public decimal OtMultiplier { get; set; } = 1.5m;
[Range(0, 127)] public int WorkingDaysMask { get; set; } = 0b0111111;
}
public sealed class UpdateWorkShiftRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[Required] public TimeSpan StartTime { get; set; }
[Required] public TimeSpan EndTime { get; set; }
public bool IsOvernight { get; set; }
[Range(0, 240)] public int GraceMinutes { get; set; }
[Range(0, 240)] public int BreakMinutes { get; set; }
[Range(1, 1440)] public int StandardWorkingMinutes { get; set; }
[Range(1, 5)] public decimal OtMultiplier { get; set; }
[Range(0, 127)] public int WorkingDaysMask { get; set; }
}
/// <summary>Shared by all five masters' PATCH .../status endpoints.</summary>
public sealed class UpdateHrMasterStatusRequest
{
[Required, EnumDataType(typeof(EntityStatus))] public EntityStatus Status { get; set; }
}
+130
View File
@@ -0,0 +1,130 @@
using System.ComponentModel.DataAnnotations;
using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Hrm;
// --- Salary components ---
public sealed record SalaryComponentDto(
int SalaryComponentId, string Code, string Name, SalaryComponentType ComponentType,
bool IsTaxable, bool IsEpfEtfApplicable, EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
public sealed class CreateSalaryComponentRequest
{
[Required, StringLength(20)] public string Code { get; set; } = string.Empty;
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
[Required, EnumDataType(typeof(SalaryComponentType))] public SalaryComponentType ComponentType { get; set; }
public bool IsTaxable { get; set; }
public bool IsEpfEtfApplicable { get; set; }
}
public sealed class UpdateSalaryComponentRequest
{
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
public bool IsTaxable { get; set; }
public bool IsEpfEtfApplicable { get; set; }
}
// --- Salary structure ---
public sealed record EmployeeSalaryStructureLineDto(int SalaryComponentId, string? SalaryComponentName, decimal Amount);
public sealed record EmployeeSalaryStructureDto(
int EmployeeSalaryStructureId, int EmployeeId, DateTime EffectiveFrom, DateTime? EffectiveTo,
decimal BasicSalary, string Currency, SalaryStructureStatus Status,
List<EmployeeSalaryStructureLineDto> Lines, DateTime CreatedAt);
public sealed class SalaryStructureLineRequest
{
[Required] public int SalaryComponentId { get; set; }
[Range(0, double.MaxValue)] public decimal Amount { get; set; }
}
public sealed class CreateSalaryStructureRequest
{
[Required] public DateTime EffectiveFrom { get; set; }
[Range(0, double.MaxValue)] public decimal BasicSalary { get; set; }
public List<SalaryStructureLineRequest> Lines { get; set; } = new();
}
// --- Loans ---
public sealed record LoanInstallmentDto(
int LoanInstallmentId, int InstallmentNumber, int DueYear, int DueMonth,
decimal ScheduledAmount, decimal? PaidAmount, int? PayrollRunId, LoanInstallmentStatus Status);
public sealed record EmployeeLoanDto(
int EmployeeLoanId, string DocNo, int EmployeeId, LoanKind LoanKind, decimal PrincipalAmount,
decimal InterestRate, decimal InstallmentAmount, int NumberOfInstallments, int StartYear, int StartMonth,
decimal OutstandingBalance, LoanStatus Status, List<LoanInstallmentDto> Installments, DateTime CreatedAt);
public sealed class CreateEmployeeLoanRequest
{
[Required, EnumDataType(typeof(LoanKind))] public LoanKind LoanKind { get; set; }
[Range(0.01, double.MaxValue)] public decimal PrincipalAmount { get; set; }
[Range(0, 1)] public decimal InterestRate { get; set; }
[Range(0.01, double.MaxValue)] public decimal InstallmentAmount { get; set; }
[Range(1, 360)] public int NumberOfInstallments { get; set; }
[Range(2000, 2100)] public int StartYear { get; set; }
[Range(1, 12)] public int StartMonth { get; set; }
}
// --- Statutory settings ---
public sealed record PayrollStatutorySettingDto(
int PayrollStatutorySettingId, decimal EpfEmployeeRate, decimal EpfEmployerRate, decimal EtfEmployerRate,
decimal OtMultiplierDefault, DateTime EffectiveFrom, DateTime? EffectiveTo);
public sealed class UpsertPayrollStatutorySettingRequest
{
[Range(0, 1)] public decimal EpfEmployeeRate { get; set; } = 0.08m;
[Range(0, 1)] public decimal EpfEmployerRate { get; set; } = 0.12m;
[Range(0, 1)] public decimal EtfEmployerRate { get; set; } = 0.03m;
[Range(1, 5)] public decimal OtMultiplierDefault { get; set; } = 1.5m;
[Required] public DateTime EffectiveFrom { get; set; }
}
public sealed record TaxSlabDto(int TaxSlabId, DateTime EffectiveFrom, DateTime? EffectiveTo, decimal LowerBound, decimal? UpperBound, decimal Rate);
public sealed class CreateTaxSlabRequest
{
[Required] public DateTime EffectiveFrom { get; set; }
[Range(0, double.MaxValue)] public decimal LowerBound { get; set; }
public decimal? UpperBound { get; set; }
[Range(0, 1)] public decimal Rate { get; set; }
}
// --- Payroll run ---
public sealed record PayrollLineComponentDto(
PayrollLineComponentCategory ComponentCategory, int? SalaryComponentId, string Label, decimal Amount, int SortOrder);
public sealed record PayrollLineDto(
int PayrollLineId, int PayrollRunId, int EmployeeId, string? EmployeeCode, string? EmployeeName,
decimal BasicSalary, decimal TotalAllowances, decimal OvertimeAmount, decimal GrossSalary,
decimal LateDeductionAmount, decimal NoPayAmount, decimal LoanDeductionAmount,
decimal EpfEmployeeAmount, decimal EpfEmployerAmount, decimal EtfEmployerAmount,
decimal TaxAmount, decimal OtherDeductionsAmount, decimal NetSalary,
int WorkingDays, int PresentDays, int AbsentDays, int LeaveDays, int OtMinutesTotal, int LateMinutesTotal);
public sealed record PayrollLineDetailDto(PayrollLineDto Line, List<PayrollLineComponentDto> Components);
public sealed record PayrollRunDto(
int PayrollRunId, string DocNo, int PeriodYear, int PeriodMonth, int? BranchId, PayrollRunStatus Status,
int GeneratedBy, DateTime GeneratedAt, int? ApprovedBy, DateTime? ApprovedAt,
int? LockedBy, DateTime? LockedAt, int? UnlockedBy, DateTime? UnlockedAt, string? UnlockReason,
decimal TotalGross, decimal TotalNet, int EmployeeCount);
public sealed class GeneratePayrollRunRequest
{
[Range(2000, 2100)] public int PeriodYear { get; set; }
[Range(1, 12)] public int PeriodMonth { get; set; }
public int? BranchId { get; set; }
}
public sealed class UnlockPayrollRunRequest
{
[Required, StringLength(500)] public string Reason { get; set; } = string.Empty;
}
public sealed record PayslipDto(int PayslipId, int PayrollLineId, DateTime GeneratedAt, DateTime? ReleasedAt, int? ReleasedBy);
+26
View File
@@ -0,0 +1,26 @@
namespace ERPCore.Dtos.Hrm;
public sealed record AttendanceSummaryRowDto(
int EmployeeId, string EmployeeCode, string EmployeeName, string? DepartmentName,
int PresentDays, int AbsentDays, int LeaveDays, int HalfDays, int OtMinutesTotal, int LateMinutesTotal);
public sealed record OvertimeReportRowDto(
int EmployeeId, string EmployeeCode, string EmployeeName, DateTime AttendanceDate, int OvertimeMinutes);
public sealed record LateArrivalReportRowDto(
int EmployeeId, string EmployeeCode, string EmployeeName, DateTime AttendanceDate, int LateMinutes);
public sealed record PayrollRegisterRowDto(
int PayrollLineId, int EmployeeId, string EmployeeCode, string EmployeeName,
decimal GrossSalary, decimal TotalDeductions, decimal NetSalary);
public sealed record SalaryHistoryRowDto(
int EmployeeSalaryStructureId, DateTime EffectiveFrom, DateTime? EffectiveTo, decimal BasicSalary, string Status);
public sealed record LeaveBalanceReportRowDto(
int EmployeeId, string EmployeeCode, string EmployeeName, string LeaveTypeName,
decimal EntitledDays, decimal TakenDays, decimal RemainingDays);
public sealed record DocumentExpiryReportRowDto(
int EmployeeDocumentId, int EmployeeId, string EmployeeCode, string EmployeeName,
string DocumentTypeName, DateTime ExpiryDate, int DaysUntilExpiry);
+7 -1
View File
@@ -4,7 +4,7 @@ using ERPCore.Domain.Enums;
namespace ERPCore.Dtos.Users;
public sealed record ManagedUserDto(
int UserId, string Username, string DisplayName, EntityStatus Status,
int UserId, string Username, string DisplayName, string? Email, EntityStatus Status,
int? RoleId, string? RoleCode, string? RoleName);
/// <summary>
@@ -24,6 +24,12 @@ public sealed class CreateUserRequest
public string? MobileNumber { get; set; }
/// <summary>Left empty to auto-generate (AuthHex emails it to <see cref="Email"/>).</summary>
public string? Password { get; set; }
/// <summary>
/// Explicit, human-confirmed link to an existing unlinked Employee found via
/// email-lookup — never automatic, even on an exact email match (docs/12-BACKEND-HRM.md A.5).
/// </summary>
public int? LinkEmployeeId { get; set; }
}
public sealed class UpdateUserRoleRequest