using GB5Shared.DTO.Framework.Criteria;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.QueryExecutor;
using GB5Shared.Resource.Response;
using GB5Shared.Validation;
using Newtonsoft.Json;
using TMSDAL.DTO.TrainingEnrollment;
using TMSDAL.Enums;
using TMSDAL.Query.TrainingEnrollment;
namespace TMSDAL.CustomCode.TrainingEnrollment
{
public class TrainingEnrollmentDAL : ITrainingEnrollmentDAL
{
private readonly IQueryExecutor _QueryExecutor;
private readonly IValidation _Validation;
public TrainingEnrollmentDAL(IQueryExecutor queryExecutor, IValidation validation)
{
_QueryExecutor = queryExecutor;
_Validation = validation;
}
// ════════════════════════════════════════════════════════════════════
// READ
// ════════════════════════════════════════════════════════════════════
/// Fetch full enrollment record by its primary key.
public async Task GetTrainingEnrollment(int enrollmentId, LoginDTO loginDTO)
{
var parameters = new { EnrollmentId = enrollmentId, TenantId = loginDTO.ClientId };
return await _QueryExecutor.QuerySingleAsync(
loginDTO, TrainingEnrollmentQB.GET_ENROLLMENT, parameters);
}
///
/// Fetch an ACTIVE enrollment by unique constraint columns
/// (InstanceId + EmployeeId + TenantId).
/// Used by BLL for duplicate-enrollment detection before insert.
/// Returns null when no active enrollment exists.
/// Withdrawn / Rejected rows are excluded — matching the filtered
/// unique index — so re-enrollment after those states is permitted.
///
public async Task GetEnrollment(int instanceId, int employeeId, LoginDTO loginDTO)
{
var parameters = new
{
InstanceId = instanceId,
EmployeeId = employeeId,
TenantId = loginDTO.ClientId
};
return await _QueryExecutor.QuerySingleAsync(
loginDTO, TrainingEnrollmentQB.GET_ENROLLMENT_BY_INSTANCE_EMPLOYEE, parameters);
}
/// All enrollments for a given training instance (JSON).
public async Task GetEnrollmentsByInstance(int instanceId, LoginDTO loginDTO)
{
var parameters = new { InstanceId = instanceId, TenantId = loginDTO.ClientId };
var result = await _QueryExecutor.QueryAsync(
loginDTO, TrainingEnrollmentQB.GET_ENROLLMENTS_BY_INSTANCE, parameters);
return JsonConvert.SerializeObject(result);
}
/// All enrollments for a given employee (JSON).
public async Task GetEnrollmentsByEmployee(int employeeId, LoginDTO loginDTO)
{
var parameters = new { EmployeeId = employeeId, TenantId = loginDTO.ClientId };
var result = await _QueryExecutor.QueryAsync(
loginDTO, TrainingEnrollmentQB.GET_ENROLLMENTS_BY_EMPLOYEE, parameters);
return JsonConvert.SerializeObject(result);
}
/// Full enrollment list for the tenant (JSON).
public async Task GetEnrollmentList(LoginDTO loginDTO)
{
var parameters = new { TenantId = loginDTO.ClientId };
var result = await _QueryExecutor.QueryAsync(
loginDTO, TrainingEnrollmentQB.GET_ENROLLMENT_LIST, parameters);
return JsonConvert.SerializeObject(result);
}
/// Paged / filtered select-list for dropdowns (JSON).
public async Task GetSelectList(CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct)
{
var result = await _QueryExecutor.QueryWithCriteriaAsync(
loginDTO,
TrainingEnrollmentQB.GET_SELECTLIST_TRAINING_ENROLLMENT,
new { TenantId = loginDTO.ClientId },
criteriaDTO,
ct);
return JsonConvert.SerializeObject(result);
}
///
/// Next available waitlist position for a given instance.
/// Returns MAX(WaitlistPosition) + 1, or 1 if no waitlisted rows exist.
///
public async Task GetNextWaitlistPosition(int instanceId, LoginDTO loginDTO)
{
var parameters = new
{
InstanceId = instanceId,
WaitlistedStatus = (byte)EnrollmentStatus.Waitlisted,
TenantId = loginDTO.ClientId
};
return await _QueryExecutor.ExecuteScalarAsync(
loginDTO, TrainingEnrollmentQB.GET_NEXT_WAITLIST_POSITION, parameters);
}
// ════════════════════════════════════════════════════════════════════
// WRITE
// ════════════════════════════════════════════════════════════════════
///
/// Insert a new enrollment record.
/// Returns Task (not Task<int>) — BLL never used rows-affected.
/// Throws a formatted exception on DB error.
///
public async Task SaveTrainingEnrollment(TrainingEnrollmentDTO dto, LoginDTO loginDTO)
{
try
{
await _QueryExecutor.ExecuteAsync(loginDTO, TrainingEnrollmentQB.SAVE_ENROLLMENT, dto);
}
catch (Exception ex)
{
string error = await _Validation.HandleException(ex, ErrorResponse.SaveErrorMessage);
throw new Exception(error);
}
}
///
/// Unified Manager + HR approval / rejection.
/// ApprovalType on the DTO determines which query and column set is used.
/// Remarks are written to tier-specific columns (MANAGERREMARKS / HRREMARKS)
/// so each approval tier has its own audit trail.
///
public async Task UpdateApproval(ApprovalUpdateDTO approvalDto, LoginDTO loginDTO)
{
try
{
var now = DateTime.UtcNow;
(object parameters, string query) = approvalDto.ApprovalType switch
{
ApprovalType.Manager => (
(object)new
{
EnrollmentId = approvalDto.EnrollmentId,
EnrollmentStatus = (byte)approvalDto.Status,
ManagerApprovalId = approvalDto.ApprovalId,
ManagerRemarks = approvalDto.Remarks,
ManagerApprovedOn = now,
ModifiedById = loginDTO.UserId,
ModifiedOn = now,
TenantId = loginDTO.ClientId
},
TrainingEnrollmentQB.UPDATE_MANAGER_APPROVAL
),
ApprovalType.HR => (
(object)new
{
EnrollmentId = approvalDto.EnrollmentId,
EnrollmentStatus = (byte)approvalDto.Status,
HRApprovalId = approvalDto.ApprovalId,
HRRemarks = approvalDto.Remarks,
HRApprovedOn = now,
ModifiedById = loginDTO.UserId,
ModifiedOn = now,
TenantId = loginDTO.ClientId
},
TrainingEnrollmentQB.UPDATE_HR_APPROVAL
),
_ => throw new ArgumentOutOfRangeException(
nameof(approvalDto.ApprovalType),
$"Unsupported ApprovalType: {approvalDto.ApprovalType}")
};
return await _QueryExecutor.ExecuteAsync(loginDTO, query, parameters);
}
catch (ArgumentOutOfRangeException)
{
// Programming error — not a DB error; bypass _Validation.HandleException
throw;
}
catch (Exception ex)
{
string error = await _Validation.HandleException(ex, ErrorResponse.UpdateErrorMessage);
throw new Exception(error);
}
}
///
/// Withdraw an enrollment.
/// Status is always EnrollmentStatus.Withdrawn — enforced by BLL.
/// Records WithdrawalReason and WithdrawnOn timestamp.
///
public async Task WithdrawEnrollment(int enrollmentId, byte status, string? reason, LoginDTO loginDTO)
{
try
{
var parameters = new
{
EnrollmentId = enrollmentId,
EnrollmentStatus = status,
WithdrawalReason = reason,
WithdrawnOn = DateTime.UtcNow,
ModifiedById = loginDTO.UserId,
ModifiedOn = DateTime.UtcNow,
TenantId = loginDTO.ClientId
};
return await _QueryExecutor.ExecuteAsync(
loginDTO, TrainingEnrollmentQB.WITHDRAW_ENROLLMENT, parameters);
}
catch (Exception ex)
{
string error = await _Validation.HandleException(ex, ErrorResponse.UpdateErrorMessage);
throw new Exception(error);
}
}
///
/// Soft-delete: sets STATUS = 0.
/// Hard delete not permitted — enrollment history must be preserved.
///
public async Task DeleteTrainingEnrollment(int enrollmentId, LoginDTO loginDTO)
{
try
{
// FIX: ModifiedById / ModifiedOn now passed for audit trail on soft-delete
var parameters = new
{
EnrollmentId = enrollmentId,
ModifiedById = loginDTO.UserId,
ModifiedOn = DateTime.UtcNow,
TenantId = loginDTO.ClientId
};
return await _QueryExecutor.ExecuteAsync(
loginDTO, TrainingEnrollmentQB.DELETE_ENROLLMENT, parameters);
}
catch (Exception ex)
{
string error = await _Validation.HandleException(ex, ErrorResponse.DeleteErrorMessage);
throw new Exception(error);
}
}
}
}