- Added VesselRegistrationApplicationPage for submitting new vessel registrations. - Created VesselRegistrationPage to list user's registrations and navigate to details. - Implemented VesselRegistrationStatusPage to display registration status and download certificates. - Integrated mock data for registrations and certificates. - Updated navigation and routing to include vessel registration paths. - Added translations for vessel registration in English and Amharic. - Documented the vessel registration workflow and API integration notes.
9.1 KiB
Vessel Registration — mock implementation notes (for API integration)
Portal (owner-facing) + Backoffice (officer/manager-facing) built as mock UI only, matching each app's existing mock-driven review-workflow convention (same pattern as docs/vessel-ownership-transfer.md). No backend calls anywhere. This doc exists to make wiring the real API fast — it says exactly what to replace and where.
Workflow
flowchart TD
A["1. Vessel owner opens Vessel Registration"] --> B{"2. Select vessel category"}
B -->|Inland Waterway Vessel| C["3. Enter vessel details"]
B -->|Sea-going Vessel International| C
C --> D["4. Enter technical & ownership details"]
D --> E["5. Upload required documents"]
E --> F["6. Review application"]
F --> G["7. Submit application"]
G --> H["8. System sends confirmation notification\n(SMS + email)"]
H --> I["9. Officer reviews application\nin registration queue"]
I --> J{"10. Officer takes action"}
J -->|Under Review| I
J -->|Correction Required| K["11. Owner updates & resubmits"]
K --> I
J -->|Rejected| Z1["Terminal: Rejected\n(remarks visible to owner)"]
J -->|Approved| L["12. System generates certificates\n(1 for Inland, 4 for Sea-going)"]
L --> M["13. Owner downloads certificates"]
M --> N["14. System tracks renewal status\n(OK / Due Soon / Overdue)"]
N --> O["15. Manager views registration reports"]
Status lifecycle: Pending → Under Review → (Correction Required → Resubmitted →)* → Approved | Rejected. Approved/Rejected are terminal — no further officer action possible once reached.
What exists
Portal (apps/portal/src/app/features/vessel-registration/)
mock.ts— types (VesselCategory,RegistrationStatus,VesselRegistration, …), reference data (VESSEL_TYPES,ENGINE_TYPES,HULL_MATERIALS,REQUIRED_DOCS,CERTIFICATESper category),MOCK_REGISTRATIONS,addRegistration(),recordDownload()pages/VesselRegistrationPage.tsx— list of the owner's registrations (/vessel-registrations)pages/VesselRegistrationApplicationPage.tsx— 5-step submit wizard: Category → Vessel Details → Technical & Ownership → Documents → Review (/vessel-registrations/apply)pages/VesselRegistrationStatusPage.tsx— status detail: timeline, renewal alert, officer remarks + resubmit, certificate downloads (/vessel-registrations/:id)
Backoffice (apps/backoffice/src/app/features/vessel-registration/)
mock.ts— separate types +MOCK_REGISTRATIONS(officer-side shape, includesdocuments[]andcorrectionFields?),applyDecision(),generateCertificates()pages/VesselRegistrationQueuePage.tsx— queue table, stats, search + 3 filters (status/category/renewal) (/vessel-registrations)pages/VesselRegistrationReviewPage.tsx— two-column review: vessel/technical/ownership info + documents/timeline/remarks/certificates; decision bar (Mark Under Review / Request Correction / Reject / Approve) (/vessel-registrations/:id)pages/VesselRegistrationReportPage.tsx— manager report: KPI cards, status distribution, vessel-type breakdown, recent-10 table, renewal-tracking table (/vessel-registration-report)
Each app defines its own separate mock array (codebase convention: no shared domain types in libs/), and they don't sync — submitting in portal does not appear in the backoffice queue. That's the main thing a real API fixes. The two VesselRegistration shapes are already close (both modeled on the same field set) but not identical — see below.
Data shape mismatch to resolve
- Portal
VesselRegistrationhas nodocuments[]array — uploaded files live only as transientFile[]in the wizard's component state (docs: Record<string, File[] | null>inVesselRegistrationApplicationPage.tsx) and are never persisted to the mock record; only file names are shown in the review step. Known gap: when wiring the real POST, the upload payload needs to be sent and stored — today it's dropped after submit. - Backoffice
VesselRegistrationhasdocuments: RegistrationDocument[]({key, label, fileName, fileType}) so the officer review page has something to show — this is invented/seeded mock data, not real uploads. The real API should standardize on the backoffice shape (documents as first-class persisted records) since it's the superset the officer review page needs. - Certificates: portal's
RegistrationCertificatehas adownloadscounter (incremented client-side viarecordDownload()); backoffice's does not track downloads. Real API should own download-count as a server-side audit log, not a client counter. - Correction targeting: only the backoffice shape has
correctionFields?: string[](officer picks which fields/docs need fixing via aMultiSelect). Portal has no corresponding "these are the fields you need to fix" UI on the status page beyond the free-textremarks— worth adding when the real API returnscorrectionFields, so the owner can be pointed at the exact fields. - Status enum: portal has 5 statuses (
Pending | Under Review | Correction Required | Approved | Rejected); backoffice has 6 (addsResubmitted, distinct fromCorrection Required, for after the owner has acted). The real API should use the backoffice's 6-value enum — portal's status page should renderResubmitted(currently unhandled — it'll fall through to no special UI).
Suggested API surface
POST /vessel-registrations— submit. Multipart body: all wizard fields (seeVesselRegistrationin eithermock.tsfor the full field list) + document files keyed by theREQUIRED_DOCS[category]slot key. ReplacesaddRegistration()in portal'smock.ts.GET /vessel-registrations?ownerId=:id— portal's "My Registrations" list (VesselRegistrationPage.tsx).GET /vessel-registrations/:id— used by portal's status page and backoffice's review page alike (both key off the same id).GET /vessel-registrations(officer, all + filters:status,category,renewal,q) — backoffice queue (VesselRegistrationQueuePage.tsx); filtering can move server-side or stay client-side over the fetched page as today.PATCH /vessel-registrations/:id— officer decision. Body:{ status: 'Under Review' | 'Correction Required' | 'Rejected' | 'Approved', remarks?: string, correctionFields?: string[] }. ReplacesapplyDecision()in backoffice'smock.ts. Backend should enforce remarks-required forCorrection Required/Rejected(UI already gates this client-side, but don't trust it alone).POST /vessel-registrations/:id/resubmit— owner resubmit after correction (portal's "Resubmit Application" button onVesselRegistrationStatusPage.tsx, currently just routes back to the wizard with no state carried over — real flow should prefill the wizard from the existing record and only require the flagged fields/docs).- On
PATCH .../:idwithstatus: 'Approved': backend generates certificate records perCERTIFICATES[category](1 for Inland, 4 for Sea-going) — replacesgenerateCertificates()in backoffice'smock.ts. GET /vessel-registrations/:id/certificates/:certId/download— real file download + audit log entry, replacing the sharedDEMO_PDFbase64 placeholder used by both apps' download buttons.GET /vessel-registration-reports/summary— KPIs + status distribution + type breakdown forVesselRegistrationReportPage.tsx(or compute client-side from a fullGET /vessel-registrationsif volume stays low — current mock computes everything client-side from the in-memory array).- Document storage: real upload + signed URL for View, and a real download endpoint — today
getDocUrl()inVesselRegistrationReviewPage.tsx(backoffice) fakes it with a hardcoded base64 PDF / placehold.co image, same pattern as the vessel-transfer feature. - SMS/email: triggered server-side on submit and on every status change — UI currently just shows a toast claiming this happened (
notify.success('... SMS and email ...')); no actual send anywhere in either app.
Where to swap mock for real calls
- Portal:
apps/portal/src/app/features/vessel-registration/mock.ts(whole file), plus theuseState/local reads ofMOCK_REGISTRATIONSin all three page files, and the transientFile[]state inVesselRegistrationApplicationPage.tsx(needs to become a real multipart upload on submit). - Backoffice:
apps/backoffice/src/app/features/vessel-registration/mock.ts(whole file), plus the directapplyDecision(record, ...)/generateCertificates(record)mutations inVesselRegistrationReviewPage.tsx(swap for a mutation call + refetch/cache-invalidate), and the client-side aggregation inVesselRegistrationReportPage.tsx(swap for the summary endpoint above, or keep as a derived selector over cached query data). - Both apps already have an RTK Query base (
@ema-platform/api→baseApi.injectEndpoints, seeapps/portal/src/app/features/payment/api/payment-api.tsorapps/backoffice/src/app/features/certification/api/certification-api.ts) — follow that pattern rather than the ad-hocuseApiQuery/useApiMutationescape hatch.