mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
Adding license generation functionality
This commit is contained in:
434
libs/api/src/lib/features/licensing/licensing-api.ts
Normal file
434
libs/api/src/lib/features/licensing/licensing-api.ts
Normal file
@@ -0,0 +1,434 @@
|
||||
import { baseApi } from '../../base-api';
|
||||
import type {
|
||||
AppNotification,
|
||||
ApplicationDetail,
|
||||
ApplicationKind,
|
||||
ApplicationPayment,
|
||||
ApplicationStaff,
|
||||
Attachment,
|
||||
InitiatePaymentResult,
|
||||
IssuedLicense,
|
||||
Inspection,
|
||||
LicenseApplication,
|
||||
LicenseCategoryDefinition,
|
||||
LicenseStatus,
|
||||
LicenseType,
|
||||
LicenseTypeRequirements,
|
||||
Paginated,
|
||||
RemarkTargetType,
|
||||
} from './licensing.types';
|
||||
|
||||
const TAGS = [
|
||||
'LicenseType',
|
||||
'LicenseApplication',
|
||||
'ApplicationQueue',
|
||||
'Attachment',
|
||||
'Notification',
|
||||
'Inspection',
|
||||
'License',
|
||||
] as const;
|
||||
|
||||
const listTag = (type: (typeof TAGS)[number]) => ({ type, id: 'LIST' }) as const;
|
||||
const itemTag = (type: (typeof TAGS)[number], id: string) => ({ type, id }) as const;
|
||||
|
||||
/**
|
||||
* Typed licensing endpoints, shared by the portal and the backoffice.
|
||||
*
|
||||
* Each resource gets its own cache tag so a transition invalidates the one
|
||||
* application and its queue, rather than the whole store.
|
||||
*/
|
||||
export const licensingApi = baseApi
|
||||
.enhanceEndpoints({ addTagTypes: TAGS })
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
// ------------------------------------------------------------- config
|
||||
/** Active licence types an applicant can choose from. */
|
||||
getLicenseTypes: builder.query<Paginated<LicenseType>, void>({
|
||||
query: () => ({ url: '/license-types' }),
|
||||
providesTags: () => [listTag('LicenseType')],
|
||||
}),
|
||||
|
||||
/**
|
||||
* Category catalogue used to group the licence cards. Static on the
|
||||
* server, so it is cached under the licence-type list tag.
|
||||
*/
|
||||
getLicenseCategories: builder.query<
|
||||
Paginated<LicenseCategoryDefinition>,
|
||||
void
|
||||
>({
|
||||
query: () => ({ url: '/license-categories' }),
|
||||
providesTags: () => [listTag('LicenseType')],
|
||||
}),
|
||||
|
||||
/**
|
||||
* Fee configuration, for licensing administrators.
|
||||
*
|
||||
* `null` is a meaningful value for both amounts and must survive the
|
||||
* round trip: null on the new-application fee means the licence is free,
|
||||
* null on the renewal fee means it is charged at the new-application
|
||||
* rate. Sending `undefined` instead would leave the column untouched.
|
||||
*/
|
||||
updateLicenseFees: builder.mutation<
|
||||
LicenseType,
|
||||
{
|
||||
id: string;
|
||||
feeNewApplication?: number | null;
|
||||
feeRenewal?: number | null;
|
||||
feeCurrency?: string;
|
||||
}
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-types/${id}/fees`,
|
||||
method: 'PATCH',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [listTag('LicenseType'), itemTag('LicenseType', id)],
|
||||
}),
|
||||
|
||||
getLicenseTypeRequirements: builder.query<
|
||||
LicenseTypeRequirements,
|
||||
{ idOrKey: string; kind?: ApplicationKind }
|
||||
>({
|
||||
query: ({ idOrKey, kind = 'NEW' }) => ({
|
||||
url: `/license-types/requirements/${idOrKey}`,
|
||||
params: { kind },
|
||||
}),
|
||||
providesTags: (_r, _e, arg) => [itemTag('LicenseType', arg.idOrKey)],
|
||||
}),
|
||||
|
||||
// -------------------------------------------------------- application
|
||||
createApplication: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ licenseType: string; kind?: ApplicationKind; previousLicenseId?: string }
|
||||
>({
|
||||
query: (body) => ({ url: '/license-applications', method: 'POST', body }),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('LicenseApplication')]),
|
||||
}),
|
||||
|
||||
getMyApplications: builder.query<Paginated<LicenseApplication>, void>({
|
||||
query: () => ({ url: '/license-applications/mine' }),
|
||||
providesTags: () => [listTag('LicenseApplication')],
|
||||
}),
|
||||
|
||||
getApplication: builder.query<ApplicationDetail, string>({
|
||||
query: (id) => ({ url: `/license-applications/${id}` }),
|
||||
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
patchSection: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; sectionKey: string; values: Record<string, unknown> }
|
||||
>({
|
||||
query: ({ id, sectionKey, values }) => ({
|
||||
url: `/license-applications/${id}/sections/${sectionKey}`,
|
||||
method: 'PATCH',
|
||||
body: { values },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
addStaff: builder.mutation<
|
||||
ApplicationStaff,
|
||||
{ id: string; roleKey: string; fullName: string; position?: string; yearsOfExperience?: number }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-applications/${id}/staff`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
removeStaff: builder.mutation<unknown, { id: string; staffId: string }>({
|
||||
query: ({ id, staffId }) => ({
|
||||
url: `/license-applications/${id}/staff/${staffId}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
submitApplication: builder.mutation<LicenseApplication, string>({
|
||||
query: (id) => ({ url: `/license-applications/${id}/submit`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('LicenseApplication'), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
resolveRemark: builder.mutation<unknown, { id: string; remarkId: string }>({
|
||||
query: ({ id, remarkId }) => ({
|
||||
url: `/license-applications/${id}/remarks/${remarkId}/resolve`,
|
||||
method: 'PATCH',
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
resubmitApplication: builder.mutation<LicenseApplication, string>({
|
||||
query: (id) => ({ url: `/license-applications/${id}/resubmit`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('LicenseApplication')],
|
||||
}),
|
||||
|
||||
// -------------------------------------------------------- attachments
|
||||
getAttachments: builder.query<Attachment[], { ownerType: string; ownerId: string }>({
|
||||
query: ({ ownerType, ownerId }) => ({
|
||||
url: '/attachments',
|
||||
params: { ownerType, ownerId, withUrls: true },
|
||||
}),
|
||||
providesTags: (_r, _e, arg) => [itemTag('Attachment', arg.ownerId)],
|
||||
}),
|
||||
|
||||
deleteAttachment: builder.mutation<unknown, { attachmentId: string; ownerId: string }>({
|
||||
query: ({ attachmentId }) => ({
|
||||
url: `/attachments/${attachmentId}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, error, { ownerId }) =>
|
||||
error ? [] : [itemTag('Attachment', ownerId)],
|
||||
}),
|
||||
|
||||
// ----------------------------------------------------------- payments
|
||||
initiatePayment: builder.mutation<
|
||||
InitiatePaymentResult,
|
||||
{ id: string; provider?: string; platform?: 'web' | 'mobile'; payerAccount?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-applications/${id}/payments/initiate`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
getApplicationPayment: builder.query<ApplicationPayment, string>({
|
||||
query: (id) => ({ url: `/license-applications/${id}/payments` }),
|
||||
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
/** Testing shortcut — only offered when the API reports it enabled. */
|
||||
bypassPayment: builder.mutation<
|
||||
{ status: LicenseStatus; certificateIssued: boolean },
|
||||
string
|
||||
>({
|
||||
query: (id) => ({
|
||||
url: `/license-applications/${id}/payments/bypass`,
|
||||
method: 'POST',
|
||||
}),
|
||||
invalidatesTags: (_r, error, id) =>
|
||||
error
|
||||
? []
|
||||
: [itemTag('LicenseApplication', id), listTag('LicenseApplication'), listTag('License')],
|
||||
}),
|
||||
|
||||
getPaymentCapabilities: builder.query<{ bypassEnabled: boolean }, void>({
|
||||
query: () => ({ url: '/license-applications/payments/capabilities' }),
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------------ licences
|
||||
getMyLicenses: builder.query<Paginated<IssuedLicense>, void>({
|
||||
query: () => ({ url: '/licenses/mine' }),
|
||||
providesTags: () => [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 ?? {} }),
|
||||
providesTags: () => [listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
getAssignedToMe: builder.query<
|
||||
Paginated<LicenseApplication>,
|
||||
{ licenseTypeId?: string; search?: string } | void
|
||||
>({
|
||||
query: (params) => ({
|
||||
url: '/license-application-review/assigned-to-me',
|
||||
params: params ?? {},
|
||||
}),
|
||||
providesTags: () => [listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
getApplicationForReview: builder.query<ApplicationDetail, string>({
|
||||
query: (id) => ({ url: `/license-application-review/${id}` }),
|
||||
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
|
||||
}),
|
||||
|
||||
claimApplication: builder.mutation<LicenseApplication, string>({
|
||||
query: (id) => ({ url: `/license-application-review/${id}/claim`, method: 'POST' }),
|
||||
invalidatesTags: (_r, error, id) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
completeReview: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; capitalAmountVerified?: number; remark?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/complete-review`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
requestAdjustment: builder.mutation<
|
||||
LicenseApplication,
|
||||
{
|
||||
id: string;
|
||||
generalRemark?: string;
|
||||
items: { targetType: RemarkTargetType; targetKey: string; remark: string }[];
|
||||
}
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/request-adjustment`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
approveDocuments: builder.mutation<LicenseApplication, { id: string; remark?: string }>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/approve-documents`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
/**
|
||||
* `capitalAmountVerified` is what the officer confirms against the bank
|
||||
* letter. Licence types with a capital threshold reject the approval
|
||||
* without it, so it is part of the contract, not an optional extra.
|
||||
*/
|
||||
finalApprove: builder.mutation<
|
||||
LicenseApplication,
|
||||
{ id: string; remark?: string; capitalAmountVerified?: number }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/license-application-review/${id}/final-approve`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
rejectApplication: builder.mutation<LicenseApplication, { id: string; reason: string }>({
|
||||
query: ({ id, reason }) => ({
|
||||
url: `/license-application-review/${id}/reject`,
|
||||
method: 'POST',
|
||||
body: { reason },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { id }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
confirmPayment: builder.mutation<LicenseApplication, string>({
|
||||
query: (id) => ({
|
||||
url: `/license-application-review/${id}/confirm-payment`,
|
||||
method: 'POST',
|
||||
}),
|
||||
invalidatesTags: (_r, error, id) =>
|
||||
error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')],
|
||||
}),
|
||||
|
||||
// --------------------------------------------------------- inspection
|
||||
scheduleInspection: builder.mutation<
|
||||
Inspection,
|
||||
{ applicationId: string; scheduledDate: string; location?: string }
|
||||
>({
|
||||
query: (body) => ({ url: '/inspections', method: 'POST', body }),
|
||||
invalidatesTags: (_r, error, { applicationId }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', applicationId), listTag('Inspection')],
|
||||
}),
|
||||
|
||||
getInspections: builder.query<Inspection[], string>({
|
||||
query: (applicationId) => ({ url: `/inspections/application/${applicationId}` }),
|
||||
providesTags: () => [listTag('Inspection')],
|
||||
}),
|
||||
|
||||
recordInspectionResult: builder.mutation<
|
||||
Inspection,
|
||||
{ inspectionId: string; applicationId: string; result: 'PASSED' | 'FAILED'; findings: string }
|
||||
>({
|
||||
query: ({ inspectionId, result, findings }) => ({
|
||||
url: `/inspections/${inspectionId}/result`,
|
||||
method: 'PATCH',
|
||||
body: { result, findings },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { applicationId }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', applicationId), listTag('Inspection')],
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------ notifications
|
||||
getNotifications: builder.query<{ count: number; items: AppNotification[] }, void>({
|
||||
query: () => ({ url: '/notifications' }),
|
||||
providesTags: () => [listTag('Notification')],
|
||||
}),
|
||||
|
||||
getUnseenNotifications: builder.query<{ count: number; items: AppNotification[] }, void>({
|
||||
query: () => ({ url: '/notifications/unseen' }),
|
||||
providesTags: () => [listTag('Notification')],
|
||||
}),
|
||||
|
||||
markNotificationRead: builder.mutation<unknown, string>({
|
||||
query: (id) => ({ url: `/notifications/${id}/read`, method: 'PATCH' }),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('Notification')]),
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetLicenseTypesQuery,
|
||||
useGetLicenseCategoriesQuery,
|
||||
useUpdateLicenseFeesMutation,
|
||||
useGetLicenseTypeRequirementsQuery,
|
||||
useCreateApplicationMutation,
|
||||
useGetMyApplicationsQuery,
|
||||
useGetApplicationQuery,
|
||||
useInitiatePaymentMutation,
|
||||
useBypassPaymentMutation,
|
||||
useGetPaymentCapabilitiesQuery,
|
||||
useGetMyLicensesQuery,
|
||||
useGetCertificateUrlMutation,
|
||||
useGetApplicationPaymentQuery,
|
||||
usePatchSectionMutation,
|
||||
useAddStaffMutation,
|
||||
useRemoveStaffMutation,
|
||||
useSubmitApplicationMutation,
|
||||
useResolveRemarkMutation,
|
||||
useResubmitApplicationMutation,
|
||||
useGetAttachmentsQuery,
|
||||
useDeleteAttachmentMutation,
|
||||
useGetQueueQuery,
|
||||
useGetAssignedToMeQuery,
|
||||
useGetApplicationForReviewQuery,
|
||||
useClaimApplicationMutation,
|
||||
useCompleteReviewMutation,
|
||||
useRequestAdjustmentMutation,
|
||||
useApproveDocumentsMutation,
|
||||
useFinalApproveMutation,
|
||||
useRejectApplicationMutation,
|
||||
useConfirmPaymentMutation,
|
||||
useScheduleInspectionMutation,
|
||||
useGetInspectionsQuery,
|
||||
useRecordInspectionResultMutation,
|
||||
useGetNotificationsQuery,
|
||||
useGetUnseenNotificationsQuery,
|
||||
useMarkNotificationReadMutation,
|
||||
} = licensingApi;
|
||||
Reference in New Issue
Block a user