fix(portal,backoffice): six reported issues in registration and the designer

Seafarer and vessel registration were filtered out of the operations
step by `requiresOperatorMode !== false`, so someone registering as a
seafarer or vessel owner landed on an onboarding screen that did not
describe them. Both now appear, grouped apart from the company modes:
declaring "I am a seafarer" is a different kind of statement from "my
company forwards freight".

The designer's preview was gated on the Handlebars source alone, which a
canvas layout does not have until the server compiles it on save -- so
the button was dead for exactly the designs the canvas exists for.

Editing looked broken rather than deliberately read-only: every seeded
template is PUBLISHED, and a published design is immutable because
certificates were issued from it. Says so, and offers the new-version
action that is the way forward.

Reviewing officers saw company, capital and staff tabs on seafarer
certificate applications, because PRESENTATION is keyed by the generic
licence keys and the fifty-odd rank-specific CoC/CoP keys fell through
to the company default. Matched by prefix instead, so a certificate
configured tomorrow gets the right presentation without a code change.

Seaman book and BTC are wired to the newly seeded licence types and no
longer marked "soon".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
fitse-yotor
2026-08-16 22:21:00 +03:00
parent c58a727a96
commit f294f67ced
22 changed files with 1237 additions and 86 deletions

View File

@@ -25,6 +25,8 @@ import type {
QueueFilter,
RemarkTargetType,
SavedQueueView,
TemplateFieldPlacement,
TemplateLogoPlacement,
TemplatePageOptions,
TemplateVariable,
} from './licensing.types';
@@ -550,6 +552,9 @@ export const licensingApi = baseApi
hbsSource?: string;
pageOptions?: TemplatePageOptions;
backgroundUrl?: string;
logoUrl?: string;
logoPlacement?: TemplateLogoPlacement;
fieldPlacements?: TemplateFieldPlacement[];
}
>({
query: ({ id, ...body }) => ({

View File

@@ -90,7 +90,9 @@ export type LicenseCategory =
| 'CARGO_FREIGHT'
| 'SHIPPING_AGENCY'
| 'INVESTMENT'
| 'MARITIME_PERSONNEL';
| 'MARITIME_PERSONNEL'
| 'VESSEL_SERVICES'
| 'WAIVER_SERVICES';
export interface LicenseCategoryDefinition {
key: LicenseCategory;
@@ -436,6 +438,44 @@ export interface TemplatePageOptions {
printBackground?: boolean;
}
/** Corner the institute logo is anchored to. */
export type TemplateLogoCorner =
| 'TOP_LEFT'
| 'TOP_CENTER'
| 'TOP_RIGHT'
| 'BOTTOM_LEFT'
| 'BOTTOM_RIGHT';
/** Where the institute logo sits on the certificate. */
export interface TemplateLogoPlacement {
corner?: TemplateLogoCorner;
/** Width as a percentage of page width. */
widthPct?: number;
/** Inset from the anchored corner, as a percentage of page width. */
offsetPct?: number;
}
/**
* One positioned block on the designer canvas.
*
* Percentages rather than pixels, so a layout survives an orientation change:
* the canvas and the rendered PDF agree without either knowing the other's
* dimensions.
*/
export interface TemplateFieldPlacement {
id: string;
/** Variable rendered here, or null when the block carries literal `text`. */
variable: string | null;
text?: string;
xPct: number;
yPct: number;
widthPct: number;
fontSize?: number;
fontWeight?: 'normal' | 'bold';
align?: 'left' | 'center' | 'right';
color?: string;
}
/** A certificate design authored in the backoffice. */
export interface LicenseTemplate {
id: string;
@@ -449,6 +489,13 @@ export interface LicenseTemplate {
* certificate — per-certificate data stays in the template layer above it.
*/
backgroundUrl?: string | null;
logoUrl?: string | null;
logoPlacement?: TemplateLogoPlacement | null;
/**
* Blocks positioned on the visual canvas. Null for a design authored as raw
* Handlebars — which is how the two editors stay distinguishable.
*/
fieldPlacements?: TemplateFieldPlacement[] | null;
status: TemplateStatus;
publishedAt: string | null;
createdAt: string;