using System; using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using RecruitmentDAL.DTO.InterviewScorecard; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using RecruitmentDAL.CustomeCode.InterviewScorecard; using static GB5Shared.GB5Constant.Constant; namespace RecruitmentBLL.InterviewScorecard { public class InterviewScorecardBLL : IInterviewScorecardBLL { // RATING (TINTERVIEWSCORECARDDETAIL): 1-5 scale. The DB CHECK constraint // (CK_TINTERVIEWSCORECARDDETAIL_RATING) already enforces this — validated again here so a // bad rating fails with a clean BLL message instead of a raw SQL constraint error reaching // the client. private const byte RATING_MIN = 1; private const byte RATING_MAX = 5; private readonly IInterviewScorecardDAL _InterviewScorecardDAL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly KeyInvalidate _KeyInvalidate; private readonly BaseEntityAppService _BaseEntityAppService; private readonly BaseEntityAppService _BaseEntityAppServiceDetail; private readonly ILogger _Logger; public InterviewScorecardBLL( IInterviewScorecardDAL interviewScorecardDAL, AutoNumber autoNumber, IValidation validation, KeyInvalidate keyInvalidate, BaseEntityAppService baseEntityAppService, BaseEntityAppService baseEntityAppServiceDetail, ILogger logger) { _InterviewScorecardDAL = interviewScorecardDAL; _AutoNumber = autoNumber; _Validation = validation; _KeyInvalidate = keyInvalidate; _BaseEntityAppService = baseEntityAppService; _BaseEntityAppServiceDetail = baseEntityAppServiceDetail; _Logger = logger; } #region Get public async Task GetInterviewScorecard(int InterviewScorecardId, LoginDTO LoginDTO, CancellationToken ct) { try { return await _InterviewScorecardDAL.GetInterviewScorecard(InterviewScorecardId, LoginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-interviewscorecard-failed", ex); _Logger.LogError(ex, "GetInterviewScorecard failed for InterviewScorecardId {InterviewScorecardId}", InterviewScorecardId); throw; } } #endregion #region Get By Interview public async Task GetInterviewScorecardByInterview(int InterviewId, LoginDTO LoginDTO, CancellationToken ct) { try { return await _InterviewScorecardDAL.GetInterviewScorecardByInterview(InterviewId, LoginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-interviewscorecard-by-interview-failed", ex); _Logger.LogError(ex, "GetInterviewScorecardByInterview failed for InterviewId {InterviewId}", InterviewId); throw; } } #endregion #region Save public async Task SaveInterviewScorecard(InterviewScorecardDTO InterviewScorecardDTO, LoginDTO LoginDTO, CancellationToken ct) { if (InterviewScorecardDTO == null) throw new ArgumentNullException(nameof(InterviewScorecardDTO)); AutoNumberDTO? autoNumberDTO = null; try { GB5Trace.Step("validate-interviewscorecard", new { InterviewScorecardDTO.InterviewScorecardId }); if (InterviewScorecardDTO.InterviewId <= 0) { throw new ValidationException( $"InterviewId is required. Received: {InterviewScorecardDTO.InterviewId}."); } if (InterviewScorecardDTO.InterviewerId <= 0) { throw new ValidationException( $"InterviewerId is required. Received: {InterviewScorecardDTO.InterviewerId}."); } bool isNew = InterviewScorecardDTO.InterviewScorecardId == 0; InterviewScorecardDTO.ModifiedById = LoginDTO.UserId; InterviewScorecardDTO.ModifiedOn = DateTime.UtcNow; InterviewScorecardDTO.TenantId = LoginDTO.ClientId; if (isNew) { autoNumberDTO = await _AutoNumber.GetNumberAsync( 1, "INTERVIEWSCORECARD", LoginDTO).ConfigureAwait(false); InterviewScorecardDTO.InterviewScorecardId = autoNumberDTO.StartNumber; InterviewScorecardDTO.CreatedById = LoginDTO.UserId; InterviewScorecardDTO.CreatedOn = DateTime.UtcNow; if (InterviewScorecardDTO.Status == 0) InterviewScorecardDTO.Status = 1; } GB5Trace.Step("save-interviewscorecard", new { InterviewScorecardDTO.InterviewScorecardId, isNew }); await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTRECRUITMENTINTERVIEWSCORECARD, isNew ? EventTypeConstant.SAVERECRUITMENTINTERVIEWSCORECARDEVENTTYPEID : EventTypeConstant.UPDATERECRUITMENTINTERVIEWSCORECARDEVENTTYPEID, InterviewScorecardDTO, LoginDTO, async tx => { _ = isNew ? await _InterviewScorecardDAL.SaveInterviewScorecard(InterviewScorecardDTO, LoginDTO, tx, ct).ConfigureAwait(false) : await _InterviewScorecardDAL.UpdateInterviewScorecard(InterviewScorecardDTO, LoginDTO, tx, ct).ConfigureAwait(false); return InterviewScorecardDTO.InterviewScorecardId; }, null, -1, -1, null, isNew).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = isNew ? EventTypeConstant.SAVERECRUITMENTINTERVIEWSCORECARDEVENTTYPEID : EventTypeConstant.UPDATERECRUITMENTINTERVIEWSCORECARDEVENTTYPEID }); var cacheKey = new CacheKeyGeneration().KeyGeneration( InterviewScorecardDTO.InterviewScorecardId, EntityConstant.OBJECTRECRUITMENTINTERVIEWSCORECARD, CacheKeyLevel.CLIENT_LEVEL, LoginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation( "InterviewScorecard {InterviewScorecardId} {Action} by user {UserId}", InterviewScorecardDTO.InterviewScorecardId, isNew ? "saved" : "updated", LoginDTO.UserId); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {InterviewScorecardDTO.InterviewScorecardId}" : $"{SuccessResponse.UpdateSuccessMessage} {InterviewScorecardDTO.InterviewScorecardId}"; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-interviewscorecard-failed", vex); if (autoNumberDTO != null && InterviewScorecardDTO.InterviewScorecardId > 0) await _AutoNumber.RollbackAutoNumber("INTERVIEWSCORECARD", InterviewScorecardDTO.InterviewScorecardId, LoginDTO).ConfigureAwait(false); throw new Exception(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-interviewscorecard-failed", ex); _Logger.LogError(ex, "SaveInterviewScorecard failed for InterviewScorecardId {InterviewScorecardId}", InterviewScorecardDTO.InterviewScorecardId); if (autoNumberDTO != null && InterviewScorecardDTO.InterviewScorecardId > 0) { await _AutoNumber.RollbackAutoNumber( "INTERVIEWSCORECARD", InterviewScorecardDTO.InterviewScorecardId, LoginDTO).ConfigureAwait(false); } throw; } } #endregion #region Delete public async Task DeleteInterviewScorecard(int InterviewScorecardId, LoginDTO LoginDTO, CancellationToken ct) { try { GB5Trace.Step("delete-interviewscorecard", new { InterviewScorecardId }); var result = await _InterviewScorecardDAL.DeleteInterviewScorecard(InterviewScorecardId, LoginDTO, ct) .ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( InterviewScorecardId, EntityConstant.OBJECTRECRUITMENTINTERVIEWSCORECARD, CacheKeyLevel.CLIENT_LEVEL, LoginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation("InterviewScorecard {InterviewScorecardId} deleted by user {UserId}", InterviewScorecardId, LoginDTO.UserId); return result; } catch (Exception ex) { GB5Trace.MarkFailed("delete-interviewscorecard-failed", ex); _Logger.LogError(ex, "DeleteInterviewScorecard failed for InterviewScorecardId {InterviewScorecardId}", InterviewScorecardId); throw; } } #endregion #region Get public async Task GetInterviewScorecardDetail(int InterviewScorecardDetailId, LoginDTO LoginDTO, CancellationToken ct) { try { return await _InterviewScorecardDAL.GetInterviewScorecardDetail(InterviewScorecardDetailId, LoginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-interviewscorecarddetail-failed", ex); _Logger.LogError(ex, "GetInterviewScorecardDetail failed for InterviewScorecardDetailId {InterviewScorecardDetailId}", InterviewScorecardDetailId); throw; } } #endregion #region Save public async Task SaveInterviewScorecardDetail(InterviewScorecardDetailDTO InterviewScorecardDetailDTO, LoginDTO LoginDTO, CancellationToken ct) { if (InterviewScorecardDetailDTO == null) throw new ArgumentNullException(nameof(InterviewScorecardDetailDTO)); AutoNumberDTO? autoNumberDetailDTO = null; try { GB5Trace.Step("validate-interviewscorecarddetail", new { InterviewScorecardDetailDTO.InterviewScorecardDetailId }); if (InterviewScorecardDetailDTO.InterviewScorecardId <= 0) { throw new ValidationException( $"InterviewScorecardId is required. Received: {InterviewScorecardDetailDTO.InterviewScorecardId}."); } if (InterviewScorecardDetailDTO.Rating < RATING_MIN || InterviewScorecardDetailDTO.Rating > RATING_MAX) { throw new ValidationException( $"Rating must be between {RATING_MIN} and {RATING_MAX}. Received: {InterviewScorecardDetailDTO.Rating}."); } bool isNew = InterviewScorecardDetailDTO.InterviewScorecardDetailId == 0; InterviewScorecardDetailDTO.ModifiedById = LoginDTO.UserId; InterviewScorecardDetailDTO.ModifiedOn = DateTime.UtcNow; InterviewScorecardDetailDTO.TenantId = LoginDTO.ClientId; if (isNew) { autoNumberDetailDTO = await _AutoNumber.GetNumberAsync( 1, "INTERVIEWSCORECARDDETAIL", LoginDTO).ConfigureAwait(false); InterviewScorecardDetailDTO.InterviewScorecardDetailId = autoNumberDetailDTO.StartNumber; InterviewScorecardDetailDTO.CreatedById = LoginDTO.UserId; InterviewScorecardDetailDTO.CreatedOn = DateTime.UtcNow; if (InterviewScorecardDetailDTO.Status == 0) InterviewScorecardDetailDTO.Status = 1; } GB5Trace.Step("save-interviewscorecarddetail", new { InterviewScorecardDetailDTO.InterviewScorecardDetailId, isNew }); await _BaseEntityAppServiceDetail.ExecuteSaveAsync( EntityConstant.OBJECTRECRUITMENTINTERVIEWSCORECARDDETAIL, isNew ? EventTypeConstant.SAVERECRUITMENTINTERVIEWSCORECARDDETAILEVENTTYPEID : EventTypeConstant.UPDATERECRUITMENTINTERVIEWSCORECARDDETAILEVENTTYPEID, InterviewScorecardDetailDTO, LoginDTO, async tx => { _ = isNew ? await _InterviewScorecardDAL.SaveInterviewScorecardDetail(InterviewScorecardDetailDTO, LoginDTO, tx, ct).ConfigureAwait(false) : await _InterviewScorecardDAL.UpdateInterviewScorecardDetail(InterviewScorecardDetailDTO, LoginDTO, tx, ct).ConfigureAwait(false); return InterviewScorecardDetailDTO.InterviewScorecardDetailId; }, null, -1, -1, null, isNew).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = isNew ? EventTypeConstant.SAVERECRUITMENTINTERVIEWSCORECARDDETAILEVENTTYPEID : EventTypeConstant.UPDATERECRUITMENTINTERVIEWSCORECARDDETAILEVENTTYPEID }); var cacheKey = new CacheKeyGeneration().KeyGeneration( InterviewScorecardDetailDTO.InterviewScorecardDetailId, EntityConstant.OBJECTRECRUITMENTINTERVIEWSCORECARDDETAIL, CacheKeyLevel.CLIENT_LEVEL, LoginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation( "InterviewScorecardDetail {InterviewScorecardDetailId} {Action} by user {UserId}", InterviewScorecardDetailDTO.InterviewScorecardDetailId, isNew ? "saved" : "updated", LoginDTO.UserId); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {InterviewScorecardDetailDTO.InterviewScorecardDetailId}" : $"{SuccessResponse.UpdateSuccessMessage} {InterviewScorecardDetailDTO.InterviewScorecardDetailId}"; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-interviewscorecarddetail-failed", vex); if (autoNumberDetailDTO != null && InterviewScorecardDetailDTO.InterviewScorecardDetailId > 0) await _AutoNumber.RollbackAutoNumber("INTERVIEWSCORECARDDETAIL", InterviewScorecardDetailDTO.InterviewScorecardDetailId, LoginDTO).ConfigureAwait(false); throw new Exception(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-interviewscorecarddetail-failed", ex); _Logger.LogError(ex, "SaveInterviewScorecardDetail failed for InterviewScorecardDetailId {InterviewScorecardDetailId}", InterviewScorecardDetailDTO.InterviewScorecardDetailId); if (autoNumberDetailDTO != null && InterviewScorecardDetailDTO.InterviewScorecardDetailId > 0) { await _AutoNumber.RollbackAutoNumber( "INTERVIEWSCORECARDDETAIL", InterviewScorecardDetailDTO.InterviewScorecardDetailId, LoginDTO).ConfigureAwait(false); } throw; } } #endregion #region Delete public async Task DeleteInterviewScorecardDetail(int InterviewScorecardDetailId, LoginDTO LoginDTO, CancellationToken ct) { try { GB5Trace.Step("delete-interviewscorecarddetail", new { InterviewScorecardDetailId }); var result = await _InterviewScorecardDAL.DeleteInterviewScorecardDetail(InterviewScorecardDetailId, LoginDTO, ct) .ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( InterviewScorecardDetailId, EntityConstant.OBJECTRECRUITMENTINTERVIEWSCORECARDDETAIL, CacheKeyLevel.CLIENT_LEVEL, LoginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation("InterviewScorecardDetail {InterviewScorecardDetailId} deleted by user {UserId}", InterviewScorecardDetailId, LoginDTO.UserId); return result; } catch (Exception ex) { GB5Trace.MarkFailed("delete-interviewscorecarddetail-failed", ex); _Logger.LogError(ex, "DeleteInterviewScorecardDetail failed for InterviewScorecardDetailId {InterviewScorecardDetailId}", InterviewScorecardDetailId); throw; } } #endregion } }