mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-28 14:50:58 +00:00
Initial End to End functionality
This commit is contained in:
@@ -14,10 +14,44 @@ import type {
|
||||
LicenseStatus,
|
||||
LicenseType,
|
||||
LicenseTypeRequirements,
|
||||
AssignableOfficer,
|
||||
DocumentDecision,
|
||||
DocumentReview,
|
||||
ExportResult,
|
||||
LicenseTemplate,
|
||||
Paginated,
|
||||
QueueCounts,
|
||||
QueueFilter,
|
||||
RemarkTargetType,
|
||||
SavedQueueView,
|
||||
TemplatePageOptions,
|
||||
TemplateVariable,
|
||||
} from './licensing.types';
|
||||
|
||||
/**
|
||||
* Drops empty filters and flattens the status array.
|
||||
*
|
||||
* The grid keeps every facet in one object and serialises it to the URL, so
|
||||
* cleared facets arrive as undefined/[]; sending those verbatim would put
|
||||
* `status=` and `assignee=` on the wire and fail DTO validation.
|
||||
*/
|
||||
function serialiseQueueFilter(
|
||||
filter: QueueFilter | void,
|
||||
): Record<string, unknown> {
|
||||
if (!filter) return {};
|
||||
const params: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (value === undefined || value === null || value === '') continue;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) continue;
|
||||
params[key] = value.join(',');
|
||||
continue;
|
||||
}
|
||||
params[key] = value;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
const TAGS = [
|
||||
'LicenseType',
|
||||
'LicenseApplication',
|
||||
@@ -26,6 +60,8 @@ const TAGS = [
|
||||
'Notification',
|
||||
'Inspection',
|
||||
'License',
|
||||
'SavedView',
|
||||
'LicenseTemplate',
|
||||
] as const;
|
||||
|
||||
const listTag = (type: (typeof TAGS)[number]) => ({ type, id: 'LIST' }) as const;
|
||||
@@ -86,6 +122,20 @@ export const licensingApi = baseApi
|
||||
error ? [] : [listTag('LicenseType'), itemTag('LicenseType', id)],
|
||||
}),
|
||||
|
||||
/** Validity is edited beside the certificate design, not with the fees. */
|
||||
updateLicenseValidity: builder.mutation<
|
||||
LicenseType,
|
||||
{ id: string; validityMonths: number }
|
||||
>({
|
||||
query: ({ id, validityMonths }) => ({
|
||||
url: `/license-types/${id}/validity`,
|
||||
method: 'PATCH',
|
||||
body: { validityMonths },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseType', id), listTag('LicenseType')],
|
||||
}),
|
||||
|
||||
getLicenseTypeRequirements: builder.query<
|
||||
LicenseTypeRequirements,
|
||||
{ idOrKey: string; kind?: ApplicationKind }
|
||||
@@ -234,30 +284,82 @@ export const licensingApi = baseApi
|
||||
providesTags: () => [listTag('License')],
|
||||
}),
|
||||
|
||||
suspendLicense: builder.mutation<IssuedLicense, { id: string; reason: string }>({
|
||||
query: ({ id, reason }) => ({
|
||||
url: `/licenses/${id}/suspend`,
|
||||
method: 'POST',
|
||||
body: { reason },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('License', id), listTag('License')],
|
||||
}),
|
||||
|
||||
revokeLicense: builder.mutation<IssuedLicense, { id: string; reason: string }>({
|
||||
query: ({ id, reason }) => ({
|
||||
url: `/licenses/${id}/revoke`,
|
||||
method: 'POST',
|
||||
body: { reason },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('License', id), listTag('License')],
|
||||
}),
|
||||
|
||||
reinstateLicense: builder.mutation<IssuedLicense, { id: string; reason: string }>({
|
||||
query: ({ id, reason }) => ({
|
||||
url: `/licenses/${id}/reinstate`,
|
||||
method: 'POST',
|
||||
body: { reason },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('License', id), listTag('License')],
|
||||
}),
|
||||
|
||||
getCertificateUrl: builder.mutation<{ url: string }, string>({
|
||||
query: (id) => ({ url: `/licenses/${id}/certificate` }),
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------------- review
|
||||
getQueue: builder.query<
|
||||
Paginated<LicenseApplication>,
|
||||
{ licenseTypeId?: string; search?: string } | void
|
||||
>({
|
||||
query: (params) => ({ url: '/license-application-review/queue', params: params ?? {} }),
|
||||
getQueue: builder.query<Paginated<LicenseApplication>, QueueFilter | void>({
|
||||
query: (params) => ({
|
||||
url: '/license-application-review/queue',
|
||||
params: serialiseQueueFilter(params),
|
||||
}),
|
||||
providesTags: () => [listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
getAssignedToMe: builder.query<
|
||||
Paginated<LicenseApplication>,
|
||||
{ licenseTypeId?: string; search?: string } | void
|
||||
QueueFilter | void
|
||||
>({
|
||||
query: (params) => ({
|
||||
url: '/license-application-review/assigned-to-me',
|
||||
params: params ?? {},
|
||||
params: serialiseQueueFilter(params),
|
||||
}),
|
||||
providesTags: () => [listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
/** Every non-draft application — backs the grid's "All" view. */
|
||||
getAllApplications: builder.query<
|
||||
Paginated<LicenseApplication>,
|
||||
QueueFilter | void
|
||||
>({
|
||||
query: (params) => ({
|
||||
url: '/license-application-review/all',
|
||||
params: serialiseQueueFilter(params),
|
||||
}),
|
||||
providesTags: () => [listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
/**
|
||||
* Counts for the saved-view tabs. Shares the ApplicationQueue tag, so a
|
||||
* claim or a decision refreshes the badges along with the list and the
|
||||
* numbers can never drift from what the grid is showing.
|
||||
*/
|
||||
getQueueCounts: builder.query<QueueCounts, void>({
|
||||
query: () => ({ url: '/license-application-review/counts' }),
|
||||
providesTags: () => [listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
getApplicationForReview: builder.query<ApplicationDetail, string>({
|
||||
query: (id) => ({ url: `/license-application-review/${id}` }),
|
||||
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
|
||||
@@ -288,6 +390,8 @@ export const licensingApi = baseApi
|
||||
id: string;
|
||||
generalRemark?: string;
|
||||
items: { targetType: RemarkTargetType; targetKey: string; remark: string }[];
|
||||
/** Officer-edited wording for the applicant notification. */
|
||||
notificationBody?: string;
|
||||
}
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
@@ -327,16 +431,213 @@ export const licensingApi = baseApi
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
rejectApplication: builder.mutation<LicenseApplication, { id: string; reason: string }>({
|
||||
query: ({ id, reason }) => ({
|
||||
rejectApplication: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; reason: string; notificationBody?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/reject`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
// ------------------------------------------------- certificate designs
|
||||
getLicenseTemplates: builder.query<LicenseTemplate[], string | void>({
|
||||
query: (licenseTypeId) => ({
|
||||
url: '/license-templates',
|
||||
params: licenseTypeId ? { licenseTypeId } : {},
|
||||
}),
|
||||
providesTags: () => [listTag('LicenseTemplate')],
|
||||
}),
|
||||
|
||||
getTemplateVariables: builder.query<TemplateVariable[], void>({
|
||||
query: () => ({ url: '/license-templates/variables' }),
|
||||
}),
|
||||
|
||||
getBuiltInTemplate: builder.query<{ hbsSource: string }, void>({
|
||||
query: () => ({ url: '/license-templates/built-in' }),
|
||||
}),
|
||||
|
||||
createLicenseTemplate: builder.mutation<
|
||||
LicenseTemplate,
|
||||
{
|
||||
licenseTypeId: string;
|
||||
name: string;
|
||||
hbsSource?: string;
|
||||
pageOptions?: TemplatePageOptions;
|
||||
}
|
||||
>({
|
||||
query: (body) => ({ url: '/license-templates', method: 'POST', body }),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('LicenseTemplate')]),
|
||||
}),
|
||||
|
||||
updateLicenseTemplate: builder.mutation<
|
||||
LicenseTemplate,
|
||||
{
|
||||
id: string;
|
||||
name?: string;
|
||||
hbsSource?: string;
|
||||
pageOptions?: TemplatePageOptions;
|
||||
}
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-templates/${id}`,
|
||||
method: 'PATCH',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('LicenseTemplate')]),
|
||||
}),
|
||||
|
||||
publishLicenseTemplate: builder.mutation<LicenseTemplate, string>({
|
||||
query: (id) => ({ url: `/license-templates/${id}/publish`, method: 'POST' }),
|
||||
// The previously published design is archived by the same call, so the
|
||||
// whole list is refetched rather than patching two rows by hand.
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('LicenseTemplate')]),
|
||||
}),
|
||||
|
||||
archiveLicenseTemplate: builder.mutation<LicenseTemplate, string>({
|
||||
query: (id) => ({ url: `/license-templates/${id}/archive`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('LicenseTemplate')]),
|
||||
}),
|
||||
|
||||
deleteLicenseTemplate: builder.mutation<unknown, string>({
|
||||
query: (id) => ({ url: `/license-templates/${id}`, method: 'DELETE' }),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('LicenseTemplate')]),
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------ document review
|
||||
getDocumentReviews: builder.query<DocumentReview[], string>({
|
||||
query: (id) => ({ url: `/license-application-review/${id}/document-reviews` }),
|
||||
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
reviewDocument: builder.mutation<
|
||||
DocumentReview,
|
||||
{
|
||||
id: string;
|
||||
documentKey: string;
|
||||
decision: DocumentDecision;
|
||||
reason?: string;
|
||||
attachmentId?: string;
|
||||
}
|
||||
>({
|
||||
query: ({ id, documentKey, ...body }) => ({
|
||||
url: `/license-application-review/${id}/documents/${encodeURIComponent(documentKey)}/review`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
clearDocumentReview: builder.mutation<unknown, { id: string; documentKey: string }>({
|
||||
query: ({ id, documentKey }) => ({
|
||||
url: `/license-application-review/${id}/documents/${encodeURIComponent(documentKey)}/review`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
// ----------------------------------------------------------- saved views
|
||||
getSavedViews: builder.query<SavedQueueView[], void>({
|
||||
query: () => ({ url: '/license-application-review/saved-views' }),
|
||||
providesTags: () => [listTag('SavedView')],
|
||||
}),
|
||||
|
||||
createSavedView: builder.mutation<
|
||||
SavedQueueView,
|
||||
{ name: string; queryString: string; isShared?: boolean; isDefault?: boolean }
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/license-application-review/saved-views',
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('SavedView')]),
|
||||
}),
|
||||
|
||||
deleteSavedView: builder.mutation<unknown, string>({
|
||||
query: (viewId) => ({
|
||||
url: `/license-application-review/saved-views/${viewId}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('SavedView')]),
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------------- export
|
||||
/**
|
||||
* The whole filtered result set, not the current page. Lazy so it only
|
||||
* runs when the officer actually asks for a file.
|
||||
*/
|
||||
exportApplications: builder.query<ExportResult, QueueFilter | void>({
|
||||
query: (params) => ({
|
||||
url: '/license-application-review/export',
|
||||
params: serialiseQueueFilter(params),
|
||||
}),
|
||||
}),
|
||||
|
||||
/** Officers the Assign and Escalate dialogs can offer. */
|
||||
getAssignableOfficers: builder.query<AssignableOfficer[], void>({
|
||||
query: () => ({ url: '/license-application-review/officers' }),
|
||||
}),
|
||||
|
||||
// ----------------------------------------------------- workflow controls
|
||||
assignApplication: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; officerId: string; remark?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/assign`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
holdApplication: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; reason: string }
|
||||
>({
|
||||
query: ({ id, reason }) => ({
|
||||
url: `/license-application-review/${id}/hold`,
|
||||
method: 'POST',
|
||||
body: { reason },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
resumeApplication: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; remark?: string }
|
||||
>({
|
||||
query: ({ id, remark }) => ({
|
||||
url: `/license-application-review/${id}/resume`,
|
||||
method: 'POST',
|
||||
body: { remark },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
escalateApplication: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; supervisorId: string; reason: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/escalate`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
confirmPayment: builder.mutation<LicenseApplication, string>({
|
||||
query: (id) => ({
|
||||
url: `/license-application-review/${id}/confirm-payment`,
|
||||
@@ -397,6 +698,7 @@ export const {
|
||||
useGetLicenseTypesQuery,
|
||||
useGetLicenseCategoriesQuery,
|
||||
useUpdateLicenseFeesMutation,
|
||||
useUpdateLicenseValidityMutation,
|
||||
useGetLicenseTypeRequirementsQuery,
|
||||
useCreateApplicationMutation,
|
||||
useGetMyApplicationsQuery,
|
||||
@@ -417,6 +719,31 @@ export const {
|
||||
useDeleteAttachmentMutation,
|
||||
useGetQueueQuery,
|
||||
useGetAssignedToMeQuery,
|
||||
useGetAllApplicationsQuery,
|
||||
useGetQueueCountsQuery,
|
||||
useGetLicenseTemplatesQuery,
|
||||
useGetTemplateVariablesQuery,
|
||||
useGetBuiltInTemplateQuery,
|
||||
useCreateLicenseTemplateMutation,
|
||||
useUpdateLicenseTemplateMutation,
|
||||
usePublishLicenseTemplateMutation,
|
||||
useArchiveLicenseTemplateMutation,
|
||||
useDeleteLicenseTemplateMutation,
|
||||
useGetDocumentReviewsQuery,
|
||||
useReviewDocumentMutation,
|
||||
useClearDocumentReviewMutation,
|
||||
useGetSavedViewsQuery,
|
||||
useCreateSavedViewMutation,
|
||||
useDeleteSavedViewMutation,
|
||||
useLazyExportApplicationsQuery,
|
||||
useGetAssignableOfficersQuery,
|
||||
useSuspendLicenseMutation,
|
||||
useRevokeLicenseMutation,
|
||||
useReinstateLicenseMutation,
|
||||
useAssignApplicationMutation,
|
||||
useHoldApplicationMutation,
|
||||
useResumeApplicationMutation,
|
||||
useEscalateApplicationMutation,
|
||||
useGetApplicationForReviewQuery,
|
||||
useClaimApplicationMutation,
|
||||
useCompleteReviewMutation,
|
||||
|
||||
@@ -57,6 +57,7 @@ export const STATUS_LABELS: Record<LicenseStatus, string> = {
|
||||
INSPECTION_COMPLETED: 'Inspection Completed',
|
||||
APPROVED: 'Approved',
|
||||
REJECTED: 'Rejected',
|
||||
ON_HOLD: 'On Hold',
|
||||
PAYMENT_PENDING: 'Payment Pending',
|
||||
PAID: 'Paid',
|
||||
PAYMENT_CONFIRMED: 'Preparing Certificate',
|
||||
@@ -75,6 +76,7 @@ export const STATUS_COLORS: Record<LicenseStatus, string> = {
|
||||
INSPECTION_COMPLETED: 'cyan',
|
||||
APPROVED: 'teal',
|
||||
REJECTED: 'red',
|
||||
ON_HOLD: 'gray',
|
||||
PAYMENT_PENDING: 'yellow',
|
||||
PAID: 'lime',
|
||||
PAYMENT_CONFIRMED: 'teal',
|
||||
@@ -97,6 +99,8 @@ export const STATUS_PROGRESS: Record<LicenseStatus, number> = {
|
||||
INSPECTION_PENDING: 55,
|
||||
INSPECTION_COMPLETED: 65,
|
||||
APPROVED: 75,
|
||||
// Parked, so it keeps the progress of wherever it was held from.
|
||||
ON_HOLD: 45,
|
||||
PAYMENT_PENDING: 80,
|
||||
PAID: 88,
|
||||
PAYMENT_CONFIRMED: 94,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type LicenseStatus =
|
||||
| 'INSPECTION_COMPLETED'
|
||||
| 'APPROVED'
|
||||
| 'REJECTED'
|
||||
| 'ON_HOLD'
|
||||
| 'PAYMENT_PENDING'
|
||||
| 'PAID'
|
||||
| 'PAYMENT_CONFIRMED'
|
||||
@@ -97,6 +98,11 @@ export interface LicenseType {
|
||||
feeCurrency: string;
|
||||
capitalThreshold: string | number | null;
|
||||
validityMonths: number;
|
||||
/**
|
||||
* Target turnaround in hours. Null means this type is not tracked against
|
||||
* an SLA, which the grid renders as "—" rather than as instantly overdue.
|
||||
*/
|
||||
slaHours: number | null;
|
||||
inspectionRequired: boolean;
|
||||
issuesCertificate: boolean;
|
||||
renewalEnabled: boolean;
|
||||
@@ -202,6 +208,8 @@ export interface Attachment {
|
||||
validFrom: string | null;
|
||||
validTo: string | null;
|
||||
files: AttachmentFile[];
|
||||
/** From BaseEntity. Used to place the upload on the activity trail. */
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface StatusHistoryEntry {
|
||||
@@ -264,6 +272,105 @@ export interface AppNotification {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** Server-side queue filters. Mirrors ApplicationQueueFilterDto in the API. */
|
||||
export interface QueueFilter {
|
||||
licenseTypeId?: string;
|
||||
search?: string;
|
||||
status?: LicenseStatus[];
|
||||
/** Officer uuid, or the literal 'unassigned'. */
|
||||
assignee?: string;
|
||||
submittedFrom?: string;
|
||||
submittedTo?: string;
|
||||
overdue?: boolean;
|
||||
sortBy?: QueueSortField;
|
||||
sortDir?: 'ASC' | 'DESC';
|
||||
take?: number;
|
||||
skip?: number;
|
||||
}
|
||||
|
||||
export type QueueSortField =
|
||||
| 'submittedAt'
|
||||
| 'applicationNumber'
|
||||
| 'companyName'
|
||||
| 'status'
|
||||
| 'dueAt';
|
||||
|
||||
/** Row counts behind the queue's saved-view tabs. */
|
||||
export interface QueueCounts {
|
||||
unassigned: number;
|
||||
mine: number;
|
||||
awaitingApplicant: number;
|
||||
overdue: number;
|
||||
readyToIssue: number;
|
||||
all: number;
|
||||
}
|
||||
|
||||
export type DocumentDecision = 'ACCEPTED' | 'REJECTED';
|
||||
|
||||
/** An officer's verdict on one uploaded document. */
|
||||
export interface DocumentReview {
|
||||
id: string;
|
||||
applicationId: string;
|
||||
documentKey: string;
|
||||
attachmentId: string | null;
|
||||
decision: DocumentDecision;
|
||||
reason: string | null;
|
||||
reviewedById: string | null;
|
||||
reviewedByName: string | null;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/** An officer-defined queue filter, persisted server-side. */
|
||||
export interface SavedQueueView {
|
||||
id: string;
|
||||
ownerUserId: string;
|
||||
name: string;
|
||||
queryString: string;
|
||||
isShared: boolean;
|
||||
isDefault: boolean;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface AssignableOfficer {
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
/** Full filtered result set for export. `truncated` when the cap was hit. */
|
||||
export interface ExportResult {
|
||||
items: LicenseApplication[];
|
||||
total: number;
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export type TemplateStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
|
||||
|
||||
export interface TemplatePageOptions {
|
||||
format?: 'A4' | 'A5' | 'Letter' | 'Legal';
|
||||
landscape?: boolean;
|
||||
printBackground?: boolean;
|
||||
}
|
||||
|
||||
/** A certificate design authored in the backoffice. */
|
||||
export interface LicenseTemplate {
|
||||
id: string;
|
||||
licenseTypeId: string;
|
||||
name: string;
|
||||
version: number;
|
||||
hbsSource: string;
|
||||
pageOptions: TemplatePageOptions | null;
|
||||
status: TemplateStatus;
|
||||
publishedAt: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/** A placeholder a certificate design may use. */
|
||||
export interface TemplateVariable {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface Paginated<T> {
|
||||
total: number;
|
||||
items: T[];
|
||||
@@ -321,6 +428,10 @@ export interface ApplicationPayment {
|
||||
|
||||
/** A licence certificate issued to the applicant. */
|
||||
export interface IssuedLicense {
|
||||
/** Why the licence was suspended, revoked or cancelled. */
|
||||
statusReason?: string | null;
|
||||
statusChangedAt?: string | null;
|
||||
statusChangedByName?: string | null;
|
||||
id: string;
|
||||
certificateNumber: string;
|
||||
licenseTypeId: string;
|
||||
|
||||
Reference in New Issue
Block a user