Files
emaui/docs/vessel-registration.md
estifanos e482a4aca6 feat(vessel-registration): implement vessel registration feature with multi-step application process
- 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.
2026-07-24 07:44:45 +00:00

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, CERTIFICATES per 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, includes documents[] and correctionFields?), 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 VesselRegistration has no documents[] array — uploaded files live only as transient File[] in the wizard's component state (docs: Record<string, File[] | null> in VesselRegistrationApplicationPage.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 VesselRegistration has documents: 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 RegistrationCertificate has a downloads counter (incremented client-side via recordDownload()); 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 a MultiSelect). Portal has no corresponding "these are the fields you need to fix" UI on the status page beyond the free-text remarks — worth adding when the real API returns correctionFields, 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 (adds Resubmitted, distinct from Correction Required, for after the owner has acted). The real API should use the backoffice's 6-value enum — portal's status page should render Resubmitted (currently unhandled — it'll fall through to no special UI).

Suggested API surface

  • POST /vessel-registrations — submit. Multipart body: all wizard fields (see VesselRegistration in either mock.ts for the full field list) + document files keyed by the REQUIRED_DOCS[category] slot key. Replaces addRegistration() in portal's mock.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[] }. Replaces applyDecision() in backoffice's mock.ts. Backend should enforce remarks-required for Correction 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 on VesselRegistrationStatusPage.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 .../:id with status: 'Approved': backend generates certificate records per CERTIFICATES[category] (1 for Inland, 4 for Sea-going) — replaces generateCertificates() in backoffice's mock.ts.
  • GET /vessel-registrations/:id/certificates/:certId/download — real file download + audit log entry, replacing the shared DEMO_PDF base64 placeholder used by both apps' download buttons.
  • GET /vessel-registration-reports/summary — KPIs + status distribution + type breakdown for VesselRegistrationReportPage.tsx (or compute client-side from a full GET /vessel-registrations if 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() in VesselRegistrationReviewPage.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 the useState/local reads of MOCK_REGISTRATIONS in all three page files, and the transient File[] state in VesselRegistrationApplicationPage.tsx (needs to become a real multipart upload on submit).
  • Backoffice: apps/backoffice/src/app/features/vessel-registration/mock.ts (whole file), plus the direct applyDecision(record, ...) / generateCertificates(record) mutations in VesselRegistrationReviewPage.tsx (swap for a mutation call + refetch/cache-invalidate), and the client-side aggregation in VesselRegistrationReportPage.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/apibaseApi.injectEndpoints, see apps/portal/src/app/features/payment/api/payment-api.ts or apps/backoffice/src/app/features/certification/api/certification-api.ts) — follow that pattern rather than the ad-hoc useApiQuery/useApiMutation escape hatch.