This commit is contained in:
Nati
2026-08-19 08:35:28 +00:00
parent 130082dcc4
commit f97eb68e25
12 changed files with 276 additions and 35 deletions

View File

@@ -721,6 +721,28 @@ export const licensingApi = baseApi
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
}),
scheduleIssuance: builder.mutation<
LicenseApplication,
{ id: string; scheduledDate: string }
>({
query: ({ id, scheduledDate }) => ({
url: `/license-application-review/${id}/schedule-issuance`,
method: 'POST',
body: { scheduledDate },
}),
invalidatesTags: (_r, error, { id }) =>
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
}),
issueCertificate: builder.mutation<LicenseApplication, string>({
query: (id) => ({
url: `/license-application-review/${id}/issue-certificate`,
method: 'POST',
}),
invalidatesTags: (_r, error, id) =>
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
}),
// --------------------------------------------------------- inspection
scheduleInspection: builder.mutation<
Inspection,
@@ -841,6 +863,8 @@ export const {
useScheduleExamMutation,
useRequestExamPaymentMutation,
useConfirmPaymentMutation,
useScheduleIssuanceMutation,
useIssueCertificateMutation,
useScheduleInspectionMutation,
useGetInspectionsQuery,
useRecordInspectionResultMutation,

View File

@@ -69,6 +69,7 @@ export const STATUS_LABELS: Record<LicenseStatus, string> = {
PAYMENT_PENDING: 'Payment Pending',
PAID: 'Paid',
PAYMENT_CONFIRMED: 'Preparing Certificate',
SCHEDULED: 'Pickup Scheduled',
CERTIFICATE_ISSUED: 'Certificate Issued',
COMPLETED: 'Completed',
ELIGIBILITY_APPROVED: 'Eligible to Sit',
@@ -94,6 +95,7 @@ export const STATUS_COLORS: Record<LicenseStatus, string> = {
PAYMENT_PENDING: 'yellow',
PAID: 'lime',
PAYMENT_CONFIRMED: 'teal',
SCHEDULED: 'cyan',
CERTIFICATE_ISSUED: 'green',
COMPLETED: 'green',
ELIGIBILITY_APPROVED: 'teal',
@@ -125,6 +127,7 @@ export const STATUS_PROGRESS: Record<LicenseStatus, number> = {
PAYMENT_PENDING: 80,
PAID: 88,
PAYMENT_CONFIRMED: 94,
SCHEDULED: 97,
CERTIFICATE_ISSUED: 100,
COMPLETED: 100,
REJECTED: 100,
@@ -169,6 +172,82 @@ export const APPLICANT_NAME_TYPE_KEYS = [
'ENDORSEMENT_GOC',
];
/**
* Which business concept a licence type actually is, for terminology and
* navigation only — never a workflow or eligibility branch. Mirrors
* `resolveFamilyKind` in emaapi's `common/utils/family-terminology.ts`; the
* two must be kept in step by hand since the value isn't sent over the wire
* (nothing needs it server-side beyond notification copy, so a shared
* package felt like more plumbing than the duplication it would save).
*
* A permission granted to a logistics operator, a seafarer's proof of
* competence, and a seafarer/vessel's identity or statutory record are three
* different things to the people using this system, even though they run
* through the identical application pipeline — see BR-MTO-020.
*/
export type FamilyKind = 'LOGISTICS_LICENSE' | 'CERTIFICATE' | 'DOCUMENT';
const FAMILY_KIND_BY_KEY: Partial<Record<string, FamilyKind>> = {
SEAMAN_BOOK: 'DOCUMENT',
VESSEL_REGISTRATION: 'DOCUMENT',
BTC_BASIC_TRAINING: 'CERTIFICATE',
CERTIFICATE_OF_COMPETENCY: 'CERTIFICATE',
CERTIFICATE_OF_PROFICIENCY: 'CERTIFICATE',
ENDORSEMENT_COC: 'CERTIFICATE',
ENDORSEMENT_GOC: 'CERTIFICATE',
FREIGHT_FORWARDER: 'LOGISTICS_LICENSE',
SHIPPING_AGENT: 'LOGISTICS_LICENSE',
COMBINED_SA_FF: 'LOGISTICS_LICENSE',
MULTIMODAL_TRANSPORT_OPERATOR: 'LOGISTICS_LICENSE',
JOINT_INVESTOR: 'LOGISTICS_LICENSE',
};
/**
* `FamilyKind` for a type key that may not be in the map yet. Falls back to
* `LOGISTICS_LICENSE`, reproducing today's "Licence ___" wording — the safe
* default for an unrecognised key, matching how the map above treats config
* it doesn't know about.
*/
export function resolveFamilyKind(licenseTypeKey: string | undefined | null): FamilyKind {
return (licenseTypeKey && FAMILY_KIND_BY_KEY[licenseTypeKey]) || 'LOGISTICS_LICENSE';
}
export interface FamilyLabels {
/** e.g. "Certificate", "Document", "Licence". */
typeLabel: string;
/** e.g. "Certificate Number", "Document Number", "Licence Number". */
numberLabel: string;
/** e.g. "Certificate Holder", "Document Holder", "Licence Holder". */
holderLabel: string;
/** e.g. "Certificate Review", "Document Review", "Licence Review". */
reviewLabel: string;
}
const FAMILY_LABELS: Record<FamilyKind, FamilyLabels> = {
CERTIFICATE: {
typeLabel: 'Certificate',
numberLabel: 'Certificate Number',
holderLabel: 'Certificate Holder',
reviewLabel: 'Certificate Review',
},
DOCUMENT: {
typeLabel: 'Document',
numberLabel: 'Document Number',
holderLabel: 'Document Holder',
reviewLabel: 'Document Review',
},
LOGISTICS_LICENSE: {
typeLabel: 'Licence',
numberLabel: 'Licence Number',
holderLabel: 'Licence Holder',
reviewLabel: 'Licence Review',
},
};
export function familyLabels(familyKind: FamilyKind): FamilyLabels {
return FAMILY_LABELS[familyKind];
}
/** Company name, or applicant name for licence types that have no company. */
export function applicantOrCompanyName(app: LicenseApplication): string | undefined {
if (!app.licenseType?.key || !APPLICANT_NAME_TYPE_KEYS.includes(app.licenseType.key)) {

View File

@@ -35,6 +35,10 @@ export type LicenseStatus =
| "PAYMENT_PENDING"
| "PAID"
| "PAYMENT_CONFIRMED"
// Payment confirmed and pickup date set — for a document printed once and
// handed over in person (seaman book, BTC). Most license types skip this
// and go straight from PAYMENT_CONFIRMED to CERTIFICATE_ISSUED.
| "SCHEDULED"
| "CERTIFICATE_ISSUED"
| "COMPLETED"
// Examined certificates (CoC, some CoP): approval establishes eligibility,
@@ -145,6 +149,8 @@ export interface LicenseType {
inspectionRequired: boolean;
issuesCertificate: boolean;
renewalEnabled: boolean;
/** Payment confirmation waits for a scheduled pickup date before issuance. */
requiresIssuanceScheduling: boolean;
/**
* False for person-centric registrations (seafarer): they are open to any
* authenticated applicant, live outside the operator catalogue, and have
@@ -274,6 +280,9 @@ export interface LicenseApplication {
feeAmount: string | null;
feeCurrency: string;
issuedLicenseId: string | null;
/** Set once an officer schedules pickup for a document requiring in-person handover. */
scheduledIssuanceDate: string | null;
scheduledBy: string | null;
createdAt: string;
}