using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using Microsoft.Extensions.Logging; using TCMSBLL.Interfaces; using TCMSDAL.DTO; using TCMSDAL.Interfaces; namespace TCMSBLL.Implementations; /// /// Business logic for Defect Tracking and CAPA (Corrective/Preventive Action) workflow. /// /// Key responsibilities: /// 1. Defect CRUD — validate mandatory fields, enforce tenant scoping via LoginDTO. /// 2. Pattern library — maintain the root-cause pattern codebook (MISSING-NEG-VALIDATION, etc.). /// 3. CAPA gap report — cross-module pattern coverage: which modules lack a test for a given defect pattern. /// 4. Auto-stub: on save, logs a warning so QC can manually create the @skip regression stub. /// Full auto-creation deferred until ITCMSTestCaseBLL is injectable here without circular DI. /// public class TCMSDefectBLL : ITCMSDefectBLL { private readonly ITCMSDefectDAL _Dal; private readonly ILogger _Logger; private static readonly HashSet ValidPriorities = new(StringComparer.OrdinalIgnoreCase) { "P1", "P2", "P3" }; private static readonly HashSet ValidStatuses = new(StringComparer.OrdinalIgnoreCase) { "Open", "InProgress", "Fixed", "Verified", "Closed" }; public TCMSDefectBLL( ITCMSDefectDAL dal, ILogger logger) { _Dal = dal; _Logger = logger; } // ── Defect pattern library ──────────────────────────────────────────────── public async Task> GetDefectPatternListAsync( LoginDTO login, CancellationToken ct) => await _Dal.GetDefectPatternListAsync(login, ct).ConfigureAwait(false); public async Task SaveDefectPatternAsync( TCMSDefectPatternDTO dto, LoginDTO login, CancellationToken ct) { if (string.IsNullOrWhiteSpace(dto.PatternCode)) return "Pattern Code is required."; if (string.IsNullOrWhiteSpace(dto.Description)) return "Description is required."; try { await _Dal.SaveDefectPatternAsync(dto, login, ct).ConfigureAwait(false); return dto.PatternId == 0 ? SuccessResponse.SaveSuccess : SuccessResponse.UpdateSuccess; } catch (Exception ex) { _Logger.LogError(ex, "SaveDefectPattern failed for code '{Code}'", dto.PatternCode); throw; } } // ── Defect CRUD ─────────────────────────────────────────────────────────── public async Task GetDefectAsync( int defectId, LoginDTO login, CancellationToken ct) => await _Dal.GetDefectAsync(defectId, login, ct).ConfigureAwait(false); public async Task> GetDefectListAsync( int? moduleId, string? status, int? patternId, LoginDTO login, CancellationToken ct) => await _Dal.GetDefectListAsync(moduleId, status, patternId, login, ct) .ConfigureAwait(false); /// /// Creates a new defect entry. /// After saving, logs a reminder for QC to create the @skip regression test stub manually. /// (Automated stub insertion is deferred — see class-level doc comment.) /// public async Task SaveDefectAsync( TCMSDefectDTO dto, LoginDTO login, CancellationToken ct) { if (string.IsNullOrWhiteSpace(dto.Title)) return "Title is required."; if (dto.ModuleId == 0) return "Module is required."; if (string.IsNullOrWhiteSpace(dto.Entity)) return "Entity is required."; if (string.IsNullOrWhiteSpace(dto.Operation)) return "Operation is required."; if (string.IsNullOrWhiteSpace(dto.Priority)) return "Priority is required."; if (!ValidPriorities.Contains(dto.Priority)) return $"Priority must be P1, P2, or P3."; try { int defectId = await _Dal.InsertDefectAsync(dto, login, ct).ConfigureAwait(false); _Logger.LogInformation( "Defect D-{Id} '{Title}' created in module {Module} by user {User}. " + "QC: create @skip @regression @from-defect-{Id} stub in TCMS Test Cases.", defectId, dto.Title, dto.ModuleId, login.UserId, defectId); return $"{SuccessResponse.SaveSuccessMessage} {defectId}"; } catch (Exception ex) { _Logger.LogError(ex, "SaveDefect failed for '{Title}'", dto.Title); throw; } } /// /// Updates defect status, root-cause pattern, and linked test-case pointer. /// Only mutable fields after creation: Status, RootCausePatternId, RequiresRca, LinkedTestCaseId. /// public async Task UpdateDefectAsync( TCMSDefectDTO dto, LoginDTO login, CancellationToken ct) { if (dto.DefectId == 0) return "DefectId is required."; if (string.IsNullOrWhiteSpace(dto.Title)) return "Title is required."; if (string.IsNullOrWhiteSpace(dto.Status)) return "Status is required."; if (!ValidStatuses.Contains(dto.Status)) return $"Status must be one of: {string.Join(", ", ValidStatuses)}."; try { await _Dal.UpdateDefectAsync(dto, login, ct).ConfigureAwait(false); _Logger.LogInformation( "Defect D-{Id} status updated to '{Status}' by user {User}", dto.DefectId, dto.Status, login.UserId); return SuccessResponse.UpdateSuccess; } catch (Exception ex) { _Logger.LogError(ex, "UpdateDefect failed for D-{Id}", dto.DefectId); throw; } } // ── CAPA gap report ─────────────────────────────────────────────────────── /// /// Returns the cross-module pattern coverage matrix: /// for each (defect pattern × module) pair, shows whether at least one test case /// covers that pattern in that module. /// public async Task> GetCapaGapReportAsync( LoginDTO login, CancellationToken ct) => await _Dal.GetCapaGapReportAsync(login, ct).ConfigureAwait(false); }