mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
818 lines
29 KiB
TypeScript
818 lines
29 KiB
TypeScript
import { baseApi } from '../../base-api';
|
|
import type {
|
|
AppNotification,
|
|
ApplicationDetail,
|
|
ApplicationKind,
|
|
ApplicationPayment,
|
|
ApplicationStaff,
|
|
Attachment,
|
|
InitiatePaymentResult,
|
|
IssuedLicense,
|
|
Inspection,
|
|
LicenseApplication,
|
|
LicenseCategoryDefinition,
|
|
LicenseStatus,
|
|
LicenseType,
|
|
LicenseTypeRequirements,
|
|
OperatorType,
|
|
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',
|
|
'OperatorType',
|
|
'LicenseApplication',
|
|
'ApplicationQueue',
|
|
'Attachment',
|
|
'Notification',
|
|
'Inspection',
|
|
'License',
|
|
'SavedView',
|
|
'LicenseTemplate',
|
|
] 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')],
|
|
}),
|
|
|
|
/**
|
|
* The signed-in applicant's declared modes of operation.
|
|
*
|
|
* Separate from the licence-type catalogue on purpose: the catalogue is
|
|
* the same for everyone and heavily cached, while this is per-user and
|
|
* changes the moment they edit their profile.
|
|
*/
|
|
getMyOperatorTypes: builder.query<{ items: OperatorType[] }, void>({
|
|
query: () => ({ url: '/profiles/me/operations' }),
|
|
providesTags: () => [listTag('OperatorType')],
|
|
}),
|
|
|
|
/** Replaces the set — see the Operations tab in the portal profile. */
|
|
updateMyOperatorTypes: builder.mutation<
|
|
{ items: OperatorType[] },
|
|
{ licenseTypeIds: string[] }
|
|
>({
|
|
query: (body) => ({
|
|
url: '/profiles/me/operations',
|
|
method: 'PUT',
|
|
body,
|
|
}),
|
|
// The catalogue is filtered by this, so it has to refetch too.
|
|
invalidatesTags: (_r, error) =>
|
|
error ? [] : [listTag('OperatorType'), 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)],
|
|
}),
|
|
|
|
/** 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 }
|
|
>({
|
|
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
|
|
/** The licence register for enforcement officers. */
|
|
getLicenses: builder.query<
|
|
Paginated<IssuedLicense>,
|
|
{ search?: string } | void
|
|
>({
|
|
query: (args) => ({
|
|
url: '/licenses',
|
|
params: args?.search ? { search: args.search } : undefined,
|
|
}),
|
|
providesTags: () => [listTag('License')],
|
|
}),
|
|
|
|
getMyLicenses: builder.query<Paginated<IssuedLicense>, void>({
|
|
query: () => ({ url: '/licenses/mine' }),
|
|
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>, QueueFilter | void>({
|
|
query: (params) => ({
|
|
url: '/license-application-review/queue',
|
|
params: serialiseQueueFilter(params),
|
|
}),
|
|
providesTags: () => [listTag('ApplicationQueue')],
|
|
}),
|
|
|
|
getAssignedToMe: builder.query<
|
|
Paginated<LicenseApplication>,
|
|
QueueFilter | void
|
|
>({
|
|
query: (params) => ({
|
|
url: '/license-application-review/assigned-to-me',
|
|
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)],
|
|
}),
|
|
|
|
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 }[];
|
|
/** Officer-edited wording for the applicant notification. */
|
|
notificationBody?: 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; 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`,
|
|
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;
|
|
/** Structured per-item outcomes (office, warehouse, vehicles, …). */
|
|
checklist?: {
|
|
key: string;
|
|
label: string;
|
|
outcome: 'PASS' | 'FAIL' | 'NEEDS_CORRECTION';
|
|
note?: string;
|
|
}[];
|
|
}
|
|
>({
|
|
query: ({ inspectionId, result, findings, checklist }) => ({
|
|
url: `/inspections/${inspectionId}/result`,
|
|
method: 'PATCH',
|
|
body: { result, findings, ...(checklist?.length ? { checklist } : {}) },
|
|
}),
|
|
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,
|
|
useUpdateLicenseValidityMutation,
|
|
useGetLicenseTypeRequirementsQuery,
|
|
useCreateApplicationMutation,
|
|
useGetMyApplicationsQuery,
|
|
useGetApplicationQuery,
|
|
useInitiatePaymentMutation,
|
|
useBypassPaymentMutation,
|
|
useGetPaymentCapabilitiesQuery,
|
|
useGetMyLicensesQuery,
|
|
useGetLicensesQuery,
|
|
useGetCertificateUrlMutation,
|
|
useGetApplicationPaymentQuery,
|
|
usePatchSectionMutation,
|
|
useAddStaffMutation,
|
|
useRemoveStaffMutation,
|
|
useSubmitApplicationMutation,
|
|
useResolveRemarkMutation,
|
|
useResubmitApplicationMutation,
|
|
useGetAttachmentsQuery,
|
|
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,
|
|
useRequestAdjustmentMutation,
|
|
useApproveDocumentsMutation,
|
|
useFinalApproveMutation,
|
|
useRejectApplicationMutation,
|
|
useConfirmPaymentMutation,
|
|
useScheduleInspectionMutation,
|
|
useGetInspectionsQuery,
|
|
useRecordInspectionResultMutation,
|
|
useGetNotificationsQuery,
|
|
useGetUnseenNotificationsQuery,
|
|
useMarkNotificationReadMutation,
|
|
useGetMyOperatorTypesQuery,
|
|
useUpdateMyOperatorTypesMutation,
|
|
} = licensingApi;
|