diff --git a/src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs b/src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs index a8aebc0..96236ba 100644 --- a/src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs +++ b/src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs @@ -1055,6 +1055,54 @@ public class PurchaseEvaluationWorkflowService( refId: evaluation.Id, ct: ct); } + + // ===== Notify approver(s) cấp hiện tại khi phiếu vào ChoDuyet ===== + // Tra Sol (Zalo): approver KHÔNG nhận chuông "có hồ sơ cần duyệt" — chỉ + // drafter được báo (block ↑). Bổ sung: mỗi lần phiếu ENTER hoặc ADVANCE + // tới 1 Cấp duyệt (toPhase==ChoDuyet) → báo NV Cấp đang chờ. + // Resolve mirror ĐÚNG canonical (ApplyDrafterBypassOnSubmit / EnsureActor + // line ~301): load ApprovalWorkflow → Step tại CurrentWorkflowStepIndex → + // Levels.Where(Order == CurrentApprovalLevelOrder) → distinct ApproverUserId. + // V2-only (ApprovalWorkflowId + pointer set); phiếu V1/no-workflow skip. + // Best-effort (try/catch) — phiếu đã SaveChanges, notify fail KHÔNG fail transition. + if (toPhase == PurchaseEvaluationPhase.ChoDuyet + && evaluation.ApprovalWorkflowId is Guid notifyAwId + && evaluation.CurrentWorkflowStepIndex is int notifyCsi + && evaluation.CurrentApprovalLevelOrder is int notifyLvl) + { + try + { + var workflow = await db.ApprovalWorkflows.AsNoTracking() + .Include(w => w.Steps).ThenInclude(s => s.Levels) + .FirstOrDefaultAsync(w => w.Id == notifyAwId, ct); + var stepsOrdered = workflow?.Steps.OrderBy(s => s.Order).ToList(); + if (stepsOrdered is not null && notifyCsi >= 0 && notifyCsi < stepsOrdered.Count) + { + var approverIds = stepsOrdered[notifyCsi].Levels + .Where(l => l.Order == notifyLvl + && l.ApproverUserId != Guid.Empty + && l.ApproverUserId != actorUserId) + .Select(l => l.ApproverUserId) + .Distinct() + .ToList(); + if (approverIds.Count > 0) + { + await notifications.NotifyManyAsync( + approverIds, + NotificationType.Generic, + $"Phiếu cần bạn duyệt: {evaluation.MaPhieu ?? evaluation.TenGoiThau}", + "Có phiếu Duyệt NCC đang chờ bạn duyệt.", + $"/purchase-evaluations/{evaluation.Id}", + evaluation.Id, + ct); + } + } + } + catch + { + // best-effort — nuốt lỗi notify để KHÔNG rollback transition đã lưu. + } + } } // Mig 26 (Session 19) — helper resolve FullName cho denorm `SignedByFullName`.