feat: add inspection, issuance, and renewal toggles to certificate requirement settings

This commit is contained in:
estifanos
2026-08-31 07:01:09 +00:00
parent e25a786139
commit 4eb9fe1ec2
4 changed files with 93 additions and 24 deletions

View File

@@ -33,6 +33,9 @@ interface Draft {
completionEffect: CompletionEffect | null; completionEffect: CompletionEffect | null;
certificateCategory: CertificateCategory | null; certificateCategory: CertificateCategory | null;
requiresExamination: boolean; requiresExamination: boolean;
inspectionRequired: boolean;
issuesCertificate: boolean;
renewalEnabled: boolean;
requiresSeafarerRegistration: boolean; requiresSeafarerRegistration: boolean;
requiresValidMedical: boolean; requiresValidMedical: boolean;
minSeaTimeDays: number | null; minSeaTimeDays: number | null;
@@ -58,6 +61,9 @@ function toDraft(licenseType: LicenseType): Draft {
completionEffect: licenseType.completionEffect ?? null, completionEffect: licenseType.completionEffect ?? null,
certificateCategory: licenseType.certificateCategory ?? null, certificateCategory: licenseType.certificateCategory ?? null,
requiresExamination: licenseType.requiresExamination ?? false, requiresExamination: licenseType.requiresExamination ?? false,
inspectionRequired: licenseType.inspectionRequired ?? true,
issuesCertificate: licenseType.issuesCertificate ?? true,
renewalEnabled: licenseType.renewalEnabled ?? true,
requiresSeafarerRegistration: requiresSeafarerRegistration:
licenseType.requiresSeafarerRegistration ?? false, licenseType.requiresSeafarerRegistration ?? false,
requiresValidMedical: licenseType.requiresValidMedical ?? false, requiresValidMedical: licenseType.requiresValidMedical ?? false,
@@ -182,6 +188,17 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
disabled={!canEdit} disabled={!canEdit}
/> />
<Switch
checked={draft.issuesCertificate}
onChange={(e) => set('issuesCertificate', e.currentTarget.checked)}
label={t('certReq.behavior.issuesCertificate', 'Issues a certificate')}
description={t(
'certReq.behavior.issuesCertificateHint',
'Turn off for a type that ends with an EMA decision and never reaches a payment stage. Turning it back on requires a new-application fee to be set, or approved applicants would be asked for a fee that does not exist.',
)}
disabled={!canEdit}
/>
<Select <Select
label={t('certReq.behavior.serviceKind', 'Service kind')} label={t('certReq.behavior.serviceKind', 'Service kind')}
description={t( description={t(
@@ -315,33 +332,53 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
/> />
</Group> </Group>
<NumberInput <Switch
label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')} checked={draft.renewalEnabled}
value={draft.renewalWindowDays} onChange={(e) => set('renewalEnabled', e.currentTarget.checked)}
onChange={(v) => set('renewalWindowDays', typeof v === 'number' ? v : draft.renewalWindowDays)} label={t('certReq.behavior.renewalEnabled', 'Holders may renew this licence')}
min={1} description={t(
max={365} 'certReq.behavior.renewalEnabledHint',
allowNegative={false} 'Off for one-off issues — a registration that does not lapse, or a waiver written for a single shipment.',
)}
disabled={!canEdit} disabled={!canEdit}
/> />
<MultiSelect {/* The window and the reminders are both measured against an expiry a
label={t('certReq.behavior.reminders', 'Expiry reminders (days before)')} non-renewing licence never reaches, so they are hidden rather than
description={t( shown as settings that quietly do nothing. */}
'certReq.behavior.remindersHint', {draft.renewalEnabled && (
'The holder is reminded at each of these offsets.', <>
)} <NumberInput
data={REMINDER_OFFSETS} label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')}
value={draft.expiryReminderDays.map(String)} value={draft.renewalWindowDays}
onChange={(values) => onChange={(v) =>
set( set('renewalWindowDays', typeof v === 'number' ? v : draft.renewalWindowDays)
'expiryReminderDays', }
values.map(Number).sort((a, b) => b - a), min={1}
) max={365}
} allowNegative={false}
disabled={!canEdit} disabled={!canEdit}
clearable />
/>
<MultiSelect
label={t('certReq.behavior.reminders', 'Expiry reminders (days before)')}
description={t(
'certReq.behavior.remindersHint',
'The holder is reminded at each of these offsets.',
)}
data={REMINDER_OFFSETS}
value={draft.expiryReminderDays.map(String)}
onChange={(values) =>
set(
'expiryReminderDays',
values.map(Number).sort((a, b) => b - a),
)
}
disabled={!canEdit}
clearable
/>
</>
)}
</Section> </Section>
<Section title={t('certReq.behavior.applicantRules', 'Applicant rules')}> <Section title={t('certReq.behavior.applicantRules', 'Applicant rules')}>
@@ -367,6 +404,17 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
disabled={!canEdit} disabled={!canEdit}
/> />
<Switch
checked={draft.inspectionRequired}
onChange={(e) => set('inspectionRequired', e.currentTarget.checked)}
label={t('certReq.behavior.inspectionRequired', 'Requires a physical inspection')}
description={t(
'certReq.behavior.inspectionRequiredHint',
'An officer cannot approve until an inspection has been recorded and passed. Safe to change with applications in progress — an officer can still assign an inspector to a file already under evaluation.',
)}
disabled={!canEdit}
/>
<Switch <Switch
checked={draft.requiresIssuanceScheduling} checked={draft.requiresIssuanceScheduling}
onChange={(e) => set('requiresIssuanceScheduling', e.currentTarget.checked)} onChange={(e) => set('requiresIssuanceScheduling', e.currentTarget.checked)}

View File

@@ -1292,6 +1292,15 @@ export const am: Translations = {
requiresExamination: "ፈተና ያስፈልገዋል", requiresExamination: "ፈተና ያስፈልገዋል",
requiresExaminationHint: requiresExaminationHint:
"ማመልከቻውን በብቁነት፣ ከዚያ በፈተና፣ ከዚያ በምስክር ወረቀት ሂደት ያሳልፋል። ሦስቱን የደረጃ ክፍያዎች በክፍያ ቅንብር ገጽ ላይ ያስቀምጡ።", "ማመልከቻውን በብቁነት፣ ከዚያ በፈተና፣ ከዚያ በምስክር ወረቀት ሂደት ያሳልፋል። ሦስቱን የደረጃ ክፍያዎች በክፍያ ቅንብር ገጽ ላይ ያስቀምጡ።",
issuesCertificate: "የምስክር ወረቀት ይሰጣል",
issuesCertificateHint:
"በኢማ ውሳኔ ተጠናቆ ወደ ክፍያ ደረጃ ለማይደርስ ዓይነት ያጥፉ። እንደገና ማብራት የአዲስ ማመልከቻ ክፍያ እንዲዘጋጅ ይጠይቃል፤ አለበለዚያ የጸደቁ አመልካቾች የሌለ ክፍያ እንዲከፍሉ ይጠየቃሉ።",
inspectionRequired: "አካላዊ ምርመራ ያስፈልገዋል",
inspectionRequiredHint:
"ምርመራ ተመዝግቦ እስኪያልፍ ድረስ ኃላፊው ማጽደቅ አይችልም። ማመልከቻዎች በሂደት ላይ እያሉ መቀየር አስተማማኝ ነው — ኃላፊው በግምገማ ላይ ላለ ማመልከቻ አሁንም መርማሪ መመደብ ይችላል።",
renewalEnabled: "ባለቤቶች ይህንን ፈቃድ ማደስ ይችላሉ",
renewalEnabledHint:
"አንድ ጊዜ ብቻ ለሚሰጡ ያጥፉ — ጊዜው ለማያልፍ ምዝገባ፣ ወይም ለአንድ ጭነት ለተጻፈ ነፃ ፈቃድ።",
serviceKind: "የአገልግሎት ዓይነት", serviceKind: "የአገልግሎት ዓይነት",
serviceKindHint: "ለዝርዝር ምድብ ብቻ — ምንም የሥራ ሂደት በእሱ ላይ አይመሠረትም።", serviceKindHint: "ለዝርዝር ምድብ ብቻ — ምንም የሥራ ሂደት በእሱ ላይ አይመሠረትም።",
license: "ፈቃድ", license: "ፈቃድ",

View File

@@ -1297,6 +1297,15 @@ export const en = {
requiresExamination: 'Requires an examination', requiresExamination: 'Requires an examination',
requiresExaminationHint: requiresExaminationHint:
'Routes the application through eligibility, then exam, then certificate. Set the three stage fees on the payment configuration screen.', 'Routes the application through eligibility, then exam, then certificate. Set the three stage fees on the payment configuration screen.',
issuesCertificate: 'Issues a certificate',
issuesCertificateHint:
'Turn off for a type that ends with an EMA decision and never reaches a payment stage. Turning it back on requires a new-application fee to be set, or approved applicants would be asked for a fee that does not exist.',
inspectionRequired: 'Requires a physical inspection',
inspectionRequiredHint:
'An officer cannot approve until an inspection has been recorded and passed. Safe to change with applications in progress — an officer can still assign an inspector to a file already under evaluation.',
renewalEnabled: 'Holders may renew this licence',
renewalEnabledHint:
'Off for one-off issues — a registration that does not lapse, or a waiver written for a single shipment.',
serviceKind: 'Service kind', serviceKind: 'Service kind',
serviceKindHint: 'Catalogue classification only — no workflow depends on it.', serviceKindHint: 'Catalogue classification only — no workflow depends on it.',
license: 'Licence', license: 'Licence',

View File

@@ -223,6 +223,9 @@ export const licensingApi = baseApi
completionEffect?: CompletionEffect | null; completionEffect?: CompletionEffect | null;
certificateCategory?: CertificateCategory | null; certificateCategory?: CertificateCategory | null;
requiresExamination?: boolean; requiresExamination?: boolean;
inspectionRequired?: boolean;
issuesCertificate?: boolean;
renewalEnabled?: boolean;
requiresSeafarerRegistration?: boolean; requiresSeafarerRegistration?: boolean;
requiresValidMedical?: boolean; requiresValidMedical?: boolean;
minSeaTimeDays?: number | null; minSeaTimeDays?: number | null;