using System; using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using RecruitmentDAL.DTO.ProfileGenerator; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using RecruitmentDAL.CustomeCode.ProfileGenerator; using static GB5Shared.GB5Constant.Constant; namespace RecruitmentBLL.ProfileGenerator { public class ProfileGeneratorBLL : IProfileGeneratorBLL { private readonly IProfileGeneratorDAL _ProfileGeneratorDAL; private readonly IValidation _Validation; private readonly IQueryExecutor _QueryExecutor; private readonly BaseEntityAppService _BaseEntityAppService; private readonly ILogger _Logger; public ProfileGeneratorBLL( IProfileGeneratorDAL profileGeneratorDAL, IValidation validation, IQueryExecutor queryExecutor, BaseEntityAppService baseEntityAppService, ILogger logger) { _ProfileGeneratorDAL = profileGeneratorDAL; _Validation = validation; _QueryExecutor = queryExecutor; _BaseEntityAppService = baseEntityAppService; _Logger = logger; } #region Get public async Task GetProfileGenerator(int ProfileGeneratorId, LoginDTO LoginDTO, CancellationToken ct) { try { return await _ProfileGeneratorDAL .GetProfileGenerator(ProfileGeneratorId, LoginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-profilegenerator-failed", ex); _Logger.LogError(ex, "GetProfileGenerator failed for ProfileGeneratorId {ProfileGeneratorId}", ProfileGeneratorId); throw; } } #endregion #region Save public async Task SaveProfileGenerator(ProfileGeneratorDTO ProfileGeneratorDTO, LoginDTO LoginDTO, CancellationToken ct) { if (ProfileGeneratorDTO == null) throw new ArgumentNullException(nameof(ProfileGeneratorDTO)); bool isNew = ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId == 0; var Trans = await _QueryExecutor.BeginTransactionAsync(LoginDTO).ConfigureAwait(false); try { GB5Trace.Step("validate-profilegenerator", new { ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId }); await _Validation.NotEmpty( ProfileGeneratorDTO.ProfileGeneratorJobDescription, nameof(ProfileGeneratorDTO.ProfileGeneratorJobDescription)).ConfigureAwait(false); ProfileGeneratorDTO.ProfileGeneratorModifiedById = LoginDTO.UserId; ProfileGeneratorDTO.ProfileGeneratorModifiedOn = DateTime.UtcNow; if (isNew) { ProfileGeneratorDTO.ProfileGeneratorCreatedById = LoginDTO.UserId; ProfileGeneratorDTO.ProfileGeneratorCreatedOn = DateTime.UtcNow; } int eventTypeId = isNew ? EventTypeConstant.SAVERECRUITMENTPROFILEGENERATOREVENTTYPEID : EventTypeConstant.UPDATERECRUITMENTPROFILEGENERATOREVENTTYPEID; GB5Trace.Step("save-profilegenerator", new { ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId, isNew }); // Routes through the universal ExecuteSaveAsync pipeline (validation/workflow // hooks, Dapr event publish, outbox write) instead of a hand-rolled tx block. // persistFunc returns the real row id: for INSERT that's the IDENTITY value // ProfileGeneratorDAL.SaveProfileGenerator reads back via ExecuteIdentityAsync // (SAVE_PROFILEGENERATOR's INSERT never lists PROFILEGENERATORID — the DB // assigns it); for UPDATE it's simply the existing id already on the DTO. await _BaseEntityAppService.ExecuteSaveAsync( entityId: EntityConstant.OBJECTRECRUITMENTPROFILEGENERATOR, eventTypeId: eventTypeId, dto: ProfileGeneratorDTO, login: LoginDTO, persistFunc: async tx => { if (isNew) { int newId = await _ProfileGeneratorDAL .SaveProfileGenerator(ProfileGeneratorDTO, LoginDTO, tx, ct) .ConfigureAwait(false); ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId = newId; return newId; } await _ProfileGeneratorDAL .UpdateProfileGenerator(ProfileGeneratorDTO, LoginDTO, tx, ct) .ConfigureAwait(false); return ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId; }, externalTransaction: Trans, isNewEntity: isNew).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = eventTypeId }); _Logger.LogInformation( "ProfileGenerator {ProfileGeneratorId} {Action} by user {UserId}", ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId, isNew ? "created" : "updated", LoginDTO.UserId); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId}" : $"{SuccessResponse.UpdateSuccessMessage} {ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId}"; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-profilegenerator-failed", vex); await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); _Logger.LogWarning(vex, "SaveProfileGenerator validation failed for ProfileGeneratorId {ProfileGeneratorId}", ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId); // ArgumentException maps to HTTP 400 in BaseEndpoint — a validation failure is a // caller-input problem, not a server fault. throw new ArgumentException(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-profilegenerator-failed", ex); await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); _Logger.LogError(ex, "SaveProfileGenerator failed for ProfileGeneratorId {ProfileGeneratorId}", ProfileGeneratorDTO.ProfileGeneratorProfileGeneratorId); throw; } } #endregion #region Select List public async Task GetSelectListProfileGenerator(CriteriaDTO criteriaDTO, LoginDTO LoginDTO, CancellationToken ct) { try { return await _ProfileGeneratorDAL .GetSelectListProfileGenerator(criteriaDTO, LoginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-selectlist-profilegenerator-failed", ex); _Logger.LogError(ex, "GetSelectListProfileGenerator failed"); throw; } } #endregion #region Delete public async Task DeleteProfileGenerator(int ProfileGeneratorId, LoginDTO LoginDTO, CancellationToken ct) { var Trans = await _QueryExecutor.BeginTransactionAsync(LoginDTO).ConfigureAwait(false); int rowsAffected = 0; try { await _BaseEntityAppService.ExecuteSaveAsync( entityId: EntityConstant.OBJECTRECRUITMENTPROFILEGENERATOR, eventTypeId: EventTypeConstant.DELETERECRUITMENTPROFILEGENERATOREVENTTYPEID, dto: new ProfileGeneratorDTO { ProfileGeneratorProfileGeneratorId = ProfileGeneratorId }, login: LoginDTO, persistFunc: async tx => { rowsAffected = await _ProfileGeneratorDAL .DeleteProfileGenerator(ProfileGeneratorId, LoginDTO, tx, ct) .ConfigureAwait(false); return ProfileGeneratorId; }, externalTransaction: Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); if (rowsAffected <= 0) { _Logger.LogWarning( "DeleteProfileGenerator found no row for ProfileGeneratorId {ProfileGeneratorId}", ProfileGeneratorId); } GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.DELETERECRUITMENTPROFILEGENERATOREVENTTYPEID }); _Logger.LogInformation( "ProfileGenerator {ProfileGeneratorId} deleted by user {UserId}", ProfileGeneratorId, LoginDTO.UserId); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-profilegenerator-failed", ex); await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); _Logger.LogError(ex, "DeleteProfileGenerator failed for ProfileGeneratorId {ProfileGeneratorId}", ProfileGeneratorId); throw; } } #endregion } }