mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
Merge branch 'WorkflowChange' of https://github.com/Tria-plc/emaui into estif-branch-1
This commit is contained in:
@@ -5,6 +5,7 @@ export * from './lib/features/licensing';
|
||||
export * from './lib/features/location';
|
||||
export * from './lib/features/seafarer';
|
||||
export * from './lib/features/seafarer-registration';
|
||||
export * from './lib/features/seafarer-document';
|
||||
export * from './lib/features/vessel';
|
||||
export { baseQueryWithReauth, configureTokenRefresh } from './lib/base-api/base-query-with-reauth';
|
||||
export { openAuthedDocument, downloadAuthedFile } from './lib/base-api/download';
|
||||
|
||||
@@ -613,7 +613,8 @@ export interface InitiatePaymentResult {
|
||||
|
||||
export interface ApplicationPayment {
|
||||
id: string;
|
||||
applicationId: string;
|
||||
applicationId: string | null;
|
||||
documentId?: string | null;
|
||||
paymentIntentId: string;
|
||||
amount: string;
|
||||
currency: string;
|
||||
|
||||
3
libs/api/src/lib/features/seafarer-document/index.ts
Normal file
3
libs/api/src/lib/features/seafarer-document/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './seafarer-document.types';
|
||||
export * from './seafarer-document.constants';
|
||||
export * from './seafarer-document-api';
|
||||
@@ -0,0 +1,131 @@
|
||||
import { baseApi } from '../../base-api';
|
||||
import type { ApplicationPayment, InitiatePaymentResult } from '../licensing/licensing.types';
|
||||
import type {
|
||||
SeafarerDocument,
|
||||
SeafarerDocumentDetail,
|
||||
SeafarerDocumentKind,
|
||||
SeafarerDocumentRow,
|
||||
SeafarerDocumentStatus,
|
||||
} from './seafarer-document.types';
|
||||
|
||||
const TAG = 'SeafarerDocument' as const;
|
||||
const LIST = { type: TAG, id: 'LIST' } as const;
|
||||
const item = (id: string) => ({ type: TAG, id }) as const;
|
||||
|
||||
export interface SeafarerDocumentListFilter {
|
||||
kind?: SeafarerDocumentKind;
|
||||
status?: SeafarerDocumentStatus;
|
||||
search?: string;
|
||||
take?: number;
|
||||
skip?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seaman Book and BTC — own endpoints, not licence applications. Fees settle
|
||||
* through the same payment gateway, under `/seafarer-documents/:id/payments`.
|
||||
*/
|
||||
export const seafarerDocumentApi = baseApi
|
||||
.enhanceEndpoints({ addTagTypes: [TAG] })
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
// ------------------------------------------------------------ applicant
|
||||
getMySeafarerDocuments: builder.query<
|
||||
{ seamanBook: SeafarerDocument | null; btc: SeafarerDocument | null },
|
||||
void
|
||||
>({
|
||||
query: () => ({ url: '/seafarer-documents/mine' }),
|
||||
providesTags: () => [LIST],
|
||||
}),
|
||||
|
||||
getMySeafarerDocumentDownload: builder.query<{ url: string }, string>({
|
||||
query: (id) => ({ url: `/seafarer-documents/${id}/download` }),
|
||||
}),
|
||||
|
||||
initiateDocumentPayment: builder.mutation<
|
||||
InitiatePaymentResult,
|
||||
{ id: string; provider?: string; platform?: 'web' | 'mobile'; payerAccount?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/seafarer-documents/${id}/payments/initiate`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
|
||||
getDocumentPayment: builder.query<ApplicationPayment, string>({
|
||||
query: (id) => ({ url: `/seafarer-documents/${id}/payments` }),
|
||||
providesTags: (_r, _e, id) => [item(id)],
|
||||
}),
|
||||
|
||||
bypassDocumentPayment: builder.mutation<{ status: SeafarerDocumentStatus }, string>({
|
||||
query: (id) => ({ url: `/seafarer-documents/${id}/payments/bypass`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
|
||||
// --------------------------------------------------------------- review
|
||||
listSeafarerDocuments: builder.query<
|
||||
{ total: number; items: SeafarerDocumentRow[] },
|
||||
SeafarerDocumentListFilter
|
||||
>({
|
||||
query: (params) => ({ url: '/seafarer-document-review', params }),
|
||||
providesTags: () => [LIST],
|
||||
}),
|
||||
|
||||
getSeafarerDocumentReview: builder.query<SeafarerDocumentDetail, string>({
|
||||
query: (id) => ({ url: `/seafarer-document-review/${id}` }),
|
||||
providesTags: (_r, _e, id) => [item(id)],
|
||||
}),
|
||||
|
||||
getSeafarerDocumentReviewDownload: builder.query<{ url: string }, string>({
|
||||
query: (id) => ({ url: `/seafarer-document-review/${id}/download` }),
|
||||
}),
|
||||
|
||||
confirmSeafarerDocumentPayment: builder.mutation<SeafarerDocument, string>({
|
||||
query: (id) => ({ url: `/seafarer-document-review/${id}/confirm-payment`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
|
||||
scheduleSeafarerDocument: builder.mutation<
|
||||
SeafarerDocument,
|
||||
{ id: string; scheduledDate: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/seafarer-document-review/${id}/schedule-issuance`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
|
||||
issueSeafarerDocument: builder.mutation<SeafarerDocument, string>({
|
||||
query: (id) => ({ url: `/seafarer-document-review/${id}/issue`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
|
||||
rejectSeafarerDocument: builder.mutation<SeafarerDocument, { id: string; reason: string }>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/seafarer-document-review/${id}/reject`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetMySeafarerDocumentsQuery,
|
||||
useLazyGetMySeafarerDocumentDownloadQuery,
|
||||
useInitiateDocumentPaymentMutation,
|
||||
useGetDocumentPaymentQuery,
|
||||
useBypassDocumentPaymentMutation,
|
||||
useListSeafarerDocumentsQuery,
|
||||
useGetSeafarerDocumentReviewQuery,
|
||||
useLazyGetSeafarerDocumentReviewDownloadQuery,
|
||||
useConfirmSeafarerDocumentPaymentMutation,
|
||||
useScheduleSeafarerDocumentMutation,
|
||||
useIssueSeafarerDocumentMutation,
|
||||
useRejectSeafarerDocumentMutation,
|
||||
} = seafarerDocumentApi;
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { SeafarerDocumentKind, SeafarerDocumentStatus } from './seafarer-document.types';
|
||||
|
||||
export const SEAFARER_DOCUMENT_KIND_LABELS: Record<SeafarerDocumentKind, string> = {
|
||||
SEAMAN_BOOK: 'Seaman Book',
|
||||
BTC_BASIC_TRAINING: 'Basic Training Certificate',
|
||||
};
|
||||
|
||||
export const SEAFARER_DOCUMENT_STATUS_LABELS: Record<SeafarerDocumentStatus, string> = {
|
||||
AWAITING_REGISTRATION: 'Awaiting Registration',
|
||||
PAYMENT_PENDING: 'Payment Pending',
|
||||
PAID: 'Paid',
|
||||
PAYMENT_CONFIRMED: 'Payment Confirmed',
|
||||
SCHEDULED: 'Pickup Scheduled',
|
||||
ISSUED: 'Issued',
|
||||
REJECTED: 'Rejected',
|
||||
CANCELLED: 'Cancelled',
|
||||
};
|
||||
|
||||
export const SEAFARER_DOCUMENT_STATUS_COLORS: Record<SeafarerDocumentStatus, string> = {
|
||||
AWAITING_REGISTRATION: 'gray',
|
||||
PAYMENT_PENDING: 'orange',
|
||||
PAID: 'blue',
|
||||
PAYMENT_CONFIRMED: 'blue',
|
||||
SCHEDULED: 'grape',
|
||||
ISSUED: 'teal',
|
||||
REJECTED: 'red',
|
||||
CANCELLED: 'gray',
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { ApplicationPayment } from '../licensing/licensing.types';
|
||||
|
||||
export type SeafarerDocumentKind = 'SEAMAN_BOOK' | 'BTC_BASIC_TRAINING';
|
||||
|
||||
export type SeafarerDocumentStatus =
|
||||
| 'AWAITING_REGISTRATION'
|
||||
| 'PAYMENT_PENDING'
|
||||
| 'PAID'
|
||||
| 'PAYMENT_CONFIRMED'
|
||||
| 'SCHEDULED'
|
||||
| 'ISSUED'
|
||||
| 'REJECTED'
|
||||
| 'CANCELLED';
|
||||
|
||||
/** A Seaman Book or BTC request — opened by a seafarer registration. */
|
||||
export interface SeafarerDocument {
|
||||
id: string;
|
||||
kind: SeafarerDocumentKind;
|
||||
requestNumber: string;
|
||||
applicantUserId: string;
|
||||
profileId: string | null;
|
||||
seafarerRegistrationId: string | null;
|
||||
status: SeafarerDocumentStatus;
|
||||
feeAmount: number | null;
|
||||
feeCurrency: string;
|
||||
submittedAt: string | null;
|
||||
paidAt: string | null;
|
||||
paymentReference: string | null;
|
||||
scheduledIssuanceDate: string | null;
|
||||
documentNumber: string | null;
|
||||
issueDate: string | null;
|
||||
expiryDate: string | null;
|
||||
documentFileKey: string | null;
|
||||
issuedAt: string | null;
|
||||
rejectionReason: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface SeafarerDocumentApplicant {
|
||||
name: string;
|
||||
seafarerNumber: string | null;
|
||||
registrationNumber: string | null;
|
||||
registrationId: string | null;
|
||||
}
|
||||
|
||||
export type SeafarerDocumentRow = SeafarerDocument & {
|
||||
applicant: SeafarerDocumentApplicant | null;
|
||||
};
|
||||
|
||||
export interface SeafarerDocumentDetail {
|
||||
document: SeafarerDocument;
|
||||
applicant: SeafarerDocumentApplicant | null;
|
||||
payment: ApplicationPayment | null;
|
||||
}
|
||||
@@ -66,11 +66,6 @@ export const seafarerRegistrationApi = baseApi
|
||||
providesTags: (_r, _e, id) => [item(id)],
|
||||
}),
|
||||
|
||||
claimSeafarerRegistration: builder.mutation<SeafarerRegistration, string>({
|
||||
query: (id) => ({ url: `/seafarer-registration-review/${id}/claim`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) => (error ? [] : [LIST, item(id)]),
|
||||
}),
|
||||
|
||||
approveSeafarerRegistration: builder.mutation<
|
||||
SeafarerRegistration,
|
||||
{ id: string; remark?: string }
|
||||
@@ -117,7 +112,6 @@ export const {
|
||||
useSubmitSeafarerRegistrationMutation,
|
||||
useListSeafarerRegistrationsQuery,
|
||||
useGetSeafarerRegistrationReviewQuery,
|
||||
useClaimSeafarerRegistrationMutation,
|
||||
useApproveSeafarerRegistrationMutation,
|
||||
useRejectSeafarerRegistrationMutation,
|
||||
useRequestSeafarerRegistrationChangesMutation,
|
||||
|
||||
@@ -102,7 +102,6 @@ export const SEAFARER_REGISTRATION_DOCUMENTS: {
|
||||
export const SEAFARER_REGISTRATION_STATUS_LABELS: Record<SeafarerRegistrationStatus, string> = {
|
||||
DRAFT: 'Draft',
|
||||
SUBMITTED: 'Submitted',
|
||||
UNDER_REVIEW: 'Under Review',
|
||||
RESUBMIT_REQUIRED: 'Corrections Requested',
|
||||
APPROVED: 'Approved',
|
||||
REJECTED: 'Rejected',
|
||||
@@ -111,7 +110,6 @@ export const SEAFARER_REGISTRATION_STATUS_LABELS: Record<SeafarerRegistrationSta
|
||||
export const SEAFARER_REGISTRATION_STATUS_COLORS: Record<SeafarerRegistrationStatus, string> = {
|
||||
DRAFT: 'gray',
|
||||
SUBMITTED: 'blue',
|
||||
UNDER_REVIEW: 'indigo',
|
||||
RESUBMIT_REQUIRED: 'orange',
|
||||
APPROVED: 'teal',
|
||||
REJECTED: 'red',
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { SeafarerDepartment } from '../seafarer/seafarer.types';
|
||||
export type SeafarerRegistrationStatus =
|
||||
| 'DRAFT'
|
||||
| 'SUBMITTED'
|
||||
| 'UNDER_REVIEW'
|
||||
| 'RESUBMIT_REQUIRED'
|
||||
| 'APPROVED'
|
||||
| 'REJECTED';
|
||||
@@ -55,8 +54,6 @@ export interface SeafarerRegistration extends SeafarerRegistrationAnswers {
|
||||
profileId: string | null;
|
||||
status: SeafarerRegistrationStatus;
|
||||
submittedAt: string | null;
|
||||
assignedOfficerId: string | null;
|
||||
claimedAt: string | null;
|
||||
decidedAt: string | null;
|
||||
decidedById: string | null;
|
||||
/** What the officer asked to be fixed, or noted at approval. */
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './seafarer.types';
|
||||
export * from './seafarer-api';
|
||||
export * from './seafarer.helpers';
|
||||
|
||||
23
libs/api/src/lib/features/seafarer/seafarer.helpers.ts
Normal file
23
libs/api/src/lib/features/seafarer/seafarer.helpers.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Days served on one engagement, counted inclusively — embarkation and
|
||||
* discharge days both count. The same arithmetic the API uses for approved
|
||||
* sea time (`SeafarerRecordService.approvedSeaTime`), so the figure a seafarer
|
||||
* sees while typing is the figure the eligibility gate will credit.
|
||||
*
|
||||
* `null` until both dates are set or while they are out of order.
|
||||
*/
|
||||
export function seaServiceDays(
|
||||
engagementDate: string | null | undefined,
|
||||
dischargeDate: string | null | undefined,
|
||||
): number | null {
|
||||
if (!engagementDate || !dischargeDate) return null;
|
||||
const from = Date.UTC(...dateParts(engagementDate));
|
||||
const to = Date.UTC(...dateParts(dischargeDate));
|
||||
if (Number.isNaN(from) || Number.isNaN(to) || to < from) return null;
|
||||
return Math.round((to - from) / 86_400_000) + 1;
|
||||
}
|
||||
|
||||
function dateParts(value: string): [number, number, number] {
|
||||
const [y, m, d] = value.slice(0, 10).split('-').map(Number);
|
||||
return [y, (m || 1) - 1, d || 1];
|
||||
}
|
||||
Reference in New Issue
Block a user