using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Validation; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using TMSDAL.CustomCode.AssesmentQuestion; using TMSDAL.DTO.AssessmentQuestion; using static GB5Shared.GB5Constant.Constant; namespace TMSBLL.AssessmentQuestion { /// /// BLL for MASSESSMENTQUESTION. /// Handles save, update, delete and retrieval of assessment questions /// that belong to a MSESSIONASSESSMENT (Simple mode only). /// /// FIXES FROM ORIGINAL (copy-paste from TrainerBLL — never updated): /// [1] All Trainer field references removed (TrainerCode, TrainerFullName, /// TrainerType, EmployeeId, ExternalVendorId, TrainerId) — wrong entity. /// [2] Validation rewritten for AssessmentQuestion fields. /// [3] AutoNumber key changed from "AssesmentQuestion" → "ASSESSMENTQUESTION" /// (consistent uppercase convention used across the codebase). /// [4] isNew declared ABOVE try so catch block can access it for rollback. /// [5] Rollback condition fixed: fires on autoNumberDTO != null only — /// removed wrong "dto.AssessmentQuestionId > 0" check. /// [6] ValidationException now re-thrown directly (not wrapped in Exception). /// [7] Cache key uses AssessmentQuestionId not TrainerId. /// [8] Empty try/catch { throw; } wrappers replaced with direct awaits. /// [9] CancellationToken added to GET methods for consistency. /// [10] Using directives cleaned up (removed unused StringBuilder, List, Linq). /// public class AssessmentQuestionBLL : IAssessmentQuestionBLL { private readonly IAssessmentQuestionDAL _AssessmentQuestionDAL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly KeyInvalidate _KeyInvalidate; public AssessmentQuestionBLL( IAssessmentQuestionDAL assessmentQuestionDAL, AutoNumber autoNumber, IValidation validation, KeyInvalidate keyInvalidate) { _AssessmentQuestionDAL = assessmentQuestionDAL; _AutoNumber = autoNumber; _Validation = validation; _KeyInvalidate = keyInvalidate; } // ── GET single ──────────────────────────────────────────────────────── // [FIX-8] Empty try/catch { throw; } removed — no value, extra stack frame public Task GetAssessmentQuestion(int assessmentQuestionId, LoginDTO loginDTO) => _AssessmentQuestionDAL.GetAssesmentQuestion(assessmentQuestionId, loginDTO); // ── GET list / picklist ─────────────────────────────────────────────── public Task GetSelectList(CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct) => _AssessmentQuestionDAL.GetSelectList(criteriaDTO, loginDTO, ct); public Task GetSelectListAssessmentQuestion(LoginDTO loginDTO) => _AssessmentQuestionDAL.GetSelectListAssesmentQuestion(loginDTO); // ── SAVE / UPDATE ───────────────────────────────────────────────────── /// /// Inserts or updates an AssessmentQuestion record. /// /// Insert: AssessmentQuestionId == 0 /// Update: AssessmentQuestionId > 0 /// /// Validation rules: /// - AssessmentId required (> 0) /// - QuestionText required (not empty) /// - CorrectAnswer required (not empty) /// - QuestionType must be 0–5 /// - Marks must be > 0 /// - OptionsJson required for types that need options (MCQ=0, MultiSelect=3, Matching=4, Ordering=5) /// public async Task SaveAssessmentQuestion(AssessmentQuestionDTO dto, LoginDTO loginDTO) { if (dto == null) throw new ArgumentNullException(nameof(dto)); // [FIX-4] Declared ABOVE try — catch block must read isNew for rollback bool isNew = dto.AssessmentQuestionId == 0; AutoNumberDTO? autoNumberDTO = null; try { // ── VALIDATION ──────────────────────────────────────────────── // AssessmentId — must reference a valid parent assessment if (dto.AssessmentId == 0) throw new ValidationException("AssessmentId is required."); // QuestionText — mandatory await _Validation.NotEmpty(dto.QuestionText, nameof(dto.QuestionText)); // CorrectAnswer — mandatory await _Validation.NotEmpty(dto.CorrectAnswer, nameof(dto.CorrectAnswer)); // QuestionType — CHECK constraint IN (0,1,2,3,4,5) if (dto.QuestionType > 5) throw new ValidationException("QuestionType must be 0 (MCQ), 1 (TrueFalse), 2 (ShortAnswer), 3 (MultiSelect), 4 (Matching) or 5 (Ordering)."); // Marks — must be positive if (dto.Marks <= 0) throw new ValidationException("Marks must be greater than 0."); // OptionsJson — required for question types that need answer options bool requiresOptions = dto.QuestionType == 0 // MCQ || dto.QuestionType == 3 // MultiSelect || dto.QuestionType == 4 // Matching || dto.QuestionType == 5; // Ordering if (requiresOptions && string.IsNullOrWhiteSpace(dto.OptionsJson)) throw new ValidationException( $"OptionsJson is required for QuestionType {dto.QuestionType} " + "(MCQ, MultiSelect, Matching, Ordering)."); // SlNo — default to 1 if not provided if (dto.SlNo <= 0) dto.SlNo = 1; // ── ID GENERATION + SAVE ────────────────────────────────────── string message; if (isNew) { // [FIX-3] Uppercase key consistent with codebase convention autoNumberDTO = await _AutoNumber.GetAutoNumber(1, "ASSESSMENTQUESTION", loginDTO); dto.AssessmentQuestionId = autoNumberDTO.StartNumber; await _AssessmentQuestionDAL.SaveAssesmentQuestion(dto, loginDTO); message = string.Format(SuccessResponse.SaveSuccessWithName, dto.QuestionText); } else { await _AssessmentQuestionDAL.UpdateAssesmentQuestion(dto, loginDTO); message = string.Format(SuccessResponse.UpdateSuccessWithName, dto.QuestionText); } // ── CACHE INVALIDATION ──────────────────────────────────────── // [FIX-7] Cache key uses AssessmentQuestionId — not TrainerId var cacheKey = new CacheKeyGeneration().KeyGeneration( dto.AssessmentQuestionId, EntityConstant.OBJECTASSESMENTQUESTION, CacheKeyLevel.CLIENT_LEVEL, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey); return message; } catch (ValidationException) { // [FIX-6] Re-throw directly — don't wrap in Exception (loses type info) throw; } catch { // [FIX-5] Rollback whenever AutoNumber block was reserved. // Removed wrong "dto.AssessmentQuestionId > 0" condition — // always true after ID assignment, would suppress rollback. if (autoNumberDTO != null) await _AutoNumber.RollbackAutoNumber("ASSESSMENTQUESTION", autoNumberDTO.StartNumber, loginDTO); throw; } } // ── DELETE ──────────────────────────────────────────────────────────── /// /// Soft-deletes an AssessmentQuestion and invalidates its cache entry. /// public async Task DeleteAssessmentQuestion(int assessmentQuestionId, LoginDTO loginDTO) { // [FIX-8] Empty try/catch { throw; } removed var result = await _AssessmentQuestionDAL.DeleteAssesmentQuestion(assessmentQuestionId, loginDTO); // [FIX-7] Cache key uses assessmentQuestionId — not trainerId var cacheKey = new CacheKeyGeneration().KeyGeneration( assessmentQuestionId, EntityConstant.OBJECTASSESMENTQUESTION, CacheKeyLevel.CLIENT_LEVEL, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey); return result; } } }