using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using QMSDAL.CustomCode.Checklist; using QMSDAL.CustomCode.ChecklistDetail; using QMSDAL.CustomCode.ChecklistLog; using QMSDAL.CustomCode.PcsCard; using QMSDAL.CustomCode.PcsCardMaterial; using QMSDAL.CustomCode.PcsCardStage; using QMSDAL.DTO; using QMSDAL.Interfaces; using static GB5Shared.GB5Constant.Constant; namespace QMSBLL.PcsCardStage { // ───────────────────────────────────────────────────────────────────────── // PcsCardStageBLL // // Owns the stage lifecycle: Open → Complete / Skip / NcrRaise. // IChecklistLogDAL and IChecklistDetailDAL are injected directly here // because OpenStageAsync creates placeholder TCHECKLISTLOG rows in the // same transaction — bypassing ChecklistLogBLL (which assumes user-scored data). // ───────────────────────────────────────────────────────────────────────── public class PcsCardStageBLL : IPcsCardStageBLL { private readonly IPcsCardStageDAL _StageDAL; private readonly IPcsCardDAL _PcsCardDAL; private readonly IPcsCardMaterialDAL _MaterialDAL; private readonly IChecklistLogDAL _ChecklistLogDAL; private readonly IChecklistDetailDAL _ChecklistDetailDAL; private readonly AutoNumber _AutoNumber; private readonly IQueryExecutor _QueryExecutor; private readonly KeyInvalidate _KeyInvalidate; public PcsCardStageBLL( IPcsCardStageDAL stageDAL, IPcsCardDAL pcsCardDAL, IPcsCardMaterialDAL materialDAL, IChecklistLogDAL checklistLogDAL, IChecklistDetailDAL checklistDetailDAL, AutoNumber autoNumber, IQueryExecutor queryExecutor, KeyInvalidate keyInvalidate) { _StageDAL = stageDAL; _PcsCardDAL = pcsCardDAL; _MaterialDAL = materialDAL; _ChecklistLogDAL = checklistLogDAL; _ChecklistDetailDAL = checklistDetailDAL; _AutoNumber = autoNumber; _QueryExecutor = queryExecutor; _KeyInvalidate = keyInvalidate; } // ── Get all stages for a card ───────────────────────────────────────── public async Task> GetByCardIdAsync( int pcsCardId, LoginDTO login, CancellationToken ct = default) { try { return await _StageDAL.GetByCardIdAsync(pcsCardId, login, ct).ConfigureAwait(false); } catch (Exception) { throw; } } // ── Open stage — set InProgress, create checklist log if applicable ─── public async Task OpenStageAsync( PcsOpenStageDTO dto, LoginDTO login, CancellationToken ct = default) { if (dto == null) throw new ArgumentNullException(nameof(dto)); var stage = await _StageDAL.GetByIdAsync(dto.PcsCardStaeId, login, ct).ConfigureAwait(false); if (stage == null) return $"Stage {dto.PcsCardStaeId} not found."; // Idempotent: already open if (stage.StageStatus == 1) return "Details saved successfully."; if (stage.StageStatus != 0) return $"Stage cannot be opened — current status is {stage.StageStatus}."; var Trans = await _QueryExecutor.BeginTransactionAsync(login); try { // Mark stage InProgress await _StageDAL .OpenStageAsync(dto.PcsCardStaeId, dto.OperatorId, login, Trans, ct) .ConfigureAwait(false); // Create placeholder checklist log if stage has a checklist and none exists yet //if (stage.ChecklistId > -1 && stage.ChecklistLogId == -1) //{ // var details = (await _ChecklistDetailDAL // .GetChecklistDetailAsync(stage.ChecklistId, login, ct) // .ConfigureAwait(false))?.ToList() ?? new List(); // int batchCount = 1 + details.Count; // var auto = await _AutoNumber // .GetNumberAsync(batchCount, AUTONUMBERCONSTANT.CHECKLISTLOG, login) // .ConfigureAwait(false); // var card = await _PcsCardDAL // .GetPcsCardAsync(stage.PcsCardId, login, ct).ConfigureAwait(false); // var logDto = new ChecklistLogDTO // { // ChecklistLogId = auto.StartNumber, // OuId = card?.OuId ?? -1, // PeriodId = card?.PeriodId ?? -1, // ChecklistId = stage.ChecklistId, // LogDate = DateTime.UtcNow, // ObjectHeaderId = stage.PcsCardId, // ObjectId = stage.PcsCardStaeId, // OverallResult = 0, // Pending // Status = 1, // TenantId = login.ClientId // }; // int offset = 1; // var logDetails = details.Select(def => new ChecklistLogDetailDTO // { // ChecklistLogDetailId = auto.StartNumber + offset++, // ChecklistLogId = auto.StartNumber, // ChecklistDetailId = def.ChecklistDetailId, // ActionType = def.ActionType, // FailAction = def.FailAction, // IsMandatory = def.IsMandatory, // MinVal = def.MinVal, // MaxVal = def.MaxVal, // Result = 0, // Pending // InputId = -1, // AttachmentId = -1, // TenantId = login.ClientId // }).ToList(); // await _ChecklistLogDAL // .SaveChecklistLogAsync(logDto, login, Trans, ct).ConfigureAwait(false); // await _ChecklistLogDAL // .SaveChecklistLogDetailsAsync(logDetails, login, Trans, ct).ConfigureAwait(false); // await _StageDAL // .SetChecklistLogIdAsync(dto.PcsCardStaeId, auto.StartNumber, login, Trans, ct) // .ConfigureAwait(false); //} // Stamp card's actual start on the first stage open var cardForStart = await _PcsCardDAL .GetPcsCardAsync(stage.PcsCardId, login, ct).ConfigureAwait(false); if (cardForStart?.ActualStart == null) { await _PcsCardDAL .UpdatePcsCardActualStartAsync(stage.PcsCardId, login, Trans, ct) .ConfigureAwait(false); } await _QueryExecutor.CommitAsync(Trans); InvalidateCardCache(stage.PcsCardId, login); return "Details saved successfully."; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans); throw; } } // ── Complete stage ──────────────────────────────────────────────────── public async Task CompleteStageAsync( PcsStageCompleteDTO dto, LoginDTO login, CancellationToken ct = default) { if (dto == null) throw new ArgumentNullException(nameof(dto)); var stage = await _StageDAL.GetByIdAsync(dto.PcsCardStaeId, login, ct).ConfigureAwait(false); if (stage == null) return $"Stage {dto.PcsCardStaeId} not found."; if (stage.StageStatus != 1) return "Only an open (InProgress) stage can be completed."; if (stage.RequiresSupervisor == 1 && dto.SupervisorId <= 0) return "Supervisor sign-off is required to complete this stage."; byte stageResult = 1; // Pass int checklistOverallResult = 0; // Checklist validation //if (stage.ChecklistId > -1) //{ // if (stage.ChecklistLogId == -1) // return "Checklist log has not been initialised — open the stage first."; // var logDetails = (await _ChecklistLogDAL // .GetChecklistLogDetailAsync(stage.ChecklistLogId, login, ct) // .ConfigureAwait(false))?.ToList() ?? new List(); // var defs = (await _ChecklistDetailDAL // .GetChecklistDetailAsync(stage.ChecklistId, login, ct) // .ConfigureAwait(false))?.ToList() ?? new List(); // var defMap = defs.ToDictionary(d => d.ChecklistDetailId); // // Mandatory items must be answered (Result != 0) // var unansweredMandatory = defs // .Where(d => d.IsMandatory == 1) // .Where(d => // { // var ld = logDetails.FirstOrDefault(l => l.ChecklistDetailId == d.ChecklistDetailId); // return ld == null || ld.Result == 0; // }) // .Select(d => d.DetailText ?? d.ChecklistDetailId.ToString()) // .ToList(); // if (unansweredMandatory.Count > 0) // return $"Mandatory checklist items not completed: {string.Join(", ", unansweredMandatory)}."; // // FailAction=2 (Block) items with Result=2 (Fail) prevent completion // foreach (var ld in logDetails) // { // if (ld.Result == 2 && ld.FailAction == 2) // { // var defText = defMap.TryGetValue(ld.ChecklistDetailId, out var bd) // ? (bd.DetailText ?? ld.ChecklistDetailId.ToString()) // : ld.ChecklistDetailId.ToString(); // return $"Checklist blocked: '{defText}' failed and is set to Block."; // } // } // // Compute StageResult from checklist responses // stageResult = logDetails.Any(l => l.Result == 2) ? (byte)2 : (byte)1; // // Compute OverallResult for the checklist log // checklistOverallResult = logDetails.Any(l => l.Result == 2) ? 2 // Fail // : logDetails.Any(l => l.Result == 0) ? 3 // Partial // : 1; // Pass //} // Material log mandatory if (stage.RequiresMaterialLog == 1) { bool hasMaterials = await _MaterialDAL .HasMaterialsForStageAsync(dto.PcsCardStaeId, login, ct).ConfigureAwait(false); if (!hasMaterials) return "Material batch log is required to complete this stage."; } var Trans = await _QueryExecutor.BeginTransactionAsync(login); try { await _StageDAL .CompleteStageAsync(dto.PcsCardStaeId, dto.SupervisorId, stageResult, dto.Remarks, login, Trans, ct) .ConfigureAwait(false); // Update checklist log OverallResult if (stage.ChecklistLogId > -1) { var clParam = new { ChecklistLogId = stage.ChecklistLogId, OverallResult = checklistOverallResult, CompletedById = dto.SupervisorId > 0 ? dto.SupervisorId : login.UserId }; await _QueryExecutor .ExecuteAsync(login, QMSDAL.Query.PcsCardStage.PcsCardStageQB.UPDATE_TCHECKLISTLOG_COMPLETE, clParam, Trans, ct) .ConfigureAwait(false); } // Advance card progress int completedCount = await _StageDAL .GetCompletedCountAsync(stage.PcsCardId, login, ct).ConfigureAwait(false) + 1; var nextStage = await _StageDAL .GetNextPendingAsync(stage.PcsCardId, stage.StageNo, login, ct).ConfigureAwait(false); if (nextStage != null) { await _StageDAL .OpenStageAsync(nextStage.PcsCardStaeId, -1, login, Trans, ct) .ConfigureAwait(false); } short newCurrentStage = nextStage?.StageNo ?? stage.StageNo; var card = await _PcsCardDAL .GetPcsCardAsync(stage.PcsCardId, login, ct).ConfigureAwait(false); bool allDone = card != null && completedCount >= card.TotalStages; byte newStatus = allDone ? (byte)2 : (byte)1; byte newResult = allDone ? (byte)1 : (byte)0; DateTime? actualEnd = allDone ? DateTime.UtcNow : null; await _PcsCardDAL.UpdatePcsCardProgressAsync( stage.PcsCardId, (short)completedCount, newCurrentStage, newStatus, newResult, actualEnd, login, Trans, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans); InvalidateCardCache(stage.PcsCardId, login); return "Details updated successfully."; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans); throw; } } // ── Raise NCR — puts card on hold ───────────────────────────────────── public async Task RaiseNcrAsync( PcsNcrDTO dto, LoginDTO login, CancellationToken ct = default) { if (dto == null) throw new ArgumentNullException(nameof(dto)); var stage = await _StageDAL.GetByIdAsync(dto.PcsCardStaeId, login, ct).ConfigureAwait(false); if (stage == null) return $"Stage {dto.PcsCardStaeId} not found."; if (stage.StageStatus == 2 || stage.StageStatus == 4) return "NCR cannot be raised on a completed or already-NCR'd stage."; if (string.IsNullOrWhiteSpace(dto.Description)) return "NCR description is required."; var Trans = await _QueryExecutor.BeginTransactionAsync(login); try { await _StageDAL .SetNcrStatusAsync(dto.PcsCardStaeId, dto.Description, login, Trans, ct) .ConfigureAwait(false); // Put card on hold (OverallStatus=3) await _PcsCardDAL .SetCardStatusAsync(stage.PcsCardId, 3, login, Trans, ct) .ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans); InvalidateCardCache(stage.PcsCardId, login); return "Details saved successfully."; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans); throw; } } // ── Skip stage ──────────────────────────────────────────────────────── public async Task SkipStageAsync( PcsSkipStageDTO dto, LoginDTO login, CancellationToken ct = default) { if (dto == null) throw new ArgumentNullException(nameof(dto)); var stage = await _StageDAL.GetByIdAsync(dto.PcsCardStaeId, login, ct).ConfigureAwait(false); if (stage == null) return $"Stage {dto.PcsCardStaeId} not found."; if (stage.RequiresSupervisor == 1) return "Stage requires supervisor sign-off and cannot be skipped."; if (stage.StageStatus == 2) return "Stage is already completed."; if (string.IsNullOrWhiteSpace(dto.Reason)) return "A reason is required to skip a stage."; var Trans = await _QueryExecutor.BeginTransactionAsync(login); try { await _StageDAL .SkipStageAsync(dto.PcsCardStaeId, dto.SupervisorId, dto.Reason, login, Trans, ct) .ConfigureAwait(false); // Advance current stage marker int completedCount = await _StageDAL .GetCompletedCountAsync(stage.PcsCardId, login, ct).ConfigureAwait(false); var nextStage = await _StageDAL .GetNextPendingAsync(stage.PcsCardId, stage.StageNo, login, ct).ConfigureAwait(false); if (nextStage != null) { await _StageDAL .OpenStageAsync(nextStage.PcsCardStaeId, -1, login, Trans, ct) .ConfigureAwait(false); } short newCurrentStage = nextStage?.StageNo ?? stage.StageNo; await _PcsCardDAL.UpdatePcsCardProgressAsync( stage.PcsCardId, (short)completedCount, newCurrentStage, 1, // InProgress 0, // OverallResult = Pending null, // no actual end login, Trans, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans); InvalidateCardCache(stage.PcsCardId, login); return "Details saved successfully."; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans); throw; } } // ── Private helpers ─────────────────────────────────────────────────── private void InvalidateCardCache(int pcsCardId, LoginDTO login) { var cacheKey = new CacheKeyGeneration().KeyGeneration( pcsCardId, EntityConstant.OBJECTPCSCARD, CacheKeyLevel.CLIENT_LEVEL, login); _ = _KeyInvalidate.AllInvalidateCache(cacheKey); } } }