diff --git a/mocks/routes/cms.js b/mocks/routes/cms.js
index 5cd8f38bb3..5eab6c3d67 100644
--- a/mocks/routes/cms.js
+++ b/mocks/routes/cms.js
@@ -1,5 +1,5 @@
-const settings = require('../settings');
const loadFixtureAndReplaceBaseUrl = require('../loadFixtureAndReplaceBaseUrl');
+const settings = require('../settings');
const ALLE_RESPONSE = loadFixtureAndReplaceBaseUrl(
'cms-maintenance-notifications-alle.json'
@@ -65,10 +65,17 @@ module.exports = [
variants: [
{
id: 'standard',
- type: 'json',
+ type: 'middleware',
options: {
- status: 200,
- body: PRODUCTEN_OP_MA,
+ middleware: (req, res, next, core) => {
+ const { articleslug } = req.params;
+ if (articleslug === 'overzicht-producten-ondernemers') {
+ const productenOndernemer = structuredClone(PRODUCTEN_OP_MA);
+ productenOndernemer.applicatie.inhoud.inleiding = `
Mock content voor BEDRIJVEN
`;
+ return res.send(productenOndernemer);
+ }
+ return res.send(PRODUCTEN_OP_MA);
+ },
},
},
],
diff --git a/src/client/pages/Parkeren/Parkeren.test.tsx b/src/client/pages/Parkeren/Parkeren.test.tsx
index d153e51464..74cbfefcca 100644
--- a/src/client/pages/Parkeren/Parkeren.test.tsx
+++ b/src/client/pages/Parkeren/Parkeren.test.tsx
@@ -30,9 +30,6 @@ describe('Parkeren', () => {
);
}
- function initializeState(snapshot: MutableSnapshot) {
- snapshot.set(appStateAtom, testState);
- }
beforeAll(() => {
window.scrollTo = vi.fn();
});
@@ -80,7 +77,14 @@ describe('determinePageContentTop', () => {
test('Renders button with parkeer vergunningen', () => {
const PageContentTop = determinePageContentTop(true, EXTERNAL_PARKEREN_URL);
- const screen = render();
+ const screen = render(
+
+ );
expect(screen.queryByText(linkButtonTxt)).toBeInTheDocument();
});
@@ -94,6 +98,10 @@ describe('determinePageContentTop', () => {
});
});
+function initializeState(snapshot: MutableSnapshot) {
+ snapshot.set(appStateAtom, testState);
+}
+
const testState = {
PARKEREN: {
content: {
diff --git a/src/client/pages/Parkeren/Parkeren.tsx b/src/client/pages/Parkeren/Parkeren.tsx
index e69597430b..a12dd1cfec 100644
--- a/src/client/pages/Parkeren/Parkeren.tsx
+++ b/src/client/pages/Parkeren/Parkeren.tsx
@@ -8,6 +8,7 @@ import { VergunningFrontendV2 } from '../../../server/services/vergunningen-v2/c
import { AppRoutes } from '../../../universal/config/routes';
import { MaButtonLink } from '../../components/MaLink/MaLink';
import { ThemaTitles } from '../../config/thema';
+import { useProfileTypeValue } from '../../hooks/useProfileType';
import ThemaPagina from '../ThemaPagina/ThemaPagina';
import ThemaPaginaTable from '../ThemaPagina/ThemaPaginaTable';
@@ -63,12 +64,20 @@ function determinePageContentTop(
parkerenUrlSSO: string
) {
if (hasMijnParkerenVergunningen) {
+ const profileType = useProfileTypeValue();
+
+ const profileTypeLabel =
+ profileType === 'commercial' ? 'bedrijven' : 'bewoners';
+
return (
<>
-
+
- Het inzien, aanvragen of wijzigen van een parkeervergunning voor
- bewoners kan via Mijn Parkeren.
+ Het inzien, aanvragen of wijzigen van een parkeervergunning voor{' '}
+ {profileTypeLabel} kan via Mijn Parkeren.
diff --git a/src/client/pages/ZaakStatus/ZaakStatus.tsx b/src/client/pages/ZaakStatus/ZaakStatus.tsx
index b7867093a6..0fc8c04b0d 100644
--- a/src/client/pages/ZaakStatus/ZaakStatus.tsx
+++ b/src/client/pages/ZaakStatus/ZaakStatus.tsx
@@ -20,7 +20,7 @@ import { useAppStateGetter, useAppStateReady } from '../../hooks/useAppState';
const ITEM_NOT_FOUND = 'not-found';
const STATE_ERROR = 'state-error';
-type ThemaQueryParam = 'vergunningen';
+type ThemaQueryParam = 'vergunningen' | 'toeristischeVerhuur';
type PageRouteResolver = {
baseRoute: AppRoute;
@@ -41,13 +41,34 @@ const pageRouteResolvers: PageRouteResolvers = {
}
if (!isLoading(appState.VERGUNNINGEN)) {
return (
- appState.VERGUNNINGEN.content?.find(
+ (appState.VERGUNNINGEN.content || []).find(
(vergunning) => vergunning.identifier === detailPageItemId
)?.link.to ?? ITEM_NOT_FOUND
);
}
},
},
+ toeristischeVerhuur: {
+ baseRoute: AppRoutes.TOERISTISCHE_VERHUUR,
+ getRoute: (detailPageItemId, appState) => {
+ if (isError(appState.TOERISTISCHE_VERHUUR)) {
+ return STATE_ERROR;
+ }
+
+ if (!isLoading(appState.TOERISTISCHE_VERHUUR)) {
+ return (
+ (
+ appState.TOERISTISCHE_VERHUUR.content
+ ?.vakantieverhuurVergunningen || []
+ ).find((toeristischeVerhuur) => {
+ if (toeristischeVerhuur.zaaknummer === detailPageItemId) {
+ return toeristischeVerhuur;
+ }
+ })?.link.to ?? ITEM_NOT_FOUND
+ );
+ }
+ },
+ },
};
function useNavigateToPage(queryParams: URLSearchParams) {
diff --git a/src/mijnamsterdam.d.ts b/src/mijnamsterdam.d.ts
index db2eb41cdc..16f1d978f0 100644
--- a/src/mijnamsterdam.d.ts
+++ b/src/mijnamsterdam.d.ts
@@ -22,10 +22,6 @@ type ReturnTypeAsync any> = T extends (
? R
: any;
-type ProfileType = 'private' | 'private-attributes' | 'commercial';
-
-type AuthMethod = 'digid' | 'eherkenning';
-
type Optional = Pick, K> & Omit;
type Prettify = {
diff --git a/src/server/auth/auth-helpers.ts b/src/server/auth/auth-helpers.ts
index 226a40d800..82b45b673e 100644
--- a/src/server/auth/auth-helpers.ts
+++ b/src/server/auth/auth-helpers.ts
@@ -19,6 +19,7 @@ import {
} from './auth-types';
import { FeatureToggle } from '../../universal/config/feature-toggles';
import { AppRoutes } from '../../universal/config/routes';
+import { PROFILE_TYPES } from '../../universal/types/App.types';
import { ExternalConsumerEndpoints } from '../routing/bff-routes';
import { generateFullApiUrlBFF } from '../routing/route-helpers';
import { captureException } from '../services/monitoring';
@@ -172,3 +173,7 @@ export function createLogoutHandler(
return res.redirect(postLogoutRedirectUrl);
};
}
+
+export function isValidProfileType(profileType: unknown) {
+ return PROFILE_TYPES.includes(profileType as ProfileType);
+}
diff --git a/src/server/config/source-api.ts b/src/server/config/source-api.ts
index d4404aff7d..e188d1d948 100644
--- a/src/server/config/source-api.ts
+++ b/src/server/config/source-api.ts
@@ -12,7 +12,6 @@ export interface DataRequestConfig extends AxiosRequestConfig {
cacheTimeout?: number;
cancelTimeout?: number;
postponeFetch?: boolean;
- urls?: Record;
// Construct an url that will be assigned to the url key in the local requestConfig.
// Example: formatUrl: (requestConfig) => requestConfig.url + '/some/additional/path/segments/,
@@ -205,11 +204,7 @@ export const ApiConfig: ApiDataRequestConfig = {
CMS_CONTENT_GENERAL_INFO: {
// eslint-disable-next-line no-magic-numbers
cacheTimeout: 4 * ONE_HOUR_MS,
- urls: {
- private: `${getFromEnv('BFF_CMS_BASE_URL')}/mijn-content/artikelen/ziet-amsterdam/?AppIdt=app-data`,
- 'private-attributes': `${getFromEnv('BFF_CMS_BASE_URL')}/mijn-content/artikelen/ziet-amsterdam/?AppIdt=app-data`,
- commercial: `${getFromEnv('BFF_CMS_BASE_URL')}/mijn-content/artikelen/overzicht-producten-ondernemers/?AppIdt=app-data`,
- },
+ url: `${getFromEnv('BFF_CMS_BASE_URL')}/mijn-content/artikelen`,
},
CMS_CONTENT_FOOTER: {
url: `${getFromEnv('BFF_CMS_BASE_URL')}/algemene_onderdelen/overige/footer/?AppIdt=app-data`,
diff --git a/src/server/services/afis/afis-facturen.test.ts b/src/server/services/afis/afis-facturen.test.ts
index 04ebecfcb3..acbc77208f 100644
--- a/src/server/services/afis/afis-facturen.test.ts
+++ b/src/server/services/afis/afis-facturen.test.ts
@@ -71,7 +71,7 @@ const ROUTES = {
},
deelbetalingen: (uri: string) =>
decodeURI(uri).includes(
- `IsCleared eq false and InvoiceReference ne '' and (AccountingDocumentType eq 'AB')`
+ `IsCleared eq false and InvoiceReference ne '' and`
),
};
@@ -699,13 +699,7 @@ describe('afis-facturen', async () => {
},
};
- remoteApi
- .get((uri) =>
- decodeURI(uri).includes(
- `IsCleared eq false and InvoiceReference ne '' and (AccountingDocumentType eq 'AB')`
- )
- )
- .reply(200, deelbetalingenResponse);
+ remoteApi.get(ROUTES.deelbetalingen).reply(200, deelbetalingenResponse);
const params: AfisFacturenParams = {
state: 'deelbetalingen',
diff --git a/src/server/services/afis/afis-facturen.ts b/src/server/services/afis/afis-facturen.ts
index 856ab1ec1f..1e8721f22c 100644
--- a/src/server/services/afis/afis-facturen.ts
+++ b/src/server/services/afis/afis-facturen.ts
@@ -56,7 +56,7 @@ const accountingDocumentTypesByState: Record<
open: ['DR', 'DG', 'DM', 'DE', 'DF', 'DV', 'DW'],
afgehandeld: ['DR', 'DE', 'DM', 'DV', 'DG', 'DF', 'DM', 'DW'],
overgedragen: ['DR', 'DE', 'DM', 'DV', 'DG', 'DF', 'DM', 'DW'],
- deelbetalingen: ['AB'],
+ deelbetalingen: ['AB', 'BA'],
};
const select = `$select=IsCleared,ReverseDocument,Paylink,PostingDate,ProfitCenterName,DocumentReferenceID,AccountingDocument,AmountInBalanceTransacCrcy,NetDueDate,DunningLevel,DunningBlockingReason,SEPAMandate,ClearingDate,PaymentMethod`;
diff --git a/src/server/services/cms-content.ts b/src/server/services/cms-content.ts
index 530ef4c4eb..912819cfc6 100644
--- a/src/server/services/cms-content.ts
+++ b/src/server/services/cms-content.ts
@@ -15,7 +15,7 @@ import {
} from '../../universal/helpers/api';
import { hash } from '../../universal/helpers/utils';
import { LinkProps } from '../../universal/types/App.types';
-import { DataRequestConfig } from '../config/source-api';
+import { isValidProfileType } from '../auth/auth-helpers';
import FileCache from '../helpers/file-cache';
import { getApiConfig } from '../helpers/source-api-helpers';
import { requestData } from '../helpers/source-api-request';
@@ -211,14 +211,14 @@ async function getGeneralPage(
sanitizeCmsContent(responseData.applicatie.inhoud.tekst),
};
},
+ formatUrl({ url }) {
+ return profileType === 'commercial'
+ ? `${url}/overzicht-producten-ondernemers/?AppIdt=app-data`
+ : `${url}/ziet-amsterdam/?AppIdt=app-data`;
+ },
});
- const requestConfigFinal: DataRequestConfig = {
- ...requestConfig,
- url: requestConfig.urls![profileType],
- };
-
- return requestData(requestConfigFinal, requestID).then(
+ return requestData(requestConfig, requestID).then(
(apiData) => {
if (
apiData.status === 'OK' &&
@@ -285,11 +285,14 @@ async function fetchCmsBase(
requestID: RequestID,
query?: QueryParamsCMSFooter
) {
- const forceRenew = !!(query?.forceRenew === 'true');
-
+ const forceRenew = query?.forceRenew === 'true';
+ const profileType =
+ query?.profileType && isValidProfileType(query?.profileType)
+ ? query.profileType
+ : undefined;
const generalInfoPageRequest = getGeneralPage(
requestID,
- query?.profileType as ProfileType,
+ profileType,
forceRenew
);
@@ -310,10 +313,10 @@ async function fetchCmsBase(
};
}
-export interface QueryParamsCMSFooter extends Record {
- forceRenew: 'true';
- profileType: ProfileType;
-}
+export type QueryParamsCMSFooter = {
+ forceRenew?: 'true';
+ profileType?: ProfileType;
+};
export async function fetchCmsFooter(
requestID: RequestID,
diff --git a/src/server/services/controller.test.ts b/src/server/services/controller.test.ts
index 2da3e1362c..6a8661f148 100644
--- a/src/server/services/controller.test.ts
+++ b/src/server/services/controller.test.ts
@@ -9,13 +9,19 @@ import {
vi,
} from 'vitest';
+import { fetchCMSCONTENT } from './cms-content';
import {
addServiceResultHandler,
+ forTesting,
getServiceResultsForTips,
getTipNotifications,
servicesTipsByProfileType,
} from './controller';
-import { getReqMockWithOidc, ResponseMock } from '../../testing/utils';
+import {
+ getReqMockWithOidc,
+ RequestMock,
+ ResponseMock,
+} from '../../testing/utils';
const mocks = vi.hoisted(() => {
return {
@@ -35,6 +41,12 @@ const mocks = vi.hoisted(() => {
};
});
+vi.mock('./cms-content', () => {
+ return {
+ fetchCMSCONTENT: vi.fn(),
+ };
+});
+
vi.mock('./tips-and-notifications', async () => {
return {
getTipsAndNotificationsFromApiResults: vi.fn(),
@@ -192,3 +204,57 @@ describe('controller', () => {
expect(result).toEqual(data);
});
});
+
+describe('request handlers', () => {
+ describe('CMS_CONTENT', async () => {
+ const reqID = 'xx-req-id-yy';
+
+ test('profileType: private', async () => {
+ const reqMock = await getReqMockWithOidc({
+ sid: 'x123y',
+ authMethod: 'digid',
+ profileType: 'private',
+ id: '9988',
+ });
+
+ await forTesting.CMS_CONTENT(reqID, reqMock);
+
+ expect(fetchCMSCONTENT).toHaveBeenCalledWith(reqID, {
+ profileType: 'private',
+ });
+ });
+
+ test('profileType: commercial', async () => {
+ const reqMock = await getReqMockWithOidc({
+ sid: 'x123y',
+ authMethod: 'eherkenning',
+ profileType: 'commercial',
+ id: '9988',
+ });
+
+ await forTesting.CMS_CONTENT(reqID, reqMock);
+
+ expect(fetchCMSCONTENT).toHaveBeenCalledWith(reqID, {
+ profileType: 'commercial',
+ });
+ });
+
+ test('arbitrary query params are passed', async () => {
+ const reqMock = await getReqMockWithOidc({
+ sid: 'x123y',
+ authMethod: 'eherkenning',
+ profileType: 'commercial',
+ id: '9988',
+ });
+
+ (reqMock as unknown as RequestMock).setQuery({ forceRenew: 'true' });
+
+ await forTesting.CMS_CONTENT(reqID, reqMock);
+
+ expect(fetchCMSCONTENT).toHaveBeenCalledWith(reqID, {
+ profileType: 'commercial',
+ forceRenew: 'true',
+ });
+ });
+ });
+});
diff --git a/src/server/services/controller.ts b/src/server/services/controller.ts
index 8bc047d20d..4931c5c4db 100644
--- a/src/server/services/controller.ts
+++ b/src/server/services/controller.ts
@@ -117,7 +117,13 @@ export function addServiceResultHandler(
* The service methods
*/
// Public services
-const CMS_CONTENT = callPublicService(fetchCMSCONTENT);
+const CMS_CONTENT = (requestID: RequestID, req: Request) => {
+ const auth = getAuth(req);
+ return fetchCMSCONTENT(requestID, {
+ profileType: auth?.profile.profileType,
+ ...queryParams(req),
+ });
+};
const CMS_MAINTENANCE_NOTIFICATIONS = callPublicService(
fetchMaintenanceNotificationsActual
);
@@ -485,3 +491,7 @@ export async function getTipNotifications(
return [];
}
+
+export const forTesting = {
+ CMS_CONTENT,
+};
diff --git a/src/universal/types/App.types.ts b/src/universal/types/App.types.ts
index 31a6b7015b..910c3f5182 100644
--- a/src/universal/types/App.types.ts
+++ b/src/universal/types/App.types.ts
@@ -122,3 +122,16 @@ export interface Match {
path: string;
url: string;
}
+
+export const PROFILE_TYPES = [
+ 'private',
+ 'commercial',
+ 'private-attributes',
+] as const;
+
+export const AUTH_METHODS = ['eherkenning', 'digid'] as const;
+
+declare global {
+ type ProfileType = (typeof PROFILE_TYPES)[number];
+ type AuthMethod = (typeof AUTH_METHODS)[number];
+}