feat: update exam stage actions and payment flow

- Refactor ExamStageActions component to handle eligibility payment and retake exam actions.
- Update MyApplicationsPage to use new retakeExam mutation instead of requestExamPayment.
- Modify licensing API to include retakeExam mutation for handling exam fee requests for failed candidates.
- Adjust mock data to reflect new application statuses and ensure consistency in eligibility and exam payment states.
- Update licensing helpers to include new status labels and colors for eligibility payment states.
- Revise licensing types to replace ELIGIBILITY_APPROVED with ELIGIBILITY_PAYMENT_PENDING and ELIGIBILITY_PAID for clarity in application status flow.
This commit is contained in:
Nati
2026-08-20 12:41:46 +00:00
parent 558cfe20a1
commit 6901e985fc
14 changed files with 231 additions and 1154 deletions

View File

@@ -337,7 +337,7 @@ export const mockApplications: Record<string, any> = {
licenseType: { id: 'license-type-coc', key: 'CERTIFICATE_OF_COMPETENCY', name: { en: 'Certificate of Competency' } },
applicantUserId: 'user-mock-001',
kind: 'RENEWAL',
status: 'ELIGIBILITY_APPROVED',
status: 'ELIGIBILITY_PAID',
assignedOfficerId: 'officer-mock-002',
claimedAt: '2026-08-01T10:00:00.000Z',
formData: { account: { applicantName: 'Abebe Tesfaye' } },

View File

@@ -494,9 +494,26 @@ export const licensingApi = baseApi
scheduleExam: builder.mutation<
LicenseApplication,
{ id: string; examId: string; admissionNumber?: string; examDate?: string }
>({
query: ({ id, examDate: _examDate, ...body }) => ({
// Matches the controller's `:id/exam-scheduled` route — `examDate`
// is UI-only context for the confirmation toast, not part of
// `MarkExamScheduledDto`, so it never goes on the wire.
url: `/license-application-review/${id}/exam-scheduled`,
method: 'POST',
body,
}),
invalidatesTags: (_r, error, { id }) =>
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
}),
/** Records a published examination result (pass or fail). */
recordExamOutcome: builder.mutation<
LicenseApplication,
{ id: string; passed: boolean; score?: number }
>({
query: ({ id, ...body }) => ({
url: `/license-application-review/${id}/schedule-exam`,
url: `/license-application-review/${id}/exam-outcome`,
method: 'POST',
body,
}),
@@ -505,12 +522,13 @@ export const licensingApi = baseApi
}),
/**
* Raises the examination fee — after eligibility approval, or again when
* a failed candidate elects to resit.
* A failed candidate asks for another sitting. Re-opens the examination
* fee (EXAM_FAILED -> EXAM_PAYMENT_PENDING); eligibility was already
* assessed and paid for on the first attempt.
*/
requestExamPayment: builder.mutation<LicenseApplication, string>({
retakeExam: builder.mutation<LicenseApplication, string>({
query: (id) => ({
url: `/license-applications/${id}/request-exam-payment`,
url: `/license-applications/${id}/retake`,
method: 'POST',
}),
invalidatesTags: (_r, error, id) =>
@@ -864,7 +882,8 @@ export const {
useFinalApproveMutation,
useRejectApplicationMutation,
useScheduleExamMutation,
useRequestExamPaymentMutation,
useRecordExamOutcomeMutation,
useRetakeExamMutation,
useConfirmPaymentMutation,
useScheduleIssuanceMutation,
useIssueCertificateMutation,

View File

@@ -74,7 +74,8 @@ export const STATUS_LABELS: Record<LicenseStatus, string> = {
SCHEDULED: 'Pickup Scheduled',
CERTIFICATE_ISSUED: 'Certificate Issued',
COMPLETED: 'Completed',
ELIGIBILITY_APPROVED: 'Eligible to Sit',
ELIGIBILITY_PAYMENT_PENDING: 'Eligibility Fee Due',
ELIGIBILITY_PAID: 'Eligibility Under Review',
EXAM_PAYMENT_PENDING: 'Exam Fee Due',
EXAM_PAID: 'Awaiting Exam Date',
EXAM_SCHEDULED: 'Exam Scheduled',
@@ -100,7 +101,8 @@ export const STATUS_COLORS: Record<LicenseStatus, string> = {
SCHEDULED: 'cyan',
CERTIFICATE_ISSUED: 'green',
COMPLETED: 'green',
ELIGIBILITY_APPROVED: 'teal',
ELIGIBILITY_PAYMENT_PENDING: 'yellow',
ELIGIBILITY_PAID: 'lime',
EXAM_PAYMENT_PENDING: 'yellow',
EXAM_PAID: 'lime',
EXAM_SCHEDULED: 'cyan',
@@ -135,7 +137,8 @@ export const STATUS_PROGRESS: Record<LicenseStatus, number> = {
REJECTED: 100,
// The exam leg sits between approval and the certificate fee, so these
// interleave with PAYMENT_PENDING (80) rather than running past it.
ELIGIBILITY_APPROVED: 60,
ELIGIBILITY_PAYMENT_PENDING: 52,
ELIGIBILITY_PAID: 56,
EXAM_PAYMENT_PENDING: 64,
EXAM_PAID: 68,
EXAM_SCHEDULED: 72,
@@ -149,6 +152,9 @@ export const APPLICANT_ACTION_STATUSES: LicenseStatus[] = [
'DRAFT',
'RESUBMIT_REQUIRED',
'PAYMENT_PENDING',
// Due the moment an examined application is submitted, before any officer
// looks at it.
'ELIGIBILITY_PAYMENT_PENDING',
// Both wait on the candidate: one to pay for a sitting, one to decide to
// sit again after a failure.
'EXAM_PAYMENT_PENDING',

View File

@@ -41,9 +41,11 @@ export type LicenseStatus =
| "SCHEDULED"
| "CERTIFICATE_ISSUED"
| "COMPLETED"
// Examined certificates (CoC, some CoP): approval establishes eligibility,
// the candidate pays to sit, and the certificate fee falls due on a pass.
| "ELIGIBILITY_APPROVED"
// Examined certificates (CoC, some CoP): the eligibility assessment fee is
// due before review starts, then the candidate pays to sit, and the
// certificate fee falls due on a pass.
| "ELIGIBILITY_PAYMENT_PENDING"
| "ELIGIBILITY_PAID"
| "EXAM_PAYMENT_PENDING"
| "EXAM_PAID"
| "EXAM_SCHEDULED"