using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using PIEDAL.CustomCode.Proposal; using PIEDAL.DTO.Proposal; using static GB5Shared.GB5Constant.Constant; namespace PIEBLL.Proposal; public class PIEProposalBLL : IPIEProposalBLL { private readonly IPIEProposalDAL _PIEProposalDAL; private readonly IQueryExecutor _QueryExecutor; private readonly BaseEntityAppService _BaseEntityAppService; public PIEProposalBLL( IPIEProposalDAL pieProposalDAL, IQueryExecutor queryExecutor, BaseEntityAppService baseEntityAppService) { _PIEProposalDAL = pieProposalDAL; _QueryExecutor = queryExecutor; _BaseEntityAppService = baseEntityAppService; } // ── Read ────────────────────────────────────────────────────────────────── public async Task GetPIEProposalAsync( Guid proposalId, LoginDTO login, CancellationToken ct) => await _PIEProposalDAL.GetPIEProposalAsync(proposalId, login, ct).ConfigureAwait(false); // ── Writes ──────────────────────────────────────────────────────────────── public async Task CreatePIEProposalAsync( PIELearningProposalDTO dto, LoginDTO login, CancellationToken ct) { if (dto is null) throw new ArgumentNullException(nameof(dto)); if (dto.ProposalType is not (1 or 2 or 3)) throw new ArgumentException("ProposalType must be 1 (AEO), 2 (Manual), or 3 (Scheduled)."); if (string.IsNullOrWhiteSpace(dto.ProposalDescription)) throw new ArgumentException("ProposalDescription is required."); if (dto.ProposalId == Guid.Empty) dto.ProposalId = Guid.NewGuid(); var Trans = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTPIEPROPOSAL, EventTypeConstant.SAVEPIEPROPOSALEVENTTYPEID, dto, login, async tx => { await _PIEProposalDAL.InsertPIEProposalAsync(dto, login, tx, ct); return 0; // PIEProposal uses a Guid PK — no int object ID to report back }, null, BIZTRANSACTIONCLASSCONSTANT.PIEPROPOSAL, BizTransactionConstant.PIEPROPOSAL, Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); return SuccessResponse.SaveSuccess; } catch (Exception ex) { GB5Shared.Telemetry.GB5Trace.MarkFailed("create-pie-proposal-failed", ex); await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); throw; } } public async Task ReviewPIEProposalAsync( PIEReviewProposalDTO dto, LoginDTO login, CancellationToken ct) { if (dto is null) throw new ArgumentNullException(nameof(dto)); // Status 2=Approved 3=Rejected 4=Deferred if (dto.Status is not (2 or 3 or 4)) throw new ArgumentException("Review status must be 2 (Approved), 3 (Rejected), or 4 (Deferred)."); // Fetch to confirm it still exists and is pending (status check is in QB) var existing = await _PIEProposalDAL.GetPIEProposalAsync(dto.ProposalId, login, ct).ConfigureAwait(false); if (existing is null) throw new InvalidOperationException("Learning proposal not found."); if (existing.Status != 1) throw new InvalidOperationException( "Only Pending proposals (status=1) can be reviewed."); var Trans = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTPIEPROPOSAL, EventTypeConstant.REVIEWPIEPROPOSALEVENTTYPEID, new PIELearningProposalDTO { ProposalId = dto.ProposalId }, login, async tx => { await _PIEProposalDAL.ReviewPIEProposalAsync( dto, login.UserId, login, tx, ct); return 0; }, null, BIZTRANSACTIONCLASSCONSTANT.PIEPROPOSAL, BizTransactionConstant.PIEPROPOSAL, Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Shared.Telemetry.GB5Trace.MarkFailed("review-pie-proposal-failed", ex); await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); throw; } } public async Task DeletePIEProposalAsync( Guid proposalId, LoginDTO login, CancellationToken ct) { var Trans = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTPIEPROPOSAL, EventTypeConstant.SAVEPIEPROPOSALEVENTTYPEID, new PIELearningProposalDTO { ProposalId = proposalId }, login, async tx => { await _PIEProposalDAL.DeletePIEProposalAsync(proposalId, login, tx, ct); return 0; }, null, BIZTRANSACTIONCLASSCONSTANT.PIEPROPOSAL, BizTransactionConstant.PIEPROPOSAL, Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Shared.Telemetry.GB5Trace.MarkFailed("delete-pie-proposal-failed", ex); await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); throw; } } }