diff --git a/src/server/routing/router-protected.ts b/src/server/routing/router-protected.ts index da2fece788..08ed25c85d 100644 --- a/src/server/routing/router-protected.ts +++ b/src/server/routing/router-protected.ts @@ -33,7 +33,7 @@ import { import { attachDocumentDownloadRoute } from '../services/shared/document-download-route-handler'; import { fetchErfpachtV2DossiersDetail } from '../services/simple-connect/erfpacht'; import { fetchBBDocument } from '../services/toeristische-verhuur/toeristische-verhuur-powerbrowser-bb-vergunning'; -import { fetchDecosDocument } from '../services/vergunningen-v2/decos-service'; +import { fetchDecosDocument } from '../services/decos/decos-service'; import { fetchVergunningDetail, fetchZakenFromSource, diff --git a/src/server/services/vergunningen-v2/decos-service.test.ts b/src/server/services/decos/decos-service.test.ts similarity index 87% rename from src/server/services/vergunningen-v2/decos-service.test.ts rename to src/server/services/decos/decos-service.test.ts index 2168398d28..2aa757c9d5 100644 --- a/src/server/services/vergunningen-v2/decos-service.test.ts +++ b/src/server/services/decos/decos-service.test.ts @@ -1,20 +1,25 @@ import uid from 'uid-safe'; -import { - DecosDocumentSource, - DecosZaakSource, - DecosZakenResponse, -} from './config-and-types'; import { fetchDecosDocumentList, - fetchDecosVergunningen, + fetchDecosZaken, fetchDecosWorkflowDate, fetchDecosZakenFromSource, forTesting, } from './decos-service'; +import { + DecosDocumentSource, + DecosZaakSource, + DecosZakenResponse, +} from './decos-types'; import { remoteApi } from '../../../testing/utils'; import { jsonCopy, range } from '../../../universal/helpers/utils'; import { AuthProfileAndToken } from '../../auth/auth-types'; +import type { WerkzaamhedenEnVervoerOpStraat } from '../vergunningen-v2/config-and-types'; +import { + decosCaseToZaakTransformers, + decosZaakTransformers, +} from '../vergunningen-v2/decos-zaken'; const zakenSource = { count: 1, @@ -237,8 +242,9 @@ describe('decos-service', () => { const workflowInstance2: typeof workflowInstance = jsonCopy(workflowInstance); - const [instance1] = workflowInstance2.content; - delete (instance1 as any).fields.text7; + const instance1 = workflowInstance2.content[0]; + const fields = instance1.fields as Partial; + delete fields.text7; remoteApi .get(/\/decos\/items\/123-abc-000\/workflowlinkinstances/) @@ -321,9 +327,10 @@ describe('decos-service', () => { .times(numberOfAddressBooksToSearch) .replyWithError('Booksearch failed'); - const responseData = await fetchDecosVergunningen( + const responseData = await fetchDecosZaken( reqID, - authProfileAndToken + authProfileAndToken, + decosZaakTransformers ); expect(responseData).toMatchInlineSnapshot(` @@ -350,9 +357,10 @@ describe('decos-service', () => { .times(numberOfAddressBooksToSearch) .reply(200, zakenSource); - const responseData = await fetchDecosVergunningen( + const responseData = await fetchDecosZaken( reqID, - authProfileAndToken + authProfileAndToken, + decosZaakTransformers ); expect(responseData.status).toBe('OK'); @@ -415,25 +423,25 @@ describe('decos-service', () => { test('Niet definitief', () => { const isValid = forTesting.filterValidDocument({ fields: { text39: 'foo.bar' }, - } as any); + } as unknown as DecosDocumentSource); expect(isValid).toBe(false); }); test('Niet openbaar', () => { const isValid = forTesting.filterValidDocument({ fields: { text39: 'definitief', text40: 'geheim' }, - } as any); + } as unknown as DecosDocumentSource); expect(isValid).toBe(false); }); test('Niet van toepassing', () => { const isValid = forTesting.filterValidDocument({ fields: { text39: 'definitief', text40: 'openbaar', text41: 'nvt' }, - } as any); + } as unknown as DecosDocumentSource); expect(isValid).toBe(false); }); test('Alles ok!', () => { const isValid = forTesting.filterValidDocument({ fields: { text39: 'definitief', text40: 'openbaar', text41: 'ok' }, - } as any); + } as unknown as DecosDocumentSource); expect(isValid).toBe(true); }); }); @@ -498,6 +506,30 @@ describe('decos-service', () => { `); }); + test('Called with filter based on caseTypes', async () => { + remoteApi + .get(/\/decos\/items\/123456789\/folders/) + .query((queryObject) => { + return ( + queryObject.filter === + 'text45 eq Aanbieden van diensten or text45 eq GPK' + ); + }) + .times(numberOfAddressBooksToSearch) + .reply(200, zakenSource); + + const responseData = await forTesting.getZakenByUserKey( + reqID, + '123456789', + [ + decosCaseToZaakTransformers['Aanbieden van diensten'], + decosCaseToZaakTransformers.GPK, + ] + ); + + expect(responseData.content?.length).toBe(1); + }); + test('Success', async () => { remoteApi .get(/\/decos\/items\/123456789\/folders/) @@ -536,7 +568,8 @@ describe('decos-service', () => { test('No date', () => { const workflowInstance2: typeof workflowInstance = jsonCopy(workflowInstance); - delete (workflowInstance2 as any).content[0].fields.date1; + const fields = workflowInstance2.content[0].fields; + delete (fields as Partial).date1; const date = forTesting.transformDecosWorkflowDateResponse( 'Zaak - behandelen', @@ -598,6 +631,7 @@ describe('decos-service', () => { const transformed = await forTesting.transformDecosZaakResponse( reqID, + decosZaakTransformers, zakenSource.content[0] ); @@ -629,6 +663,7 @@ describe('decos-service', () => { const transformed = await forTesting.transformDecosZaakResponse( reqID, + decosZaakTransformers, zaak ); expect(transformed).toBe(null); @@ -639,17 +674,15 @@ describe('decos-service', () => { zaak.fields.bol17 = true; zaak.fields.bol8 = true; - const transformed = await forTesting.transformDecosZaakResponse( - reqID, - zaak - ); + const transformed: WerkzaamhedenEnVervoerOpStraat | null = + await forTesting.transformDecosZaakResponse( + reqID, + decosZaakTransformers, + zaak + ); expect(transformed).not.toBe(null); - expect(transformed !== null && 'werkzaamheden' in transformed).toBe(true); - expect( - transformed !== null && 'werkzaamheden' in transformed - ? transformed.werkzaamheden - : [] - ).toStrictEqual([ + expect(transformed).toHaveProperty('werkzaamheden'); + expect(transformed?.werkzaamheden).toStrictEqual([ 'Werkzaamheden verrichten in de nacht', 'Verhuizing tussen twee locaties binnen Amsterdam', ]); @@ -662,10 +695,21 @@ describe('decos-service', () => { const transformed = await forTesting.transformDecosZaakResponse( reqID, + decosZaakTransformers, zaak ); expect(transformed?.decision).toBe('Zie besluit'); }); + + test('Null response when no valid transformer', async () => { + const zaak: DecosZaakSource = jsonCopy(zakenSource.content[0]); + const transformed = await forTesting.transformDecosZaakResponse( + reqID, + [], + zaak + ); + expect(transformed).toBe(null); + }); }); describe('transformDecosZakenResponse', () => { @@ -682,11 +726,22 @@ describe('decos-service', () => { }); const zakenTransformed = await forTesting.transformDecosZakenResponse( reqID, + decosZaakTransformers, zaken ); expect( zakenTransformed.map(({ identifier }) => identifier) ).toStrictEqual(['3 - xxx', '2 - xxx', '1 - xxx', '0 - xxx']); }); + + test('Empty response when no valid transformer', async () => { + const zaak: DecosZaakSource = jsonCopy(zakenSource.content[0]); + const zakenTransformed = await forTesting.transformDecosZakenResponse( + reqID, + [], + [zaak] + ); + expect(zakenTransformed).toStrictEqual([]); + }); }); }); diff --git a/src/server/services/vergunningen-v2/decos-service.ts b/src/server/services/decos/decos-service.ts similarity index 74% rename from src/server/services/vergunningen-v2/decos-service.ts rename to src/server/services/decos/decos-service.ts index ba26aca920..e27d6cc643 100644 --- a/src/server/services/vergunningen-v2/decos-service.ts +++ b/src/server/services/decos/decos-service.ts @@ -1,30 +1,33 @@ +import assert from 'assert'; + import memoizee from 'memoizee'; import { + caseType, + DecosZaakBase, + DecosZaakTransformer, + MA_DECISION_DEFAULT, + adresBoekenByProfileType, AddressBookEntry, - DecosDocumentBlobSource, - DecosDocumentSource, DecosFieldValue, + DecosZaakDocument, DecosWorkflowStepDate, DecosWorkflowStepTitle, + DecosDocumentBlobSource, + DecosDocumentSource, DecosZaakSource, DecosZakenResponse, - MA_DECISION_DEFAULT, - VergunningDocument, - VergunningV2, - adresBoekenByProfileType, -} from './config-and-types'; -import { SELECT_FIELDS_META, SELECT_FIELDS_TRANSFORM_BASE, - decosZaakTransformers, -} from './decos-zaken'; +} from './decos-types'; import { getDecosZaakTypeFromSource, getUserKeysSearchQuery, isExcludedFromTransformation, } from './helpers'; import { + ApiErrorResponse, + ApiSuccessResponse, apiSuccessResult, getSettledResult, } from '../../../universal/helpers/api'; @@ -39,9 +42,9 @@ import { requestData } from '../../helpers/source-api-request'; import { captureException, captureMessage } from '../monitoring'; import { DocumentDownloadData } from '../shared/document-download-route-handler'; /** - * The Decos service ties responses of various api calls together and produces a set of transformed set of vergunningen. + * The Decos service ties responses of various api calls together and produces a set of transformed set of decosZaken. * - * Service: **fetchDecosVergunningen** + * Service: **fetchDecosZaken** * * First we query Decos for a `key` to see if a certain userID (BSN or KVKNUMBER) is known in the system. We look for the values of the userID in so called AddressBooks. * The id's (keys) of the addressbooks are static values. So we put together a search query that looks for an AddressBook entry in a @@ -59,13 +62,13 @@ import { DocumentDownloadData } from '../shared/document-download-route-handler' * After transformation of the api attribute names and possibly values we apply another, optional, transform to the data. * At this point we can fetch other services to enrich the data we retrieved initially or maybe assign values to some properties based on business logic. * - * Service: **fetchDecosVergunning** + * Service: **fetchDecosZaak** * - * Retrieves one zaak based on a key found in the fetchDecosVergunningen call. This service is used to provide the data for the detailed view of a vergunning. + * Retrieves one zaak based on a key found in the fetchDecosZaken call. This service is used to provide the data for the detailed view of a decos zaak. * This api call to Decos does not specify any fields to be selected and instead retrieves all the fields related to the requested zaak. Source validity checks / filtering is - * not performed at this stage because we should only have the keys of individual zaken that we got from the fetchDecosVergunningen service. + * not performed at this stage because we should only have the keys of individual zaken that we got from the fetchDecosZaken service. * - * Event hough we retrieve all source fields/values for a specific case, only ones specified (transformFields) end up in the data sent to the user. + * Event though we retrieve all source fields/values for a specific case, only ones specified (transformFields) end up in the data sent to the user. * * Service: **fetchDecosWorkflowDate** * @@ -77,7 +80,7 @@ import { DocumentDownloadData } from '../shared/document-download-route-handler' * ZaakID is the value of the `key` attribute in Decos. `Identifier` is the attribute name we use in Mijn Amsterdam as `Zaaknummer / Kenmerk`, a value readable to the user. * * - * Servuce: **fetchDecosDocument** + * Service: **fetchDecosDocument** * * Retrieves the document (binary) data which can be presented to the user as Document download. * @@ -129,19 +132,25 @@ async function getUserKeys( return apiSuccessResult(userKeys); } -async function transformDecosZaakResponse( +async function transformDecosZaakResponse< + T extends DecosZaakTransformer, + DZ extends DecosZaakBase = NestedType, +>( requestID: RequestID, + decosZaakTransformers: T[], decosZaakSource: DecosZaakSource -) { +): Promise { const zaakType = getDecosZaakTypeFromSource(decosZaakSource); - const zaakTypeTransformer = decosZaakTransformers[zaakType]; + const decosZaakTransformer = decosZaakTransformers.find( + (transformer) => transformer.caseType == zaakType + ); - if (!zaakTypeTransformer || !zaakTypeTransformer.transformFields) { + if (!decosZaakTransformer || !decosZaakTransformer.transformFields) { captureMessage(`No transformer for zaakType ${zaakType}`); return null; } - if (isExcludedFromTransformation(decosZaakSource, zaakTypeTransformer)) { + if (isExcludedFromTransformation(decosZaakSource, decosZaakTransformer)) { return null; } @@ -152,7 +161,7 @@ async function transformDecosZaakResponse( // Iterates over the desired data fields (key=>value pairs) and transforms values if necessary. const transformedFieldEntries = Object.entries( - zaakTypeTransformer.transformFields + decosZaakTransformer.transformFields ).map(([fieldNameSource, fieldTransformer]) => { const fieldNameTransformed = typeof fieldTransformer === 'object' @@ -168,7 +177,7 @@ async function transformDecosZaakResponse( typeof fieldTransformer === 'object' && typeof fieldTransformer.transform === 'function' ? fieldTransformer.transform(value, { - zaakTypeTransformer, + decosZaakTransformer, fetchDecosWorkflowDate: fetchWorkflowDate, }) : value; @@ -182,107 +191,133 @@ async function transformDecosZaakResponse( // Create an object from the transformed fieldNames and values const transformedFields = Object.fromEntries(transformedFieldEntries); - // Create the base data for the vergunning. This object is not guaranteed to have all fields defined in the type for a specific vergunning. - // It depends on the query and resturned result to the decos api which field value ends up in the vergunning. - // For example, if we selected only the sourcefield `mark` we'd have a vergunning with a value for `identifier`.. - let vergunning: VergunningV2 = { + // Create the base data for the decosZaak. This object is not guaranteed to have all fields defined in the type for a specific decosZaak. + // It depends on the query and resturned result to the decos api which field value ends up in the decosZaak. + // For example, if we selected only the sourcefield `mark` we'd have a decosZaak with a value for `identifier`.. + let decosZaak: DZ = { id: transformedFields.identifier?.replace(/\//g, '-') ?? - 'unknown-vergunning-id', + 'unknown-decoszaak-id', key: decosZaakSource.key, - title: zaakTypeTransformer.title, + title: decosZaakTransformer.title, dateInBehandeling: null, // Serves as placeholder, value for this property will be added async below. ...transformedFields, }; // Try to fetch and assign a specific date on which the zaak was "In behandeling" - if (zaakTypeTransformer.dateInBehandelingWorkflowStepTitle) { + if (decosZaakTransformer.dateInBehandelingWorkflowStepTitle) { const { content: dateInBehandeling } = await fetchWorkflowDate( - zaakTypeTransformer.dateInBehandelingWorkflowStepTitle + decosZaakTransformer.dateInBehandelingWorkflowStepTitle ); if (dateInBehandeling) { - vergunning.dateInBehandeling = dateInBehandeling; + decosZaak.dateInBehandeling = dateInBehandeling; } } - if (vergunning.processed && !vergunning.decision) { - vergunning.decision = MA_DECISION_DEFAULT; + if (decosZaak.processed && !decosZaak.decision) { + decosZaak.decision = MA_DECISION_DEFAULT; } // After initial transformation of the data is done, perform a Post transform action. // It's possible to handle some data quality improvements and/or business logic operations in the after transform function. - if (zaakTypeTransformer.afterTransform) { - vergunning = await zaakTypeTransformer.afterTransform( - vergunning, + if (decosZaakTransformer.afterTransform) { + decosZaak = await decosZaakTransformer.afterTransform( + decosZaak, decosZaakSource, { fetchDecosWorkflowDate: fetchWorkflowDate, - zaakTypeTransformer, + decosZaakTransformer, } ); } - return vergunning; + return decosZaak; } -async function transformDecosZakenResponse( +async function transformDecosZakenResponse< + T extends DecosZaakTransformer, + DZ extends DecosZaakBase = NestedType, +>( requestID: RequestID, + decosZaakTransformers: T[], decosZakenSource: DecosZaakSource[] ) { - const zakenToBeTransformed = []; - + const zakenToBeTransformed: [T, DecosZaakSource][] = []; for (const decosZaakSource of decosZakenSource) { const zaakType = getDecosZaakTypeFromSource(decosZaakSource); - const zaakTypeTransformer = decosZaakTransformers[zaakType]; + const decosZaakTransformer = decosZaakTransformers.find( + (transformer) => transformer.caseType == zaakType + ); - if (!zaakTypeTransformer) { - captureMessage(`Decos: ${zaakType} transformer not implemented`); + // exclude decosZaakSources that do not have a matching decosZaakTransformer + if (!decosZaakTransformer) { continue; } // Exclude zaken that match the following conditions - if (isExcludedFromTransformation(decosZaakSource, zaakTypeTransformer)) { + if (isExcludedFromTransformation(decosZaakSource, decosZaakTransformer)) { continue; } - zakenToBeTransformed.push(decosZaakSource); + zakenToBeTransformed.push([decosZaakTransformer, decosZaakSource]); } - let vergunningen: Array = []; + let decosZaken: Array = []; try { - vergunningen = await Promise.all( - zakenToBeTransformed.map((decosZaak) => { - return transformDecosZaakResponse(requestID, decosZaak); + decosZaken = await Promise.all( + zakenToBeTransformed.map(([decosZaakTransformer, decosZaak]) => { + return transformDecosZaakResponse( + requestID, + [decosZaakTransformer], + decosZaak + ); }) ); } catch (err) { captureException(err); } - return vergunningen - .filter( - (vergunning: VergunningV2 | null): vergunning is VergunningV2 => - vergunning !== null - ) + return decosZaken + .filter((decosZaak: DZ | null): decosZaak is DZ => decosZaak !== null) .sort(sortAlpha('identifier', 'desc')); } -async function getZakenByUserKey(requestID: RequestID, userKey: string) { - const selectFieldsAllCases = Object.keys(SELECT_FIELDS_TRANSFORM_BASE); - const additionalSelectFields = Object.values(decosZaakTransformers).flatMap( - (zaakTransformer) => zaakTransformer.addToSelectFieldsBase ?? [] +async function getZakenByUserKey( + requestID: RequestID, + userKey: string, + zaakTypeTransformers: Pick< + DecosZaakTransformer, + 'addToSelectFieldsBase' | 'caseType' + >[] = [] +) { + const caseField = 'text45'; + assert( + SELECT_FIELDS_TRANSFORM_BASE[caseField] == caseType, + `getZakenByUserKey expects field ${caseField} to be the caseType` ); - const selectFields = uniqueArray([ + const fields = uniqueArray([ ...SELECT_FIELDS_META, - ...selectFieldsAllCases, - ...additionalSelectFields, + ...Object.keys(SELECT_FIELDS_TRANSFORM_BASE), + ...zaakTypeTransformers.flatMap( + (zaakTransformer) => zaakTransformer.addToSelectFieldsBase ?? [] + ), ]).join(','); + const caseTypes = zaakTypeTransformers + .map((transformer) => `${caseField} eq ${transformer.caseType}`) + .join(' or '); + + const decosUrlParams = new URLSearchParams({ + top: '50', + ...(fields && { select: fields }), + ...(caseTypes && { filter: caseTypes }), + }); + const apiConfig = getApiConfig('DECOS_API', { formatUrl: (config) => { - return `${config.url}/items/${userKey}/folders?top=50&select=${selectFields}`; + return `${config.url}/items/${userKey}/folders?${decosUrlParams}`; }, transformResponse: (responseData: DecosZakenResponse) => { if (!Array.isArray(responseData?.content)) { @@ -302,7 +337,11 @@ async function getZakenByUserKey(requestID: RequestID, userKey: string) { export async function fetchDecosZakenFromSource( requestID: RequestID, - authProfileAndToken: AuthProfileAndToken + authProfileAndToken: AuthProfileAndToken, + zaakTypeTransformers: Pick< + DecosZaakTransformer, + 'addToSelectFieldsBase' | 'caseType' + >[] = [] ) { const userKeysResponse = await getUserKeys(requestID, authProfileAndToken); @@ -312,7 +351,7 @@ export async function fetchDecosZakenFromSource( const zakenSourceResponses = await Promise.allSettled( userKeysResponse.content.map((userKey) => - getZakenByUserKey(requestID, userKey) + getZakenByUserKey(requestID, userKey, zaakTypeTransformers) ) ); @@ -331,28 +370,34 @@ export async function fetchDecosZakenFromSource( return apiSuccessResult(zakenSource); } -async function fetchDecosVergunningen_( +export async function fetchDecosZaken_< + T extends DecosZaakTransformer, + DZ extends DecosZaakBase = NestedType, +>( requestID: RequestID, - authProfileAndToken: AuthProfileAndToken -) { + authProfileAndToken: AuthProfileAndToken, + zaakTypeTransformers: T[] +): Promise | ApiErrorResponse> { const zakenSourceResponse = await fetchDecosZakenFromSource( requestID, - authProfileAndToken + authProfileAndToken, + zaakTypeTransformers ); if (zakenSourceResponse.status === 'OK') { - const vergunningen = await transformDecosZakenResponse( + const decosZakenSource = zakenSourceResponse.content; + const zaken = await transformDecosZakenResponse( requestID, - zakenSourceResponse.content + zaakTypeTransformers, + decosZakenSource ); - - return apiSuccessResult(vergunningen); + return apiSuccessResult(zaken); } return zakenSourceResponse; } -export const fetchDecosVergunningen = memoizee(fetchDecosVergunningen_, { +export const fetchDecosZaken = memoizee(fetchDecosZaken_, { maxAge: DEFAULT_API_CACHE_TTL_MS, }); @@ -381,7 +426,7 @@ function transformDecosWorkflowDateResponse( export async function fetchDecosWorkflowDate( requestID: RequestID, - zaakID: VergunningV2['key'], + zaakID: DecosZaakBase['key'], stepTitle: DecosWorkflowStepTitle ) { const apiConfigWorkflows = getApiConfig('DECOS_API', { @@ -413,7 +458,7 @@ export async function fetchDecosWorkflowDate( async function fetchIsPdfDocument( requestID: RequestID, - documentKey: VergunningDocument['key'] + documentKey: DecosZaakDocument['key'] ) { // items / { document_id } / blob ? select = bol10 const apiConfigDocuments = getApiConfig('DECOS_API', { @@ -456,20 +501,20 @@ async function transformDecosDocumentListResponse( .map(async ({ fields: documentMetadata, key }) => { const isPdfResponse = await fetchIsPdfDocument(requestID, key); if (isPdfResponse.status === 'OK' && isPdfResponse.content.isPDF) { - const vergunningDocument: VergunningDocument = { + const decosZaakDocument: DecosZaakDocument = { id: documentMetadata.mark, key: isPdfResponse.content.key, title: documentMetadata.text41, datePublished: documentMetadata.received_date, url: '', // Url is constructed in vergunningen.ts }; - return vergunningDocument; + return decosZaakDocument; } return null; }); const documents = (await Promise.all(documentsSourceFiltered)).filter( - (document: VergunningDocument | null): document is VergunningDocument => + (document: DecosZaakDocument | null): document is DecosZaakDocument => document !== null ); return documents; @@ -480,7 +525,7 @@ async function transformDecosDocumentListResponse( export async function fetchDecosDocumentList( requestID: RequestID, - zaakID: VergunningV2['key'] + zaakID: DecosZaakBase['key'] ) { const apiConfigDocuments = getApiConfig('DECOS_API', { formatUrl: (config) => { @@ -504,7 +549,7 @@ export async function fetchDecosDocumentList( export async function fetchDecosZaakFromSource( requestID: RequestID, - zaakID: VergunningV2['key'], + zaakID: DecosZaakBase['key'], includeProperties: boolean = false ) { // Fetch the zaak from Decos, this request will return all the fieldNames, no need to specify the ?select= query. @@ -523,9 +568,15 @@ export async function fetchDecosZaakFromSource( return requestData(apiConfig, requestID); } -export async function fetchDecosVergunning( +type NestedType = T extends DecosZaakTransformer ? R : never; + +export async function fetchDecosZaak< + T extends DecosZaakTransformer, + DZ extends DecosZaakBase = NestedType, +>( requestID: RequestID, - zaakID: VergunningV2['key'] + decosZaakTransformers: T[], + zaakID: DecosZaakBase['key'] ) { const decosZaakSourceRequest = fetchDecosZaakFromSource(requestID, zaakID); const decosZaakDocumentsRequest = fetchDecosDocumentList(requestID, zaakID); @@ -539,16 +590,16 @@ export async function fetchDecosVergunning( const zaakSourceResponse = getSettledResult(zaakSourceResponseSettled); const documentsResponse = getSettledResult(documentsResponseSettled); - let documents: VergunningDocument[] = []; - let vergunning: VergunningV2 | null = null; + let documents: DecosZaakDocument[] = []; + let decosZaak: DecosZaakBase | null = null; if (zaakSourceResponse.status == 'OK') { const decosZaakResponseData = zaakSourceResponse.content; - if (decosZaakResponseData) { try { - vergunning = await transformDecosZaakResponse( + decosZaak = await transformDecosZaakResponse( requestID, + decosZaakTransformers, decosZaakResponseData ); } catch (error) { @@ -556,12 +607,12 @@ export async function fetchDecosVergunning( } } - if (documentsResponse.status === 'OK' && vergunning !== null) { + if (documentsResponse.status === 'OK' && decosZaak !== null) { documents = documentsResponse.content; } return apiSuccessResult({ - vergunning, + decosZaak: decosZaak as DZ, documents, }); } diff --git a/src/server/services/decos/decos-types.ts b/src/server/services/decos/decos-types.ts new file mode 100644 index 0000000000..b56266dee4 --- /dev/null +++ b/src/server/services/decos/decos-types.ts @@ -0,0 +1,284 @@ +import { ApiResponse } from '../../../universal/helpers/api'; +import { GenericDocument } from '../../../universal/types'; +import { DecosCaseType } from '../../../universal/types/vergunningen'; +import { NotificationLabelByType } from '../vergunningen-v2/config-and-types'; + +type DecosDocumentBase = { + text39: string; + text40: string; + text41: string; + // identifier / zaaknummer + mark: string; + // datePublished + received_date: string; +}; +type DecosDocumentBlobBase = { + // IS PDF + bol10: boolean; +}; + +export type DecosFieldsObject = Record< + DecosFieldNameSource, + string | boolean | null | number +>; + +export type DecosFieldTransformerObject< + T extends DecosZaakBase = DecosZaakBase, +> = Record | keyof T>; + +export type DecosZaakSource = { + key: DecosZaakID; + links: string[]; + fields: DecosZaakFieldsSource & DecosFieldsObject; +}; + +export type DecosDocumentSource = { + key: DecosZaakID; + links: string[]; + fields: DecosDocumentBase & DecosFieldsObject; +}; + +export type DecosDocumentBlobSource = { + key: DecosZaakID; + links: string[]; + fields: DecosDocumentBlobBase & DecosFieldsObject; +}; + +export type DecosZakenResponse = { + count: number; + content: T; +}; + +export type DecosResponse = { + itemDataResultSet: { + content: T[]; + }; +}; +export type DecosWorkflowStepTitle = string; +export type DecosWorkflowStepDate = string; +export type DecosZaakDocument = GenericDocument & { key: string }; +export type DecosZaakID = string; +export type DecosFieldNameSource = string; +export type DecosFieldValue = string | number | boolean | null; +export type DecosZaakFieldsSource = { + // status + title: string; + // caseType + text45: DecosCaseType | string; + // decision + dfunction?: string | null; + // identifier / zaaknummer + mark: string; + // processed + processed: boolean; + // dateDecision + date5?: string | null; + // dateRequest + document_date: string; + // dateStart + date6?: string | null; + // dateEnd + date7?: string | null; + + subject1?: string; + // Info regarding possible payment + text11?: string | null; + // Info regarding possible payment + text12?: string | null; +}; +export type AddressBookEntry = { + key: string; +}; +export const adresBoekenBSN = + process.env.BFF_DECOS_API_ADRES_BOEKEN_BSN?.split(',') ?? []; + +export const adresBoekenKVK = + process.env.BFF_DECOS_API_ADRES_BOEKEN_KVK?.split(',') ?? []; + +export const adresBoekenByProfileType: Record = { + private: adresBoekenBSN, + commercial: adresBoekenKVK, + 'private-attributes': [], +}; + +export const MA_DECISION_DEFAULT = 'Zie besluit'; +export type DecosFieldTransformer = { + name: keyof T; + transform?: ( + input: any, + options?: DecosTransformerOptions + ) => DecosFieldValue; +}; +export type DecosTransformerOptions = { + decosZaakTransformer?: DecosZaakTransformer; + fetchDecosWorkflowDate?: ( + stepTitle: DecosWorkflowStepTitle + ) => Promise>; +}; + +export type DecosZaakTransformer = { + // The caseType (zaaktype) of the sourceData. + caseType: DecosCaseType; + // Title of the DecosZaakBase, mostly a slightly different variant of the $caseType + title: string; + // A mapping object that can be used to assign a readable attribute to the data sent to the frontend. + // For example: date6 becomes dateStart. Additionally a function can be provided to perform some compute on the value assigned to the sourceField. + // For example String operations like, trim, split, uppercase etc. + transformFields?: Partial>; + // After transform is used to perform additional transformations after the initial transform. + // Business logic is implemented at this point, also async calls to other services to enrich the data can be done here. + afterTransform?: ( + decosZaak: T, + decosZaakSource: DecosZaakSource, + options?: DecosTransformerOptions + ) => Promise; + // A function to check if the source data quality and/or prerequisites for showing the data to the user are valid. + // This function is run before transformation of the zaak. + hasValidSourceData?: (decosZaakSource: DecosZaakSource) => boolean; + // Indicate if the zaak requires payment to be processed and complete. This function is run before transformation of the zaak. + requirePayment?: boolean; + // Decision (resultaat) values are generalized here. For example. The sourceValues can be one of: `Toegekend met borden`, `Toegekend zonder dingen` which we want to show to the user as `Toegekend`. + decisionTranslations?: Record; + // The title of the workflow step that is used to find a date for the InBehandeling status. + dateInBehandelingWorkflowStepTitle?: string; + // Indicates if the Zaak should be shown to the user / is expected to be transformed. + isActive: boolean; + // Initially we request a set of fields to be included in the responseData (?select=). For some cases we need a (few) custom field(s) included in the initial response. + // For example to show a kenteken in the Notifications. Sadly the requested fields cannot be specified on a case basis. + // This means even though we do not want, for example, date7 for case A we will receive it anyway. + // We select a specific set of fields because otherwise we receive all the fields of a zaak which are bloated and mostly unused. + addToSelectFieldsBase?: string[]; + // Notifications for this specific + notificationLabels?: Partial; +}; +export type MADecision = string; +export type DecosDecision = string; +export type ZakenFilter = (zaak: DecosZaakBase) => boolean; +export type DecosZakenSourceFilter = ( + decosZaakSource: DecosZaakSource +) => boolean; +export interface DecosZaakBase { + caseType: DecosCaseType; + dateDecision: string | null; + dateInBehandeling: string | null; + dateRequest: string; + + // DateStart and DateEnd are not applicable to every single decosZaak but general enough to but in base Type. + dateStart: string | null; + dateEnd: string | null; + + decision: string | null; + description: string; + + // Url to BFF Detail paga api + fetchUrl: string; + + identifier: ZaakKenmerk; + title: string; + id: string; + + // Decos key (uuid) used as primary id's in api communication. + key: string; + + processed: boolean; + status: ZaakStatus; + + paymentStatus: string | null; + paymentMethod: string | null; +} +export type ZaakKenmerk = `Z/${number}/${number}`; // Z/23/2230346 +export type ZaakStatus = + | 'Ontvangen' + | 'In behandeling' + | 'Afgehandeld' + | string; +export interface DecosZaakWithLocation extends DecosZaakBase { + location: string | null; +} + +export interface DecosZaakWithKentekens extends DecosZaakBase { + kentekens: string | null; +} + +export interface DecosZaakWithDateRange extends DecosZaakBase { + dateStart: string | null; + dateEnd: string | null; +} + +export interface DecosZaakWithTimeRange extends DecosZaakBase { + timeStart: string | null; + timeEnd: string | null; +} +export interface DecosZaakWithDateTimeRange + extends DecosZaakWithDateRange, + DecosZaakWithTimeRange {} // A list of common readable api attributes +const status = 'status'; +export const caseType = 'caseType'; +const identifier = 'identifier'; +const processed = 'processed'; +const dateDecision = 'dateDecision'; +const dateRequest = 'dateRequest'; +export const dateStart = 'dateStart'; +export const dateEnd = 'dateEnd'; +export const location = 'location'; +export const timeStart = 'timeStart'; +export const timeEnd = 'timeEnd'; +export const destination = 'destination'; +export const description = 'description'; +// Fields are selected per case initially but don't end up in the data we send to front end. +// These fields are fore example used to determine payment status. + +export const SELECT_FIELDS_META = ['text11', 'text12', 'subject1']; +// The set of field transforms that applies to every case. +// { $api_attribute_name_source: $api_attribute_name_mijn_amsterdam } + +export const decision: DecosFieldTransformer = { + name: 'decision', + transform: (decision: string, options) => { + const decisionTranslations = + options?.decosZaakTransformer?.decisionTranslations; + + if (decisionTranslations) { + const maDecision = Object.entries(decisionTranslations).find( + ([maDecision, decosDecisions]) => { + return decosDecisions.includes(decision); + } + )?.[0]; + return maDecision ?? decision; + } + return decision; + }, +}; + +export const SELECT_FIELDS_TRANSFORM_BASE: DecosFieldTransformerObject = { + title: status, + text45: caseType, + dfunction: decision, + mark: identifier, + processed: processed, + date5: dateDecision, + document_date: dateRequest, + date6: dateStart, + date7: dateEnd, +}; +// Cases with this one of these dfunction values will not be included in the cases shown to the user. + +export const DECOS_EXCLUDE_CASES_WITH_INVALID_DFUNCTION = [ + 'buiten behandeling', + 'geannuleerd', + 'geen aanvraag of dubbel', +]; +// Cases with one of these subject1 values will not be included in the cases shown to the user. Payment is not yet processed or failed. + +export const DECOS_EXCLUDE_CASES_WITH_PENDING_PAYMENT_CONFIRMATION_SUBJECT1 = [ + 'wacht op online betaling', + 'wacht op ideal betaling', +]; +// Cases with this dfunction value will not be included in the cases shown to the user. + +export const DECOS_PENDING_REMOVAL_DFUNCTION = '*verwijder'; +// Cases with this text11 value will not be included in the cases shown to the user. Payment is not yet processed or failed. +export const DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT11 = 'nogniet'; +// Cases with this text12 value will not be included in the cases shown to the user. Payment is not yet processed or failed. +export const DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT12 = + 'wacht op online betaling'; diff --git a/src/server/services/vergunningen-v2/helpers.test.ts b/src/server/services/decos/helpers.test.ts similarity index 70% rename from src/server/services/vergunningen-v2/helpers.test.ts rename to src/server/services/decos/helpers.test.ts index 06f53f50e3..e056afbad1 100644 --- a/src/server/services/vergunningen-v2/helpers.test.ts +++ b/src/server/services/decos/helpers.test.ts @@ -1,13 +1,13 @@ import { + DecosZaakBase, DecosZaakSource, - TouringcarDagontheffing, -} from './config-and-types'; -import { decosZaakTransformers } from './decos-zaken'; + DecosZaakTransformer, +} from './decos-types'; import { - getCustomTitleForVergunningWithLicensePlates, + getCustomTitleForDecosZaakWithLicensePlates, getDecosZaakTypeFromSource, hasInvalidDecision, - hasOtherActualVergunningOfSameType, + hasOtherActualDecosZaakOfSameType, isExcludedFromTransformation, isExpired, isNearEndDate, @@ -18,103 +18,105 @@ import { transformKenteken, } from './helpers'; import { CaseTypeV2 } from '../../../universal/types/vergunningen'; +import { TouringcarDagontheffing } from '../vergunningen-v2/config-and-types'; +import { decosCaseToZaakTransformers } from '../vergunningen-v2/decos-zaken'; -describe('helpers/Vergunningen', () => { +describe('helpers/decos', () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2022-10-06')); test('isNearEndDate', () => { { // No end date - const vergunning: any = { + const decosZaak = { dateEnd: null, }; - expect(isNearEndDate(vergunning)).toBe(false); + expect(isNearEndDate(decosZaak)).toBe(false); } { // Near end - const vergunning: any = { + const decosZaak = { dateEnd: '2022-10-28', }; - expect(isNearEndDate(vergunning)).toBe(true); + expect(isNearEndDate(decosZaak)).toBe(true); } { // Not near end - const vergunning: any = { + const decosZaak = { dateEnd: '2023-10-28', }; - expect(isNearEndDate(vergunning)).toBe(false); + expect(isNearEndDate(decosZaak)).toBe(false); } }); test('isExpired', () => { { // Not expired - const vergunning: any = { + const decosZaak = { dateEnd: '2023-10-28', }; - expect(isExpired(vergunning)).toBe(false); + expect(isExpired(decosZaak)).toBe(false); } { - const vergunning: any = { + const decosZaak = { dateEnd: '2022-10-07', }; - expect(isExpired(vergunning)).toBe(false); + expect(isExpired(decosZaak)).toBe(false); } { // Is expired - const vergunning: any = { + const decosZaak = { dateEnd: '2022-10-06', }; - expect(isExpired(vergunning)).toBe(true); + expect(isExpired(decosZaak)).toBe(true); } { - const vergunning: any = { + const decosZaak = { dateEnd: '2022-10-06', }; - expect(isExpired(vergunning)).toBe(true); + expect(isExpired(decosZaak)).toBe(true); } { - const vergunning: any = { + const decosZaak = { dateEnd: '2022-09-28', }; - expect(isExpired(vergunning)).toBe(true); + expect(isExpired(decosZaak)).toBe(true); } }); - test('hasOtherActualVergunningOfSameType', () => { - const vergunning: any = { + test('hasOtherActualDecosZaakOfSameType', () => { + const decosZaak = { caseType: 'test1', dateEnd: null, identifier: 'xx1', - }; + } as unknown as DecosZaakBase; { - const vergunningen: any = [ + const decosZaken = [ { caseType: 'test1', dateEnd: null, identifier: 'xx2' }, - vergunning, - ]; + decosZaak, + ] as unknown as DecosZaakBase[]; - expect(hasOtherActualVergunningOfSameType(vergunningen, vergunning)).toBe( + expect(hasOtherActualDecosZaakOfSameType(decosZaken, decosZaak)).toBe( true ); } { - const vergunningen: any = [vergunning]; + const decosZaken = [decosZaak]; - expect(hasOtherActualVergunningOfSameType(vergunningen, vergunning)).toBe( + expect(hasOtherActualDecosZaakOfSameType(decosZaken, decosZaak)).toBe( false ); } { - const vergunningen: any = [ + const decosZaken = [ { caseType: 'test1', dateEnd: '2022-05-06', identifier: 'xx2' }, - vergunning, - ]; + decosZaak, + ] as unknown as DecosZaakBase[]; - expect(hasOtherActualVergunningOfSameType(vergunningen, vergunning)).toBe( + expect(hasOtherActualDecosZaakOfSameType(decosZaken, decosZaak)).toBe( false ); } @@ -130,7 +132,12 @@ describe('helpers/Vergunningen', () => { }, } as DecosZaakSource; - expect(isWaitingForPaymentConfirmation(zaak)).toBe(true); + expect( + isWaitingForPaymentConfirmation( + zaak, + decosCaseToZaakTransformers['Werk en vervoer op straat'] + ) + ).toBe(true); }); test('Is not waiting: wrong casetype', () => { @@ -142,7 +149,12 @@ describe('helpers/Vergunningen', () => { }, } as DecosZaakSource; - expect(isWaitingForPaymentConfirmation(zaak)).toBe(false); + expect( + isWaitingForPaymentConfirmation( + zaak, + {} as unknown as DecosZaakTransformer + ) + ).toBe(false); }); test('Is not waiting', () => { @@ -154,7 +166,12 @@ describe('helpers/Vergunningen', () => { }, } as DecosZaakSource; - expect(isWaitingForPaymentConfirmation(zaak)).toBe(false); + expect( + isWaitingForPaymentConfirmation( + zaak, + decosCaseToZaakTransformers['Werk en vervoer op straat'] + ) + ).toBe(false); }); test('Is still waiting', () => { @@ -165,7 +182,12 @@ describe('helpers/Vergunningen', () => { }, } as DecosZaakSource; - expect(isWaitingForPaymentConfirmation(zaak)).toBe(true); + expect( + isWaitingForPaymentConfirmation( + zaak, + decosCaseToZaakTransformers['Werk en vervoer op straat'] + ) + ).toBe(true); }); }); @@ -234,7 +256,7 @@ describe('helpers/Vergunningen', () => { expect( isExcludedFromTransformation( { fields: { subject1: '*verwijder' } } as DecosZaakSource, - decosZaakTransformers[CaseTypeV2.AanbiedenDiensten] + decosCaseToZaakTransformers[CaseTypeV2.AanbiedenDiensten] ) ).toBe(true); }); @@ -243,7 +265,7 @@ describe('helpers/Vergunningen', () => { expect( isExcludedFromTransformation( { fields: { dfunction: 'buiten behandeling' } } as DecosZaakSource, - decosZaakTransformers[CaseTypeV2.AanbiedenDiensten] + decosCaseToZaakTransformers[CaseTypeV2.AanbiedenDiensten] ) ).toBe(true); }); @@ -258,7 +280,7 @@ describe('helpers/Vergunningen', () => { text12: 'wacht op online betaling', }, } as DecosZaakSource, - decosZaakTransformers[CaseTypeV2.WVOS] + decosCaseToZaakTransformers[CaseTypeV2.WVOS] ) ).toBe(true); }); @@ -271,7 +293,7 @@ describe('helpers/Vergunningen', () => { text45: CaseTypeV2.WVOS, }, } as DecosZaakSource, - { ...decosZaakTransformers[CaseTypeV2.WVOS], isActive: false } + { ...decosCaseToZaakTransformers[CaseTypeV2.WVOS], isActive: false } ) ).toBe(true); }); @@ -284,7 +306,7 @@ describe('helpers/Vergunningen', () => { text45: CaseTypeV2.WVOS, }, } as DecosZaakSource, - { ...decosZaakTransformers[CaseTypeV2.WVOS] } + { ...decosCaseToZaakTransformers[CaseTypeV2.WVOS] } ) ).toBe(false); }); @@ -304,7 +326,7 @@ describe('helpers/Vergunningen', () => { describe('getCustomTitleForVergunningWithLicensePlates', () => { test('Single kenteken title', () => { expect( - getCustomTitleForVergunningWithLicensePlates({ + getCustomTitleForDecosZaakWithLicensePlates({ title: 'blaap', kentekens: 'AA-BB-CC', } as TouringcarDagontheffing) @@ -313,7 +335,7 @@ describe('helpers/Vergunningen', () => { test('Multiple kenteken title', () => { expect( - getCustomTitleForVergunningWithLicensePlates({ + getCustomTitleForDecosZaakWithLicensePlates({ title: 'blaap', kentekens: 'AA-BB-CC | DDD-EE-F | ZZ-XX-00 | THJ-789-I', } as TouringcarDagontheffing) @@ -325,7 +347,7 @@ describe('helpers/Vergunningen', () => { expect( getDecosZaakTypeFromSource({ fields: { text45: 'Werk en vervoer op straat' }, - } as any) + } as unknown as DecosZaakSource) ).toBe(CaseTypeV2.WVOS); }); @@ -342,21 +364,21 @@ describe('helpers/Vergunningen', () => { const d = new Date(); d.getDate(); d.setDate(d.getDate() + 30); - expect(isNearEndDate({ dateEnd: d.toISOString() } as any)).toBe(true); + expect(isNearEndDate({ dateEnd: d.toISOString() })).toBe(true); }); test('Not near', () => { const d = new Date(); d.getDate(); d.setDate(d.getDate() + 120); - expect(isNearEndDate({ dateEnd: d.toISOString() } as any)).toBe(false); + expect(isNearEndDate({ dateEnd: d.toISOString() })).toBe(false); }); test('In past', () => { const d = new Date(); d.getDate(); d.setDate(d.getDate() - 120); - expect(isNearEndDate({ dateEnd: d.toISOString() } as any)).toBe(false); + expect(isNearEndDate({ dateEnd: d.toISOString() })).toBe(false); }); }); @@ -365,24 +387,20 @@ describe('helpers/Vergunningen', () => { const d = new Date(); d.getDate(); d.setDate(d.getDate() + 1); - expect(isExpired({ dateEnd: new Date().toISOString() } as any, d)).toBe( - true - ); + expect(isExpired({ dateEnd: new Date().toISOString() }, d)).toBe(true); }); test('Is not expired', () => { const d = new Date(); d.getDate(); d.setDate(d.getDate() - 1); - expect(isExpired({ dateEnd: new Date().toISOString() } as any, d)).toBe( - false - ); + expect(isExpired({ dateEnd: new Date().toISOString() }, d)).toBe(false); }); test('Is expired same date', () => { - expect( - isExpired({ dateEnd: new Date().toISOString() } as any, new Date()) - ).toBe(true); + expect(isExpired({ dateEnd: new Date().toISOString() }, new Date())).toBe( + true + ); }); }); diff --git a/src/server/services/vergunningen-v2/helpers.ts b/src/server/services/decos/helpers.ts similarity index 71% rename from src/server/services/vergunningen-v2/helpers.ts rename to src/server/services/decos/helpers.ts index b057b1abc3..2aa740caba 100644 --- a/src/server/services/vergunningen-v2/helpers.ts +++ b/src/server/services/decos/helpers.ts @@ -1,29 +1,30 @@ +import type { + DecosZaakTransformer, + DecosZaakSource, + DecosZaakBase, + DecosZaakWithKentekens, +} from './decos-types'; import { DECOS_EXCLUDE_CASES_WITH_INVALID_DFUNCTION, DECOS_EXCLUDE_CASES_WITH_PENDING_PAYMENT_CONFIRMATION_SUBJECT1, DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT11, DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT12, DECOS_PENDING_REMOVAL_DFUNCTION, - DecosZaakSource, - DecosZaakTypeTransformer, - NOTIFICATION_REMINDER_FROM_MONTHS_NEAR_END, - VergunningV2, -} from './config-and-types'; -import { decosZaakTransformers } from './decos-zaken'; +} from './decos-types'; import { defaultDateFormat, isDateInPast, monthsFromNow, } from '../../../universal/helpers/date'; +import { NOTIFICATION_REMINDER_FROM_MONTHS_NEAR_END } from '../../../universal/helpers/vergunningen'; import { DecosCaseType } from '../../../universal/types/vergunningen'; import { AuthProfileAndToken } from '../../auth/auth-types'; // Checks to see if a payment was not processed correctly/completely yet. export function isWaitingForPaymentConfirmation( - decosZaakSource: DecosZaakSource + decosZaakSource: DecosZaakSource, + zaakTypeTransformer: DecosZaakTransformer ) { - const zaakType = getDecosZaakTypeFromSource(decosZaakSource); - const isWaitingForPaymentConfirmation = decosZaakSource.fields.text11?.toLowerCase() == DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT11 && @@ -37,7 +38,7 @@ export function isWaitingForPaymentConfirmation( ); return ( - !!decosZaakTransformers[zaakType]?.requirePayment && + !!zaakTypeTransformer.requirePayment && (isWaitingForPaymentConfirmation || isWaitingForPaymentConfirmation2) ); } @@ -60,11 +61,11 @@ export function isScheduledForRemoval(decosZaakSource: DecosZaakSource) { export function isExcludedFromTransformation( zaakSource: DecosZaakSource, - zaakTypeTransformer: DecosZaakTypeTransformer + zaakTypeTransformer: DecosZaakTransformer ) { return ( isScheduledForRemoval(zaakSource) || - isWaitingForPaymentConfirmation(zaakSource) || + isWaitingForPaymentConfirmation(zaakSource, zaakTypeTransformer) || hasInvalidDecision(zaakSource) || !zaakTypeTransformer.isActive || // Check if we have data we want to transform or not. @@ -89,21 +90,23 @@ export function transformKenteken(kentekenSource: string | null) { return kentekenSource; } -export function getCustomTitleForVergunningWithLicensePlates( - vergunning: VergunningV2 +export function getCustomTitleForDecosZaakWithLicensePlates( + decosZaak: DecosZaakWithKentekens ) { - if ('kentekens' in vergunning) { - const plates = vergunning.kentekens?.split(' | '); + if ('kentekens' in decosZaak) { + const plates = decosZaak.kentekens?.split(' | '); if (plates?.length === 1) { - return `${vergunning.title} (${vergunning.kentekens})`; + return `${decosZaak.title} (${decosZaak.kentekens})`; } else if (!!plates && plates.length > 1) { - return `${vergunning.title} (${plates[0]}... +${plates.length - 1})`; + return `${decosZaak.title} (${plates[0]}... +${plates.length - 1})`; } } - return vergunning.title; + return decosZaak.title; } -export function getDecosZaakTypeFromSource(decosZaakSource: DecosZaakSource) { +export function getDecosZaakTypeFromSource( + decosZaakSource: T +) { return decosZaakSource.fields.text45 as DecosCaseType; } @@ -130,42 +133,42 @@ export function getUserKeysSearchQuery( } export function isNearEndDate( - vergunning: { dateEnd: string | null }, + decosZaak: { dateEnd: string | null }, dateNow: Date = new Date() ) { - if (!vergunning.dateEnd) { + if (!decosZaak.dateEnd) { return false; } - const monthsTillEnd = monthsFromNow(vergunning.dateEnd, dateNow); + const monthsTillEnd = monthsFromNow(decosZaak.dateEnd, dateNow); return ( - !isExpired(vergunning, dateNow) && + !isExpired(decosZaak, dateNow) && monthsTillEnd < NOTIFICATION_REMINDER_FROM_MONTHS_NEAR_END && monthsTillEnd >= 0 // Only show the notification if we have a long-running permit validity ); } export function isExpired( - vergunning: { dateEnd: string | null }, + decosZaak: { dateEnd: string | null }, dateNow: Date = new Date() ) { - if (!vergunning.dateEnd) { + if (!decosZaak.dateEnd) { return false; } - return isDateInPast(vergunning.dateEnd, dateNow); + return isDateInPast(decosZaak.dateEnd, dateNow); } -export function hasOtherActualVergunningOfSameType( - items: Array, - item: VergunningV2 +export function hasOtherActualDecosZaakOfSameType( + items: Array, + item: DecosZaakBase ): boolean { return items.some( - (otherVergunning: VergunningV2) => - otherVergunning.caseType === item.caseType && - otherVergunning.identifier !== item.identifier && - !isExpired(otherVergunning) + (otherDecosZaak: DecosZaakBase) => + otherDecosZaak.caseType === item.caseType && + otherDecosZaak.identifier !== item.identifier && + !isExpired(otherDecosZaak) ); } diff --git a/src/server/services/toeristische-verhuur/toeristische-verhuur-notifications.ts b/src/server/services/toeristische-verhuur/toeristische-verhuur-notifications.ts index 7375a65f45..e0e97faf6b 100644 --- a/src/server/services/toeristische-verhuur/toeristische-verhuur-notifications.ts +++ b/src/server/services/toeristische-verhuur/toeristische-verhuur-notifications.ts @@ -13,8 +13,8 @@ import { dateFormat, isDateInPast } from '../../../universal/helpers/date'; import { isRecentNotification } from '../../../universal/helpers/utils'; import { MyNotification } from '../../../universal/types'; import { AuthProfileAndToken } from '../../auth/auth-types'; -import { NOTIFICATION_REMINDER_FROM_MONTHS_NEAR_END } from '../vergunningen-v2/config-and-types'; -import { isNearEndDate } from '../vergunningen-v2/helpers'; +import { NOTIFICATION_REMINDER_FROM_MONTHS_NEAR_END } from '../../../universal/helpers/vergunningen'; +import { isNearEndDate } from '../decos/helpers'; export function createToeristischeVerhuurNotification( vergunning: ToeristischeVerhuurVergunning, diff --git a/src/server/services/vergunningen-v2/config-and-types.ts b/src/server/services/vergunningen-v2/config-and-types.ts index 3f8a231da5..b71b0e5a77 100644 --- a/src/server/services/vergunningen-v2/config-and-types.ts +++ b/src/server/services/vergunningen-v2/config-and-types.ts @@ -1,16 +1,19 @@ -import { ApiResponse } from '../../../universal/helpers/api'; -import { - GenericDocument, - LinkProps, - ZaakDetail, -} from '../../../universal/types'; +import { LinkProps, ZaakDetail } from '../../../universal/types'; import { CaseTypeV2, DecosCaseType, GetCaseType, } from '../../../universal/types/vergunningen'; +import { + DecosZaakBase, + DecosZaakWithDateRange, + DecosZaakWithKentekens, + DecosZaakWithLocation, + DecosZaakWithDateTimeRange, + ZaakStatus, + ZakenFilter, +} from '../decos/decos-types'; -export const NOTIFICATION_REMINDER_FROM_MONTHS_NEAR_END = 3; export const NOTIFICATION_MAX_MONTHS_TO_SHOW_EXPIRED = 3; export const EXCLUDE_CASE_TYPES_FROM_VERGUNNINGEN_THEMA: DecosCaseType[] = [ @@ -18,254 +21,16 @@ export const EXCLUDE_CASE_TYPES_FROM_VERGUNNINGEN_THEMA: DecosCaseType[] = [ CaseTypeV2.ExploitatieHorecabedrijf, ]; -// Cases with this one of these dfunction values will not be included in the cases shown to the user. -export const DECOS_EXCLUDE_CASES_WITH_INVALID_DFUNCTION = [ - 'buiten behandeling', - 'geannuleerd', - 'geen aanvraag of dubbel', -]; - -// Cases with one of these subject1 values will not be included in the cases shown to the user. Payment is not yet processed or failed. -export const DECOS_EXCLUDE_CASES_WITH_PENDING_PAYMENT_CONFIRMATION_SUBJECT1 = [ - 'wacht op online betaling', - 'wacht op ideal betaling', -]; - -// Cases with this dfunction value will not be included in the cases shown to the user. -export const DECOS_PENDING_REMOVAL_DFUNCTION = '*verwijder'; -// Cases with this text11 value will not be included in the cases shown to the user. Payment is not yet processed or failed. -export const DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT11 = 'nogniet'; -// Cases with this text12 value will not be included in the cases shown to the user. Payment is not yet processed or failed. -export const DECOS_PENDING_PAYMENT_CONFIRMATION_TEXT12 = - 'wacht op online betaling'; - -const adresBoekenBSN = - process.env.BFF_DECOS_API_ADRES_BOEKEN_BSN?.split(',') ?? []; - -const adresBoekenKVK = - process.env.BFF_DECOS_API_ADRES_BOEKEN_KVK?.split(',') ?? []; - -export const adresBoekenByProfileType: Record = { - private: adresBoekenBSN, - commercial: adresBoekenKVK, - 'private-attributes': [], -}; - -export const MA_DECISION_DEFAULT = 'Zie besluit'; - -export type ZaakKenmerk = `Z/${number}/${number}`; // Z/23/2230346 -export type DecosZaakID = string; -export type ZaakStatus = - | 'Ontvangen' - | 'In behandeling' - | 'Afgehandeld' - | string; - -type MADecision = string; -type DecosDecision = string; - -export type DecosFieldNameSource = string; -export type DecosFieldValue = string | number | boolean | null; - -type DecosTransformerOptions = { - zaakTypeTransformer?: DecosZaakTypeTransformer; - fetchDecosWorkflowDate?: ( - stepTitle: DecosWorkflowStepTitle - ) => Promise>; -}; - -export type DecosFieldTransformer = { - name: keyof T; - transform?: ( - input: any, - options?: DecosTransformerOptions - ) => DecosFieldValue; -}; - -export type DecosZaakTypeTransformer = { - // The caseType (zaaktype) of the sourceData. - caseType: DecosCaseType; - // Title of the VergunningV2, mostly a slightly different variant of the $caseType - title: string; - // A mapping object that can be used to assign a readable attribute to the data sent to the frontend. - // For example: date6 becomes dateStart. Additionally a function can be provided to perform some compute on the value assigned to the sourceField. - // For example String operations like, trim, split, uppercase etc. - transformFields?: Partial>; - // After transform is used to perform additional transformations after the initial transform. - // Business logic is implemented at this point, also async calls to other services to enrich the data can be done here. - afterTransform?: ( - vergunning: T, - decosZaakSource: DecosZaakSource, - options?: DecosTransformerOptions - ) => Promise; - // A function to check if the source data quality and/or prerequisites for showing the data to the user are valid. - // This function is run before transformation of the zaak. - hasValidSourceData?: (decosZaakSource: DecosZaakSource) => boolean; - // Indicate if the zaak requires payment to be processed and complete. This function is run before transformation of the zaak. - requirePayment?: boolean; - // Decision (resultaat) values are generalized here. For example. The sourceValues can be one of: `Toegekend met borden`, `Toegekend zonder dingen` which we want to show to the user as `Toegekend`. - decisionTranslations?: Record; - // The title of the workflow step that is used to find a date for the InBehandeling status. - dateInBehandelingWorkflowStepTitle?: string; - // Indicates if the Zaak should be shown to the user / is expected to be transformed. - isActive: boolean; - // Initially we request a set of fields to be included in the responseData (?select=). For some cases we need a (few) custom field(s) included in the initial response. - // For example to show a kenteken in the Notifications. Sadly the requested fields cannot be specified on a case basis. - // This means even though we do not want, for example, date7 for case A we will receive it anyway. - // We select a specific set of fields because otherwise we receive all the fields of a zaak which are bloated and mostly unused. - addToSelectFieldsBase?: string[]; - // Notifications for this specific - notificationLabels?: Partial; -}; - -type DecosZaakBase = { - // status - title: string; - // caseType - text45: DecosCaseType | string; - // decision - dfunction?: string | null; - // identifier / zaaknummer - mark: string; - // processed - processed: boolean; - // dateDecision - date5?: string | null; - // dateRequest - document_date: string; - // dateStart - date6?: string | null; - // dateEnd - date7?: string | null; - - subject1?: string; - // Info regarding possible payment - text11?: string | null; - // Info regarding possible payment - text12?: string | null; -}; - -type DecosDocumentBase = { - text39: string; - text40: string; - text41: string; - // identifier / zaaknummer - mark: string; - // datePublished - received_date: string; -}; - -type DecosDocumentBlobBase = { - // IS PDF - bol10: boolean; -}; - -export type DecosFieldsObject = Record< - DecosFieldNameSource, - string | boolean | null | number ->; - -export type DecosFieldTransformerObject = - Record | keyof T>; - -export type DecosZaakSource = { - key: DecosZaakID; - links: string[]; - fields: DecosZaakBase & DecosFieldsObject; -}; - -export type DecosDocumentSource = { - key: DecosZaakID; - links: string[]; - fields: DecosDocumentBase & DecosFieldsObject; -}; - -export type DecosDocumentBlobSource = { - key: DecosZaakID; - links: string[]; - fields: DecosDocumentBlobBase & DecosFieldsObject; -}; - -export type DecosZakenResponse = { - count: number; - content: T; -}; - -export type DecosResponse = { - itemDataResultSet: { - content: T[]; - }; -}; - -export type VergunningCaseTypeFilter = (vergunning: VergunningV2) => boolean; - -export type AddressBookEntry = { - key: string; -}; - -export type DecosWorkflowStepTitle = string; -export type DecosWorkflowStepDate = string; - -export interface VergunningBase { - caseType: DecosCaseType; - dateDecision: string | null; - dateInBehandeling: string | null; - dateRequest: string; - - // DateStart and DateEnd are not applicable to every single vergunning but general enough to but in base Type. - dateStart: string | null; - dateEnd: string | null; - - decision: string | null; - description: string; - - // Url to BFF Detail paga api - fetchUrl: string; - - identifier: ZaakKenmerk; - title: string; - id: string; - - // Decos key (uuid) used as primary id's in api communication. - key: string; - - processed: boolean; - status: ZaakStatus; - - paymentStatus: string | null; - paymentMethod: string | null; -} - -export interface VergunningWithLocation extends VergunningBase { - location: string | null; -} - -export interface VergunningWithKentekens extends VergunningBase { - kentekens: string | null; -} - -export interface VergunningWithDateRange extends VergunningBase { - dateStart: string | null; - dateEnd: string | null; -} - -export interface VergunningWithTimeRange extends VergunningBase { - timeStart: string | null; - timeEnd: string | null; -} - -export interface VergunningWithDateTimeRange - extends VergunningWithDateRange, - VergunningWithTimeRange {} +export type VergunningBase = DecosZaakBase; export interface TVMRVVObject - extends VergunningWithLocation, - VergunningWithDateTimeRange, - VergunningWithKentekens { + extends DecosZaakWithLocation, + DecosZaakWithDateTimeRange, + DecosZaakWithKentekens { caseType: GetCaseType<'TVMRVVObject'>; } -export interface GPK extends VergunningWithLocation { +export interface GPK extends DecosZaakWithLocation { caseType: GetCaseType<'GPK'>; cardType: 'driver' | 'passenger'; cardNumber: number | null; @@ -273,44 +38,44 @@ export interface GPK extends VergunningWithLocation { requestReason: string | null; } -export interface GPP extends VergunningWithLocation { +export interface GPP extends DecosZaakWithLocation { caseType: GetCaseType<'GPP'>; kentekens: string | null; } export interface EvenementMelding - extends VergunningWithLocation, - VergunningWithDateTimeRange { + extends DecosZaakWithLocation, + DecosZaakWithDateTimeRange { caseType: GetCaseType<'EvenementMelding'>; } export interface EvenementVergunning - extends VergunningWithLocation, - VergunningWithDateTimeRange { + extends DecosZaakWithLocation, + DecosZaakWithDateTimeRange { caseType: GetCaseType<'EvenementVergunning'>; } -export interface Omzettingsvergunning extends VergunningWithLocation { +export interface Omzettingsvergunning extends DecosZaakWithLocation { caseType: GetCaseType<'Omzettingsvergunning'>; dateInBehandeling: string | null; } export interface ERVV - extends VergunningWithLocation, - VergunningWithDateTimeRange { + extends DecosZaakWithLocation, + DecosZaakWithDateTimeRange { caseType: GetCaseType<'ERVV'>; } export interface VakantieverhuurVergunningaanvraag - extends VergunningWithLocation, - VergunningWithDateRange { + extends DecosZaakWithLocation, + DecosZaakWithDateRange { caseType: GetCaseType<'VakantieverhuurVergunningaanvraag'>; title: 'VergunningV2 vakantieverhuur'; decision: 'Verleend' | 'Ingetrokken'; } // BZB is short for Parkeerontheffingen Blauwe zone bedrijven -export interface BZB extends VergunningWithDateRange { +export interface BZB extends DecosZaakWithDateRange { caseType: GetCaseType<'BZB'>; companyName: string | null; numberOfPermits: string | null; @@ -318,47 +83,47 @@ export interface BZB extends VergunningWithDateRange { } // BZP is short for Parkeerontheffingen Blauwe zone particulieren -export interface BZP extends VergunningWithDateRange, VergunningWithKentekens { +export interface BZP extends DecosZaakWithDateRange, DecosZaakWithKentekens { caseType: GetCaseType<'BZP'>; kentekens: string | null; decision: string | null; } export interface Flyeren - extends VergunningWithLocation, - VergunningWithDateTimeRange { + extends DecosZaakWithLocation, + DecosZaakWithDateTimeRange { caseType: GetCaseType<'Flyeren'>; decision: 'Verleend' | 'Niet verleend' | 'Ingetrokken'; } export interface AanbiedenDiensten - extends VergunningWithLocation, - VergunningWithDateRange { + extends DecosZaakWithLocation, + DecosZaakWithDateRange { caseType: GetCaseType<'AanbiedenDiensten'>; } export interface Nachtwerkontheffing - extends VergunningWithLocation, - VergunningWithDateTimeRange { + extends DecosZaakWithLocation, + DecosZaakWithDateTimeRange { caseType: GetCaseType<'NachtwerkOntheffing'>; } export interface ZwaarVerkeer - extends VergunningWithKentekens, - VergunningWithDateRange { + extends DecosZaakWithKentekens, + DecosZaakWithDateRange { caseType: GetCaseType<'ZwaarVerkeer'>; exemptionKind: string | null; } export interface RVVHeleStad - extends VergunningWithKentekens, - VergunningWithDateRange { + extends DecosZaakWithKentekens, + DecosZaakWithDateRange { caseType: GetCaseType<'RVVHeleStad'>; } export interface RVVSloterweg - extends VergunningWithKentekens, - VergunningWithDateRange { + extends DecosZaakWithKentekens, + DecosZaakWithDateRange { caseType: GetCaseType<'RVVSloterweg'>; vorigeKentekens: string | null; dateWorkflowVerleend: string | null; @@ -369,49 +134,49 @@ export interface RVVSloterweg } export interface TouringcarDagontheffing - extends VergunningWithKentekens, - VergunningWithDateTimeRange { + extends DecosZaakWithKentekens, + DecosZaakWithDateTimeRange { caseType: GetCaseType<'TouringcarDagontheffing'>; destination: string | null; } export interface TouringcarJaarontheffing - extends VergunningWithKentekens, - VergunningWithDateRange { + extends DecosZaakWithKentekens, + DecosZaakWithDateRange { caseType: GetCaseType<'TouringcarJaarontheffing'>; destination: string | null; routetest: boolean; } -export interface Samenvoegingsvergunning extends VergunningWithLocation { +export interface Samenvoegingsvergunning extends DecosZaakWithLocation { caseType: GetCaseType<'Samenvoegingsvergunning'>; } -export interface Onttrekkingsvergunning extends VergunningWithLocation { +export interface Onttrekkingsvergunning extends DecosZaakWithLocation { caseType: GetCaseType<'Onttrekkingsvergunning'>; } -export interface OnttrekkingsvergunningSloop extends VergunningWithLocation { +export interface OnttrekkingsvergunningSloop extends DecosZaakWithLocation { caseType: GetCaseType<'OnttrekkingsvergunningSloop'>; } -export interface VormenVanWoonruimte extends VergunningWithLocation { +export interface VormenVanWoonruimte extends DecosZaakWithLocation { caseType: GetCaseType<'VormenVanWoonruimte'>; } -export interface Splitsingsvergunning extends VergunningWithLocation { +export interface Splitsingsvergunning extends DecosZaakWithLocation { caseType: GetCaseType<'Splitsingsvergunning'>; } export interface ExploitatieHorecabedrijf - extends VergunningWithLocation, - VergunningWithDateRange { + extends DecosZaakWithLocation, + DecosZaakWithDateRange { caseType: GetCaseType<'ExploitatieHorecabedrijf'>; dateStartPermit: string | null; numberOfPermits: string | null; } -export interface Ligplaatsvergunning extends VergunningWithLocation { +export interface Ligplaatsvergunning extends DecosZaakWithLocation { caseType: GetCaseType<'VOB'>; requestKind: string | null; reason: string | null; @@ -436,8 +201,8 @@ export type EigenParkeerplaatsRequestType = export interface EigenParkeerplaats extends VergunningBase, - VergunningWithKentekens, - VergunningWithDateRange { + DecosZaakWithKentekens, + DecosZaakWithDateRange { caseType: GetCaseType<'EigenParkeerplaats'>; vorigeKentekens: string | null; requestTypes: EigenParkeerplaatsRequestType[]; @@ -462,9 +227,9 @@ export type WVOSActiviteit = | 'Filmen'; export interface WerkzaamhedenEnVervoerOpStraat - extends VergunningWithLocation, - VergunningWithDateRange, - VergunningWithKentekens { + extends DecosZaakWithLocation, + DecosZaakWithDateRange, + DecosZaakWithKentekens { caseType: GetCaseType<'WVOS'>; werkzaamheden: WVOSActiviteit[]; } @@ -506,14 +271,10 @@ export type VergunningenSourceData = { status: 'OK' | 'ERROR'; }; -export type VergunningExpirable = VergunningV2 & { dateEnd?: string | null }; - -export type VergunningDocument = GenericDocument & { key: string }; - -export type VergunningenData = VergunningV2[]; +export type DecosZaakExpirable = VergunningV2 & { dateEnd?: string | null }; export interface VergunningOptions { - filter?: (vergunning: VergunningV2) => boolean; + filter?: ZakenFilter; appRoute: string | ((vergunning: VergunningV2) => string); } @@ -526,8 +287,6 @@ export type VergunningFrontendV2 = T & { isExpired?: boolean; } & ZaakDetail; -export type VergunningFilter = (vergunning: VergunningV2) => boolean; - export type NotificationProperty = | 'title' | 'description' diff --git a/src/server/services/vergunningen-v2/decos-zaken.ts b/src/server/services/vergunningen-v2/decos-zaken.ts index 7bf70d3b51..3e8541c4b5 100644 --- a/src/server/services/vergunningen-v2/decos-zaken.ts +++ b/src/server/services/vergunningen-v2/decos-zaken.ts @@ -4,10 +4,6 @@ import { AanbiedenDiensten as AanbiedenDienstenType, BZB as BZBType, BZP as BZPType, - DecosFieldNameSource, - DecosFieldTransformer, - DecosFieldTransformerObject, - DecosZaakTypeTransformer, ERVV, EigenParkeerplaatsOpheffen as EigenParkeerplaatsOpheffenType, EigenParkeerplaatsRequestType, @@ -31,17 +27,11 @@ import { TouringcarDagontheffing as TouringcarDagontheffingType, TouringcarJaarontheffing as TouringcarJaarontheffingType, VakantieverhuurVergunningaanvraag as VakantieverhuurVergunningaanvraagType, - VergunningV2, VormenVanWoonruimte as VormenVanWoonruimteType, WVOSActiviteit as WVOSActiviteitType, WerkzaamhedenEnVervoerOpStraat as WerkzaamhedenEnVervoerOpStraatType, ZwaarVerkeer as ZwaarVerkeerType, } from './config-and-types'; -import { - getCustomTitleForVergunningWithLicensePlates, - transformBoolean, - transformKenteken, -} from './helpers'; import { caseNotificationLabelsDefault, caseNotificationLabelsExpirables, @@ -53,66 +43,32 @@ import { CaseTypeV2, DecosCaseType, } from '../../../universal/types/vergunningen'; - -const decision: DecosFieldTransformer = { - name: 'decision', - transform: (decision: string, options) => { - const decisionTranslations = - options?.zaakTypeTransformer?.decisionTranslations; - - if (decisionTranslations) { - const maDecision = Object.entries(decisionTranslations).find( - ([maDecision, decosDecisions]) => { - return decosDecisions.includes(decision); - } - )?.[0]; - return maDecision ?? decision; - } - return decision; - }, -}; - -// A list of common readable api attributes -const status = 'status'; -const caseType = 'caseType'; -const identifier = 'identifier'; -const processed = 'processed'; -const dateDecision = 'dateDecision'; -const dateRequest = 'dateRequest'; -const dateStart = 'dateStart'; -const dateEnd = 'dateEnd'; -const location = 'location'; -const timeStart = 'timeStart'; -const timeEnd = 'timeEnd'; -const destination = 'destination'; -const description = 'description'; +import { + dateEnd, + dateStart, + DecosFieldNameSource, + DecosZaakTransformer, + DecosZaakWithKentekens, + description, + destination, + location, + SELECT_FIELDS_TRANSFORM_BASE, + timeEnd, + timeStart, +} from '../decos/decos-types'; +import { + getCustomTitleForDecosZaakWithLicensePlates, + transformBoolean, + transformKenteken, +} from '../decos/helpers'; // 1 or multiple kenteken(s) const kentekens = { - name: 'kentekens' as keyof VergunningV2, // TODO: Can this be typed stricter without casting? + name: 'kentekens' as keyof DecosZaakWithKentekens, // TODO: Can this be typed stricter without casting? transform: transformKenteken, }; -// Fields are selected per case initially but don't end up in the data we send to front end. -// These fields are fore example used to determine payment status. -export const SELECT_FIELDS_META = ['text11', 'text12', 'subject1']; - -// The set of field transforms that applies to every case. -// { $api_attribute_name_source: $api_attribute_name_mijn_amsterdam } -export const SELECT_FIELDS_TRANSFORM_BASE: Partial = - { - title: status, - text45: caseType, - dfunction: decision, - mark: identifier, - processed: processed, - date5: dateDecision, - document_date: dateRequest, - date6: dateStart, - date7: dateEnd, - }; - -export const TVMRVVObject: DecosZaakTypeTransformer = { +export const TVMRVVObject: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.TVMRVVObject, title: 'Tijdelijke verkeersmaatregel (TVM-RVV-Object)', @@ -144,14 +100,14 @@ export const TVMRVVObject: DecosZaakTypeTransformer = { vergunning.dateEnd = vergunning.dateStart; } - vergunning.title = getCustomTitleForVergunningWithLicensePlates(vergunning); + vergunning.title = getCustomTitleForDecosZaakWithLicensePlates(vergunning); return vergunning; }, notificationLabels: caseNotificationLabelsExpirables, }; -export const VakantieverhuurVergunningaanvraag: DecosZaakTypeTransformer = +export const VakantieverhuurVergunningaanvraag: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.VakantieverhuurVergunningaanvraag, @@ -191,7 +147,7 @@ export const VakantieverhuurVergunningaanvraag: DecosZaakTypeTransformer = { +export const GPP: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.GPP, title: 'Vaste parkeerplaats voor gehandicapten (GPP)', @@ -201,7 +157,7 @@ export const GPP: DecosZaakTypeTransformer = { text8: location, }, async afterTransform(vergunning, decosZaakSource, options) { - vergunning.title = getCustomTitleForVergunningWithLicensePlates(vergunning); + vergunning.title = getCustomTitleForDecosZaakWithLicensePlates(vergunning); return vergunning; }, decisionTranslations: { @@ -211,7 +167,7 @@ export const GPP: DecosZaakTypeTransformer = { notificationLabels: caseNotificationLabelsDefault, }; -export const GPK: DecosZaakTypeTransformer = { +export const GPK: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.GPK, title: 'Europese gehandicaptenparkeerkaart (GPK)', @@ -242,32 +198,31 @@ export const GPK: DecosZaakTypeTransformer = { notificationLabels: caseNotificationLabelsExpirables, }; -export const EvenementMelding: DecosZaakTypeTransformer = - { - isActive: true, - caseType: CaseTypeV2.EvenementMelding, - title: 'Evenement melding', - transformFields: { - ...SELECT_FIELDS_TRANSFORM_BASE, - date6: dateStart, - date7: dateEnd, - text6: location, - text7: timeStart, - text8: timeEnd, - }, - decisionTranslations: { - Toegestaan: [ - 'Verleend', - 'Verleend (Bijzonder/Bewaren)', - 'Verleend zonder borden', - ], - 'Niet toegestaan': ['Niet verleend'], - '': ['Nog niet bekend', 'Nog niet bekend'], - }, - notificationLabels: caseNotificationLabelsDefault, - }; +export const EvenementMelding: DecosZaakTransformer = { + isActive: true, + caseType: CaseTypeV2.EvenementMelding, + title: 'Evenement melding', + transformFields: { + ...SELECT_FIELDS_TRANSFORM_BASE, + date6: dateStart, + date7: dateEnd, + text6: location, + text7: timeStart, + text8: timeEnd, + }, + decisionTranslations: { + Toegestaan: [ + 'Verleend', + 'Verleend (Bijzonder/Bewaren)', + 'Verleend zonder borden', + ], + 'Niet toegestaan': ['Niet verleend'], + '': ['Nog niet bekend', 'Nog niet bekend'], + }, + notificationLabels: caseNotificationLabelsDefault, +}; -export const EvenementVergunning: DecosZaakTypeTransformer = +export const EvenementVergunning: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.EvenementVergunning, @@ -287,7 +242,7 @@ export const EvenementVergunning: DecosZaakTypeTransformer = +export const Omzettingsvergunning: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.Omzettingsvergunning, @@ -304,7 +259,7 @@ export const Omzettingsvergunning: DecosZaakTypeTransformer = { +export const ERVV_TVM: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.Omzettingsvergunning, title: 'e-RVV (Gratis verkeersontheffing voor elektrisch goederenvervoer)', @@ -329,7 +284,7 @@ export const ERVV_TVM: DecosZaakTypeTransformer = { notificationLabels: caseNotificationLabelsExpirables, }; -export const BZP: DecosZaakTypeTransformer = { +export const BZP: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.BZP, title: CaseTypeV2.BZP, @@ -341,13 +296,13 @@ export const BZP: DecosZaakTypeTransformer = { text8: kentekens, }, async afterTransform(vergunning, decosZaakSource, options) { - vergunning.title = getCustomTitleForVergunningWithLicensePlates(vergunning); + vergunning.title = getCustomTitleForDecosZaakWithLicensePlates(vergunning); return vergunning; }, notificationLabels: caseNotificationLabelsExpirables, }; -export const BZB: DecosZaakTypeTransformer = { +export const BZB: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.BZB, title: CaseTypeV2.BZB, @@ -361,7 +316,7 @@ export const BZB: DecosZaakTypeTransformer = { notificationLabels: caseNotificationLabelsDefault, }; -export const Flyeren: DecosZaakTypeTransformer = { +export const Flyeren: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.Flyeren, title: 'Verspreiden reclamemateriaal (sampling)', @@ -377,21 +332,20 @@ export const Flyeren: DecosZaakTypeTransformer = { notificationLabels: caseNotificationLabelsDefault, }; -export const AanbiedenDiensten: DecosZaakTypeTransformer = - { - isActive: true, - caseType: CaseTypeV2.AanbiedenDiensten, - title: CaseTypeV2.AanbiedenDiensten, - transformFields: { - ...SELECT_FIELDS_TRANSFORM_BASE, - date6: dateStart, - date7: dateEnd, - text6: location, - }, - notificationLabels: caseNotificationLabelsDefault, - }; +export const AanbiedenDiensten: DecosZaakTransformer = { + isActive: true, + caseType: CaseTypeV2.AanbiedenDiensten, + title: CaseTypeV2.AanbiedenDiensten, + transformFields: { + ...SELECT_FIELDS_TRANSFORM_BASE, + date6: dateStart, + date7: dateEnd, + text6: location, + }, + notificationLabels: caseNotificationLabelsDefault, +}; -export const NachtwerkOntheffing: DecosZaakTypeTransformer = +export const NachtwerkOntheffing: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.NachtwerkOntheffing, @@ -413,7 +367,7 @@ export const NachtwerkOntheffing: DecosZaakTypeTransformer = { +export const ZwaarVerkeer: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.ZwaarVerkeer, decisionTranslations: { @@ -473,13 +427,13 @@ export const ZwaarVerkeer: DecosZaakTypeTransformer = { }, addToSelectFieldsBase: ['text49'], // kenteken, async afterTransform(vergunning, decosZaakSource, options) { - vergunning.title = getCustomTitleForVergunningWithLicensePlates(vergunning); + vergunning.title = getCustomTitleForDecosZaakWithLicensePlates(vergunning); return vergunning; }, notificationLabels: caseNotificationLabelsDefault, }; -export const Samenvoegingsvergunning: DecosZaakTypeTransformer = +export const Samenvoegingsvergunning: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.Samenvoegingsvergunning, @@ -493,7 +447,7 @@ export const Samenvoegingsvergunning: DecosZaakTypeTransformer = +export const Onttrekkingsvergunning: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.Onttrekkingsvergunning, @@ -507,7 +461,7 @@ export const Onttrekkingsvergunning: DecosZaakTypeTransformer = +export const OnttrekkingsvergunningSloop: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.OnttrekkingsvergunningSloop, @@ -521,7 +475,7 @@ export const OnttrekkingsvergunningSloop: DecosZaakTypeTransformer = +export const VormenVanWoonruimte: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.VormenVanWoonruimte, @@ -535,7 +489,7 @@ export const VormenVanWoonruimte: DecosZaakTypeTransformer = +export const Splitsingsvergunning: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.Splitsingsvergunning, @@ -548,24 +502,23 @@ export const Splitsingsvergunning: DecosZaakTypeTransformer = - { - isActive: true, - caseType: CaseTypeV2.VOB, - title: 'Ligplaatsvergunning', - dateInBehandelingWorkflowStepTitle: 'VOB - Beoordelen en besluiten', - transformFields: { - ...SELECT_FIELDS_TRANSFORM_BASE, - text9: { name: 'requestKind' }, - text18: { name: 'reason' }, - text6: location, - text10: { name: 'vesselKind' }, // soort vaartuig - text14: { name: 'vesselName' }, // naam vaartuig - }, - notificationLabels: caseNotificationLabelsDefault, - }; +export const VOBvergunning: DecosZaakTransformer = { + isActive: true, + caseType: CaseTypeV2.VOB, + title: 'Ligplaatsvergunning', + dateInBehandelingWorkflowStepTitle: 'VOB - Beoordelen en besluiten', + transformFields: { + ...SELECT_FIELDS_TRANSFORM_BASE, + text9: { name: 'requestKind' }, + text18: { name: 'reason' }, + text6: location, + text10: { name: 'vesselKind' }, // soort vaartuig + text14: { name: 'vesselName' }, // naam vaartuig + }, + notificationLabels: caseNotificationLabelsDefault, +}; -export const ExploitatieHorecabedrijf: DecosZaakTypeTransformer = +export const ExploitatieHorecabedrijf: DecosZaakTransformer = { isActive: FeatureToggle.horecaActive, caseType: CaseTypeV2.ExploitatieHorecabedrijf, @@ -581,7 +534,7 @@ export const ExploitatieHorecabedrijf: DecosZaakTypeTransformer = { +export const RVVHeleStad: DecosZaakTransformer = { isActive: !IS_PRODUCTION, caseType: CaseTypeV2.RVVHeleStad, title: 'RVV-verkeersontheffing', @@ -596,13 +549,13 @@ export const RVVHeleStad: DecosZaakTypeTransformer = { }, addToSelectFieldsBase: ['text49'], // Kenteken, async afterTransform(vergunning, decosZaakSource, options) { - vergunning.title = getCustomTitleForVergunningWithLicensePlates(vergunning); + vergunning.title = getCustomTitleForDecosZaakWithLicensePlates(vergunning); return vergunning; }, notificationLabels: caseNotificationLabelsExpirables, }; -export const RVVSloterweg: DecosZaakTypeTransformer = { +export const RVVSloterweg: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.RVVSloterweg, title: 'RVV ontheffing Sloterweg', @@ -629,7 +582,7 @@ export const RVVSloterweg: DecosZaakTypeTransformer = { await options?.fetchDecosWorkflowDate?.('Status naar actief'); if (dateVerleend) { - vergunning['processed'] = true; + vergunning.processed = true; // if the workflow verleend has run but there is no decision then its actually Verleend. // this decision (verleend) is not set by decos eventhough the actual permit is granted. // This is some hack to have an overview of active permits in the Decos back-office. @@ -643,7 +596,7 @@ export const RVVSloterweg: DecosZaakTypeTransformer = { !vergunning.processed && (vergunning.dateDecision || vergunning.decision) ) { - vergunning['processed'] = true; + vergunning.processed = true; } // Add zone to title @@ -658,7 +611,7 @@ export const RVVSloterweg: DecosZaakTypeTransformer = { notificationLabels: caseNotificationLabelsRevoke, }; -export const EigenParkeerplaats: DecosZaakTypeTransformer = +export const EigenParkeerplaats: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.EigenParkeerplaats, @@ -682,7 +635,7 @@ export const EigenParkeerplaats: DecosZaakTypeTransformer = +export const EigenParkeerplaatsOpheffen: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.EigenParkeerplaatsOpheffen, @@ -761,7 +714,7 @@ export const EigenParkeerplaatsOpheffen: DecosZaakTypeTransformer = +export const TouringcarDagontheffing: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.TouringcarDagontheffing, @@ -780,13 +733,13 @@ export const TouringcarDagontheffing: DecosZaakTypeTransformer = +export const TouringcarJaarontheffing: DecosZaakTransformer = { isActive: true, caseType: CaseTypeV2.TouringcarJaarontheffing, @@ -807,14 +760,14 @@ export const TouringcarJaarontheffing: DecosZaakTypeTransformer = +export const WerkEnVervoerOpStraat: DecosZaakTransformer = { isActive: !IS_PRODUCTION, caseType: CaseTypeV2.WVOS, @@ -862,14 +815,14 @@ export const WerkEnVervoerOpStraat: DecosZaakTypeTransformer { - return [zaakTransformer.caseType, zaakTransformer] as const; -}); - -export const decosZaakTransformers = Object.fromEntries( - zaakTransformerEntries -) as Record; +]; +export const decosCaseToZaakTransformers = decosZaakTransformers.reduce( + (acc, zaakTransformer) => ({ + ...acc, + [zaakTransformer.caseType]: zaakTransformer, + }), + {} as Record> +); diff --git a/src/server/services/vergunningen-v2/vergunningen-notification-labels.ts b/src/server/services/vergunningen-v2/vergunningen-notification-labels.ts index 7219dfc94e..4ad6721947 100644 --- a/src/server/services/vergunningen-v2/vergunningen-notification-labels.ts +++ b/src/server/services/vergunningen-v2/vergunningen-notification-labels.ts @@ -3,7 +3,7 @@ import { subMonths } from 'date-fns'; import { NotificationLabels, RVVSloterweg, - VergunningExpirable, + DecosZaakExpirable, VergunningFrontendV2, } from './config-and-types'; import { dateFormat } from '../../../universal/helpers/date'; @@ -44,7 +44,7 @@ const statusAfgehandeld: NotificationLabels = { const verlooptBinnenkort: NotificationLabels = { title: (vergunning) => `Uw ${vergunning.title} loopt af`, description: (vergunning) => `Uw ${vergunning.title} loopt binnenkort af.`, - datePublished: (vergunning: VergunningExpirable) => + datePublished: (vergunning: DecosZaakExpirable) => vergunning.dateEnd ? dateFormat( subMonths( @@ -63,7 +63,7 @@ const verlooptBinnenkort: NotificationLabels = { const isVerlopen: NotificationLabels = { title: (vergunning) => `Uw ${vergunning.caseType} is verlopen`, description: (vergunning) => `Uw ${vergunning.title} is verlopen.`, - datePublished: (vergunning: VergunningExpirable) => vergunning.dateEnd, + datePublished: (vergunning: DecosZaakExpirable) => vergunning.dateEnd, link: (vergunning) => ({ title: `Vraag zonodig een nieuwe vergunning aan`, to: vergunning.link.to, diff --git a/src/server/services/vergunningen-v2/vergunningen-notifications.ts b/src/server/services/vergunningen-v2/vergunningen-notifications.ts index 732d4a75a8..9075ae5a99 100644 --- a/src/server/services/vergunningen-v2/vergunningen-notifications.ts +++ b/src/server/services/vergunningen-v2/vergunningen-notifications.ts @@ -6,13 +6,10 @@ import { NotificationLabelByType, NotificationLabels, NotificationProperty, - VergunningFilter, VergunningFrontendV2, } from './config-and-types'; -import { decosZaakTransformers } from './decos-zaken'; -import { isNearEndDate } from './helpers'; +import { decosCaseToZaakTransformers } from './decos-zaken'; import { - FILTER_VERGUNNINGEN_DEFAULT, fetchVergunningenV2, } from './vergunningen'; import { AppRoute, AppRoutes } from '../../../universal/config/routes'; @@ -25,6 +22,7 @@ import { isRecentNotification } from '../../../universal/helpers/utils'; import { MyNotification } from '../../../universal/types'; import { AuthProfileAndToken } from '../../auth/auth-types'; import { DEFAULT_API_CACHE_TTL_MS } from '../../config/source-api'; +import { isNearEndDate } from '../decos/helpers'; // prettier-ignore export function getNotificationLabels( @@ -86,7 +84,7 @@ export function createVergunningNotification( vergunningen: VergunningFrontendV2[], thema: Thema ): MyNotification | null { - const zaakTypeTransformer = decosZaakTransformers[vergunning.caseType]; + const zaakTypeTransformer = decosCaseToZaakTransformers[vergunning.caseType]; const labels = zaakTypeTransformer.notificationLabels; if (labels) { @@ -126,14 +124,12 @@ async function fetchVergunningenV2Notifications_( requestID: RequestID, authProfileAndToken: AuthProfileAndToken, appRoute: AppRoute = AppRoutes['VERGUNNINGEN/DETAIL'], - filter: VergunningFilter = FILTER_VERGUNNINGEN_DEFAULT, thema: Thema = Themas.VERGUNNINGEN ) { const VERGUNNINGEN = await fetchVergunningenV2( requestID, authProfileAndToken, - appRoute, - filter + appRoute ); if (VERGUNNINGEN.status === 'OK') { diff --git a/src/server/services/vergunningen-v2/vergunningen-route-handlers.ts b/src/server/services/vergunningen-v2/vergunningen-route-handlers.ts index 0fb9e8b385..2ad8102167 100644 --- a/src/server/services/vergunningen-v2/vergunningen-route-handlers.ts +++ b/src/server/services/vergunningen-v2/vergunningen-route-handlers.ts @@ -1,10 +1,6 @@ import { Request, Response } from 'express'; -import { DecosZaakSource } from './config-and-types'; -import { - fetchDecosZaakFromSource, - fetchDecosZakenFromSource, -} from './decos-service'; +import { decosZaakTransformers } from './decos-zaken'; import { fetchVergunningV2 } from './vergunningen'; import { apiSuccessResult } from '../../../universal/helpers/api'; import { getAuth } from '../../auth/auth-helpers'; @@ -13,6 +9,11 @@ import { generateFullApiUrlBFF, sendUnauthorized, } from '../../routing/route-helpers'; +import { + fetchDecosZaakFromSource, + fetchDecosZakenFromSource, +} from '../decos/decos-service'; +import { DecosZaakSource } from '../decos/decos-types'; export async function fetchVergunningDetail(req: Request, res: Response) { const authProfileAndToken = getAuth(req); @@ -63,7 +64,8 @@ export async function fetchZakenFromSource( const zakenResponseData = await fetchDecosZakenFromSource( res.locals.requestID, - authProfileAndToken + authProfileAndToken, + decosZaakTransformers ); if (zakenResponseData.status === 'OK') { diff --git a/src/server/services/vergunningen-v2/vergunningen.ts b/src/server/services/vergunningen-v2/vergunningen.ts index d7d9d6d5d0..fb5e646713 100644 --- a/src/server/services/vergunningen-v2/vergunningen.ts +++ b/src/server/services/vergunningen-v2/vergunningen.ts @@ -4,14 +4,11 @@ import slug from 'slugme'; import { EXCLUDE_CASE_TYPES_FROM_VERGUNNINGEN_THEMA, - VergunningCaseTypeFilter, - VergunningDocument, - VergunningFilter, + VergunningBase, VergunningFrontendV2, - VergunningV2, } from './config-and-types'; -import { fetchDecosVergunning, fetchDecosVergunningen } from './decos-service'; -import { isExpired, toDateFormatted } from './helpers'; +import { VergunningV2 } from './config-and-types'; +import { decosZaakTransformers } from './decos-zaken'; import { getStatusSteps } from './vergunningen-status-steps'; import { AppRoute, AppRoutes } from '../../../universal/config/routes'; import { apiSuccessResult } from '../../../universal/helpers/api'; @@ -21,10 +18,13 @@ import { DEFAULT_API_CACHE_TTL_MS } from '../../config/source-api'; import { encryptSessionIdWithRouteIdParam } from '../../helpers/encrypt-decrypt'; import { BffEndpoints } from '../../routing/bff-routes'; import { generateFullApiUrlBFF } from '../../routing/route-helpers'; +import { fetchDecosZaak, fetchDecosZaken } from '../decos/decos-service'; +import { DecosZaakDocument, ZakenFilter } from '../decos/decos-types'; +import { isExpired, toDateFormatted } from '../decos/helpers'; import { decryptEncryptedRouteParamAndValidateSessionID } from '../shared/decrypt-route-param'; -export const FILTER_VERGUNNINGEN_DEFAULT: VergunningFilter = ( - vergunning: VergunningV2 +export const FILTER_VERGUNNINGEN_DEFAULT: ZakenFilter = ( + vergunning: VergunningBase ) => { return !EXCLUDE_CASE_TYPES_FROM_VERGUNNINGEN_THEMA.includes( vergunning.caseType @@ -82,19 +82,19 @@ function transformVergunningFrontend( return vergunningFrontend; } -async function fetchAndFilterVergunningenV2_( +async function fetchVergunningenV2_( requestID: RequestID, authProfileAndToken: AuthProfileAndToken, - appRoute: AppRoute, - caseTypeFilter?: VergunningCaseTypeFilter + appRoute: AppRoute = AppRoutes['VERGUNNINGEN/DETAIL'] ) { - const response = await fetchDecosVergunningen(requestID, authProfileAndToken); + const response = await fetchDecosZaken( + requestID, + authProfileAndToken, + decosZaakTransformers + ); if (response.status === 'OK') { - let decosVergunningen = response.content; - if (caseTypeFilter) { - decosVergunningen = decosVergunningen.filter(caseTypeFilter); - } + const decosVergunningen = response.content; const vergunningenFrontend: VergunningFrontendV2[] = decosVergunningen.map( (vergunning) => transformVergunningFrontend( @@ -109,31 +109,13 @@ async function fetchAndFilterVergunningenV2_( return response; } -export const fetchAndFilterVergunningenV2 = memoizee( - fetchAndFilterVergunningenV2_, - { - maxAge: DEFAULT_API_CACHE_TTL_MS, - length: 4, - } -); - -export async function fetchVergunningenV2( - requestID: RequestID, - authProfileAndToken: AuthProfileAndToken, - appRoute: AppRoute = AppRoutes['VERGUNNINGEN/DETAIL'], - filter: VergunningFilter = FILTER_VERGUNNINGEN_DEFAULT -) { - return fetchAndFilterVergunningenV2( - requestID, - authProfileAndToken, - appRoute, - filter - ); -} +export const fetchVergunningenV2 = memoizee(fetchVergunningenV2_, { + maxAge: DEFAULT_API_CACHE_TTL_MS, +}); -function addEncryptedDocumentIdToUrl( +function setEncryptedDocumentDownloadUrl( sessionID: SessionID, - document: VergunningDocument + document: DecosZaakDocument ) { const documentIdEncrypted = encryptSessionIdWithRouteIdParam( sessionID, @@ -160,20 +142,24 @@ export async function fetchVergunningV2( ); if (decryptResult.status === 'OK') { - const response = await fetchDecosVergunning( + const response = await fetchDecosZaak( requestID, + decosZaakTransformers, decryptResult.content ); - if (response.status === 'OK' && response.content?.vergunning) { - const { vergunning, documents } = response.content; + if (response.status === 'OK' && response.content?.decosZaak) { + const { decosZaak, documents } = response.content; const documentsTransformed = documents.map((document) => - addEncryptedDocumentIdToUrl(authProfileAndToken.profile.sid, document) + setEncryptedDocumentDownloadUrl( + authProfileAndToken.profile.sid, + document + ) ); return apiSuccessResult({ vergunning: transformVergunningFrontend( authProfileAndToken.profile.id, - vergunning, + decosZaak, AppRoutes['VERGUNNINGEN/DETAIL'] ), documents: documentsTransformed,