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);
}