From 42ccda94010ceac2ba117207aad4d2c30b08cec2 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Mon, 13 May 2024 09:56:21 +0300 Subject: [PATCH 01/46] Code freeze 1.5.0 release --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf246efc..165b5bc89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [1.5.0] + ## [1.3.4](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.3.3...v1.3.4) ## Breaking changes From 0bf5fdaf1b959983fc334b433aea2ce2ef4fbdfa Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Fri, 10 Nov 2023 10:47:07 +0200 Subject: [PATCH 02/46] Update auth to go through gateway (#781) --- src/client-config.js | 2 +- src/client-config.prod.js | 2 +- src/login-config.js | 2 +- src/login-config.prod.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client-config.js b/src/client-config.js index 07a25f243..5f88b3aa3 100644 --- a/src/client-config.js +++ b/src/client-config.js @@ -12,7 +12,7 @@ window.config = { API_GATEWAY_URL: 'http://localhost:7070/', CONFIG_API_URL: 'http://localhost:2021', LOGIN_URL: 'http://localhost:3020', - AUTH_URL: 'http://localhost:4040', + AUTH_URL: 'http://localhost:7070/auth', MINIO_BUCKET: 'ocrvs', COUNTRY_CONFIG_URL: 'http://localhost:3040', // Country code in uppercase ALPHA-3 format diff --git a/src/client-config.prod.js b/src/client-config.prod.js index 027562a2c..06793a82d 100644 --- a/src/client-config.prod.js +++ b/src/client-config.prod.js @@ -12,7 +12,7 @@ window.config = { API_GATEWAY_URL: 'https://gateway.{{hostname}}/', CONFIG_API_URL: 'https://config.{{hostname}}', LOGIN_URL: 'https://login.{{hostname}}', - AUTH_URL: 'https://auth.{{hostname}}', + AUTH_URL: 'https://gateway.{{hostname}}/auth', MINIO_BUCKET: 'ocrvs', COUNTRY_CONFIG_URL: 'https://countryconfig.{{hostname}}', // Country code in uppercase ALPHA-3 format diff --git a/src/login-config.js b/src/login-config.js index 3f358c39d..19ba245ef 100644 --- a/src/login-config.js +++ b/src/login-config.js @@ -9,7 +9,7 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ window.config = { - AUTH_API_URL: 'http://localhost:4040/', + AUTH_API_URL: 'http://localhost:7070/auth/', CONFIG_API_URL: 'http://localhost:2021', // Country code in uppercase ALPHA-3 format COUNTRY: 'FAR', diff --git a/src/login-config.prod.js b/src/login-config.prod.js index a54303731..31fbed749 100644 --- a/src/login-config.prod.js +++ b/src/login-config.prod.js @@ -9,7 +9,7 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ window.config = { - AUTH_API_URL: 'https://auth.{{hostname}}/', + AUTH_API_URL: 'https://gateway.{{hostname}}/auth/', CONFIG_API_URL: 'https://config.{{hostname}}', // Country code in uppercase ALPHA-3 format COUNTRY: 'FAR', From 103f4a3a95754584735d6141e296b5e99df1500a Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Mon, 13 Nov 2023 21:50:29 +0200 Subject: [PATCH 03/46] fix auth api urls --- src/client-config.js | 2 +- src/client-config.prod.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client-config.js b/src/client-config.js index 5f88b3aa3..9fb0049e5 100644 --- a/src/client-config.js +++ b/src/client-config.js @@ -12,7 +12,7 @@ window.config = { API_GATEWAY_URL: 'http://localhost:7070/', CONFIG_API_URL: 'http://localhost:2021', LOGIN_URL: 'http://localhost:3020', - AUTH_URL: 'http://localhost:7070/auth', + AUTH_URL: 'http://localhost:7070/auth/', MINIO_BUCKET: 'ocrvs', COUNTRY_CONFIG_URL: 'http://localhost:3040', // Country code in uppercase ALPHA-3 format diff --git a/src/client-config.prod.js b/src/client-config.prod.js index 06793a82d..71445a4cf 100644 --- a/src/client-config.prod.js +++ b/src/client-config.prod.js @@ -12,7 +12,7 @@ window.config = { API_GATEWAY_URL: 'https://gateway.{{hostname}}/', CONFIG_API_URL: 'https://config.{{hostname}}', LOGIN_URL: 'https://login.{{hostname}}', - AUTH_URL: 'https://gateway.{{hostname}}/auth', + AUTH_URL: 'https://gateway.{{hostname}}/auth/', MINIO_BUCKET: 'ocrvs', COUNTRY_CONFIG_URL: 'https://countryconfig.{{hostname}}', // Country code in uppercase ALPHA-3 format From 6055c5138fccb5992d42ec38d8bbbcc5a6a86548 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Tue, 14 Nov 2023 17:40:27 +0200 Subject: [PATCH 04/46] add mongo url for search service --- infrastructure/docker-compose.deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 6ffd83733..a00826135 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -751,6 +751,7 @@ services: - ES_HOST=search-user:${ROTATING_SEARCH_ELASTIC_PASSWORD}@elasticsearch:9200 - APN_SERVICE_URL=http://apm-server:8200 - CERT_PUBLIC_KEY_PATH=/run/secrets/jwt-public-key.{{ts}} + - HEARTH_MONGO_URL=mongodb://hearth:${HEARTH_MONGODB_PASSWORD}@mongo1/hearth-dev?replicaSet=rs0 deploy: labels: - 'traefik.enable=false' From 79acc30348ea3cda45f0fe771a753d34e8122341 Mon Sep 17 00:00:00 2001 From: "Md. Ashikul Alam" <32668488+Nil20@users.noreply.github.com> Date: Tue, 28 Nov 2023 20:29:17 +0600 Subject: [PATCH 05/46] refactor: keep individual certificate endpoint only in dev --- src/data-seeding/certificates/handler.ts | 9 ++++++++- src/index.ts | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/data-seeding/certificates/handler.ts b/src/data-seeding/certificates/handler.ts index 1aa2fbf74..57ee2ed35 100644 --- a/src/data-seeding/certificates/handler.ts +++ b/src/data-seeding/certificates/handler.ts @@ -12,7 +12,14 @@ import { Request, ResponseToolkit } from '@hapi/hapi' import { readFileSync } from 'fs' -export async function certificateHandler(_: Request, h: ResponseToolkit) { +export async function certificateHandler(request: Request, h: ResponseToolkit) { + if (request.params.event) { + const res = readFileSync( + `./src/data-seeding/certificates/source/Farajaland-${request.params.event}-certificate-v2.svg` + ).toString() + return h.response(res).code(200) + } + const Certificates = [ { event: 'birth', diff --git a/src/index.ts b/src/index.ts index 09d0badd8..87e9e2602 100644 --- a/src/index.ts +++ b/src/index.ts @@ -237,13 +237,19 @@ export async function createServer() { server.auth.default('jwt') + if (process.env.NODE_ENV !== 'production') { + server.route({ + method: 'GET', + path: '/certificates/{event}.svg', + handler: certificateHandler, + options: { + auth: false, + tags: ['api', 'certificates'], + description: 'Returns only one certificate metadata' + } + }) + } // add ping route by default for health check - server.route({ - method: 'GET', - path: '/certificates/{event}.svg', - handler: certificateHandler - }) - server.route({ method: 'GET', path: '/ping', From da9e19e85a5cdf4b233aaf167173af364cea295b Mon Sep 17 00:00:00 2001 From: "Md. Ashikul Alam" <32668488+Nil20@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:45:09 +0600 Subject: [PATCH 06/46] return composition id confirm registration endpoint in core (#797) --- src/api/event-registration/handler.ts | 2 +- src/api/event-registration/service.ts | 2 ++ src/utils/index.ts | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/api/event-registration/handler.ts b/src/api/event-registration/handler.ts index 28a273cf3..f317482ec 100644 --- a/src/api/event-registration/handler.ts +++ b/src/api/event-registration/handler.ts @@ -35,7 +35,7 @@ export async function eventRegistrationHandler( const eventRegistrationIdentifiersResponse = await createUniqueRegistrationNumberFromBundle(bundle) - fetch(CONFIRM_REGISTRATION_URL, { + await fetch(CONFIRM_REGISTRATION_URL, { method: 'POST', body: JSON.stringify(eventRegistrationIdentifiersResponse), headers: request.headers diff --git a/src/api/event-registration/service.ts b/src/api/event-registration/service.ts index 03c5b4ed2..cab3a207f 100644 --- a/src/api/event-registration/service.ts +++ b/src/api/event-registration/service.ts @@ -9,6 +9,7 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ import { + getCompositionId, getTaskResource, getTrackingIdFromTaskResource } from '@countryconfig/utils' @@ -39,6 +40,7 @@ export async function createUniqueRegistrationNumberFromBundle( return { trackingId, registrationNumber: generateRegistrationNumber(trackingId), + compositionId: getCompositionId(bundle), ...(taskResource.code?.coding?.[0].code === 'BIRTH' && { // Some countries desire to create multiple identifiers for citizens at the point of birth registration using external systems. // OpenCRVS supports up to 3 additional, custom identifiers that can be created diff --git a/src/utils/index.ts b/src/utils/index.ts index ce6083b10..04bb740b6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -64,6 +64,12 @@ export interface IApplicationConfigResponse { config: IApplicationConfig } +export function getCompositionId(resBody: fhir.Bundle) { + return resBody.entry + ?.map((e) => e.resource) + .find((res) => res?.resourceType === 'Composition')?.id +} + export function getTaskResource( bundle: fhir.Bundle & fhir.BundleEntry ): fhir.Task | undefined { From 712a3e03943d5b44cd2cc438a5b6eb7d70e3393f Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Tue, 12 Dec 2023 14:54:36 +0600 Subject: [PATCH 07/46] Add feature flags in application default config (#806) --- src/api/application/application-config-default.ts | 10 +++++++--- src/client-config.js | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/api/application/application-config-default.ts b/src/api/application/application-config-default.ts index c50eac78d..28db8ebf4 100644 --- a/src/api/application/application-config-default.ts +++ b/src/api/application/application-config-default.ts @@ -41,10 +41,14 @@ export const defaultApplicationConfig = { // Following constants aren't configurable via UI FIELD_AGENT_AUDIT_LOCATIONS: 'DISTRICT', DECLARATION_AUDIT_LOCATIONS: 'DISTRICT', - EXTERNAL_VALIDATION_WORKQUEUE: false, - MARRIAGE_REGISTRATION: false, + FEATURES: { + DEATH_REGISTRATION: true, + MARRIAGE_REGISTRATION: false, + EXTERNAL_VALIDATION_WORKQUEUE: false, + INFORMANT_SIGNATURE: false, + PRINT_DECLARATION: false + }, DATE_OF_BIRTH_UNKNOWN: true, - INFORMANT_SIGNATURE: false, INFORMANT_SIGNATURE_REQUIRED: false, USER_NOTIFICATION_DELIVERY_METHOD: 'email', // or 'sms', or '' ... You can use 'sms' for WhatsApp INFORMANT_NOTIFICATION_DELIVERY_METHOD: 'email', // or 'sms', or '' ... You can use 'sms' for WhatsApp diff --git a/src/client-config.js b/src/client-config.js index 9fb0049e5..e8d079f02 100644 --- a/src/client-config.js +++ b/src/client-config.js @@ -26,5 +26,6 @@ window.config = { // http://localhost:4444/public/dashboard/fec78656-e4f9-4b51-b540-0fed81dbd821#bordered=false&titled=false&refresh=300 REGISTRATIONS_DASHBOARD_URL: '', // http://localhost:4444/public/dashboard/a17e9bc0-15a2-4bd1-92fa-ab0f346227ca#bordered=false&titled=false&refresh=300 - STATISTICS_DASHBOARD_URL: '' + STATISTICS_DASHBOARD_URL: '', + FEATURES: {} } From 284c4a88b653c5347eeecc3ba78fda408f603a75 Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Fri, 15 Dec 2023 09:48:53 +0200 Subject: [PATCH 08/46] chore: remove logrocket references (#811) --- src/client-config.js | 1 - src/client-config.prod.js | 1 - src/login-config.js | 3 +-- src/login-config.prod.js | 3 +-- src/utils/index.ts | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/client-config.js b/src/client-config.js index e8d079f02..19aa2a2d5 100644 --- a/src/client-config.js +++ b/src/client-config.js @@ -19,7 +19,6 @@ window.config = { COUNTRY: 'FAR', LANGUAGES: 'en,fr', SENTRY: '', - LOGROCKET: '', // Use the values in comments when Metabase is running locally // http://localhost:4444/public/dashboard/acae0527-74be-4804-a3ee-f8b3c9c8784c#bordered=false&titled=false&refresh=300 LEADERBOARDS_DASHBOARD_URL: '', diff --git a/src/client-config.prod.js b/src/client-config.prod.js index 71445a4cf..743075368 100644 --- a/src/client-config.prod.js +++ b/src/client-config.prod.js @@ -19,7 +19,6 @@ window.config = { COUNTRY: 'FAR', LANGUAGES: 'en,fr', SENTRY: '{{sentry}}', - LOGROCKET: '', LEADERBOARDS_DASHBOARD_URL: 'https://metabase.{{hostname}}/public/dashboard/acae0527-74be-4804-a3ee-f8b3c9c8784c#bordered=false&titled=false&refresh=300', REGISTRATIONS_DASHBOARD_URL: diff --git a/src/login-config.js b/src/login-config.js index 19ba245ef..964444ddf 100644 --- a/src/login-config.js +++ b/src/login-config.js @@ -16,6 +16,5 @@ window.config = { LANGUAGES: 'en,fr', CLIENT_APP_URL: 'http://localhost:3000/', COUNTRY_CONFIG_URL: 'http://localhost:3040', - SENTRY: '', - LOGROCKET: '' + SENTRY: '' } diff --git a/src/login-config.prod.js b/src/login-config.prod.js index 31fbed749..11e835324 100644 --- a/src/login-config.prod.js +++ b/src/login-config.prod.js @@ -16,6 +16,5 @@ window.config = { LANGUAGES: 'en,fr', CLIENT_APP_URL: 'https://register.{{hostname}}/', COUNTRY_CONFIG_URL: 'https://countryconfig.{{hostname}}', - SENTRY: '{{sentry}}', - LOGROCKET: '' + SENTRY: '{{sentry}}' } diff --git a/src/utils/index.ts b/src/utils/index.ts index 04bb740b6..1b720b487 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -55,7 +55,6 @@ export interface IApplicationConfig { COUNTRY: string COUNTRY_LOGO: ICountryLogo SENTRY: string - LOGROCKET: string LOGIN_BACKGROUND: ILoginBackground USER_NOTIFICATION_DELIVERY_METHOD: string INFORMANT_NOTIFICATION_DELIVERY_METHOD: string From 14e27064ed8202ff3b29f971c89dc8936cee4de3 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Fri, 1 Mar 2024 14:38:35 +0600 Subject: [PATCH 09/46] feature flag hotfix for client config prod --- src/client-config.prod.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client-config.prod.js b/src/client-config.prod.js index 743075368..7b5e597bf 100644 --- a/src/client-config.prod.js +++ b/src/client-config.prod.js @@ -24,5 +24,6 @@ window.config = { REGISTRATIONS_DASHBOARD_URL: 'https://metabase.{{hostname}}/public/dashboard/fec78656-e4f9-4b51-b540-0fed81dbd821#bordered=false&titled=false&refresh=300', STATISTICS_DASHBOARD_URL: - 'https://metabase.{{hostname}}/public/dashboard/a17e9bc0-15a2-4bd1-92fa-ab0f346227ca#bordered=false&titled=false&refresh=300' + 'https://metabase.{{hostname}}/public/dashboard/a17e9bc0-15a2-4bd1-92fa-ab0f346227ca#bordered=false&titled=false&refresh=300', + FEATURES: {} } From ecc7c902e7162d34e5e236d806ab6ecbe84363b1 Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:02:34 +0600 Subject: [PATCH 10/46] Upgrade node to 18 (#847) * Update node version in Dockerfile * Update node version in workflow yml files * Add os configuration option in package.json * Add engines configuration option in package.json * Feedback changes * Add node options NODE_OPTIONS dns-result-order `ipv4first` * Upgrade node version in workflow file * Remove matrix block --- .github/workflows/test.yml | 4 ++-- Dockerfile | 2 +- package.json | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f68640ad8..9f0c74dcc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,10 +10,10 @@ jobs: - name: Checking out git repo uses: actions/checkout@v2 - - name: Use Node.js 16.20 + - name: Use Node.js 18.19 uses: actions/setup-node@v2 with: - node-version: '16.20' + node-version: '18.19' cache: 'npm' - name: Runs dependency installation diff --git a/Dockerfile b/Dockerfile index 25c6d8aa6..189fd8192 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:gallium-alpine +FROM node:hydrogen-alpine WORKDIR /usr/src/app # Override the base log level (info). diff --git a/package.json b/package.json index 440e604e3..8bde6b003 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,13 @@ "name": "@opencrvs/countryconfig", "version": "1.4.1", "description": "OpenCRVS country configuration for reference data", + "os": [ + "darwin", + "linux" + ], + "engines": { + "node": "18.19.x" + }, "license": "MPL-2.0", "husky": { "hooks": { @@ -13,7 +20,8 @@ "precommit": "lint-staged", "test": "echo 'no tests, yet'", "test:compilation": "tsc --noEmit", - "start": "cross-env NODE_ENV=development nodemon --exec ts-node -r tsconfig-paths/register src/index.ts", + "lint": "eslint -c .eslintrc.js", + "start": "cross-env NODE_ENV=development NODE_OPTIONS=--dns-result-order=ipv4first nodemon --exec ts-node -r tsconfig-paths/register src/index.ts", "start:prod": "ts-node --transpile-only -r tsconfig-paths/register src/index.ts", "deploy": "bash infrastructure/deployment/deploy.sh", "restore-snapshot": "bash infrastructure/backups/restore-snapshot.sh", From d32b6b35f46e9c4f8ba7ffd8529625da76495775 Mon Sep 17 00:00:00 2001 From: "Md. Ashikul Alam" <32668488+Nil20@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:14:13 +0600 Subject: [PATCH 11/46] (state-transitions): remove openhim url and modify confirm registration url (#932) * Remove openhim url and modify confirm registration url * Remove openhim core and openhim console services --- infrastructure/docker-compose.deploy.yml | 51 ------------------- .../docker-compose.development-deploy.yml | 3 +- .../docker-compose.production-deploy.yml | 15 +----- infrastructure/docker-compose.qa-deploy.yml | 3 +- .../docker-compose.staging-deploy.yml | 14 +---- src/constants.ts | 4 +- 6 files changed, 5 insertions(+), 85 deletions(-) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index a00826135..b9cc06433 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -908,57 +908,6 @@ services: options: gelf-address: 'udp://127.0.0.1:12201' tag: 'hearth' - openhim-core: - environment: - - mongo_url=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1/openhim-dev?replicaSet=rs0 - - mongo_atnaUrl=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1/openhim-dev?replicaSet=rs0 - deploy: - labels: - - 'traefik.enable=false' - - # To expose OpenHIM to external systems as an interoperability layer - # Given OpenCRVS has it's own integrations UI and control. This functionality is currently disabled - # If you wish to reopen OpenHIM interoperability, you can enable these lines - # Any subdomain labelled "api" is regularly spammed and can lead to DDOS attack - # Care must be taken to whitelist any access to this endpoint - - # - 'traefik.http.routers.api.rule=Host(`api.{{hostname}}`)' - # - 'traefik.http.routers.api.tls=true' - # - 'traefik.http.routers.api.service=api' - # - 'traefik.http.routers.api.tls.certresolver=certResolver' - # - 'traefik.http.routers.api.entrypoints=web,websecure' - # - 'traefik.http.services.api.loadbalancer.server.port=5001' - # - 'traefik.http.middlewares.openhimapi-whitelist.ipwhitelist.sourcerange=' - # - "traefik.http.routers.openhimapi.middlewares=openhimapi-whitelist@docker" - - - 'traefik.docker.network=opencrvs_overlay_net' - - - 'traefik.http.middlewares.openhimcore.headers.stsseconds=31536000' - - 'traefik.http.middlewares.openhimcore.headers.stsincludesubdomains=true' - - 'traefik.http.middlewares.openhimcore.headers.stspreload=true' - replicas: 1 - networks: - - overlay_net - logging: - driver: gelf - options: - gelf-address: 'udp://127.0.0.1:12201' - tag: 'openhim-core' - openhim-console: - deploy: - labels: - - 'traefik.enable=false' - replicas: 1 - configs: - - source: openhim-console-conf.{{ts}} - target: /usr/share/nginx/html/config/default.json - networks: - - overlay_net - logging: - driver: gelf - options: - gelf-address: 'udp://127.0.0.1:12201' - tag: 'openhim-console' webhooks: secrets: diff --git a/infrastructure/docker-compose.development-deploy.yml b/infrastructure/docker-compose.development-deploy.yml index 276f4f8c5..c0c33dcb5 100644 --- a/infrastructure/docker-compose.development-deploy.yml +++ b/infrastructure/docker-compose.development-deploy.yml @@ -19,8 +19,7 @@ services: - FHIR_URL=http://hearth:3447/fhir - AUTH_URL=http://auth:4040 - APPLICATION_CONFIG_URL=http://config:2021 - - OPENHIM_URL=http://openhim-core:5001/fhir - - CONFIRM_REGISTRATION_URL=http://openhim-core:5001/confirm/registration + - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 - SENTRY_DSN=${SENTRY_DSN} diff --git a/infrastructure/docker-compose.production-deploy.yml b/infrastructure/docker-compose.production-deploy.yml index 0e9130b2a..5847dbfbe 100644 --- a/infrastructure/docker-compose.production-deploy.yml +++ b/infrastructure/docker-compose.production-deploy.yml @@ -103,8 +103,7 @@ services: - FHIR_URL=http://hearth:3447/fhir - AUTH_URL=http://auth:4040 - APPLICATION_CONFIG_URL=http://config:2021 - - OPENHIM_URL=http://openhim-core:5001/fhir - - CONFIRM_REGISTRATION_URL=http://openhim-core:5001/confirm/registration + - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - SENTRY_DSN=${SENTRY_DSN} - SENDER_EMAIL_ADDRESS=${SENDER_EMAIL_ADDRESS} @@ -155,18 +154,6 @@ services: - OPENHIM_MONGO_URL=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1,mongo2/openhim-dev?replicaSet=rs0 - WAIT_HOSTS=mongo1:27017,mongo2:27017,influxdb:8086,minio:9000,elasticsearch:9200 - openhim-core: - environment: - - mongo_url=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1,mongo2/openhim-dev?replicaSet=rs0 - - mongo_atnaUrl=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1,mongo2/openhim-dev?replicaSet=rs0 - - deploy: - replicas: 2 - - openhim-console: - deploy: - replicas: 2 - mongo2: image: mongo:4.4 hostname: 'mongo2' diff --git a/infrastructure/docker-compose.qa-deploy.yml b/infrastructure/docker-compose.qa-deploy.yml index 541ceb731..1af4b0c2f 100644 --- a/infrastructure/docker-compose.qa-deploy.yml +++ b/infrastructure/docker-compose.qa-deploy.yml @@ -49,8 +49,7 @@ services: - FHIR_URL=http://hearth:3447/fhir - AUTH_URL=http://auth:4040 - APPLICATION_CONFIG_URL=http://config:2021 - - OPENHIM_URL=http://openhim-core:5001/fhir - - CONFIRM_REGISTRATION_URL=http://openhim-core:5001/confirm/registration + - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 - SENTRY_DSN=${SENTRY_DSN} diff --git a/infrastructure/docker-compose.staging-deploy.yml b/infrastructure/docker-compose.staging-deploy.yml index f108abf50..04810edb2 100644 --- a/infrastructure/docker-compose.staging-deploy.yml +++ b/infrastructure/docker-compose.staging-deploy.yml @@ -103,8 +103,7 @@ services: - FHIR_URL=http://hearth:3447/fhir - AUTH_URL=http://auth:4040 - APPLICATION_CONFIG_URL=http://config:2021 - - OPENHIM_URL=http://openhim-core:5001/fhir - - CONFIRM_REGISTRATION_URL=http://openhim-core:5001/confirm/registration + - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - SENTRY_DSN=${SENTRY_DSN} - SENDER_EMAIL_ADDRESS=${SENDER_EMAIL_ADDRESS} @@ -154,17 +153,6 @@ services: - OPENHIM_MONGO_URL=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1/openhim-dev?replicaSet=rs0 - WAIT_HOSTS=mongo1:27017,influxdb:8086,minio:9000,elasticsearch:9200 - openhim-core: - environment: - - mongo_url=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1/openhim-dev?replicaSet=rs0 - - mongo_atnaUrl=mongodb://openhim:${OPENHIM_MONGODB_PASSWORD}@mongo1/openhim-dev?replicaSet=rs0 - deploy: - replicas: 1 - - openhim-console: - deploy: - replicas: 1 - mongo-on-update: environment: - REPLICAS=1 diff --git a/src/constants.ts b/src/constants.ts index ed24d917f..0ff671d46 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,8 +14,6 @@ export const LOGIN_URL = process.env.LOGIN_URL || 'http://localhost:3020/' export const CLIENT_APP_URL = process.env.CLIENT_APP_URL || 'http://localhost:3000/' export const FHIR_URL = process.env.FHIR_URL || 'http://localhost:3447/fhir' -export const OPENHIM_URL = - process.env.OPENHIM_URL || 'http://localhost:5001/fhir' export const ORG_URL = 'http://opencrvs.org' export const COUNTRY_CONFIG_HOST = process.env.COUNTRY_CONFIG_HOST || '0.0.0.0' export const COUNTRY_CONFIG_PORT = process.env.COUNTRY_CONFIG_PORT || 3040 @@ -30,7 +28,7 @@ export const SENTRY_DSN = process.env.SENTRY_DSN export const CHECK_INVALID_TOKEN = process.env.CHECK_INVALID_TOKEN || 'false' export const CONFIRM_REGISTRATION_URL = process.env.CONFIRM_REGISTRATION_URL || - 'http://localhost:5001/confirm/registration' + 'http://localhost:5050/confirm/registration' export const DEFAULT_TIMEOUT = 600000 export const PRODUCTION = process.env.NODE_ENV === 'production' export const QA_ENV = process.env.QA_ENV || false From 92c97dfae27cdd14586ca4bfe2bdcf785e001e11 Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Wed, 27 Mar 2024 12:24:05 +0200 Subject: [PATCH 12/46] feat: enable gzip compression (#947) * feat: enable gzip compression * enable gzip for gateway and login --- infrastructure/docker-compose.deploy.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index b9cc06433..5089542ad 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -49,7 +49,7 @@ services: replicas: 1 labels: - 'traefik.http.services.traefik.loadbalancer.server.port=80' - - 'traefik.http.middlewares.test-compress.compress=true' + - 'traefik.http.middlewares.gzip-compression.compress=true' placement: constraints: - node.role == manager @@ -497,7 +497,7 @@ services: labels: - 'traefik.enable=true' - 'traefik.http.routers.client.rule=Host(`register.{{hostname}}`,`{{hostname}}`)' - - 'traefik.http.routers.client.middlewares=test-replacepathregex' + - 'traefik.http.routers.client.middlewares=test-replacepathregex,gzip-compression' - 'traefik.http.routers.client.service=client' - 'traefik.http.services.client.loadbalancer.server.port=80' - 'traefik.http.routers.client.tls=true' @@ -558,6 +558,7 @@ services: - 'traefik.http.routers.countryconfig.tls=true' - 'traefik.http.routers.countryconfig.tls.certresolver=certResolver' - 'traefik.http.routers.countryconfig.entrypoints=web,websecure' + - 'traefik.http.routers.countryconfig.middlewares=gzip-compression' - 'traefik.docker.network=opencrvs_overlay_net' - 'traefik.http.middlewares.countryconfig.headers.customresponseheaders.Pragma=no-cache' - 'traefik.http.middlewares.countryconfig.headers.customresponseheaders.Cache-control=no-store' @@ -608,6 +609,7 @@ services: - 'traefik.http.routers.login.tls=true' - 'traefik.http.routers.login.tls.certresolver=certResolver' - 'traefik.http.routers.login.entrypoints=web,websecure' + - 'traefik.http.routers.login.middlewares=gzip-compression' - 'traefik.docker.network=opencrvs_overlay_net' - 'traefik.http.middlewares.login.headers.customresponseheaders.Pragma=no-cache' - 'traefik.http.middlewares.login.headers.customresponseheaders.Cache-control=no-store' @@ -712,6 +714,7 @@ services: - 'traefik.http.routers.gateway.tls=true' - 'traefik.http.routers.gateway.tls.certresolver=certResolver' - 'traefik.http.routers.gateway.entrypoints=web,websecure' + - 'traefik.http.routers.gateway.middlewares=gzip-compression' - 'traefik.docker.network=opencrvs_overlay_net' - 'traefik.http.middlewares.gateway.headers.customresponseheaders.Pragma=no-cache' - 'traefik.http.middlewares.gateway.headers.customresponseheaders.Cache-control=no-store' From 13b8f8c44e42c2a1f07b1c0f71906c6098f0b104 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Tue, 2 Apr 2024 15:58:03 +0300 Subject: [PATCH 13/46] make SENTRY_DSN variable optional --- .../docker-compose.development-deploy.yml | 20 +++++++++---------- .../docker-compose.production-deploy.yml | 20 +++++++++---------- infrastructure/docker-compose.qa-deploy.yml | 20 +++++++++---------- .../docker-compose.staging-deploy.yml | 20 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/infrastructure/docker-compose.development-deploy.yml b/infrastructure/docker-compose.development-deploy.yml index c0c33dcb5..95c892336 100644 --- a/infrastructure/docker-compose.development-deploy.yml +++ b/infrastructure/docker-compose.development-deploy.yml @@ -4,7 +4,7 @@ services: notification: environment: - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production @@ -22,7 +22,7 @@ services: - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - SENDER_EMAIL_ADDRESS=${SENDER_EMAIL_ADDRESS} - ALERT_EMAIL=${ALERT_EMAIL} - SMTP_HOST=${SMTP_HOST} @@ -42,7 +42,7 @@ services: gateway: environment: - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - COUNTRY=FAR - QA_ENV=true - NODE_ENV=production @@ -50,43 +50,43 @@ services: workflow: environment: - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production search: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production metrics: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production auth: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production user-mgnt: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production webhooks: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production config: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production diff --git a/infrastructure/docker-compose.production-deploy.yml b/infrastructure/docker-compose.production-deploy.yml index 5847dbfbe..87ceda12e 100644 --- a/infrastructure/docker-compose.production-deploy.yml +++ b/infrastructure/docker-compose.production-deploy.yml @@ -18,7 +18,7 @@ services: environment: - NODE_ENV=production - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 2 @@ -26,21 +26,21 @@ services: environment: - NODE_ENV=production - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 2 search: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 2 metrics: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://metrics:${METRICS_MONGODB_PASSWORD}@mongo1,mongo2/metrics?replicaSet=rs0 - HEARTH_MONGO_URL=mongodb://hearth:${HEARTH_MONGODB_PASSWORD}@mongo1,mongo2/hearth-dev?replicaSet=rs0 - DASHBOARD_MONGO_URL=mongodb://performance:${PERFORMANCE_MONGODB_PASSWORD}@mongo1,mongo2/performance?replicaSet=rs0 @@ -48,14 +48,14 @@ services: auth: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 2 user-mgnt: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://user-mgnt:${USER_MGNT_MONGODB_PASSWORD}@mongo1,mongo2/user-mgnt?replicaSet=rs0 deploy: replicas: 2 @@ -64,14 +64,14 @@ services: environment: - NODE_ENV=production - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 2 webhooks: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://webhooks:${WEBHOOKS_MONGODB_PASSWORD}@mongo1,mongo2/webhooks?replicaSet=rs0 deploy: replicas: 2 @@ -79,7 +79,7 @@ services: config: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://config:${CONFIG_MONGODB_PASSWORD}@mongo1,mongo2/application-config?replicaSet=rs0 deploy: replicas: 2 @@ -105,7 +105,7 @@ services: - APPLICATION_CONFIG_URL=http://config:2021 - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - SENDER_EMAIL_ADDRESS=${SENDER_EMAIL_ADDRESS} - ALERT_EMAIL=${ALERT_EMAIL} - SMTP_HOST=${SMTP_HOST} diff --git a/infrastructure/docker-compose.qa-deploy.yml b/infrastructure/docker-compose.qa-deploy.yml index 1af4b0c2f..1b0f5976f 100644 --- a/infrastructure/docker-compose.qa-deploy.yml +++ b/infrastructure/docker-compose.qa-deploy.yml @@ -34,7 +34,7 @@ services: notification: environment: - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production @@ -52,7 +52,7 @@ services: - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 1 networks: @@ -65,7 +65,7 @@ services: gateway: environment: - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - COUNTRY=FAR - QA_ENV=true - NODE_ENV=production @@ -73,43 +73,43 @@ services: workflow: environment: - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production search: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production metrics: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production auth: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production user-mgnt: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production webhooks: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production config: environment: - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - QA_ENV=true - NODE_ENV=production diff --git a/infrastructure/docker-compose.staging-deploy.yml b/infrastructure/docker-compose.staging-deploy.yml index 04810edb2..726634829 100644 --- a/infrastructure/docker-compose.staging-deploy.yml +++ b/infrastructure/docker-compose.staging-deploy.yml @@ -18,7 +18,7 @@ services: environment: - NODE_ENV=production - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 1 @@ -26,21 +26,21 @@ services: environment: - NODE_ENV=production - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 1 search: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 1 metrics: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://metrics:${METRICS_MONGODB_PASSWORD}@mongo1/metrics?replicaSet=rs0 - HEARTH_MONGO_URL=mongodb://hearth:${HEARTH_MONGODB_PASSWORD}@mongo1/hearth-dev?replicaSet=rs0 - DASHBOARD_MONGO_URL=mongodb://performance:${PERFORMANCE_MONGODB_PASSWORD}@mongo1/performance?replicaSet=rs0 @@ -48,14 +48,14 @@ services: auth: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 1 user-mgnt: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://user-mgnt:${USER_MGNT_MONGODB_PASSWORD}@mongo1/user-mgnt?replicaSet=rs0 deploy: replicas: 1 @@ -64,14 +64,14 @@ services: environment: - NODE_ENV=production - LANGUAGES=en,fr - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} deploy: replicas: 1 webhooks: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://webhooks:${WEBHOOKS_MONGODB_PASSWORD}@mongo1/webhooks?replicaSet=rs0 deploy: replicas: 1 @@ -79,7 +79,7 @@ services: config: environment: - NODE_ENV=production - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - MONGO_URL=mongodb://config:${CONFIG_MONGODB_PASSWORD}@mongo1/application-config?replicaSet=rs0 deploy: replicas: 1 @@ -105,7 +105,7 @@ services: - APPLICATION_CONFIG_URL=http://config:2021 - CONFIRM_REGISTRATION_URL=http://workflow:5050/confirm/registration - CHECK_INVALID_TOKEN=true - - SENTRY_DSN=${SENTRY_DSN} + - SENTRY_DSN=${SENTRY_DSN:-""} - SENDER_EMAIL_ADDRESS=${SENDER_EMAIL_ADDRESS} - ALERT_EMAIL=${ALERT_EMAIL} - SMTP_HOST=${SMTP_HOST} From 4838294bf1b626be8982705d0792ccf204204574 Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:39:37 +0600 Subject: [PATCH 14/46] fix: support node versions 18.x.x (#955) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8bde6b003..80c3e5063 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "linux" ], "engines": { - "node": "18.19.x" + "node": "18.x.x" }, "license": "MPL-2.0", "husky": { @@ -124,4 +124,4 @@ "minimist": "^1.2.2", "acorn": "^6.4.1" } -} +} \ No newline at end of file From bc2c05d404bdf7e191109389a975962bae2bbe32 Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:42:32 +0600 Subject: [PATCH 15/46] build: use docker compose v2 in github workflow files (#956) --- build-and-push.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-and-push.sh b/build-and-push.sh index 59ab4607c..ea7e45b02 100755 --- a/build-and-push.sh +++ b/build-and-push.sh @@ -9,5 +9,5 @@ # Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. set -e -docker-compose build -docker-compose push +docker compose build +docker compose push From ed06831235a52bfd71d9dd7cae04920dad8c2d99 Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:29:44 +0600 Subject: [PATCH 16/46] Merge pull request #937 from opencrvs/ocrvs-6410-mass-email-users --- infrastructure/deployment/deploy.sh | 1 + infrastructure/docker-compose.deploy.yml | 6 ++- .../docker-compose.production-deploy.yml | 1 + .../docker-compose.staging-deploy.yml | 1 + infrastructure/mongodb/on-deploy.sh | 22 ++++++++ src/api/notification/email-service.ts | 16 ++++-- src/api/notification/email-templates/index.ts | 12 +++++ .../other/all-user-notification.html | 53 +++++++++++++++++++ src/api/notification/handler.ts | 17 +++--- src/index.ts | 1 + src/translations/client.csv | 9 ++++ 11 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 src/api/notification/email-templates/other/all-user-notification.html diff --git a/infrastructure/deployment/deploy.sh b/infrastructure/deployment/deploy.sh index d87e18192..0838d8c8f 100755 --- a/infrastructure/deployment/deploy.sh +++ b/infrastructure/deployment/deploy.sh @@ -307,6 +307,7 @@ export METRICS_MONGODB_PASSWORD=`generate_password` export PERFORMANCE_MONGODB_PASSWORD=`generate_password` export OPENHIM_MONGODB_PASSWORD=`generate_password` export WEBHOOKS_MONGODB_PASSWORD=`generate_password` +export NOTIFICATION_MONGODB_PASSWORD=`generate_password` # # Elasticsearch credentials diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 5089542ad..e58f2f1f4 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -241,6 +241,7 @@ services: - METRICS_MONGODB_PASSWORD=${METRICS_MONGODB_PASSWORD} - OPENHIM_MONGODB_PASSWORD=${OPENHIM_MONGODB_PASSWORD} - WEBHOOKS_MONGODB_PASSWORD=${WEBHOOKS_MONGODB_PASSWORD} + - NOTIFICATION_MONGODB_PASSWORD=${NOTIFICATION_MONGODB_PASSWORD} networks: - overlay_net logging: @@ -553,7 +554,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`)' + - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`)' - 'traefik.http.services.countryconfig.loadbalancer.server.port=3040' - 'traefik.http.routers.countryconfig.tls=true' - 'traefik.http.routers.countryconfig.tls.certresolver=certResolver' @@ -571,6 +572,8 @@ services: - 'traefik.http.middlewares.block-email.ipwhitelist.sourcerange=255.255.255.255' - 'traefik.http.routers.block-email.rule=Host(`countryconfig.{{hostname}}`) && Path(`/email`)' - 'traefik.http.routers.block-email.middlewares=block-email' + - 'traefik.http.routers.block-notification.rule=Host(`countryconfig.{{hostname}}`) && Path(`/notification`)' + - 'traefik.http.routers.block-notification.middlewares=block-email' replicas: 1 environment: - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 @@ -686,6 +689,7 @@ services: environment: - APN_SERVICE_URL=http://apm-server:8200 - CERT_PUBLIC_KEY_PATH=/run/secrets/jwt-public-key.{{ts}} + - MONGO_URL=mongodb://notification:${NOTIFICATION_MONGODB_PASSWORD}@mongo1/notification?replicaSet=rs0 deploy: replicas: 1 labels: diff --git a/infrastructure/docker-compose.production-deploy.yml b/infrastructure/docker-compose.production-deploy.yml index 87ceda12e..06217603c 100644 --- a/infrastructure/docker-compose.production-deploy.yml +++ b/infrastructure/docker-compose.production-deploy.yml @@ -65,6 +65,7 @@ services: - NODE_ENV=production - LANGUAGES=en,fr - SENTRY_DSN=${SENTRY_DSN:-""} + - MONGO_URL=mongodb://notification:${NOTIFICATION_MONGODB_PASSWORD}@mongo1,mongo2/notification?replicaSet=rs0 deploy: replicas: 2 diff --git a/infrastructure/docker-compose.staging-deploy.yml b/infrastructure/docker-compose.staging-deploy.yml index 726634829..eb7e05695 100644 --- a/infrastructure/docker-compose.staging-deploy.yml +++ b/infrastructure/docker-compose.staging-deploy.yml @@ -65,6 +65,7 @@ services: - NODE_ENV=production - LANGUAGES=en,fr - SENTRY_DSN=${SENTRY_DSN:-""} + - MONGO_URL=mongodb://notification:${NOTIFICATION_MONGODB_PASSWORD}@mongo1/notification?replicaSet=rs0 deploy: replicas: 1 diff --git a/infrastructure/mongodb/on-deploy.sh b/infrastructure/mongodb/on-deploy.sh index 999ab279a..fc10bc854 100755 --- a/infrastructure/mongodb/on-deploy.sh +++ b/infrastructure/mongodb/on-deploy.sh @@ -231,3 +231,25 @@ else }) EOF fi + +NOTIFICATION_USER=$(echo $(checkIfUserExists "notification")) +if [[ $NOTIFICATION_USER != "FOUND" ]]; then + echo "notification user not found" + mongo $(mongo_credentials) --host $HOST < { const replaceVariables = (text: string) => Handlebars.compile(text)({ @@ -65,13 +66,20 @@ export const sendEmail = async (params: { pass: SMTP_PASSWORD } }) + const mailOptions = params.bcc + ? { ...formattedParams, bcc: params.bcc } + : formattedParams try { - await emailTransport.sendMail(formattedParams) + await emailTransport.sendMail(mailOptions) } catch (error) { - logger.error( - `Unable to send email to ${formattedParams.to} for error : ${error}` - ) + if (params.bcc) { + logger.error(`Unable to send mass email for error : ${error}`) + } else { + logger.error( + `Unable to send email to ${formattedParams.to} for error : ${error}` + ) + } if (error.response) { logger.error(error.response.body) diff --git a/src/api/notification/email-templates/index.ts b/src/api/notification/email-templates/index.ts index 0939b5bbe..e65463e73 100644 --- a/src/api/notification/email-templates/index.ts +++ b/src/api/notification/email-templates/index.ts @@ -119,6 +119,11 @@ type RejectionDeclarationVariables = DeclarationCommonVariables & { name: string } +type AllUserNotificationVariables = { + subject: string + body: string +} + const templates = { 'onboarding-invite': { type: 'onboarding-invite', @@ -223,6 +228,13 @@ const templates = { type: 'deathRejectionNotification', subject: 'Death declaration required update', template: readDeathTemplate('rejection') + }, + allUserNotification: { + type: 'allUserNotification', + subject: '', // Subject defined from National Sys Admin Dashboard + template: readOtherTemplate( + 'all-user-notification' + ) } } as const diff --git a/src/api/notification/email-templates/other/all-user-notification.html b/src/api/notification/email-templates/other/all-user-notification.html new file mode 100644 index 000000000..7e2b34391 --- /dev/null +++ b/src/api/notification/email-templates/other/all-user-notification.html @@ -0,0 +1,53 @@ + + + + + + + + + + country_logo +

{{subject}}

+ +

+ {{body}} +

+
+

+ Best regards, +
+ Farajaland CRVS Team +

+
+ This is an automated message. Please do not reply to this email. + + + diff --git a/src/api/notification/handler.ts b/src/api/notification/handler.ts index bc10e54d8..9ad69f1ce 100644 --- a/src/api/notification/handler.ts +++ b/src/api/notification/handler.ts @@ -40,6 +40,7 @@ type EmailNotificationPayload = { } recipient: { email: string + bcc?: string[] } type: 'user' | 'informant' locale: string @@ -64,13 +65,14 @@ type NotificationPayload = SMSNotificationPayload | EmailNotificationPayload export const notificationSchema = Joi.object({ templateName: Joi.object({ - email: Joi.string().required(), - sms: Joi.string().required() - }), + email: Joi.string(), + sms: Joi.string() + }).xor('email', 'sms'), recipient: Joi.object({ - email: Joi.string().allow(null, '').optional(), - sms: Joi.string().allow(null, '').optional() - }), + email: Joi.string(), + sms: Joi.string(), + bcc: Joi.array().items(Joi.string().required()).optional() + }).xor('email', 'sms'), type: Joi.string().valid('user', 'informant').required() }).unknown(true) @@ -116,7 +118,8 @@ export async function notificationHandler( subject: emailSubject, html: emailBody, from: SENDER_EMAIL_ADDRESS, - to: recipient.email + to: recipient.email, + bcc: recipient.bcc }) } else { const { templateName, variables, recipient, locale } = payload diff --git a/src/index.ts b/src/index.ts index 87e9e2602..286fe9d50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -451,6 +451,7 @@ export async function createServer() { handler: notificationHandler, options: { tags: ['api'], + auth: false, validate: { payload: notificationSchema }, diff --git a/src/translations/client.csv b/src/translations/client.csv index d7963ad1c..b1f1f9398 100644 --- a/src/translations/client.csv +++ b/src/translations/client.csv @@ -253,6 +253,10 @@ config.deathDefaultTempDesc,Label for default death certificate template,Default config.deathTemplate,Label for death certificate template,Death certificate,Acte de mariage config.deathUpdatedTempDesc,,Updated {deathLongDate},Mise à jour de {deathLongDate} config.downloadTemplate,Download action in certificate config action menu,Download,Télécharger +config.emailAllUsers.modal.supportingCopy,Label for send email all users confirmation supporting copy,User will receive emails over the next 24 hours,L'utilisateur recevra des courriels au cours des prochaines 24 heures +config.emailAllUsers.modal.title,Label for send email all users confirmation title,Send email to all users?,Envoyer un e-mail à tous les utilisateurs ? +config.emailAllUsers.subtitle,Subtitle for email all users,This email will be sent to all users you are active. Emails will be sent over the next 24 hours. Only one email can be sent per day,Cet e-mail sera envoyé à tous les utilisateurs que vous activez. Les courriels seront envoyés au cours des prochaines 24 heures. Un seul courriel peut être envoyé par jour +config.emailAllUsers.title,Title for email all users,Email all users,Envoyer un e-mail à tous les utilisateurs config.eventUpdatedTempDesc,Label for updated birth certificate template,"Updated {lastModified, date, ::dd MMMM yyyy}","Mis à jour {lastModified, date, ::dd MMMM yyyy}" config.form.settings.time,,Time input,Saisie de l'heure config.form.tools.input.customSelectWithDynamicOptions,,Custom select with dynamic options,Sélection personnalisée avec options dynamiques @@ -344,6 +348,8 @@ constants.downloading,Label for declaration download status Downloading,Download constants.draft,A label for draft,Draft,Brouillon constants.duplicateOf,table header for `duplicate of` in record audit,Duplicate of,Duplicata de constants.emailAddress,Email label,Email Address,Adresse e-mail +constants.emailBody,Label for email body input,Message,Message +constants.emailSubject,Label for email subject input,Subject,Sujet constants.entrepeneur,The description for ENTREPENEUR type,Entrepeneur,Entrepeneur constants.estimatedNumberOfEvents,A label for Estimated number of events,"Estimated{lineBreak}no. of {eventType, select, birth {birth} death {death} other {birth}}s","Estimation{lineBreak}no. de {eventType, select, naissance {birth} décès {death} autre {birth}}s" constants.estimatedNumberOfRegistartion,A label for estimated no. of registrations,Estimated no. of registrations,Nombre estimé déclaration @@ -1451,6 +1457,8 @@ misc.nidCallback.failedToAuthenticateNid,Label for nid authention failed phase,F misc.notif.declarationsSynced,The message that appears in notification when background sync takes place,"As you have connectivity, we can synchronize your declarations.","Comme vous disposez d'une connectivité, nous pouvons synchroniser vos déclarations." misc.notif.draftsSaved,The message that appears in notification when save drafts button is clicked,Your draft has been saved,Votre brouillon a été enregistré misc.notif.duplicateRecord,Label for when a duplicate record is detected when registering a record.,{trackingId} is a potential duplicate. Record is ready for review.,{trackingId} est un doublon potentiel. L'enregistrement est prêt à être examiné. +misc.notif.emailAllUsersError,Label for Email all users error toast,Only one email can be sent per day,Un seul e-mail peut être envoyé par jour +misc.notif.emailAllUsersSuccess,Label for Email all users success toast,Email sent to all users,Email envoyé à tous les utilisateurs misc.notif.offlineError,The message that appears in notification when a new user creation fails in offline mode,Offline. Try again when reconnected,Hors ligne. Réessayez une fois reconnecté misc.notif.onlineUserStatus,Label for online user status toast notification,You are back online,Vous êtes de nouveau en ligne misc.notif.outboxText,Declaration outbox text,Outbox ({num}),Boîte d'envoi({num}) @@ -1477,6 +1485,7 @@ navigation.completenessRates,Completeness rates in navigation,Completeness rates navigation.config,Config label in navigation,Configuration,Paramétrages navigation.dashboard,Dashboard Section,Dashboard,Tableau de bord navigation.declarationForms,Declaration forms label in navigation,Declaration forms,Formulaires de déclaration +navigation.emailAllUsers,Email all users label in navigation,Email all users,Envoyer un e-mail à tous les utilisateurs navigation.informantNotification,Informant notifications label in navigation,Informant notifications,Notifications des informateurs navigation.integration,Integration forms label in navigation,Integrations,Intégrations navigation.leaderboards,Leaderboards Dashboard Section,Leaderboards,Classements From 4dc9215217f1a94dea6fb665f3a183638e6d01a4 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Tue, 16 Apr 2024 11:34:26 +0600 Subject: [PATCH 17/46] deps: remove openhim (#963) --- CHANGELOG.md | 3 +++ infrastructure/docker-compose.deploy.yml | 2 -- infrastructure/openhim-console-config.deploy.json | 13 ------------- infrastructure/openhim-console-config.json | 13 ------------- infrastructure/run-migrations.sh | 3 --- infrastructure/setup-deploy-config.sh | 3 --- src/index.ts | 3 +-- 7 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 infrastructure/openhim-console-config.deploy.json delete mode 100644 infrastructure/openhim-console-config.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf246efc..a1a33c796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## 1.5.0 (TBD) + +- Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in v1.6 ## [1.3.4](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.3.3...v1.3.4) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index e58f2f1f4..aaf5ba3bb 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -992,8 +992,6 @@ secrets: jwt-private-key.{{ts}}: external: true configs: - openhim-console-conf.{{ts}}: - file: /opt/opencrvs/infrastructure/openhim-console-config.deploy.json hearth-check-dupe-plugin.{{ts}}: file: /opt/opencrvs/infrastructure/hearth-plugins/checkDuplicateTask.js hearth-queryparam-extensions-conf.{{ts}}: diff --git a/infrastructure/openhim-console-config.deploy.json b/infrastructure/openhim-console-config.deploy.json deleted file mode 100644 index 0b3c221bd..000000000 --- a/infrastructure/openhim-console-config.deploy.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "1.10.0", - "minimumCoreVersion": "3.4.0", - "protocol": "https", - "host": "openhim-api.{{hostname}}", - "port": 443, - "title": "OpenCRVS - Admin Console", - "footerTitle": "OpenCRVS - OpenHIM Administration Console", - "footerPoweredBy": "Powered by OpenHIM", - "loginBanner": "", - "mediatorLastHeartbeatWarningSeconds": 60, - "mediatorLastHeartbeatDangerSeconds": 120 -} \ No newline at end of file diff --git a/infrastructure/openhim-console-config.json b/infrastructure/openhim-console-config.json deleted file mode 100644 index 484919950..000000000 --- a/infrastructure/openhim-console-config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "1.10.0", - "minimumCoreVersion": "3.4.0", - "protocol": "https", - "host": "localhost", - "port": 8080, - "title": "OpenCRVS - Admin Console", - "footerTitle": "OpenCRVS - OpenHIM Administration Console", - "footerPoweredBy": "Powered by OpenHIM", - "loginBanner": "", - "mediatorLastHeartbeatWarningSeconds": 60, - "mediatorLastHeartbeatDangerSeconds": 120 -} \ No newline at end of file diff --git a/infrastructure/run-migrations.sh b/infrastructure/run-migrations.sh index 2c20a3759..d68ee546b 100755 --- a/infrastructure/run-migrations.sh +++ b/infrastructure/run-migrations.sh @@ -35,6 +35,3 @@ create_elastic_index "ocrvs" # run migration by restarting migration service docker service update --force --update-parallelism 1 --update-delay 30s opencrvs_migration - -# restart openhim for the db changes to take effect -docker service update --force --update-parallelism 1 --update-delay 30s opencrvs_openhim-core diff --git a/infrastructure/setup-deploy-config.sh b/infrastructure/setup-deploy-config.sh index 111956993..f1d0a0f64 100755 --- a/infrastructure/setup-deploy-config.sh +++ b/infrastructure/setup-deploy-config.sh @@ -13,9 +13,6 @@ HOST=$1 echo "Setting up deployment config for $HOST - `date --iso-8601=ns`" -# Set hostname in openhim-console config -sed -i "s/{{hostname}}/$HOST/g" /opt/opencrvs/infrastructure/openhim-console-config.deploy.json - # Set hostname in compose file for file in /opt/opencrvs/infrastructure/docker-compose*.yml; do sed -i "s/{{hostname}}/$HOST/g" "$file" diff --git a/src/index.ts b/src/index.ts index 286fe9d50..72e14afcf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -474,8 +474,7 @@ export async function createServer() { validate: { payload: emailSchema }, - description: - 'Handles sending either SMS or email using a predefined template file' + description: 'Handles sending email using a predefined template file' } }) From 01933841b25379b3ac815a70479b535b43fec1b0 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Thu, 18 Apr 2024 13:31:47 +0600 Subject: [PATCH 18/46] Add smtp env vars in qa deploy config --- infrastructure/docker-compose.qa-deploy.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infrastructure/docker-compose.qa-deploy.yml b/infrastructure/docker-compose.qa-deploy.yml index 1b0f5976f..14e3524d8 100644 --- a/infrastructure/docker-compose.qa-deploy.yml +++ b/infrastructure/docker-compose.qa-deploy.yml @@ -53,6 +53,13 @@ services: - CHECK_INVALID_TOKEN=true - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 - SENTRY_DSN=${SENTRY_DSN:-""} + - SENDER_EMAIL_ADDRESS=${SENDER_EMAIL_ADDRESS} + - ALERT_EMAIL=${ALERT_EMAIL} + - SMTP_HOST=${SMTP_HOST} + - SMTP_PORT=${SMTP_PORT} + - SMTP_USERNAME=${SMTP_USERNAME} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - SMTP_SECURE=${SMTP_SECURE} deploy: replicas: 1 networks: From 02ae2991ec44bed62532379ccb49d42286e4c216 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Wed, 24 Apr 2024 18:17:37 +0600 Subject: [PATCH 19/46] Revert "Minor amend notification schema" This reverts commit b2c6e396f0756fb3ca5e2717ff2f9bc401be3f24. --- src/api/notification/handler.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/notification/handler.ts b/src/api/notification/handler.ts index 9ad69f1ce..5cf1443c1 100644 --- a/src/api/notification/handler.ts +++ b/src/api/notification/handler.ts @@ -65,14 +65,14 @@ type NotificationPayload = SMSNotificationPayload | EmailNotificationPayload export const notificationSchema = Joi.object({ templateName: Joi.object({ - email: Joi.string(), - sms: Joi.string() - }).xor('email', 'sms'), + email: Joi.string().required(), + sms: Joi.string().required() + }), recipient: Joi.object({ - email: Joi.string(), - sms: Joi.string(), + email: Joi.string().allow(null, '').optional(), + sms: Joi.string().allow(null, '').optional(), bcc: Joi.array().items(Joi.string().required()).optional() - }).xor('email', 'sms'), + }), type: Joi.string().valid('user', 'informant').required() }).unknown(true) From a554f5739cf292ebd2919f69d2888754af977f46 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Tue, 14 May 2024 13:41:16 +0600 Subject: [PATCH 20/46] fix: remove duplicate handlebars.js route --- src/index.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index 72e14afcf..e7c6bc94c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -357,18 +357,6 @@ export async function createServer() { } }) - server.route({ - method: 'GET', - path: '/handlebars.js', - handler: handlebarsHandler, - options: { - auth: false, - tags: ['api'], - description: - 'Serves custom handlebar helper functions as JS to be used in certificates' - } - }) - server.route({ method: 'GET', path: '/content/{application}', From 470573f9395c981c3398fde4e862b0135ca06cc0 Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Thu, 25 Apr 2024 14:35:47 +0300 Subject: [PATCH 21/46] chore: update the certificate images with instead of the patterns (#977) --- .../source/Farajaland-birth-certificate-v2.svg | 14 ++------------ .../source/Farajaland-death-certificate-v2.svg | 14 ++------------ .../source/Farajaland-marriage-certificate-v2.svg | 14 ++++---------- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/src/data-seeding/certificates/source/Farajaland-birth-certificate-v2.svg b/src/data-seeding/certificates/source/Farajaland-birth-certificate-v2.svg index 87172cd87..4e7dc7f2d 100644 --- a/src/data-seeding/certificates/source/Farajaland-birth-certificate-v2.svg +++ b/src/data-seeding/certificates/source/Farajaland-birth-certificate-v2.svg @@ -1,8 +1,9 @@ + +{{#ifCond printInAdvance '!==' true}}{{/ifCond}} CAUTION : THERE ARE OFFENCES RELATING TO FALSIFYING OR ALTERING A CERTIFICATE AND USING OR POSSESSING A FALSE CERTIFICATE IS NOT PROOF OF IDENTITY / ATTENTION : IL EXISTE DES INFRACTIONS RELATIVES A LA FALSIFIATION OU À LA MODIFICATION D'UN CERTIFICAT ET A L'UTILISATION OU LA POSSESSION D'UN FAUX CERTIFICAT. UN CERTIFICAT N'EST PAS UNE PREUVE D'IDENTITE - @@ -39,9 +40,6 @@ {{/ifCond}} - - - Registrar / L'Officier de l'État Civil {{registrar.name}} @@ -131,12 +129,6 @@ - - - - - - @@ -179,7 +171,5 @@ - -{{#ifCond printInAdvance '!==' true}}{{/ifCond}} diff --git a/src/data-seeding/certificates/source/Farajaland-death-certificate-v2.svg b/src/data-seeding/certificates/source/Farajaland-death-certificate-v2.svg index a2770a38c..086490d8b 100644 --- a/src/data-seeding/certificates/source/Farajaland-death-certificate-v2.svg +++ b/src/data-seeding/certificates/source/Farajaland-death-certificate-v2.svg @@ -1,17 +1,15 @@ + +{{#ifCond printInAdvance '!==' true}}{{/ifCond}} CAUTION : THERE ARE OFFENCES RELATING TO FALSIFYING OR ALTERING A CERTIFICATE AND USING OR POSSESSING A FALSE CERTIFICATE IS NOT PROOF OF IDENTITY / ATTENTION : IL EXISTE DES INFRACTIONS RELATIVES A LA FALSIFIATION OU À LA MODIFICATION D'UN CERTIFICAT ET A L'UTILISATION OU LA POSSESSION D'UN FAUX CERTIFICAT. UN CERTIFICAT N'EST PAS UNE PREUVE D'IDENTITE - {{certificateDate}} Date of certification / Date de délivrance {{location registrar.officeId 'name'}}{{location registrar.districtId 'name'}}, {{location registrar.stateId 'name'}}, Farajaland Place of certification / Lieu de certification - - - Registrar / L'Officier de l'État Civil {{registrar.name}} @@ -116,12 +114,6 @@ REPUBLIC OF FARAJALAND / REPUBLIQUE DE FARAJALAND CERTIFICATE OF DEATH / ACTE DE DEATH - - - - - - @@ -161,7 +153,5 @@ - -{{#ifCond printInAdvance '!==' true}}{{/ifCond}} diff --git a/src/data-seeding/certificates/source/Farajaland-marriage-certificate-v2.svg b/src/data-seeding/certificates/source/Farajaland-marriage-certificate-v2.svg index a3615ede4..cda9f1845 100644 --- a/src/data-seeding/certificates/source/Farajaland-marriage-certificate-v2.svg +++ b/src/data-seeding/certificates/source/Farajaland-marriage-certificate-v2.svg @@ -1,8 +1,11 @@ + + + +{{#ifCond printInAdvance '!==' true}}{{/ifCond}} CAUTION : THERE ARE OFFENCES RELATING TO FALSIFYING OR ALTERING A CERTIFICATE AND USING OR POSSESSING A FALSE CERTIFICATE IS NOT PROOF OF IDENTITY / ATTENTION : IL EXISTE DES INFRACTIONS RELATIVES A LA FALSIFIATION OU À LA MODIFICATION D'UN CERTIFICAT ET A L'UTILISATION OU LA POSSESSION D'UN FAUX CERTIFICAT. UN CERTIFICAT N'EST PAS UNE PREUVE D'IDENTITE - @@ -26,9 +29,6 @@ Date of certification / Date de délivrance {{location registrar.officeId 'name'}}{{location registrar.districtId 'name'}}, {{location registrar.stateId 'name'}}, Farajaland Place of certification / Lieu de certification - - - Registrar / L'Officier de l'État Civil {{registrar.name}} @@ -115,12 +115,6 @@ - - - - - - From c3f14f510a7ed3b4a8831e3f9b2671c5b264a369 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Fri, 3 May 2024 17:41:37 +0600 Subject: [PATCH 22/46] feat: generate default address according to user's location (#978) * feat: add GATEWAY_URL env variable * feat: generate default address from user location * chore: remove initialValue from dynamicOptions --- infrastructure/docker-compose.deploy.yml | 1 + package.json | 4 +- src/constants.ts | 1 + src/form/addresses/address-fields.ts | 90 ++++++++++++++++------ src/form/index.ts | 21 +++-- src/form/types/types.ts | 1 - src/utils/address-utils.ts | 20 +++-- src/utils/users.ts | 97 ++++++++++++++++++++++++ yarn.lock | 7 +- 9 files changed, 207 insertions(+), 35 deletions(-) create mode 100644 src/utils/users.ts diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index aaf5ba3bb..b8fdc06ea 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -581,6 +581,7 @@ services: - APN_SERVICE_URL=http://apm-server:8200 - COUNTRY_CONFIG_URL=https://countryconfig.{{hostname}} - LOGIN_URL=https://login.{{hostname}} + - GATEWAY_URL=https://gateway.{{hostname}} - CLIENT_APP_URL=https://register.{{hostname}} - NOTIFICATION_TRANSPORT=${NOTIFICATION_TRANSPORT} - ALERT_EMAIL=${ALERT_EMAIL:-""} diff --git a/package.json b/package.json index 80c3e5063..363a7f8b4 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,8 @@ "dotenv": "^6.1.0", "esbuild": "^0.18.9", "google-libphonenumber": "^3.2.32", + "graphql": "^16.3.0", + "graphql-tag": "^2.12.6", "handlebars": "^4.7.7", "hapi-auth-jwt2": "10.4.0", "hapi-pino": "^6.3.0", @@ -124,4 +126,4 @@ "minimist": "^1.2.2", "acorn": "^6.4.1" } -} \ No newline at end of file +} diff --git a/src/constants.ts b/src/constants.ts index 0ff671d46..795d1cc3e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -10,6 +10,7 @@ */ export const TEST_SOURCE = `${process.cwd()}/src/tests/` export const DOMAIN = process.env.DOMAIN || '*' +export const GATEWAY_URL = process.env.GATEWAY_URL || 'http://localhost:7070' export const LOGIN_URL = process.env.LOGIN_URL || 'http://localhost:3020/' export const CLIENT_APP_URL = process.env.CLIENT_APP_URL || 'http://localhost:3000/' diff --git a/src/form/addresses/address-fields.ts b/src/form/addresses/address-fields.ts index 8db00a146..b53dff6b6 100644 --- a/src/form/addresses/address-fields.ts +++ b/src/form/addresses/address-fields.ts @@ -74,11 +74,13 @@ export function getAddressLocationSelect({ location, useCase, fhirLineArrayPosition, - isLowestAdministrativeLevel + isLowestAdministrativeLevel, + initialValue }: { section: string location: string useCase: string + initialValue: string /** Position where the location gets mapped into within a fhir.Address line-array */ fhirLineArrayPosition?: number /** If the structure the smallest possible level. Allows saving fhir.Address.partOf */ @@ -99,7 +101,7 @@ export function getAddressLocationSelect({ ? useCase : `${useCase}Address`, required: true, - initialValue: '', + initialValue, validator: [], placeholder: { defaultMessage: 'Select', @@ -108,8 +110,7 @@ export function getAddressLocationSelect({ }, dynamicOptions: { resource: 'locations', - dependency: getDependency(location, useCase, section), - initialValue: 'agentDefault' + dependency: getDependency(location, useCase, section) }, conditionals: isUseCaseForPlaceOfEvent(useCase) ? getPlaceOfEventConditionals( @@ -134,7 +135,8 @@ export function getAddressLocationSelect({ // We recommend that you do not edit this function function getAdminLevelSelects( section: string, - useCase: string + useCase: string, + addressHierarchy: string[] ): SerializedFormField[] { switch (ADMIN_LEVELS) { case 1: @@ -143,71 +145,114 @@ function getAdminLevelSelects( section, location: 'state', useCase, - isLowestAdministrativeLevel: true + isLowestAdministrativeLevel: true, + initialValue: addressHierarchy[0] }) ] case 2: return [ - getAddressLocationSelect({ section, location: 'state', useCase }), + getAddressLocationSelect({ + section, + location: 'state', + useCase, + initialValue: addressHierarchy[0] + }), getAddressLocationSelect({ section, location: 'district', useCase, - isLowestAdministrativeLevel: true + isLowestAdministrativeLevel: true, + initialValue: addressHierarchy[1] }) ] case 3: return [ - getAddressLocationSelect({ section, location: 'state', useCase }), - getAddressLocationSelect({ section, location: 'district', useCase }), + getAddressLocationSelect({ + section, + location: 'state', + useCase, + initialValue: addressHierarchy[0] + }), + getAddressLocationSelect({ + section, + location: 'district', + useCase, + initialValue: addressHierarchy[1] + }), getAddressLocationSelect({ section, location: 'locationLevel3', useCase, fhirLineArrayPosition: 10, - isLowestAdministrativeLevel: true + isLowestAdministrativeLevel: true, + initialValue: addressHierarchy[2] }) ] case 4: return [ - getAddressLocationSelect({ section, location: 'state', useCase }), - getAddressLocationSelect({ section, location: 'district', useCase }), + getAddressLocationSelect({ + section, + location: 'state', + useCase, + initialValue: addressHierarchy[0] + }), + getAddressLocationSelect({ + section, + location: 'district', + useCase, + initialValue: addressHierarchy[1] + }), getAddressLocationSelect({ section, location: 'locationLevel3', useCase, - fhirLineArrayPosition: 10 + fhirLineArrayPosition: 10, + initialValue: addressHierarchy[2] }), getAddressLocationSelect({ section, location: 'locationLevel4', useCase, fhirLineArrayPosition: 11, - isLowestAdministrativeLevel: true + isLowestAdministrativeLevel: true, + initialValue: addressHierarchy[3] }) ] case 5: return [ - getAddressLocationSelect({ section, location: 'state', useCase }), - getAddressLocationSelect({ section, location: 'district', useCase }), + getAddressLocationSelect({ + section, + location: 'state', + useCase, + initialValue: addressHierarchy[0] + }), + getAddressLocationSelect({ + section, + location: 'district', + useCase, + initialValue: addressHierarchy[1] + }), getAddressLocationSelect({ section, location: 'locationLevel3', useCase, - fhirLineArrayPosition: 10 + fhirLineArrayPosition: 10, + initialValue: addressHierarchy[2] }), getAddressLocationSelect({ section, location: 'locationLevel4', useCase, - fhirLineArrayPosition: 11 + fhirLineArrayPosition: 11, + initialValue: addressHierarchy[3] }), getAddressLocationSelect({ section, location: 'locationLevel5', useCase, fhirLineArrayPosition: 12, - isLowestAdministrativeLevel: true + isLowestAdministrativeLevel: true, + initialValue: addressHierarchy[4] }) ] } @@ -241,7 +286,8 @@ function getPlaceOfEventFields(useCase: EventLocationAddressCases) { // ==================================== END WARNING ==================================== export function getAddressFields( section: string, - addressCase: EventLocationAddressCases | AddressCases + addressCase: EventLocationAddressCases | AddressCases, + addressHierarchy: string[] ): SerializedFormField[] { let useCase = addressCase as string let placeOfEventFields: SerializedFormField[] = [] @@ -289,7 +335,7 @@ export function getAddressFields( }) }, // Required // Select fields are added for each administrative location level from Humdata - ...getAdminLevelSelects(section, useCase), // Required + ...getAdminLevelSelects(section, useCase, addressHierarchy), // Required { name: `city${sentenceCase(useCase)}${sentenceCase(section)}`, type: 'TEXT', diff --git a/src/form/index.ts b/src/form/index.ts index 1dc89b8ab..4fc80de45 100644 --- a/src/form/index.ts +++ b/src/form/index.ts @@ -8,14 +8,21 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ - +import { Request } from '@hapi/hapi' import { decorateFormsWithAddresses } from '../utils/address-utils' import { birthForm } from './birth' import { deathForm } from './death' import { marriageForm } from './marriage' import { IForms, Event } from './types/types' +import { getUserOfficeLocationHierarchy } from '@countryconfig/utils/users' -export async function formHandler(): Promise { +export async function formHandler(req: Request): Promise { + const addressHierarchy = ( + await getUserOfficeLocationHierarchy( + req.headers.authorization, + req.auth.credentials.sub as string + ) + ).map(({ id }) => id) // ====================== NOTE REGARDING MIGRATING FROM OPNCRVS v1.2 OR EARLIER ====================== // SIMPLY RETURN A JSON OF YOUR FULL FORM HERE, WITH THE ADDITION OF THE NEW MARRIAGE AND VERSION PROP @@ -51,8 +58,12 @@ export async function formHandler(): Promise { // THIS DECORATOR FUNCTION POPULATES ADDRESSES ACCORDING TO THE defaultAddressConfiguration in address-settings.ts // SO YOU ONLY NEED TO CONFIGURE ADDRESS FIELDS IN A SINGLE LOCATION FOR ALL DECORATED INSTANCES. - birth: decorateFormsWithAddresses(birthForm, Event.Birth), - death: decorateFormsWithAddresses(deathForm, Event.Death), - marriage: decorateFormsWithAddresses(marriageForm, Event.Marriage) + birth: decorateFormsWithAddresses(birthForm, Event.Birth, addressHierarchy), + death: decorateFormsWithAddresses(deathForm, Event.Death, addressHierarchy), + marriage: decorateFormsWithAddresses( + marriageForm, + Event.Marriage, + addressHierarchy + ) } } diff --git a/src/form/types/types.ts b/src/form/types/types.ts index 1cce9b9e6..657626242 100644 --- a/src/form/types/types.ts +++ b/src/form/types/types.ts @@ -528,7 +528,6 @@ export interface IDynamicOptions { jurisdictionType?: string resource?: string options?: { [key: string]: ISelectOption[] } - initialValue?: string } export type IFormFieldTemplateMapOperation = diff --git a/src/utils/address-utils.ts b/src/utils/address-utils.ts index fcfa198af..0272c9ee6 100644 --- a/src/utils/address-utils.ts +++ b/src/utils/address-utils.ts @@ -984,23 +984,26 @@ export const getAddressSubsection = ( // You should never need to edit this function. If there is a bug here raise an issue in [Github](https://github.com/opencrvs/opencrvs-farajaland) function getAddressFieldsByConfiguration( configuration: AllowedAddressConfigurations, - section: string + section: string, + addressHierarchy: string[] ): SerializedFormField[] { switch (configuration.config) { case EventLocationAddressCases.PLACE_OF_BIRTH: case EventLocationAddressCases.PLACE_OF_DEATH: case EventLocationAddressCases.PLACE_OF_MARRIAGE: - return getAddressFields('', configuration.config) + return getAddressFields('', configuration.config, addressHierarchy) case AddressCases.PRIMARY_ADDRESS: return getAddress( section, AddressCases.PRIMARY_ADDRESS, + addressHierarchy, configuration.conditionalCase ) case AddressCases.SECONDARY_ADDRESS: return getAddress( section, AddressCases.SECONDARY_ADDRESS, + addressHierarchy, configuration.conditionalCase ) case AddressCopyConfigCases.PRIMARY_ADDRESS_SAME_AS_OTHER_PRIMARY: @@ -1039,7 +1042,8 @@ function getAddressFieldsByConfiguration( // You should never need to edit this function. If there is a bug here raise an issue in [Github](https://github.com/opencrvs/opencrvs-farajaland) export function decorateFormsWithAddresses( defaultEventForm: ISerializedForm, - event: string + event: string, + addressHierarchy: string[] ): ISerializedForm { const newForm = cloneDeep(defaultEventForm) defaultAddressConfiguration.forEach( @@ -1052,7 +1056,11 @@ export function decorateFormsWithAddresses( let previewGroups: IPreviewGroup[] = [] configurations.forEach((configuration) => { addressFields = addressFields.concat( - getAddressFieldsByConfiguration(configuration, sectionId) + getAddressFieldsByConfiguration( + configuration, + sectionId, + addressHierarchy + ) ) previewGroups = previewGroups.concat(getPreviewGroups(configuration)) }) @@ -1079,11 +1087,13 @@ export function decorateFormsWithAddresses( function getAddress( section: string, addressCase: AddressCases, + addressHierarchy: string[], conditionalCase?: string ): SerializedFormField[] { const defaultFields: SerializedFormField[] = getAddressFields( section, - addressCase + addressCase, + addressHierarchy ) if (conditionalCase) { defaultFields.forEach((field) => { diff --git a/src/utils/users.ts b/src/utils/users.ts new file mode 100644 index 000000000..c1ecea7a1 --- /dev/null +++ b/src/utils/users.ts @@ -0,0 +1,97 @@ +import { APPLICATION_CONFIG_URL, GATEWAY_URL } from '@countryconfig/constants' +import fetch from 'node-fetch' +import gql from 'graphql-tag' +import { print } from 'graphql/language/printer' +import { URL } from 'url' + +type GetUser = { + primaryOffice?: { + id: string + } +} + +type Location = { + id: string +} + +async function getUser(token: string, userId: string): Promise { + const url = new URL('graphql', GATEWAY_URL) + const getUsersRes = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `${token}` + }, + body: JSON.stringify({ + operationName: 'fetchUser', + variables: { + userId: userId + }, + query: print(gql` + query fetchUser($userId: String!) { + getUser(userId: $userId) { + id + creationDate + username + practitionerId + mobile + systemRole + role { + _id + labels { + lang + label + __typename + } + __typename + } + status + name { + use + firstNames + familyName + __typename + } + primaryOffice { + id + name + alias + status + __typename + } + __typename + } + } + `) + }) + }) + + const res = (await getUsersRes.json()) as { + data: { getUser: GetUser } + } + + return res.data.getUser +} + +async function getLocationHierarchy(locationId: string): Promise { + const url = new URL( + `location/${locationId}/hierarchy`, + APPLICATION_CONFIG_URL + ) + const res = await fetch(url) + if (!res.ok) { + throw new Error('Unable to retrieve location hierarchy') + } + return res.json() +} + +export async function getUserOfficeLocationHierarchy( + token: string, + userId: string +): Promise { + const user = await getUser(token, userId) + if (!user.primaryOffice) { + throw new Error('No primary office found for user') + } + return getLocationHierarchy(user.primaryOffice.id) +} diff --git a/yarn.lock b/yarn.lock index 116200757..8d13b9a48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4157,7 +4157,7 @@ graphql-request@^6.0.0: "@graphql-typed-document-node/core" "^3.2.0" cross-fetch "^3.1.5" -graphql-tag@^2.11.0: +graphql-tag@^2.11.0, graphql-tag@^2.12.6: version "2.12.6" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== @@ -4174,6 +4174,11 @@ graphql-ws@^5.4.1: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.11.2.tgz#d5e0acae8b4d4a4cf7be410a24135cfcefd7ddc0" integrity sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w== +graphql@^16.3.0: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== + handlebars@*: version "4.7.8" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" From c09042bb042a09c0ae062389c748159ff8d61594 Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Tue, 7 May 2024 12:16:10 +0600 Subject: [PATCH 23/46] [HOTFIX] Mass email subject & content styles (#983) * Update body content styles to use linebreaks * Forward subject from variables instead of template --- src/api/notification/email-templates/index.ts | 3 ++- .../email-templates/other/all-user-notification.html | 6 +++++- src/api/notification/handler.ts | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/api/notification/email-templates/index.ts b/src/api/notification/email-templates/index.ts index e65463e73..acdfe7e1d 100644 --- a/src/api/notification/email-templates/index.ts +++ b/src/api/notification/email-templates/index.ts @@ -119,7 +119,7 @@ type RejectionDeclarationVariables = DeclarationCommonVariables & { name: string } -type AllUserNotificationVariables = { +export type AllUserNotificationVariables = { subject: string body: string } @@ -262,3 +262,4 @@ export type TemplateVariables = | InReviewDeclarationVariables | RegistrationDeclarationVariables | RejectionDeclarationVariables + | AllUserNotificationVariables diff --git a/src/api/notification/email-templates/other/all-user-notification.html b/src/api/notification/email-templates/other/all-user-notification.html index 7e2b34391..0b9da50c7 100644 --- a/src/api/notification/email-templates/other/all-user-notification.html +++ b/src/api/notification/email-templates/other/all-user-notification.html @@ -24,6 +24,10 @@ margin-bottom: 24px; } + .mail-body { + white-space: pre-line; + } + i { color: #666; font-weight: 400; @@ -37,7 +41,7 @@ max-width: 100%;">

{{subject}}

-

+

{{body}}


diff --git a/src/api/notification/handler.ts b/src/api/notification/handler.ts index 5cf1443c1..e28947d13 100644 --- a/src/api/notification/handler.ts +++ b/src/api/notification/handler.ts @@ -16,6 +16,7 @@ import { COUNTRY_LOGO_URL, LOGIN_URL, SENDER_EMAIL_ADDRESS } from './constant' import { sendEmail } from './email-service' import { SMSTemplateType, sendSMS } from './sms-service' import { + AllUserNotificationVariables, EmailTemplateType, TemplateVariables, getTemplate, @@ -105,7 +106,10 @@ export async function notificationHandler( logger.info(`Notification method is email and recipient ${recipient.email}`) const template = getTemplate(templateName.email) - const emailSubject = template.subject + const emailSubject = + template.type === 'allUserNotification' + ? (variables as AllUserNotificationVariables).subject + : template.subject const emailBody = renderTemplate(template, { ...variables, From 290d4f5aba579ea7f7c2b8a2a40dee0ce40a0907 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Tue, 7 May 2024 17:32:10 +0600 Subject: [PATCH 24/46] fix: change gateway web url to internal swarm one (#990) --- infrastructure/docker-compose.deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index b8fdc06ea..2d34292bc 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -581,7 +581,9 @@ services: - APN_SERVICE_URL=http://apm-server:8200 - COUNTRY_CONFIG_URL=https://countryconfig.{{hostname}} - LOGIN_URL=https://login.{{hostname}} - - GATEWAY_URL=https://gateway.{{hostname}} + # This needs to be the internal swarm url + # as containers cannot connect to the web + - GATEWAY_URL=http://gateway:7070 - CLIENT_APP_URL=https://register.{{hostname}} - NOTIFICATION_TRANSPORT=${NOTIFICATION_TRANSPORT} - ALERT_EMAIL=${ALERT_EMAIL:-""} From 06f683e450776b6dcb74730e1c92abf02d74b965 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Tue, 14 May 2024 15:51:19 +0600 Subject: [PATCH 25/46] docs: update CHANGELOG --- CHANGELOG.md | 111 +++++++++------------------------------------------ 1 file changed, 19 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1a33c796..a1c38a063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,21 @@ # Changelog ## 1.5.0 (TBD) +- Change auth URLs to access them via gateway +- Add hearth URL to search service +- Include an endpoint for serving individual certificates in development mode +- Include compositionId in confirm registration payload +- Move individual configuration options to feature flags +- Remove logrocket refrences +- Upgrade to node 18 +- Enable gzip compression in client & login +- Make SENTRY_DSN variable optional +- Use docker compose v2 in github workflows +- Mass email from national system admin - Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in v1.6 - -## [1.3.4](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.3.3...v1.3.4) - -## Breaking changes - -## New features - -## Bug fixes - -- Fix typo in certificate handlebar names - -See [Releases](https://github.com/opencrvs/opencrvs-countryconfig/releases) for release notes of older releases. +- Add smtp environment variables in qa compose file +- Use image tag instead of patterns in certificate SVGs +- Generate default address according to logged-in user's location ## [1.4.1](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.4.0...v1.4.1) @@ -69,90 +71,15 @@ In the next OpenCRVS release v1.5.0, there will be two significant changes: - The `infrastructure` directory and related pipelines will be moved to a new repository. - Both the new infrastructure repository and the OpenCRVS country resource package repositories will start following their own release cycles, mostly independent from the core's release cycle. From this release forward, both packages are released as "OpenCRVS minor compatible" releases, meaning that the OpenCRVS countryconfig 1.3.0- is compatible with OpenCRVS 1.3.0, 1.3.1, 1.3.2, etc. This allows for the release of new hotfix versions of the core without having to publish a new version of the infrastructure or countryconfig. -### Bug fixes - -## [1.3.3](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.3.2...v1.3.3) - -## Breaking changes - -## New features - -- #### Greater customizability of location data in certificates - - The various admin level handlebars e.g. **statePlaceofbirth**, - **districtPrimaryMother** only contained the name of that location which was - not able to take advantage of all the information OpenCRVS had available - about the various admin levels e.g. the name of that location in the - secondary language. So we are introducing a new set of admin level - handlebars that would contain the **id** of that location which we can - resolve into a value of the shape - - ``` - { - name: string - alias: string - } - ``` - - using the new **"location"** handlebar helper. Here name is the primary - label of the location and alias being the secondary one. Currently only - these 2 fields are available but we will be adding more fields depending on - various countries requirements. If previously the certificate svg used to - contain `{{districtPlaceofbirth}}` then now we can replace it with - `{{location districtPlaceofbirthId 'name'}}`. To access alias, the `'name'` - needs to be replaced with `'alias'`. - - Below is a list of all the new handlebars that are meant to be used with the - "location" handlebar helper. - - - statePrimaryInformantId - - districtPrimaryInformantId - - statePlaceofbirthId - - districtPlaceofbirthId - - statePrimaryMotherId - - districtPrimaryMotherId - - statePrimaryFatherId - - districtPrimaryFatherId - - statePrimaryDeceasedId - - districtPrimaryDeceasedId - - statePlaceofdeathId - - districtPlaceofdeathId - - statePrimaryGroomId - - districtPrimaryGroomId - - statePrimaryBrideId - - districtPrimaryBrideId - - statePlaceofmarriageId - - districtPlaceofmarriageId - - registrar.stateId - - registrar.districtId - - registrar.officeId - - registrationAgent.stateId - - registrationAgent.districtId - - registrationAgent.officeId - - ##### We will be deprecating the counterpart of the above mentioned handlebars that contains only the label of the specified location in a future version so we highly recommend that implementers update their certificates to use these new ones. - -- #### "Spouse" section in Farajaland death form - - Spouse section is an optional section in death form. Going forward it will be included in Farajaland example configuration. - -- #### Type of ID dropdown - Farajaland forms will now include a dropdown to select the type of ID an individual is providing e.g. National ID, Driving License etc. instead of being restricted to only national ID number. -- #### Number of dependents of deceased field - As an example of custom field, the deceased section in death form will now include the **numberOfDependants** field. -- #### Reason for late registration field - The birth & death forms will include another custom field, **reasonForLateRegistration**, which makes use of "LATE_REGISTRATION_TARGET" configuration option in it's visibility conditional. +## [1.3.4](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.3.3...v1.3.4) -## Bug fixes +### Bug fixes -- Updated translations for form introduction page and sending for approval to reflect the default notification method being email. -- # Remove hard-coded conditionals from "occupation" field to make it usable in the deceased form +- Fix typo in certificate handlebar names ## [1.3.3](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.3.2...v1.3.3) -## Breaking changes - -## New features +### New features - #### Greater customizability of location data in certificates @@ -220,7 +147,7 @@ In the next OpenCRVS release v1.5.0, there will be two significant changes: - #### Reason for late registration field The birth & death forms will include another custom field, **reasonForLateRegistration**, which makes use of "LATE_REGISTRATION_TARGET" configuration option in it's visibility conditional. -## Bug fixes +### Bug fixes - Updated translations for form introduction page and sending for approval to reflect the default notification method being email. - Remove hard-coded conditionals from "occupation" field to make it usable in the deceased form From 7be22de4d2bf8821329f5122e436958f7eff8872 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Wed, 15 May 2024 15:18:33 +0300 Subject: [PATCH 26/46] set owner for backup server authorized_keys to be the backup user --- infrastructure/server-setup/backups.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/server-setup/backups.yml b/infrastructure/server-setup/backups.yml index fa8717219..c690e2f8a 100644 --- a/infrastructure/server-setup/backups.yml +++ b/infrastructure/server-setup/backups.yml @@ -111,6 +111,8 @@ marker: '# {mark} ANSIBLE MANAGED BLOCK docker-manager-first {{ manager_hostname }}' create: yes mode: 0600 + owner: '{{ external_backup_server_user }}' + tags: - backups From 12845b44f39328dc29d9a0aa07165cff213990f9 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Fri, 17 May 2024 14:34:03 +0300 Subject: [PATCH 27/46] minor fix to how download script cleans backup directories before applying downloaded backups --- infrastructure/backups/download.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/infrastructure/backups/download.sh b/infrastructure/backups/download.sh index 5e4ca8391..0d87a1783 100644 --- a/infrastructure/backups/download.sh +++ b/infrastructure/backups/download.sh @@ -99,7 +99,13 @@ mkdir -p $BACKUP_RAW_FILES_DIR/extract tar -xvf $BACKUP_RAW_FILES_DIR/${LABEL}.tar.gz -C $BACKUP_RAW_FILES_DIR/extract # Delete previous days restore(s) and move the newly downloaded one in place -rm -rf /data/backups/* +for BACKUP_DIR in /data/backups/*; do + if [ -d "$BACKUP_DIR" ]; then + rm -rf $BACKUP_DIR/* + fi +done + + mv $BACKUP_RAW_FILES_DIR/extract/elasticsearch /data/backups/elasticsearch mv $BACKUP_RAW_FILES_DIR/extract/influxdb /data/backups/influxdb/${LABEL} From e3253dd7a8907d869c67fa6ce3f120b43be6476c Mon Sep 17 00:00:00 2001 From: "Md. Ashikul Alam" <32668488+Nil20@users.noreply.github.com> Date: Mon, 20 May 2024 15:41:28 +0600 Subject: [PATCH 28/46] chore!: move configuration options (#1005) --- src/api/application/application-config-default.ts | 6 +++--- src/form/common/common-optional-fields.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/application/application-config-default.ts b/src/api/application/application-config-default.ts index 28db8ebf4..9b2db665a 100644 --- a/src/api/application/application-config-default.ts +++ b/src/api/application/application-config-default.ts @@ -46,10 +46,10 @@ export const defaultApplicationConfig = { MARRIAGE_REGISTRATION: false, EXTERNAL_VALIDATION_WORKQUEUE: false, INFORMANT_SIGNATURE: false, - PRINT_DECLARATION: false + PRINT_DECLARATION: false, + DATE_OF_BIRTH_UNKNOWN: true, + INFORMANT_SIGNATURE_REQUIRED: false }, - DATE_OF_BIRTH_UNKNOWN: true, - INFORMANT_SIGNATURE_REQUIRED: false, USER_NOTIFICATION_DELIVERY_METHOD: 'email', // or 'sms', or '' ... You can use 'sms' for WhatsApp INFORMANT_NOTIFICATION_DELIVERY_METHOD: 'email', // or 'sms', or '' ... You can use 'sms' for WhatsApp SIGNATURE_REQUIRED_FOR_ROLES: ['LOCAL_REGISTRAR', 'NATIONAL_REGISTRAR'] diff --git a/src/form/common/common-optional-fields.ts b/src/form/common/common-optional-fields.ts index 419125a8a..d37f8ff84 100644 --- a/src/form/common/common-optional-fields.ts +++ b/src/form/common/common-optional-fields.ts @@ -22,7 +22,7 @@ import { Validator } from '../types/validators' const exactDobConditional: Conditional[] = [ { action: 'hide', - expression: '!window.config.DATE_OF_BIRTH_UNKNOWN' + expression: '!window.config.FEATURES.DATE_OF_BIRTH_UNKNOWN' } ] From a4a37a58bf708df619fc6bf05646f2238920c425 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Tue, 21 May 2024 19:06:56 +0600 Subject: [PATCH 29/46] Remove authentication from dashboard queries endpoint and update traefik rules --- infrastructure/docker-compose.deploy.yml | 17 +++++++++++------ src/index.ts | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 2d34292bc..82fdfe861 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -554,7 +554,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`)' + - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`) &!Path(`/dashboards/queries.json`)' - 'traefik.http.services.countryconfig.loadbalancer.server.port=3040' - 'traefik.http.routers.countryconfig.tls=true' - 'traefik.http.routers.countryconfig.tls.certresolver=certResolver' @@ -567,13 +567,15 @@ services: - 'traefik.http.middlewares.countryconfig.headers.stsseconds=31536000' - 'traefik.http.middlewares.countryconfig.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.countryconfig.headers.stspreload=true' - # This is an invalid IP range, effectively blocking all IPs from accessing /email path. + # This is an invalid IP range, effectively blocking all IPs from accessing below paths. # It's only meant to be accessed from the internal docker network. - - 'traefik.http.middlewares.block-email.ipwhitelist.sourcerange=255.255.255.255' + - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' - 'traefik.http.routers.block-email.rule=Host(`countryconfig.{{hostname}}`) && Path(`/email`)' - - 'traefik.http.routers.block-email.middlewares=block-email' + - 'traefik.http.routers.block-email.middlewares=block-internal-routes' - 'traefik.http.routers.block-notification.rule=Host(`countryconfig.{{hostname}}`) && Path(`/notification`)' - - 'traefik.http.routers.block-notification.middlewares=block-email' + - 'traefik.http.routers.block-notification.middlewares=block-internal-routes' + - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboards/queries.json`)' + - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 environment: - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 @@ -848,7 +850,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`)' + - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`) && !Path(`/dashboardQueries`)' - 'traefik.http.services.config.loadbalancer.server.port=2021' - 'traefik.http.routers.config.tls=true' - 'traefik.http.routers.config.tls.certresolver=certResolver' @@ -860,6 +862,9 @@ services: - 'traefik.http.middlewares.config.headers.stsseconds=31536000' - 'traefik.http.middlewares.config.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.config.headers.stspreload=true' + - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' + - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboardQueries`)' + - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 networks: - overlay_net diff --git a/src/index.ts b/src/index.ts index e7c6bc94c..56b69f257 100644 --- a/src/index.ts +++ b/src/index.ts @@ -374,6 +374,7 @@ export async function createServer() { handler: dashboardQueriesHandler, options: { tags: ['api'], + auth: false, description: 'Serves dashboard view refresher queries' } }) From f2e2816b0ba1427a73b5da556ad27b45bc614b2d Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Mon, 27 May 2024 14:02:16 +0600 Subject: [PATCH 30/46] fix: remove authentication from dashboard queries endpoint and update traefik rules (#120) --- infrastructure/docker-compose.deploy.yml | 17 +++++++++++------ src/index.ts | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 2d34292bc..82fdfe861 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -554,7 +554,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`)' + - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`) &!Path(`/dashboards/queries.json`)' - 'traefik.http.services.countryconfig.loadbalancer.server.port=3040' - 'traefik.http.routers.countryconfig.tls=true' - 'traefik.http.routers.countryconfig.tls.certresolver=certResolver' @@ -567,13 +567,15 @@ services: - 'traefik.http.middlewares.countryconfig.headers.stsseconds=31536000' - 'traefik.http.middlewares.countryconfig.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.countryconfig.headers.stspreload=true' - # This is an invalid IP range, effectively blocking all IPs from accessing /email path. + # This is an invalid IP range, effectively blocking all IPs from accessing below paths. # It's only meant to be accessed from the internal docker network. - - 'traefik.http.middlewares.block-email.ipwhitelist.sourcerange=255.255.255.255' + - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' - 'traefik.http.routers.block-email.rule=Host(`countryconfig.{{hostname}}`) && Path(`/email`)' - - 'traefik.http.routers.block-email.middlewares=block-email' + - 'traefik.http.routers.block-email.middlewares=block-internal-routes' - 'traefik.http.routers.block-notification.rule=Host(`countryconfig.{{hostname}}`) && Path(`/notification`)' - - 'traefik.http.routers.block-notification.middlewares=block-email' + - 'traefik.http.routers.block-notification.middlewares=block-internal-routes' + - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboards/queries.json`)' + - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 environment: - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 @@ -848,7 +850,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`)' + - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`) && !Path(`/dashboardQueries`)' - 'traefik.http.services.config.loadbalancer.server.port=2021' - 'traefik.http.routers.config.tls=true' - 'traefik.http.routers.config.tls.certresolver=certResolver' @@ -860,6 +862,9 @@ services: - 'traefik.http.middlewares.config.headers.stsseconds=31536000' - 'traefik.http.middlewares.config.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.config.headers.stspreload=true' + - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' + - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboardQueries`)' + - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 networks: - overlay_net diff --git a/src/index.ts b/src/index.ts index e7c6bc94c..56b69f257 100644 --- a/src/index.ts +++ b/src/index.ts @@ -374,6 +374,7 @@ export async function createServer() { handler: dashboardQueriesHandler, options: { tags: ['api'], + auth: false, description: 'Serves dashboard view refresher queries' } }) From 58fb8cc607021ca07248723c627e8ad105a21bc3 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Mon, 27 May 2024 14:47:25 +0600 Subject: [PATCH 31/46] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4672b07..0907be5da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + ## [1.5.0] - Change auth URLs to access them via gateway @@ -12,10 +13,12 @@ - Make SENTRY_DSN variable optional - Use docker compose v2 in github workflows - Mass email from national system admin -- Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in v1.6 +- Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in + v1.6 - Add smtp environment variables in qa compose file - Use image tag instead of patterns in certificate SVGs - Generate default address according to logged-in user's location +- Remove authentication from dashboard queries route ## [1.4.1](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.4.0...v1.4.1) From 549acefdd49dc5c0fd44573f8926eb3e5d7eca27 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Mon, 27 May 2024 14:53:46 +0600 Subject: [PATCH 32/46] Remove unintended formatting changes --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0907be5da..5d7a76a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,7 @@ - Make SENTRY_DSN variable optional - Use docker compose v2 in github workflows - Mass email from national system admin -- Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in - v1.6 +- Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in v1.6 - Add smtp environment variables in qa compose file - Use image tag instead of patterns in certificate SVGs - Generate default address according to logged-in user's location From b6832e8cc246856583898582eb60bc8d7a0d20a9 Mon Sep 17 00:00:00 2001 From: Niko Kurtti Date: Mon, 27 May 2024 12:20:17 +0300 Subject: [PATCH 33/46] Update jq images to official ones to avoid deprecation breakage (#122) --- infrastructure/monitoring/kibana/setup-config.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/monitoring/kibana/setup-config.sh b/infrastructure/monitoring/kibana/setup-config.sh index 38f439bd7..a2f5cca78 100755 --- a/infrastructure/monitoring/kibana/setup-config.sh +++ b/infrastructure/monitoring/kibana/setup-config.sh @@ -23,7 +23,7 @@ if [ "$status_code" -ne 200 ]; then fi # Delete all alerts -$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net stedolan/jq -r '.data[].id' | while read -r id; do +$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net ghcr.io/jqlang/jq -r '.data[].id' | while read -r id; do $docker_command --connect-timeout 60 -X DELETE -H 'kbn-xsrf: true' -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "http://kibana:5601/api/alerting/rule/$id" done @@ -31,7 +31,7 @@ done $docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD -X POST "http://kibana:5601/api/saved_objects/_import?overwrite=true" -H 'kbn-xsrf: true' --form file=@/config.ndjson > /dev/null # Re-enable all alerts -$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net stedolan/jq -r '.data[].id' | while read -r id; do +$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net ghcr.io/jqlang/jq -r '.data[].id' | while read -r id; do $docker_command --connect-timeout 60 -X POST -H 'kbn-xsrf: true' -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "http://kibana:5601/api/alerting/rule/$id/_disable" $docker_command --connect-timeout 60 -X POST -H 'kbn-xsrf: true' -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "http://kibana:5601/api/alerting/rule/$id/_enable" done From d94f0e40e13712d0c6509262d9d36fd7070de23b Mon Sep 17 00:00:00 2001 From: Niko Kurtti Date: Mon, 27 May 2024 12:20:40 +0300 Subject: [PATCH 34/46] Update Docker and checkout actions to get rid of warnings (#121) --- .github/workflows/deploy-prod.yml | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/publish-release.yml | 4 ++-- .github/workflows/publish-to-dockerhub.yml | 6 +++--- .github/workflows/test.yml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 69189010c..8d75aa765 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -75,7 +75,7 @@ jobs: echo "KNOWN_HOSTS=" >> $GITHUB_ENV - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9cfa54135..9630a00f9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -77,7 +77,7 @@ jobs: echo "KNOWN_HOSTS=" >> $GITHUB_ENV - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b3d0fe56e..3ffcabf0c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -15,7 +15,7 @@ jobs: base: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: '${{ github.event.inputs.branch_name }}' @@ -47,7 +47,7 @@ jobs: custom_tag: ${{ env.TAG }} - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/publish-to-dockerhub.yml b/.github/workflows/publish-to-dockerhub.yml index 49ccfc90a..11739fc01 100644 --- a/.github/workflows/publish-to-dockerhub.yml +++ b/.github/workflows/publish-to-dockerhub.yml @@ -16,20 +16,20 @@ jobs: push: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 if: github.event_name == 'workflow_dispatch' with: fetch-depth: 2 ref: '${{ github.event.inputs.branch_name }}' - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 if: github.event_name == 'push' - name: Get tags run: git fetch --tags origin - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f0c74dcc..cb7a42e5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Checking out git repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Use Node.js 18.19 uses: actions/setup-node@v2 From aa64ac63d06b6a53ee6b468b5c7ea13befd25bb9 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Wed, 29 May 2024 11:20:51 +0300 Subject: [PATCH 35/46] Add some basic test framework for birth test section (#997) * add some basic test framework for birth * try nesting tests more with step * some structure for birth tests * feat: completed birth event declaration tests * refactor: made 1.10 and 1.11 parallel to (1.1 - 1.9) * refactor: made `1.10` and `1.11` parallel * chore: fix description * feat: created re-usable test for validating names * feat: implemented upto 2.6 * refactor: created `goToSection()` for better nav * feat: implemented birth-8 validation declaration review page * feat: implemented change validations from review page * :feat: implemented `birth declaration-1` * feat: implemented `birth declaration - 2` * refactor: verifying in the last page only * feat: implemented `birth declaration - 3` * fix: declaration started by RA * feat: implemented `birth declaration - 4` * feat: implemented `birth declaration - 5` * feat: implemented `birth declaration - 6` * refactor: removed Duplicate Assertion * feat: implemented `birth declaration - 7` * feat: implemented `birth declaration - 8` * fix: made declaration - 7 and 8 incomplete * feat: implemented `birth declaration - 9` and `10` * amend: used `-`, `_`, `3` in names * fix: updated logic to select declaration with no name * fix: waiting for 500ms after going to informant details page * refactor: used faker.email in birth-8 * amend: used faker.email in birth-8 * fix: reversed `skip validation in preview page` * fix: waited 500ms before continue in some pages * fix: waited 500ms before continue in some more pages --------- Co-authored-by: jamil314 --- e2e/constants.ts | 1 + e2e/helpers.ts | 23 +- .../birth/1-birth-event-declaration.spec.ts | 572 +++++- .../2-validate-the-child-details-page.spec.ts | 341 ++++ ...8-validate-declaration-review-page.spec.ts | 1623 +++++++++++++++++ .../declarations/birth-declaration-1.spec.ts | 804 ++++++++ .../declarations/birth-declaration-10.spec.ts | 299 +++ .../declarations/birth-declaration-2.spec.ts | 876 +++++++++ .../declarations/birth-declaration-3.spec.ts | 1073 +++++++++++ .../declarations/birth-declaration-4.spec.ts | 1070 +++++++++++ .../declarations/birth-declaration-5.spec.ts | 578 ++++++ .../declarations/birth-declaration-6.spec.ts | 593 ++++++ .../declarations/birth-declaration-7.spec.ts | 329 ++++ .../declarations/birth-declaration-8.spec.ts | 429 +++++ .../declarations/birth-declaration-9.spec.ts | 297 +++ e2e/testcases/birth/helpers.ts | 257 +++ tsconfig.json | 1 + 17 files changed, 9132 insertions(+), 34 deletions(-) create mode 100644 e2e/testcases/birth/2-validate-the-child-details-page.spec.ts create mode 100644 e2e/testcases/birth/8-validate-declaration-review-page.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-1.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-10.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-2.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-3.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-4.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-5.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-6.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-7.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-8.spec.ts create mode 100644 e2e/testcases/birth/declarations/birth-declaration-9.spec.ts create mode 100644 e2e/testcases/birth/helpers.ts diff --git a/e2e/constants.ts b/e2e/constants.ts index 7683fdde3..e86d68b50 100644 --- a/e2e/constants.ts +++ b/e2e/constants.ts @@ -2,3 +2,4 @@ export const DOMAIN = process.env.DOMAIN || 'farajaland-dev.opencrvs.org' export const LOGIN_URL = 'https://login.' + DOMAIN export const AUTH_URL = 'https://auth.' + DOMAIN export const CLIENT_URL = 'https://register.' + DOMAIN +export const GATEWAY_HOST = 'https://gateway.' + DOMAIN diff --git a/e2e/helpers.ts b/e2e/helpers.ts index a768d6132..cb9d5d144 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -14,7 +14,7 @@ export async function createPIN(page: Page) { } } -async function getToken(username: string, password: string) { +export async function getToken(username: string, password: string) { const authUrl = `${AUTH_URL}/authenticate` const verifyUrl = `${AUTH_URL}/verifyCode` @@ -45,6 +45,27 @@ async function getToken(username: string, password: string) { return verifyBody.token } +export const goToSection = async ( + page: Page, + section: 'child' | 'informant' | 'father' | 'mother' | 'documents' | 'preview' +) => { + while (!page.url().includes(section)) { + await page.getByRole('button', { name: 'Continue' }).click() + } +} + +export const getRandomDate = (minAge: number, range: number) => { + const randomDate = new Date() + randomDate.setDate( + new Date().getDate() - + Math.random() * range - + minAge * 365 - + (minAge + 3) / 4 + ) + const [yyyy, mm, dd] = randomDate.toISOString().split('T')[0].split('-') + return { dd, mm, yyyy } +} + export async function ensureLoginPageReady(page: Page) { /* * Wait until config for loading page has been loaded diff --git a/e2e/testcases/birth/1-birth-event-declaration.spec.ts b/e2e/testcases/birth/1-birth-event-declaration.spec.ts index e156db129..9dc82bf28 100644 --- a/e2e/testcases/birth/1-birth-event-declaration.spec.ts +++ b/e2e/testcases/birth/1-birth-event-declaration.spec.ts @@ -1,54 +1,560 @@ -import { expect, test } from '@playwright/test' -import { createPIN, login } from '../../helpers' +import { expect, test, type Page } from '@playwright/test' +import { createPIN, getToken, login } from '../../helpers' +import { createDeclaration } from './helpers' + +import TEST_DATA_1 from './data/1-both-mother-and-father.json' +import faker from '@faker-js/faker' test.describe('1. Birth event declaration', () => { - test.beforeEach(async ({ page }) => { - await login(page, 'k.mweene', 'test') - await createPIN(page) - }) + test.describe.serial('Fill all form sections. Save & Exit', () => { + let page: Page + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test('1.1. Navigate to the birth event declaration page', async () => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.click('#header_new_event') + await expect(page.getByText('New Declaration')).toBeVisible() + await expect(page.getByText('Event type')).toBeVisible() + }) + + test.describe('1.2. Validate event selection page', async () => { + test('1.2.1 Validate the contents of the event type page', async () => { + /* + * Expected result: should show + * - Radio buttons of the events + * - Continue button + * - Exit button + */ + await expect(page.getByLabel('Birth')).toBeVisible() + await expect(page.getByLabel('Death')).toBeVisible() + await expect(page.getByLabel('Marriage')).toBeVisible() + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect(page.getByRole('button', { name: 'Exit' })).toBeVisible() + }) + + test('1.2.2 Click the "Continue" button without selecting any event', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + /* + * Expected result: should throw an error as below: + * "Please select the type of event" + */ + await expect( + page.getByText('Please select the type of event') + ).toBeVisible() + }) + + test('1.2.3 Select the "Birth" event and click "Continue" button', async () => { + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: User should navigate to the "Introduction" page + */ + }) + }) + + test.describe('1.3 Validate "Introduction" page', async () => { + test('1.3.1 Validate the contents of introduction page', async () => { + /* + * Expected result: should show + * - verbiage of birth event introduction + * - Continue Button + * - Exit Button + * - Save and exit button + * - 3dot menu (delete option) + */ + // await expect(page.getByLabel('Continue')).toBeVisible() + await expect( + page.getByText( + 'Introduce the birth registration process to the informant' + ) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Exit', exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Save & Exit' }) + ).toBeVisible() + + await page.locator('#eventToggleMenuToggleButton').click() + await expect( + page.getByRole('button', { name: 'Delete declaration' }) + ).toBeVisible() + }) + + test('1.3.2 Verify the verbiage of introduction page of birth event', async () => { + /* + * Expected result: should show + * - I am going to help you make a declaration of birth. + * - As the legal Informant it is important that all the information provided by you is accurate. + * - Once the declaration is processed you will receive you will receive an email to tell you + * when to visit the office to collect the certificate - Take your ID with you. + * - Make sure you collect the certificate. A birth certificate is critical for this child, + * especially to make their life easy later on. It will help to access health services, school examinations and government benefits. + */ + await expect( + page.getByText('I am going to help you make a declaration of birth.') + ).toBeVisible() + await expect( + page.getByText( + 'As the legal Informant it is important that all the information provided by you is accurate.' + ) + ).toBeVisible() + await expect( + page.getByText( + 'Once the declaration is processed you will receive you will receive an email to tell you when to visit the office to collect the certificate - Take your ID with you.' + ) + ).toBeVisible() + await expect( + page.getByText( + 'Make sure you collect the certificate. A birth certificate is critical for this child, especially to make their life easy later on. It will help to access health services, school examinations and government benefits.' + ) + ).toBeVisible() + }) + + test('1.3.3 Click the "continue" button', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + /* + * Expected result: should navigate to the "Child details" page + */ + await expect(page.getByText("Child's details")).toBeVisible() + }) + + test.describe('1.4 Validate "Child Details" page', async () => { + test('1.4.1 Validate the contents of Child details page', async () => { + /* + * Expected result: should see + * - Child details block + * - Continue Button + * - Exit Button + * - Save and exit button + * - 3dot menu (delete option) + */ + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Exit', exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Save & Exit' }) + ).toBeVisible() + + await page.locator('#eventToggleMenuToggleButton').click() + await expect( + page.getByRole('button', { name: 'Delete declaration' }) + ).toBeVisible() + }) + + test.skip('1.4.2 Validate Child details block', async () => {}) + + test('1.4.3 Click "continue"', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: should navigate to 'informant details' page + */ + await expect(page.getByText("Informant's details")).toBeVisible() + }) + }) + + test.describe('1.5 Validate "Informant details" page', async () => { + test('1.5.1 Validate the contents of informant details page', async () => { + /* + * Expected result: should see + * - Relationship to child dropdown + * - Phone number field + * - Email address field + * - Continue button + * - Exit button + * - Save &exit button + * - 3dot menu (delete option) + */ + await expect(page.getByText('Relationship to child')).toBeVisible() + await expect(page.getByText('Phone number')).toBeVisible() + await expect(page.getByText('Email')).toBeVisible() + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Exit', exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Save & Exit' }) + ).toBeVisible() + await page.locator('#eventToggleMenuToggleButton').click() + await expect( + page.getByRole('button', { name: 'Delete declaration' }) + ).toBeVisible() + }) + + // This test will fail + test.skip('1.5.2 Click the "continue" button without selecting any relationship to child', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: should throw error: + * - Required for registration + */ + await expect(page.getByText("Informant's details")).toBeVisible() + await expect( + page.getByText('Required for registration') + ).toBeVisible() + }) + + test('1.5.3 Select eny option in relatinship to child and click the "continue" button', async () => { + await page.getByText('Select').click() + await page.getByText('Mother', { exact: true }).click() + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: should navigate to "mother's details" page + */ + await expect( + page.getByText("Mother's details", { exact: true }) + ).toBeVisible() + }) + }) + }) + + test.describe('1.6 Validate "Mother Details" page', async () => { + test("1.6.1 validate the contents of mother's details page", async () => { + /* + * Expected result: should see + * - Mother's details block + * - Continue button + * - Exit button + * - Save & exit button + * - 3dot menu (delete option) + */ + + await expect( + page.getByText("Mother's details", { exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Exit', exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Save & Exit' }) + ).toBeVisible() + await page.locator('#eventToggleMenuToggleButton').click() + await expect( + page.getByRole('button', { name: 'Delete declaration' }) + ).toBeVisible() + }) + + test.skip("1.6.2 Validate Mother's details block", async () => {}) + + test('1.6.3 click continue', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: should navigate to "Father's details" page + */ + await expect( + page.getByText("Father's details", { exact: true }) + ).toBeVisible() + }) + }) + + test.describe('1.7 Validate "Father Details" page', async () => { + test("1.7.1 validate the contents of Father's details page", async () => { + /* + * Expected result: should see + * - Father's details block + * - Continue button + * - Exit button + * - Save & exit button + * - 3dot menu (delete option) + */ + + await expect( + page.getByText("Father's details", { exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Exit', exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Save & Exit' }) + ).toBeVisible() + await page.locator('#eventToggleMenuToggleButton').click() + await expect( + page.getByRole('button', { name: 'Delete declaration' }) + ).toBeVisible() + }) + + test.skip("1.7.2 Validate Father's details block", async () => {}) + + test('1.7.3 click continue', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: should navigate to "Supporting documents" page + */ + await expect( + page.getByText('Upload supporting documents', { exact: true }) + ).toBeVisible() + }) + }) + + test.describe('1.8 Validate "Supporting Document" page', async () => { + test('1.8.1 validate the contents of Supporting Document page', async () => { + /* + * Expected result: should see + * - Supporting Document block + * - Continue button + * - Exit button + * - Save & exit button + * - 3dot menu (delete option) + */ + + await expect( + page.getByText('Upload supporting documents', { exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Continue' }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Exit', exact: true }) + ).toBeVisible() + await expect( + page.getByRole('button', { name: 'Save & Exit' }) + ).toBeVisible() + await page.locator('#eventToggleMenuToggleButton').click() + await expect( + page.getByRole('button', { name: 'Delete declaration' }) + ).toBeVisible() + }) - test('1.1. Navigate to the birth event declaration page', async ({ - page - }) => { - await page.click('#header_new_event') - await page.waitForSelector('#continue') + test.skip('1.8.2 Validate Supporting Document block', async () => {}) + + test('1.8.3 click continue', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + + /* + * Expected result: should navigate to "Review" page + */ + await expect( + page + .getByRole('button', { name: 'Send for review' }) + .or(page.getByRole('button', { name: 'Register' })) + ).toBeVisible() + }) + }) + + test.describe('1.9 Validate "Save & Exit" Button ', async () => { + test('1.9.1 Click the "Save & Exit" button from any page', async () => { + await page.getByRole('button', { name: 'Save & Exit' }).click() + + /* + * Expected result: should open modal with: + * - Title: Save & Exit + * - Helper text: All inputted data will be kept secure for future editing. + * Are you ready to save any changes to this declaration form? + * - Cancel Button + * - Confirm Button + */ + await expect( + page.getByRole('heading', { name: 'Save & exit?' }) + ).toBeVisible() + await expect( + page.getByText( + 'All inputted data will be kept secure for future editing. Are you ready to save any changes to this declaration form?' + ) + ).toBeVisible() + await expect(page.getByRole('button', { name: 'Cancel' })).toBeVisible() + await expect( + page.getByRole('button', { name: 'Confirm' }) + ).toBeVisible() + }) + + test('1.9.2 Click Cancel', async () => { + await page.getByRole('button', { name: 'Cancel' }).click() + + /* + * Expected result: should close the modal + */ + await expect( + page.getByRole('heading', { name: 'Save & exit?' }) + ).toBeHidden() + }) + + test('1.9.3 Click Confirm', async () => { + await page.getByRole('button', { name: 'Save & Exit' }).click() + await page.getByRole('button', { name: 'Confirm' }).click() + + /* + * Expected result: should + * - be navigated to "in-progress" tab + * - find the declared birth event record on this page list with saved data + */ + await expect(page.locator('#content-name')).toHaveText('In progress') + await expect(page.getByText('0 seconds ago')).toBeVisible() + }) + }) }) + test.describe('1.10 Validate "Exit" Button', async () => { + test.beforeEach(async ({ page }) => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() - test('1.2. Validate event selection page', async ({ page }) => { - await page.click('#header_new_event') + await page.getByRole('button', { name: 'Exit', exact: true }).click() + }) - await test.step('1.2.1 Validate the contents of the event type page', async () => { + test('1.10.1 Click the "Exit" button from any page', async ({ page }) => { /* - * Expected result: should show - * - Radio buttons of the events - * - Continue button - * - Exit button + * Expected result: should open modal with: + * - Title: Exit without saving changes? + * - Helper text: You have unsaved changes on your declaration form. Are you sure you want to exit without saving? + * - Cancel Button + * - Confirm Button */ - await expect(page.locator('#select_birth_event')).toBeVisible() - await expect(page.locator('#select_death_event')).toBeVisible() - await expect(page.locator('#select_marriage_event')).toBeVisible() - await expect(page.locator('#goBack')).toBeVisible() - await expect(page.locator('#continue')).toBeVisible() + await expect( + page.getByRole('heading', { name: 'Exit without saving changes?' }) + ).toBeVisible() + await expect( + page.getByText( + 'You have unsaved changes on your declaration form. Are you sure you want to exit without saving?' + ) + ).toBeVisible() + await expect(page.getByRole('button', { name: 'Cancel' })).toBeVisible() + await expect(page.getByRole('button', { name: 'Confirm' })).toBeVisible() }) - await test.step('1.2.2 Click the "Continue" button without selecting any event', async () => { - await page.click('#continue') + test('1.10.2 Click Cancel', async ({ page }) => { + await page.getByRole('button', { name: 'Cancel' }).click() + /* - * Expected result: should throw an error as below: - * "Please select the type of event" + * Expected result: should close the modal */ - await expect(page.locator('#require-error')).toBeVisible() + await expect( + page.getByRole('heading', { name: 'Exit without saving changes?' }) + ).toBeHidden() }) - await test.step('1.2.3 Select the "Birth" event and click "Continue" button', async () => { - await page.click('#select_birth_event') - await page.click('#continue') + test('1.10.3 Click Confirm', async ({ page }) => { + await page.getByRole('button', { name: 'Confirm' }).click() /* - * Expected result: User should navigate to the "Introduction" page + * Expected result: should be navigated to "in-progress" tab but no draft will be saved */ + expect(page.locator('#content-name', { hasText: 'In progress' })) + await expect(page.getByText('0 seconds ago')).toBeHidden() + }) + }) + + test.describe('1.11 Validate "Delete Declaration" Button ', async () => { + test.beforeEach(async ({ page }) => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + + await page.locator('#eventToggleMenuToggleButton').click() + await page + .getByRole('button', { name: 'Delete declaration', exact: true }) + .click() + }) + + test('1.11.1 Click the "Delete Declaration" button from any page', async ({ + page + }) => { + /* + * Expected result: should open modal with: + * - Title: Delete draft? + * - Helper text: Are you certain you want to delete this draft declaration form? Please note, this action cant be undone. + * - Cancel Button + * - Confirm Button + */ + await expect( + page.getByRole('heading', { name: 'Delete draft?' }) + ).toBeVisible() await expect( - page.locator('#form_section_id_information-group') + page.getByText( + 'Are you certain you want to delete this draft declaration form? Please note, this action cant be undone.' + ) ).toBeVisible() + await expect(page.getByRole('button', { name: 'Cancel' })).toBeVisible() + await expect(page.getByRole('button', { name: 'Confirm' })).toBeVisible() + }) + + test('1.11.2 Click Cancel', async ({ page }) => { + await page.getByRole('button', { name: 'Cancel' }).click() + + /* + * Expected result: should close the modal + */ + await expect( + page.getByRole('heading', { name: 'Exit without saving changes?' }) + ).toBeHidden() + }) + + test('1.11.3 Click Confirm', async ({ page }) => { + await page.getByRole('button', { name: 'Confirm' }).click() + + /* + * Expected result: should be navigated to "in-progress" tab but no draft will be saved + */ + expect(page.locator('#content-name', { hasText: 'In progress' })) + await expect(page.getByText('0 seconds ago')).toBeHidden() + }) + }) + + test.describe('1.12 Technical test for shortcuts', () => { + test('Shortcut for quickly creating declarations', async () => { + const token = await getToken('k.mweene', 'test') + const res = await createDeclaration(token, { + child: { + firstNames: faker.name.firstName(), + familyName: faker.name.firstName(), + gender: TEST_DATA_1['Child details'].Sex.toLowerCase() as 'male' + }, + informant: { + type: TEST_DATA_1['Informant details'][ + 'Relationship to child' + ].toUpperCase() as 'MOTHER' + }, + attendant: { + type: TEST_DATA_1['Child details'][ + 'Attendant at birth' + ].toUpperCase() as 'PHYSICIAN' + }, + mother: { + firstNames: faker.name.firstName(), + familyName: faker.name.firstName() + }, + father: { + firstNames: faker.name.firstName(), + familyName: faker.name.firstName() + } + }) + expect(res).toStrictEqual({ + trackingId: expect.any(String), + compositionId: expect.any(String), + isPotentiallyDuplicate: false, + __typename: 'CreatedIds' + }) }) }) }) diff --git a/e2e/testcases/birth/2-validate-the-child-details-page.spec.ts b/e2e/testcases/birth/2-validate-the-child-details-page.spec.ts new file mode 100644 index 000000000..72609a098 --- /dev/null +++ b/e2e/testcases/birth/2-validate-the-child-details-page.spec.ts @@ -0,0 +1,341 @@ +import { test, expect } from '@playwright/test' +import { createPIN, goToSection, login } from '../../helpers' + +test.describe("2. Validate the child's details page", () => { + test.beforeEach(async ({ page }) => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + + await page.click('#header_new_event') + + await expect(page.getByText('New Declaration')).toBeVisible() + await expect(page.getByText('Event type')).toBeVisible() + await expect(page.getByLabel('Birth')).toBeVisible() + + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + + await page.getByRole('button', { name: 'Continue' }).click() + + await expect(page.getByText("Child's details")).toBeVisible() + }) + + test.describe('2.1 Validate "First Name(s)" text field', async () => { + test.describe('2.1.1 Enter Non-English characters', async () => { + test('Using name: Richard the 3rd', async ({ page }) => { + await page.locator('#firstNamesEng').fill('Richard the 3rd') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the input and not throw any error + */ + await expect(page.locator('#firstNamesEng_error')).toBeHidden() + }) + + test('Using name: John_Peter', async ({ page }) => { + await page.locator('#firstNamesEng').fill('John_Peter') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the input and not throw any error + */ + await expect(page.locator('#firstNamesEng_error')).toBeHidden() + }) + + test('Using name: John-Peter', async ({ page }) => { + await page.locator('#firstNamesEng').fill('John-Peter') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the input and not throw any error + */ + await expect(page.locator('#firstNamesEng_error')).toBeHidden() + }) + + test("Using name: O'Neill", async ({ page }) => { + await page.locator('#firstNamesEng').fill("O'Neill") + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the input and not throw any error + */ + await expect(page.locator('#firstNamesEng_error')).toBeHidden() + }) + + test('Using name: &er$on', async ({ page }) => { + await page.locator('#firstNamesEng').fill('&er$on') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the input and not throw any error + */ + await expect(page.locator('#firstNamesEng_error')).toBeVisible() + }) + + test.skip('Using name: X Æ A-Xii', async ({ page }) => { + await page.locator('#firstNamesEng').fill('X Æ A-Xii') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should throw error: + * - Input contains invalid characters. Please use only letters (a-z), numbers (0-9), hyphens (-), and underscores (_) + */ + await expect(page.locator('#firstNamesEng_error')).toBeVisible() + }) + }) + + test('2.1.2 Enter less than 33 English characters', async ({ page }) => { + await page.locator('#firstNamesEng').fill('Rakibul Islam') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the input and not throw any error + */ + await expect(page.locator('#firstNamesEng_error')).toBeHidden() + }) + + test('2.1.3 Enter Field as NULL', async ({ page }) => { + goToSection(page, 'preview') + + /* + * Expected result: should throw error in application review page: + * - Required for registration + */ + await expect( + page.locator('#required_label_child_firstNamesEng') + ).toBeVisible() + }) + + test('2.1.4 Enter more than 32 English characters', async ({ page }) => { + const LONG_NAME = 'Ovuvuevuevue Enyetuenwuevue Ugbemugbem Osas' + await page.locator('#firstNamesEng').fill(LONG_NAME) + await page.getByText('Birth declaration').click() + + /* + * Expected result: should clip the name to first 32 character + */ + await expect(page.locator('#firstNamesEng')).toHaveValue( + LONG_NAME.slice(0, 32) + ) + }) + }) + + test.describe('2.3 Validate the Sex dropdown field', async () => { + test('2.3.1 Select any dropdown value: "Female"', async ({ page }) => { + await page.locator('#gender').click() + await page.getByText('Female', { exact: true }).click() + + /* + * Expected result: "Female" should be selected + */ + await expect(page.locator('#gender', { hasText: 'Female' })).toBeVisible() + }) + + test('2.3.2 Set the field as null', async ({ page }) => { + goToSection(page, 'preview') + + /* + * Expected result: should throw error in application review page: + * - Required for registration + */ + await expect( + page + .locator('[data-test-id="row-value-Sex"]') + .getByText('Required for registration') + ).toBeVisible() + }) + }) + + test.describe('2.4 Validate the "DOB" field', async () => { + test('2.4.1 Enter date less than the current date', async ({ page }) => { + const yesterday = new Date() + yesterday.setDate(new Date().getDate() - 1) + const [yyyy, mm, dd] = yesterday.toISOString().split('T')[0].split('-') + + await page.getByPlaceholder('dd').fill(dd) + await page.getByPlaceholder('mm').fill(mm) + await page.getByPlaceholder('yyyy').fill(yyyy) + await page.getByText('Birth declaration').click() + + /* + * Expected result: should accept the date + */ + await expect(page.locator('#childBirthDate_error')).toBeHidden() + }) + + test('2.4.2 Enter invalid date', async ({ page }) => { + await page.getByPlaceholder('dd').fill('0') + await page.getByPlaceholder('mm').fill('0') + await page.getByPlaceholder('yyyy').fill('0') + await page.getByText('Birth declaration').click() + + /* + * Expected result: should not accept the invalid date and show error: + * - Must be a valid birth date + */ + await expect(page.locator('#childBirthDate_error')).toHaveText( + 'Must be a valid birth date' + ) + }) + + test('2.4.3 Enter future date', async ({ page }) => { + const futureDate = new Date() + futureDate.setDate(new Date().getDate() + 5) + const [yyyy, mm, dd] = futureDate.toISOString().split('T')[0].split('-') + + await page.getByPlaceholder('dd').fill(dd) + await page.getByPlaceholder('mm').fill(mm) + await page.getByPlaceholder('yyyy').fill(yyyy) + await page.getByText('Birth declaration').click() + + /* + * Expected result: should not accept the future date and show error: + * - Must be a valid birth date + */ + await expect(page.locator('#childBirthDate_error')).toHaveText( + 'Must be a valid birth date' + ) + }) + + test('2.4.4 Set the field as null', async ({ page }) => { + goToSection(page, 'preview') + + /* + * Expected result: should throw error in application review page: + * - Required for registration + */ + await expect( + page.locator('#required_label_child_childBirthDate') + ).toHaveText('Required for registration') + }) + }) + + test.describe('2.5 Validate delayed registration', async () => { + test.beforeEach(async ({ page }) => { + const delayedDate = new Date() + delayedDate.setDate(new Date().getDate() - 365 - 5) + const [yyyy, mm, dd] = delayedDate.toISOString().split('T')[0].split('-') + + await page.getByPlaceholder('dd').fill(dd) + await page.getByPlaceholder('mm').fill(mm) + await page.getByPlaceholder('yyyy').fill(yyyy) + await page.getByText('Birth declaration').click() + }) + + test('2.5.1 Enter date after delayed registration time period', async ({ + page + }) => { + const lateDate = new Date() + lateDate.setDate(new Date().getDate() - 365 + 5) + const [yyyy, mm, dd] = lateDate.toISOString().split('T')[0].split('-') + + await page.getByPlaceholder('dd').fill(dd) + await page.getByPlaceholder('mm').fill(mm) + await page.getByPlaceholder('yyyy').fill(yyyy) + await page.getByText('Birth declaration').click() + + /* + * Expected result: should show field: + * - Reason for delayed registration + */ + await expect( + page.getByText('Reason for delayed registration') + ).toBeHidden() + }) + + test('2.5.2 Enter date before delayed registration time period', async ({ + page + }) => { + /* + * Expected result: should show field: + * - Reason for delayed registration + */ + await expect( + page.getByText('Reason for delayed registration') + ).toBeVisible() + }) + + test('2.5.3 Enter "Reason for late registration"', async ({ page }) => { + await page.locator('#reasonForLateRegistration').fill('Lack of awareness') + + /* + * Expected result: should accept text + */ + await expect( + page.locator('#reasonForLateRegistration_error') + ).toBeHidden() + }) + + test('2.5.4 Set the field as null', async ({ page }) => { + goToSection(page, 'preview') + + /* + * Expected result: should throw error in application review page: + * - Required for registration + */ + await expect( + page + .getByRole('row', { name: 'Reason for delayed' }) + .locator('[data-test-id="row-value-Reason"]') + ).toHaveText('Required for registration') + }) + }) + + test.describe('2.6 Validate place of delivery field', async () => { + test('2.6.1 Keep field as null', async ({ page }) => { + goToSection(page, 'preview') + + /* + * Expected result: should throw error in application review page: + * - Required for registration + */ + await expect(page.locator('[data-test-id="row-value-Place"]')).toHaveText( + 'Required for registration' + ) + }) + + test('2.6.2.a Validate Health Institution', async ({ page }) => { + await test.step('Select Health Institution', async () => { + await page.locator('#placeOfBirth').click() + await page.getByText('Health Institution', { exact: true }).click() + /* + * Expected result: should show input field for: + * - Health institution + */ + await expect(page.getByText('Health Institution *')).toBeVisible() + }) + await test.step('Enter any health institution', async () => { + await page.locator('#birthLocation').fill('b') + await page.getByText('Bombwe Health Post').click() + /* + * Expected result: should select "Bombwe Health Post" as health institute + */ + await expect(page.locator('#birthLocation')).toHaveValue( + 'Bombwe Health Post' + ) + }) + }) + + test('2.6.2.b Select Residential address', async ({ page }) => { + await page.locator('#placeOfBirth').click() + await page.getByText('Residential address', { exact: true }).click() + + /* + * Expected result: should select "Residential address" as place of birth + */ + await expect(page.locator('#placeOfBirth')).toContainText( + 'Residential address' + ) + }) + + test('2.6.2.c Select Other', async ({ page }) => { + await page.locator('#placeOfBirth').click() + await page.getByText('Other', { exact: true }).click() + + /* + * Expected result: should select "Other" as place of birth + */ + await expect(page.locator('#placeOfBirth')).toContainText('Other') + }) + }) +}) diff --git a/e2e/testcases/birth/8-validate-declaration-review-page.spec.ts b/e2e/testcases/birth/8-validate-declaration-review-page.spec.ts new file mode 100644 index 000000000..3b8847451 --- /dev/null +++ b/e2e/testcases/birth/8-validate-declaration-review-page.spec.ts @@ -0,0 +1,1623 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('8. Validate declaration review page', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + gender: 'Male', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Physician', + birthType: 'Single', + weightAtBirth: 2.4, + placeOfBirth: 'Health Institution', + birthLocation: 'Bombwe Health Post', + informantType: 'Mother', + informantEmail: faker.internet.email(), + mother: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + birthDate: getRandomDate(20, 200), + nationality: 'Farajaland', + identifier: { + id: faker.random.numeric(10), + type: 'National ID' + }, + address: { + Country: 'Farajaland', + Province: 'Pualula', + District: 'Pili' + } + }, + father: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + birthDate: getRandomDate(22, 200), + nationality: 'Farajaland', + identifier: { + id: faker.random.numeric(10), + type: 'National ID' + }, + address: 'Same as mother' + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('8.1 Field agent actions', async () => { + test.describe('8.1.0 Fill up birth registration form', async () => { + test('8.1.0.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + await page + .locator('#birthLocation') + .fill(declaration.birthLocation.slice(0, 3)) + await page.getByText(declaration.birthLocation).click() + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page + .locator('#weightAtBirth') + .fill(declaration.weightAtBirth.toString()) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('8.1.0.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page + .locator('#registrationEmail') + .fill(declaration.informantEmail) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("8.1.0.3 Fill mother's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.mother.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.mother.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.mother.birthDate.yyyy) + + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { exact: true }) + .click() + + await page + .locator('#motherNationalId') + .fill(declaration.mother.identifier.id) + + await page.locator('#statePrimaryMother').click() + await page + .getByText(declaration.mother.address.Province, { exact: true }) + .click() + await page.locator('#districtPrimaryMother').click() + await page + .getByText(declaration.mother.address.District, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("8.1.0.4 Fill father's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.father.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.father.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.father.birthDate.yyyy) + + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { exact: true }) + .click() + + await page + .locator('#fatherNationalId') + .fill(declaration.father.identifier.id) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + }) + + test.describe('8.1.1 Navigate to declaration preview page', async () => { + test('8.1.1.1 Verify informations added in previous pages', async () => { + goToSection(page, 'preview') + + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + * - Change button + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + await expect(page.locator('#child-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Gender + * - Change button + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + await expect(page.locator('#child-content #Sex')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's date of birth + * - Change button + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + * - Change button + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation + ) + await expect(page.locator('#child-content #Place')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + * - Change button + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + await expect(page.locator('#child-content #Attendant')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Birth type + * - Change button + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + await expect(page.locator('#child-content #Type')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Weight at birth + * - Change button + */ + await expect(page.locator('#child-content #Weight')).toContainText( + declaration.weightAtBirth.toString() + ) + await expect(page.locator('#child-content #Weight')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Informant's relation to child + * - Change button + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + await expect( + page.locator('#informant-content #Relationship') + ).toContainText('Change') + + /* + * Expected result: should include + * - Informant's Email + * - Change button + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + await expect(page.locator('#informant-content #Email')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + * - Change button + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + await expect(page.locator('#mother-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's date of birth + * - Change button + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + await expect(page.locator('#mother-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's Nationality + * - Change button + */ + await expect( + page.locator('#mother-content #Nationality') + ).toContainText(declaration.mother.nationality) + await expect( + page.locator('#mother-content #Nationality') + ).toContainText('Change') + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + * - Change button + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + await expect(page.locator('#mother-content #Type')).toContainText( + 'Change' + ) + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + await expect(page.locator('#mother-content #ID')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's address + * - Change button + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.District + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + * - Change button + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + await expect(page.locator('#father-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's date of birth + * - Change button + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + await expect(page.locator('#father-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's Nationality + * - Change button + */ + await expect( + page.locator('#father-content #Nationality') + ).toContainText(declaration.father.nationality) + await expect( + page.locator('#father-content #Nationality') + ).toContainText('Change') + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + * - Change button + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + await expect(page.locator('#father-content #Type')).toContainText( + 'Change' + ) + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + await expect(page.locator('#father-content #ID')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's address + * - Change button + */ + await expect(page.locator('#father-content #Usual')).toContainText( + 'Yes' + ) + await expect(page.locator('#father-content #Usual')).toContainText( + 'Change' + ) + }) + }) + + test.describe('8.1.2 Click any "Change" link', async () => { + test("8.1.2.1 Change child's name", async () => { + await page.locator('#child-content #Full').getByText('Change').click() + + declaration.child.name = { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + } + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change child's name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + }) + + test("8.1.2.2 Change child's gender", async () => { + await page.locator('#child-content #Sex').getByText('Change').click() + + declaration.child.gender = 'Female' + + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change child's gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + }) + + test("8.1.2.3 Change child's birthday", async () => { + await page.locator('#child-content #Date').getByText('Change').click() + + declaration.child.birthDate = getRandomDate(0, 200) + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.child.birthDate.yyyy) + + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change child's birthday + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + }) + + test("8.1.2.4 Change child's birth location", async () => { + await page.locator('#child-content #Place').getByText('Change').click() + + declaration.birthLocation = 'Chikonkomene Health Post' + await page + .locator('#birthLocation') + .fill(declaration.birthLocation.slice(0, 3)) + await page.getByText(declaration.birthLocation).click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change child's place of birth + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation + ) + }) + + test('8.1.2.5 Change attendant at birth', async () => { + await page + .locator('#child-content #Attendant') + .getByText('Change') + .click() + + declaration.attendantAtBirth = 'Midwife' + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + }) + + test('8.1.2.6 Change type of birth', async () => { + await page.locator('#child-content #Type').getByText('Change').click() + + declaration.birthType = 'Twin' + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change type of birth + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + }) + + test("8.1.2.7 Change child's weight at birth", async () => { + await page.locator('#child-content #Weight').getByText('Change').click() + + declaration.weightAtBirth = 2.7 + await page + .locator('#weightAtBirth') + .fill(declaration.weightAtBirth.toString()) + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change weight at birth + */ + await expect(page.locator('#child-content #Weight')).toContainText( + declaration.weightAtBirth.toString() + ) + }) + + test('8.1.2.8 Change informant type', async () => { + await page + .locator('#informant-content #Relationship') + .getByText('Change') + .click() + + declaration.informantType = 'Father' + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change informant type + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + }) + + test('8.1.2.9 Change registration email', async () => { + await page + .locator('#informant-content #Email') + .getByText('Change') + .click() + + declaration.informantEmail = + declaration.father.name.firstNames.toLowerCase() + + '.' + + declaration.father.name.familyName.toLowerCase() + + (Math.random() * 1000).toFixed(0) + + '@gmail.com' + await page + .locator('#registrationEmail') + .fill(declaration.informantEmail) + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change registration email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + }) + + test("8.1.2.10 Change mother's name", async () => { + await page.locator('#mother-content #Full').getByText('Change').click() + + declaration.mother.name.firstNames = faker.name.firstName('female') + declaration.mother.name.familyName = faker.name.lastName('female') + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change mother's name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + }) + + test("8.1.2.11 Change mother's birthday", async () => { + await page.locator('#mother-content #Date').getByText('Change').click() + + declaration.mother.birthDate = getRandomDate(19, 200) + await page.getByPlaceholder('dd').fill(declaration.mother.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.mother.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.mother.birthDate.yyyy) + + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change mother's birthday + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + }) + + test("8.1.2.12 Change mother's nationality", async () => { + await page + .locator('#mother-content #Nationality') + .getByText('Change') + .click() + + declaration.mother.nationality = 'Holy See' + await page.locator('#nationality').click() + await page + .getByText(declaration.mother.nationality, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change mother's nationality + */ + await expect( + page.locator('#mother-content #Nationality') + ).toContainText(declaration.mother.nationality) + }) + + test("8.1.2.13 Change mother's ID type", async () => { + await page.locator('#mother-content #Type').getByText('Change').click() + + declaration.mother.identifier.type = 'Passport' + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change mother's ID type + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + }) + + test("8.1.2.14 Change mother's ID", async () => { + await page.locator('#mother-content #ID').getByText('Change').click() + declaration.mother.identifier.id = faker.random.numeric(10) + await page + .locator('#motherPassport') + .fill(declaration.mother.identifier.id) + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change mother's ID + */ + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + }) + + test("8.1.2.15 Change mother's address", async () => { + await page.locator('#mother-content #Usual').getByText('Change').click() + + declaration.mother.address.Province = 'Sulaka' + declaration.mother.address.District = 'Afue' + await page.locator('#statePrimaryMother').click() + await page + .getByText(declaration.mother.address.Province, { exact: true }) + .click() + await page.locator('#districtPrimaryMother').click() + await page + .getByText(declaration.mother.address.District, { exact: true }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.District + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Province + ) + }) + + test("8.1.2.16 Change father's name", async () => { + await page.locator('#father-content #Full').getByText('Change').click() + + declaration.father.name.firstNames = faker.name.firstName('male') + declaration.father.name.familyName = faker.name.lastName('male') + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change father's name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + }) + + test("8.1.2.17 Change father's birthday", async () => { + await page.locator('#father-content #Date').getByText('Change').click() + + declaration.father.birthDate = getRandomDate(21, 200) + await page.getByPlaceholder('dd').fill(declaration.father.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.father.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.father.birthDate.yyyy) + + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change father's birthday + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + }) + + test("8.1.2.18 Change father's nationality", async () => { + await page + .locator('#father-content #Nationality') + .getByText('Change') + .click() + + declaration.father.nationality = 'Holy See' + await page.locator('#nationality').click() + await page + .getByText(declaration.father.nationality, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change father's nationality + */ + await expect( + page.locator('#father-content #Nationality') + ).toContainText(declaration.father.nationality) + }) + + test("8.1.2.19 Change father's ID type", async () => { + await page.locator('#father-content #Type').getByText('Change').click() + + declaration.father.identifier.type = 'Passport' + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { + exact: true + }) + .click() + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change father's ID type + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + }) + + test("8.1.2.20 Change father's ID", async () => { + await page.locator('#father-content #ID').getByText('Change').click() + declaration.father.identifier.id = faker.random.numeric(10) + await page + .locator('#fatherPassport') + .fill(declaration.father.identifier.id) + await page.getByRole('button', { name: 'Back to review' }).click() + + /* + * Expected result: should change father's ID + */ + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + }) + }) + + test.describe('8.1.3 Validate supporting document', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.1.4 Validate additional comments box', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.1.5 Validate the declaration send button', async () => { + test.skip('Skipped for now', async () => {}) + }) + + test('8.1.6 Click send button', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + }) + + test('8.1.7 Confirm the declaration to send for review', async () => { + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await page.getByRole('button', { name: 'Sent for review' }).click() + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + /* + * Expected result: The declaration should be in sent for review + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('8.2 Registration agent actions', async () => { + test('8.2.1 Navigate to the declaration preview page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'Ready for review' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Review', exact: true }).click() + }) + test('8.2.1.1 Verify informations added in previous pages', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + * - Change button + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + await expect(page.locator('#child-content #Full')).toContainText('Change') + + /* + * Expected result: should include + * - Child's Gender + * - Change button + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + await expect(page.locator('#child-content #Sex')).toContainText('Change') + + /* + * Expected result: should include + * - Child's date of birth + * - Change button + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + * - Change button + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation + ) + await expect(page.locator('#child-content #Place')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + * - Change button + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + await expect(page.locator('#child-content #Attendant')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Birth type + * - Change button + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + await expect(page.locator('#child-content #Type')).toContainText('Change') + + /* + * Expected result: should include + * - Child's Weight at birth + * - Change button + */ + await expect(page.locator('#child-content #Weight')).toContainText( + declaration.weightAtBirth.toString() + ) + await expect(page.locator('#child-content #Weight')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Informant's relation to child + * - Change button + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + await expect( + page.locator('#informant-content #Relationship') + ).toContainText('Change') + + /* + * Expected result: should include + * - Informant's Email + * - Change button + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + await expect(page.locator('#informant-content #Email')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + * - Change button + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + await expect(page.locator('#mother-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's date of birth + * - Change button + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + await expect(page.locator('#mother-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's Nationality + * - Change button + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + await expect(page.locator('#mother-content #Nationality')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + * - Change button + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + await expect(page.locator('#mother-content #Type')).toContainText( + 'Change' + ) + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + await expect(page.locator('#mother-content #ID')).toContainText('Change') + + /* + * Expected result: should include + * - Mother's address + * - Change button + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.District + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + * - Change button + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + await expect(page.locator('#father-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's date of birth + * - Change button + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + await expect(page.locator('#father-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's Nationality + * - Change button + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + await expect(page.locator('#father-content #Nationality')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + * - Change button + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + await expect(page.locator('#father-content #Type')).toContainText( + 'Change' + ) + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + await expect(page.locator('#father-content #ID')).toContainText('Change') + + /* + * Expected result: should include + * - Father's address + * - Change button + */ + await expect(page.locator('#father-content #Usual')).toContainText('Yes') + await expect(page.locator('#father-content #Usual')).toContainText( + 'Change' + ) + }) + + test.describe('8.2.2 Click any "Change" link', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.2.3 Validate supporting document', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.2.4 Validate additional comments box', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.2.5 Validate the declaration send button', async () => { + test.skip('Skipped for now', async () => {}) + }) + + test('8.2.6 Click send button', async () => { + await page.getByRole('button', { name: 'Send for approval' }).click() + await expect(page.getByText('Send for approval?')).toBeVisible() + }) + + test('8.2.7 Confirm the declaration to send for approval', async () => { + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await page.getByRole('button', { name: 'Sent for approval' }).click() + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + /* + * Expected result: The declaration should be in sent for approval + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('8.3 Local registrar actions', async () => { + test('8.3.1 Navigate to the declaration preview page', async () => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'Ready for review' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Review', exact: true }).click() + }) + test('8.3.1.1 Verify informations added in previous pages', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + * - Change button + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + await expect(page.locator('#child-content #Full')).toContainText('Change') + + /* + * Expected result: should include + * - Child's Gender + * - Change button + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + await expect(page.locator('#child-content #Sex')).toContainText('Change') + + /* + * Expected result: should include + * - Child's date of birth + * - Change button + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + * - Change button + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation + ) + await expect(page.locator('#child-content #Place')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + * - Change button + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + await expect(page.locator('#child-content #Attendant')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Child's Birth type + * - Change button + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + await expect(page.locator('#child-content #Type')).toContainText('Change') + + /* + * Expected result: should include + * - Child's Weight at birth + * - Change button + */ + await expect(page.locator('#child-content #Weight')).toContainText( + declaration.weightAtBirth.toString() + ) + await expect(page.locator('#child-content #Weight')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Informant's relation to child + * - Change button + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + await expect( + page.locator('#informant-content #Relationship') + ).toContainText('Change') + + /* + * Expected result: should include + * - Informant's Email + * - Change button + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + await expect(page.locator('#informant-content #Email')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + * - Change button + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + await expect(page.locator('#mother-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's date of birth + * - Change button + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + await expect(page.locator('#mother-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's Nationality + * - Change button + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + await expect(page.locator('#mother-content #Nationality')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + * - Change button + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + await expect(page.locator('#mother-content #Type')).toContainText( + 'Change' + ) + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + await expect(page.locator('#mother-content #ID')).toContainText('Change') + + /* + * Expected result: should include + * - Mother's address + * - Change button + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.District + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.Province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + * - Change button + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + await expect(page.locator('#father-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's date of birth + * - Change button + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + await expect(page.locator('#father-content #Full')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's Nationality + * - Change button + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + await expect(page.locator('#father-content #Nationality')).toContainText( + 'Change' + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + * - Change button + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + await expect(page.locator('#father-content #Type')).toContainText( + 'Change' + ) + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + await expect(page.locator('#father-content #ID')).toContainText('Change') + + /* + * Expected result: should include + * - Father's address + * - Change button + */ + await expect(page.locator('#father-content #Usual')).toContainText('Yes') + await expect(page.locator('#father-content #Usual')).toContainText( + 'Change' + ) + }) + + test.describe('8.3.2 Click any "Change" link', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.3.3 Validate supporting document', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.3.4 Validate additional comments box', async () => { + test.skip('Skipped for now', async () => {}) + }) + test.describe('8.3.5 Validate the register button', async () => { + test.skip('Skipped for now', async () => {}) + }) + + test('8.3.6 Click send button', async () => { + await page.getByRole('button', { name: 'Register' }).click() + await expect(page.getByText('Register the birth?')).toBeVisible() + }) + + test('8.3.7 Confirm the declaration to ready for print', async () => { + await page.locator('#submit_confirm').click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await page.getByRole('button', { name: 'Ready to print' }).click() + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + /* + * Expected result: The declaration should be in Ready to print + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-1.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-1.spec.ts new file mode 100644 index 000000000..bb1dc5c27 --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-1.spec.ts @@ -0,0 +1,804 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('1. Birth declaration case - 1', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + gender: 'Male', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Physician', + birthType: 'Single', + weightAtBirth: 2.4, + placeOfBirth: 'Health Institution', + birthLocation: 'Golden Valley Rural Health Centre', + informantType: 'Mother', + informantEmail: faker.internet.email(), + mother: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + birthDate: getRandomDate(20, 200), + nationality: 'Farajaland', + identifier: { + id: faker.random.numeric(10), + type: 'National ID' + }, + address: { + country: 'Farajaland', + province: 'Sulaka', + district: 'Irundu', + urbanOrRural: 'Urban', + town: faker.address.city(), + residentialArea: faker.address.county(), + street: faker.address.streetName(), + number: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + maritalStatus: 'Single', + levelOfEducation: 'No schooling' + }, + father: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + birthDate: getRandomDate(22, 200), + nationality: 'Gabon', + identifier: { + id: faker.random.numeric(10), + type: 'National ID' + }, + maritalStatus: 'Single', + levelOfEducation: 'No schooling', + address: { + sameAsMother: true + } + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('1.1 Declaratin started by FA', async () => { + test.beforeAll(async () => { + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('1.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page.getByPlaceholder('yyyy').fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + await page + .locator('#birthLocation') + .fill(declaration.birthLocation.slice(0, 3)) + await page.getByText(declaration.birthLocation).click() + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page + .locator('#weightAtBirth') + .fill(declaration.weightAtBirth.toString()) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('1.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page.locator('#registrationEmail').fill(declaration.informantEmail) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("1.1.3 Fill mother's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.mother.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.mother.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.mother.birthDate.yyyy) + + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { exact: true }) + .click() + + await page + .locator('#motherNationalId') + .fill(declaration.mother.identifier.id) + + await page.locator('#statePrimaryMother').click() + await page + .getByText(declaration.mother.address.province, { exact: true }) + .click() + await page.locator('#districtPrimaryMother').click() + await page + .getByText(declaration.mother.address.district, { exact: true }) + .click() + + await page + .locator('#cityPrimaryMother') + .fill(declaration.mother.address.town) + await page + .locator('#addressLine1UrbanOptionPrimaryMother') + .fill(declaration.mother.address.residentialArea) + await page + .locator('#addressLine2UrbanOptionPrimaryMother') + .fill(declaration.mother.address.street) + await page + .locator('#addressLine3UrbanOptionPrimaryMother') + .fill(declaration.mother.address.number) + await page + .locator('#postalCodePrimaryMother') + .fill(declaration.mother.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.mother.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.mother.levelOfEducation, { exact: true }) + .click() + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("1.1.4 Fill father's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.father.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.father.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.father.birthDate.yyyy) + + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { exact: true }) + .click() + + await page + .locator('#fatherNationalId') + .fill(declaration.father.identifier.id) + + await page.locator('#nationality').click() + await page + .getByText(declaration.father.nationality, { exact: true }) + .click() + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.father.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.father.levelOfEducation, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('1.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('1.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Child's Weight at birth + */ + await expect(page.locator('#child-content #Weight')).toContainText( + declaration.weightAtBirth.toString() + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.residentialArea + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.street + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.number + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('Yes') + }) + + test('1.1.7 Send for review', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for review' }).click() + + /* + * Expected result: The declaration should be in sent for review + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('1.2 Declaration Review by RA', async () => { + test('1.2.1 Navigate to the declaration review page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'Ready for review' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Review', exact: true }).click() + }) + + test('1.2.2 Verify informations in review page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Child's Weight at birth + */ + await expect(page.locator('#child-content #Weight')).toContainText( + declaration.weightAtBirth.toString() + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.residentialArea + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.street + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.number + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('Yes') + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-10.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-10.spec.ts new file mode 100644 index 000000000..007904898 --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-10.spec.ts @@ -0,0 +1,299 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' + +test.describe.serial('10. Birth declaration case - 10', () => { + let page: Page + const required = 'Required for registration' + const declaration = { + child: { + name: { + firstNames: faker.name.lastName() + } + }, + informantType: 'Father', + mother: { + detailsDontExist: true + }, + father: { + detailsDontExist: false + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('10.1 Declaration started by FA', async () => { + test.beforeAll(async () => { + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('10.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('10.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("10.1.3 Fill mother's details", async () => { + await page.getByLabel("Mother's details are not available").check() + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("10.1.4 Fill father's details", async () => { + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('10.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('10.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * * should require + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText(required) + + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + + /* + * Expected result: should require + * - Reason of why mother's details not available + */ + await expect(page.locator('#mother-content #Reason')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + required + ) + }) + + test('10.1.7 Send for review', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for review' }).click() + + /* + * Expected result: The declaration should be in sent for review + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames}` + }) + ).toBeVisible() + }) + }) + + test.describe('10.2 Declaration Review by RA', async () => { + test('10.2.1 Navigate to the declaration review page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'In Progress' }).click() + await page.getByRole('button', { name: 'Field Agents' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Update', exact: true }).click() + }) + + test('10.2.2 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * * should require + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText(required) + + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + + /* + * Expected result: should require + * - Reason of why mother's details not available + */ + await expect(page.locator('#mother-content #Reason')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + required + ) + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-2.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-2.spec.ts new file mode 100644 index 000000000..92594a632 --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-2.spec.ts @@ -0,0 +1,876 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('2. Birth declaration case - 2', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + gender: 'Female', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Nurse', + birthType: 'Twin', + placeOfBirth: 'Residential address', + birthLocation: { + country: 'Farajaland', + province: 'Pualula', + district: 'Funabuli', + urbanOrRural: 'Urban', + town: faker.address.city(), + residentialArea: faker.address.county(), + street: faker.address.streetName(), + number: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + informantType: 'Father', + informantEmail: faker.internet.email(), + mother: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + age: 21, + nationality: 'Fiji', + identifier: { + id: faker.random.numeric(12), + type: 'Passport' + }, + address: { + country: 'Farajaland', + province: 'Sulaka', + district: 'Irundu', + urbanOrRural: 'Rural', + village: faker.address.county() + }, + maritalStatus: 'Married', + levelOfEducation: 'Primary' + }, + father: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + age: 25, + nationality: 'Farajaland', + identifier: { + id: faker.random.numeric(8), + type: 'Passport' + }, + maritalStatus: 'Married', + levelOfEducation: 'Primary', + address: { + sameAsMother: 'No', + country: 'Farajaland', + province: 'Sulaka', + district: 'Zobwe', + urbanOrRural: 'Urban', + town: faker.address.city(), + residentialArea: faker.address.county(), + street: faker.address.streetName(), + number: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + } + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('2.1 Declaratin started by FA', async () => { + test.beforeAll(async () => { + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('2.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page.getByPlaceholder('yyyy').fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + + await page.locator('#statePlaceofbirth').click() + await page + .getByText(declaration.birthLocation.province, { + exact: true + }) + .click() + + await page.locator('#districtPlaceofbirth').click() + await page + .getByText(declaration.birthLocation.district, { + exact: true + }) + .click() + + await page + .locator('#cityPlaceofbirth') + .fill(declaration.birthLocation.town) + await page + .locator('#addressLine1UrbanOptionPlaceofbirth') + .fill(declaration.birthLocation.residentialArea) + await page + .locator('#addressLine2UrbanOptionPlaceofbirth') + .fill(declaration.birthLocation.street) + await page + .locator('#addressLine3UrbanOptionPlaceofbirth') + .fill(declaration.birthLocation.number) + await page + .locator('#postalCodePlaceofbirth') + .fill(declaration.birthLocation.postcodeOrZip) + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('2.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page.locator('#registrationEmail').fill(declaration.informantEmail) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("2.1.3 Fill mother's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.mother.age.toString()) + + await page.locator('#nationality').click() + await page + .getByText(declaration.mother.nationality, { exact: true }) + .click() + + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { exact: true }) + .click() + + await page + .locator('#motherPassport') + .fill(declaration.mother.identifier.id) + + await page.locator('#statePrimaryMother').click() + await page + .getByText(declaration.mother.address.province, { exact: true }) + .click() + await page.locator('#districtPrimaryMother').click() + await page + .getByText(declaration.mother.address.district, { exact: true }) + .click() + + await page.getByLabel('Rural').check() + await page + .locator('#addressLine1RuralOptionPrimaryMother') + .fill(declaration.mother.address.village) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.mother.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.mother.levelOfEducation, { exact: true }) + .click() + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("2.1.4 Fill father's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.father.age.toString()) + + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { exact: true }) + .click() + + await page + .locator('#fatherPassport') + .fill(declaration.father.identifier.id) + + await page.getByLabel('No', { exact: true }).check() + await page.locator('#statePrimaryFather').click() + await page + .getByText(declaration.father.address.province, { exact: true }) + .click() + await page.locator('#districtPrimaryFather').click() + await page + .getByText(declaration.father.address.district, { exact: true }) + .click() + await page.getByLabel(declaration.father.address.urbanOrRural).check() + await page + .locator('#cityPrimaryFather') + .fill(declaration.father.address.town) + await page + .locator('#addressLine1UrbanOptionPrimaryFather') + .fill(declaration.father.address.residentialArea) + await page + .locator('#addressLine2UrbanOptionPrimaryFather') + .fill(declaration.father.address.street) + await page + .locator('#addressLine3UrbanOptionPrimaryFather') + .fill(declaration.father.address.number) + await page + .locator('#postalCodePrimaryFather') + .fill(declaration.father.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.father.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.father.levelOfEducation, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + test('2.1.5 Go To Preview', async () => { + goToSection(page, 'preview') + }) + + test('2.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.town + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.residentialArea + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.street + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.number + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.postcodeOrZip + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's age + */ + await expect(page.locator('#mother-content #Age')).toContainText( + declaration.mother.age + ' years' + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.village + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Age')).toContainText( + declaration.father.age + ' years' + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('No') + + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.province + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.town + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.residentialArea + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.street + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.number + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.postcodeOrZip + ) + }) + + test('2.1.7 Send for review', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for review' }).click() + + /* + * Expected result: The declaration should be in sent for review + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('2.2 Declaration Review by RA', async () => { + test('2.2.1 Navigate to the declaration review page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'Ready for review' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Review', exact: true }).click() + }) + + test('2.2.2 Verify informations in review page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.town + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.residentialArea + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.street + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.number + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.postcodeOrZip + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's age + */ + await expect(page.locator('#mother-content #Age')).toContainText( + declaration.mother.age + ' years' + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.province + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.village + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Age')).toContainText( + declaration.father.age + ' years' + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('No') + + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.province + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.town + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.residentialArea + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.street + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.number + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.postcodeOrZip + ) + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-3.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-3.spec.ts new file mode 100644 index 000000000..1fc9770ff --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-3.spec.ts @@ -0,0 +1,1073 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('3. Birth declaration case - 3', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName() + '_Peter', + familyName: faker.name.lastName() + }, + gender: 'Unknown', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Midwife', + birthType: 'Triplet', + placeOfBirth: 'Residential address', + birthLocation: { + country: 'Farajaland', + province: 'Pualula', + district: 'Funabuli', + urbanOrRural: 'Rural', + village: faker.address.county() + }, + informantType: 'Grandfather', + informantEmail: faker.internet.email(), + informant: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + birthDate: getRandomDate(40, 200), + nationality: 'Farajaland', + identifier: { + id: faker.random.numeric(10), + type: 'National ID' + }, + address: { + country: 'Farajaland', + province: 'Chuminga', + district: 'Ama', + urbanOrRural: 'Urban', + town: faker.address.city(), + residentialArea: faker.address.county(), + street: faker.address.streetName(), + number: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + } + }, + mother: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + birthDate: getRandomDate(20, 200), + nationality: 'Farajaland', + identifier: { + id: faker.random.numeric(9), + type: 'Birth Registration Number' + }, + address: { + country: 'Djibouti', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + maritalStatus: 'Widowed', + levelOfEducation: 'Secondary' + }, + father: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + birthDate: getRandomDate(22, 200), + nationality: 'Gabon', + identifier: { + id: faker.random.numeric(11), + type: 'Birth Registration Number' + }, + maritalStatus: 'Widowed', + levelOfEducation: 'Secondary', + address: { + sameAsMother: false, + country: 'Farajaland', + province: 'Chuminga', + district: 'Nsali', + urbanOrRural: 'Rural', + village: faker.address.county() + } + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('3.1 Declaratin started by RA', async () => { + test.beforeAll(async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('3.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page.getByPlaceholder('yyyy').fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + await page.locator('#statePlaceofbirth').click() + await page + .getByText(declaration.birthLocation.province, { + exact: true + }) + .click() + + await page.locator('#districtPlaceofbirth').click() + await page + .getByText(declaration.birthLocation.district, { + exact: true + }) + .click() + await page.getByLabel(declaration.birthLocation.urbanOrRural).check() + await page + .locator('#addressLine1RuralOptionPlaceofbirth') + .fill(declaration.birthLocation.village) + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('3.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page.locator('#registrationEmail').fill(declaration.informantEmail) + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + /* + * Expected result: should show additional fields: + * - Full Name + * - Date of birth + * - Nationality + * - Id + * - Usual place of residence + */ + await page + .locator('#firstNamesEng') + .fill(declaration.informant.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.informant.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.informant.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.informant.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.informant.birthDate.yyyy) + + await page.locator('#informantIdType').click() + await page + .getByText(declaration.informant.identifier.type, { exact: true }) + .click() + + await page + .locator('#informantNationalId') + .fill(declaration.informant.identifier.id) + + await page.locator('#statePrimaryInformant').click() + await page + .getByText(declaration.informant.address.province, { exact: true }) + .click() + await page.locator('#districtPrimaryInformant').click() + await page + .getByText(declaration.informant.address.district, { exact: true }) + .click() + + await page + .locator('#cityPrimaryInformant') + .fill(declaration.informant.address.town) + await page + .locator('#addressLine1UrbanOptionPrimaryInformant') + .fill(declaration.informant.address.residentialArea) + await page + .locator('#addressLine2UrbanOptionPrimaryInformant') + .fill(declaration.informant.address.street) + await page + .locator('#addressLine3UrbanOptionPrimaryInformant') + .fill(declaration.informant.address.number) + await page + .locator('#postalCodePrimaryInformant') + .fill(declaration.informant.address.postcodeOrZip) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("3.1.3 Fill mother's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.mother.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.mother.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.mother.birthDate.yyyy) + + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { exact: true }) + .click() + + await page + .locator('#motherBirthRegistrationNumber') + .fill(declaration.mother.identifier.id) + + await page.locator('#countryPrimaryMother').click() + await page + .getByText(declaration.mother.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryMother') + .fill(declaration.mother.address.state) + await page + .locator('#internationalDistrictPrimaryMother') + .fill(declaration.mother.address.district) + await page + .locator('#internationalCityPrimaryMother') + .fill(declaration.mother.address.town) + await page + .locator('#internationalAddressLine1PrimaryMother') + .fill(declaration.mother.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryMother') + .fill(declaration.mother.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryMother') + .fill(declaration.mother.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryMother') + .fill(declaration.mother.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.mother.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.mother.levelOfEducation, { exact: true }) + .click() + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("3.1.4 Fill father's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.father.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.father.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.father.birthDate.yyyy) + + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { exact: true }) + .click() + + await page + .locator('#fatherBirthRegistrationNumber') + .fill(declaration.father.identifier.id) + + await page.locator('#nationality').click() + await page + .getByText(declaration.father.nationality, { exact: true }) + .click() + + await page.getByLabel('No', { exact: true }).check() + await page.locator('#statePrimaryFather').click() + await page + .getByText(declaration.father.address.province, { exact: true }) + .click() + await page.locator('#districtPrimaryFather').click() + await page + .getByText(declaration.father.address.district, { exact: true }) + .click() + await page.getByLabel(declaration.father.address.urbanOrRural).check() + await page + .locator('#addressLine1RuralOptionPrimaryFather') + .fill(declaration.father.address.village) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.father.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.father.levelOfEducation, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test.skip('3.1.5 Add documents', async () => { + goToSection(page, 'documents') + }) + + test('3.1.6 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('3.1.7 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.village + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + /* + * Expected result: should include + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.firstNames + ) + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.familyName + ) + + /* + * Expected result: should include + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Date')).toContainText( + format( + new Date( + Number(declaration.informant.birthDate.yyyy), + Number(declaration.informant.birthDate.mm) - 1, + Number(declaration.informant.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Informant's Nationality + */ + await expect( + page.locator('#informant-content #Nationality') + ).toContainText(declaration.informant.nationality) + + /* + * Expected result: should include + * - Informant's address + */ + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.country + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.district + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.province + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.town + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.residentialArea + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.street + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.number + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.state + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine1 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine2 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine3 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('No') + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.province + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.village + ) + }) + + test('3.1.8 Send for approval', async () => { + await page.getByRole('button', { name: 'Send for approval' }).click() + await expect(page.getByText('Send for approval?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for approval' }).click() + + /* + * Expected result: The declaration should be in sent for approval + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('3.2 Declaration Review by Local Registrar', async () => { + test('3.2.1 Navigate to the declaration review page', async () => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'Ready for review' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Review', exact: true }).click() + }) + + test('3.2.2 Verify informations in review page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.village + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + /* + * Expected result: should include + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.firstNames + ) + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.familyName + ) + + /* + * Expected result: should include + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Date')).toContainText( + format( + new Date( + Number(declaration.informant.birthDate.yyyy), + Number(declaration.informant.birthDate.mm) - 1, + Number(declaration.informant.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Informant's Nationality + */ + await expect( + page.locator('#informant-content #Nationality') + ).toContainText(declaration.informant.nationality) + + /* + * Expected result: should include + * - Informant's address + */ + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.country + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.district + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.province + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.town + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.residentialArea + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.street + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.number + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + format( + new Date( + Number(declaration.mother.birthDate.yyyy), + Number(declaration.mother.birthDate.mm) - 1, + Number(declaration.mother.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + * - Mother's Id Number + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + await expect(page.locator('#mother-content #ID')).toContainText( + declaration.mother.identifier.id + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.state + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine1 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine2 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine3 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + * - Father's Id Number + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + await expect(page.locator('#father-content #ID')).toContainText( + declaration.father.identifier.id + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('No') + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.province + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.village + ) + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-4.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-4.spec.ts new file mode 100644 index 000000000..7651e147f --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-4.spec.ts @@ -0,0 +1,1070 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('4. Birth declaration case - 4', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName() + '-Peter', + familyName: faker.name.lastName() + }, + gender: 'Unknown', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Other paramedical personnel', + birthType: 'Quadruplet', + placeOfBirth: 'Other', + birthLocation: { + country: 'Farajaland', + province: 'Pualula', + district: 'Funabuli', + urbanOrRural: 'Urban', + town: faker.address.city(), + residentialArea: faker.address.county(), + street: faker.address.streetName(), + number: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + informantType: 'Grandmother', + informantEmail: faker.internet.email(), + informant: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + age: 60, + nationality: 'Guernsey', + identifier: { + id: faker.random.numeric(10), + type: 'Passport' + }, + address: { + country: 'Farajaland', + province: 'Chuminga', + district: 'Ama', + urbanOrRural: 'Rural', + village: faker.address.county() + } + }, + mother: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + age: 25, + nationality: 'Farajaland', + identifier: { + type: 'None' + }, + address: { + country: 'Guam', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + maritalStatus: 'Divorced', + levelOfEducation: 'Tertiary' + }, + father: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + birthDate: getRandomDate(22, 200), + nationality: 'Farajaland', + identifier: { + type: 'None' + }, + maritalStatus: 'Divorced', + levelOfEducation: 'Tertiary', + address: { + sameAsMother: false, + country: 'Grenada', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + } + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('4.1 Declaratin started by RA', async () => { + test.beforeAll(async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('4.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page.getByPlaceholder('yyyy').fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + await page.locator('#statePlaceofbirth').click() + await page + .getByText(declaration.birthLocation.province, { + exact: true + }) + .click() + + await page.locator('#districtPlaceofbirth').click() + await page + .getByText(declaration.birthLocation.district, { + exact: true + }) + .click() + + await page + .locator('#cityPlaceofbirth') + .fill(declaration.birthLocation.town) + await page + .locator('#addressLine1UrbanOptionPlaceofbirth') + .fill(declaration.birthLocation.residentialArea) + await page + .locator('#addressLine2UrbanOptionPlaceofbirth') + .fill(declaration.birthLocation.street) + await page + .locator('#addressLine3UrbanOptionPlaceofbirth') + .fill(declaration.birthLocation.number) + await page + .locator('#postalCodePlaceofbirth') + .fill(declaration.birthLocation.postcodeOrZip) + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('4.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page.locator('#registrationEmail').fill(declaration.informantEmail) + + await page.waitForTimeout(500) + + /* + * Expected result: should show additional fields: + * - Full Name + * - Date of birth + * - Nationality + * - Id + * - Usual place of residence + */ + await page + .locator('#firstNamesEng') + .fill(declaration.informant.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.informant.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.informant.age.toString()) + + await page.locator('#nationality').click() + await page + .getByText(declaration.informant.nationality, { exact: true }) + .click() + + await page.locator('#informantIdType').click() + await page + .getByText(declaration.informant.identifier.type, { exact: true }) + .click() + + await page + .locator('#informantPassport') + .fill(declaration.informant.identifier.id) + + await page.locator('#statePrimaryInformant').click() + await page + .getByText(declaration.informant.address.province, { exact: true }) + .click() + await page.locator('#districtPrimaryInformant').click() + await page + .getByText(declaration.informant.address.district, { exact: true }) + .click() + await page.getByLabel(declaration.informant.address.urbanOrRural).check() + + await page + .locator('#addressLine1RuralOptionPrimaryInformant') + .fill(declaration.informant.address.village) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("4.1.3 Fill mother's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.mother.age.toString()) + + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { exact: true }) + .click() + + await page.locator('#countryPrimaryMother').click() + await page + .getByText(declaration.mother.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryMother') + .fill(declaration.mother.address.state) + await page + .locator('#internationalDistrictPrimaryMother') + .fill(declaration.mother.address.district) + await page + .locator('#internationalCityPrimaryMother') + .fill(declaration.mother.address.town) + await page + .locator('#internationalAddressLine1PrimaryMother') + .fill(declaration.mother.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryMother') + .fill(declaration.mother.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryMother') + .fill(declaration.mother.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryMother') + .fill(declaration.mother.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.mother.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.mother.levelOfEducation, { exact: true }) + .click() + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("4.1.4 Fill father's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + + await page.getByPlaceholder('dd').fill(declaration.father.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.father.birthDate.mm) + await page + .getByPlaceholder('yyyy') + .fill(declaration.father.birthDate.yyyy) + + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { exact: true }) + .click() + + await page.getByLabel('No', { exact: true }).check() + await page.locator('#countryPrimaryFather').click() + await page + .getByText(declaration.father.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryFather') + .fill(declaration.father.address.state) + await page + .locator('#internationalDistrictPrimaryFather') + .fill(declaration.father.address.district) + await page + .locator('#internationalCityPrimaryFather') + .fill(declaration.father.address.town) + await page + .locator('#internationalAddressLine1PrimaryFather') + .fill(declaration.father.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryFather') + .fill(declaration.father.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryFather') + .fill(declaration.father.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryFather') + .fill(declaration.father.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.father.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.father.levelOfEducation, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('4.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('4.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.town + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.residentialArea + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.street + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.number + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.postcodeOrZip + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + /* + * Expected result: should include + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.firstNames + ) + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.familyName + ) + + /* + * Expected result: should include + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Age')).toContainText( + declaration.informant.age + ' years' + ) + + /* + * Expected result: should include + * - Informant's Nationality + */ + await expect( + page.locator('#informant-content #Nationality') + ).toContainText(declaration.informant.nationality) + + /* + * Expected result: should include + * - Informant's address + */ + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.country + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.district + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.province + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.village + ) + + /* + * Expected result: should include + * - Informant's Type of Id + * - Informant's Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + declaration.informant.identifier.type + ) + await expect(page.locator('#informant-content #ID')).toContainText( + declaration.informant.identifier.id + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Age')).toContainText( + declaration.mother.age + ' years' + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.state + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine1 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine2 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine3 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('No') + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.state + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine1 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine2 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine3 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.postcodeOrZip + ) + }) + + test('4.1.7 Send for approval', async () => { + await page.getByRole('button', { name: 'Send for approval' }).click() + await expect(page.getByText('Send for approval?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for approval' }).click() + + /* + * Expected result: The declaration should be in sent for approval + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('4.2 Declaration Review by Local Registrar', async () => { + test('4.2.1 Navigate to the declaration review page', async () => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'Ready for review' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Review', exact: true }).click() + }) + + test('4.2.2 Verify informations in review page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.town + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.residentialArea + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.street + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.number + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.postcodeOrZip + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + /* + * Expected result: should include + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.firstNames + ) + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.familyName + ) + + /* + * Expected result: should include + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Age')).toContainText( + declaration.informant.age + ' years' + ) + + /* + * Expected result: should include + * - Informant's Nationality + */ + await expect( + page.locator('#informant-content #Nationality') + ).toContainText(declaration.informant.nationality) + + /* + * Expected result: should include + * - Informant's address + */ + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.country + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.district + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.province + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.village + ) + + /* + * Expected result: should include + * - Informant's Type of Id + * - Informant's Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + declaration.informant.identifier.type + ) + await expect(page.locator('#informant-content #ID')).toContainText( + declaration.informant.identifier.id + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Age')).toContainText( + declaration.mother.age + ' years' + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.state + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine1 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine2 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine3 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + format( + new Date( + Number(declaration.father.birthDate.yyyy), + Number(declaration.father.birthDate.mm) - 1, + Number(declaration.father.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText('No') + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.state + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine1 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine2 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine3 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.postcodeOrZip + ) + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-5.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-5.spec.ts new file mode 100644 index 000000000..7fe1c6d9e --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-5.spec.ts @@ -0,0 +1,578 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('5. Birth declaration case - 5', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName() + ' the 3rd', + familyName: faker.name.lastName() + }, + gender: 'Unknown', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Layperson', + birthType: 'Higher multiple delivery', + placeOfBirth: 'Other', + birthLocation: { + country: 'Farajaland', + province: 'Pualula', + district: 'Funabuli', + urbanOrRural: 'Rural', + village: faker.address.county() + }, + informantType: 'Brother', + informantEmail: faker.internet.email(), + informant: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + age: 16, + nationality: 'Guernsey', + identifier: { + id: faker.random.numeric(10), + type: 'Birth Registration Number' + }, + address: { + country: 'Haiti', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + } + }, + mother: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + age: 25, + nationality: 'Farajaland', + identifier: { + type: 'None' + }, + address: { + country: 'Guam', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + maritalStatus: 'Separated', + levelOfEducation: 'Tertiary' + }, + father: { + detailsDontExist: true, + reason: 'Father is a ghost' + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('5.1 Declaratin started by Local Registrar', async () => { + test.beforeAll(async () => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('5.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page.getByPlaceholder('yyyy').fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + await page.locator('#statePlaceofbirth').click() + await page + .getByText(declaration.birthLocation.province, { + exact: true + }) + .click() + + await page.locator('#districtPlaceofbirth').click() + await page + .getByText(declaration.birthLocation.district, { + exact: true + }) + .click() + + await page.getByLabel(declaration.birthLocation.urbanOrRural).check() + + await page + .locator('#addressLine1RuralOptionPlaceofbirth') + .fill(declaration.birthLocation.village) + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('5.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page.locator('#registrationEmail').fill(declaration.informantEmail) + + /* + * Expected result: should show additional fields: + * - Full Name + * - Date of birth + * - Nationality + * - Id + * - Usual place of residence + */ + await page + .locator('#firstNamesEng') + .fill(declaration.informant.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.informant.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.informant.age.toString()) + + await page.locator('#nationality').click() + await page + .getByText(declaration.informant.nationality, { exact: true }) + .click() + + await page.locator('#informantIdType').click() + await page + .getByText(declaration.informant.identifier.type, { exact: true }) + .click() + + await page + .locator('#informantBirthRegistrationNumber') + .fill(declaration.informant.identifier.id) + + await page.locator('#countryPrimaryInformant').click() + await page + .getByText(declaration.informant.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryInformant') + .fill(declaration.informant.address.state) + await page + .locator('#internationalDistrictPrimaryInformant') + .fill(declaration.informant.address.district) + await page + .locator('#internationalCityPrimaryInformant') + .fill(declaration.informant.address.town) + await page + .locator('#internationalAddressLine1PrimaryInformant') + .fill(declaration.informant.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryInformant') + .fill(declaration.informant.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryInformant') + .fill(declaration.informant.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryInformant') + .fill(declaration.informant.address.postcodeOrZip) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("5.1.3 Fill mother's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.mother.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.mother.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.mother.age.toString()) + + await page.locator('#motherIdType').click() + await page + .getByText(declaration.mother.identifier.type, { exact: true }) + .click() + + await page.locator('#countryPrimaryMother').click() + await page + .getByText(declaration.mother.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryMother') + .fill(declaration.mother.address.state) + await page + .locator('#internationalDistrictPrimaryMother') + .fill(declaration.mother.address.district) + await page + .locator('#internationalCityPrimaryMother') + .fill(declaration.mother.address.town) + await page + .locator('#internationalAddressLine1PrimaryMother') + .fill(declaration.mother.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryMother') + .fill(declaration.mother.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryMother') + .fill(declaration.mother.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryMother') + .fill(declaration.mother.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.mother.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.mother.levelOfEducation, { exact: true }) + .click() + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("5.1.4 Fill father's details", async () => { + await page.getByLabel("Father's details are not available").check() + await page.locator('#reasonNotApplying').fill(declaration.father.reason) + }) + + test('5.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('5.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.province + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.village + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + /* + * Expected result: should include + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.firstNames + ) + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.familyName + ) + + /* + * Expected result: should include + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Age')).toContainText( + declaration.informant.age + ' years' + ) + + /* + * Expected result: should include + * - Informant's Nationality + */ + await expect( + page.locator('#informant-content #Nationality') + ).toContainText(declaration.informant.nationality) + + /* + * Expected result: should include + * - Informant's address + */ + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.country + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.district + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.state + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.addressLine1 + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.addressLine2 + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.addressLine3 + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Informant's Type of Id + * - Informant's Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + declaration.informant.identifier.type + ) + await expect(page.locator('#informant-content #ID')).toContainText( + declaration.informant.identifier.id + ) + + /* + * Expected result: should include + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.firstNames + ) + await expect(page.locator('#mother-content #Full')).toContainText( + declaration.mother.name.familyName + ) + + /* + * Expected result: should include + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Age')).toContainText( + declaration.mother.age + ' years' + ) + + /* + * Expected result: should include + * - Mother's Nationality + */ + await expect(page.locator('#mother-content #Nationality')).toContainText( + declaration.mother.nationality + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should include + * - Mother's level of education + */ + await expect(page.locator('#mother-content #Level')).toContainText( + declaration.mother.levelOfEducation + ) + + /* + * Expected result: should include + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + declaration.mother.identifier.type + ) + + /* + * Expected result: should include + * - Mother's address + */ + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.country + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.district + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.state + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.town + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine1 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine2 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.addressLine3 + ) + await expect(page.locator('#mother-content #Usual')).toContainText( + declaration.mother.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Father's Details not available: true + * - Reason of why father's details not available + */ + await expect(page.locator('#father-content')).toContainText( + "Father's details are not availableYes" + ) + await expect(page.locator('#father-content #Reason')).toContainText( + declaration.father.reason + ) + }) + + test('5.1.7 Register', async () => { + await page.getByRole('button', { name: 'Register' }).click() + await expect(page.getByText('Register the birth?')).toBeVisible() + await page.locator('#submit_confirm').click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Ready to print' }).click() + + /* + * Expected result: The declaration should be in Ready to print + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-6.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-6.spec.ts new file mode 100644 index 000000000..398e04e0d --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-6.spec.ts @@ -0,0 +1,593 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, getRandomDate, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' +import { format } from 'date-fns' + +test.describe.serial('6. Birth declaration case - 6', () => { + let page: Page + const declaration = { + child: { + name: { + firstNames: faker.name.firstName() + " O'Neil", + familyName: faker.name.lastName() + }, + gender: 'Unknown', + birthDate: getRandomDate(0, 200) + }, + attendantAtBirth: 'Traditional birth attendant', + birthType: 'Higher multiple delivery', + placeOfBirth: 'Other', + birthLocation: { + country: 'Greenland', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + informantType: 'Sister', + informantEmail: faker.internet.email(), + informant: { + name: { + firstNames: faker.name.firstName('female'), + familyName: faker.name.lastName('female') + }, + age: 17, + nationality: 'Guernsey', + identifier: { + type: 'None' + }, + address: { + country: 'Haiti', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + } + }, + father: { + name: { + firstNames: faker.name.firstName('male'), + familyName: faker.name.lastName('male') + }, + age: 25, + nationality: 'Farajaland', + identifier: { + type: 'None' + }, + address: { + country: 'Guam', + state: faker.address.state(), + district: faker.address.county(), + town: faker.address.city(), + addressLine1: faker.address.county(), + addressLine2: faker.address.streetName(), + addressLine3: faker.address.buildingNumber(), + postcodeOrZip: faker.address.zipCode() + }, + maritalStatus: 'Separated', + levelOfEducation: 'Tertiary' + }, + mother: { + detailsDontExist: true, + reason: 'Mother is a ghost' + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('6.1 Declaratin started by National Registrar', async () => { + test.beforeAll(async () => { + await login(page, 'j.musonda', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('6.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + await page.locator('#gender').click() + await page.getByText(declaration.child.gender, { exact: true }).click() + + await page.getByPlaceholder('dd').fill(declaration.child.birthDate.dd) + await page.getByPlaceholder('mm').fill(declaration.child.birthDate.mm) + await page.getByPlaceholder('yyyy').fill(declaration.child.birthDate.yyyy) + + await page.locator('#placeOfBirth').click() + await page + .getByText(declaration.placeOfBirth, { + exact: true + }) + .click() + + await page.locator('#countryPlaceofbirth').click() + await page + .getByText(declaration.birthLocation.country, { + exact: true + }) + .click() + + await page + .locator('#internationalStatePlaceofbirth') + .fill(declaration.birthLocation.state) + await page + .locator('#internationalDistrictPlaceofbirth') + .fill(declaration.birthLocation.district) + await page + .locator('#internationalCityPlaceofbirth') + .fill(declaration.birthLocation.town) + await page + .locator('#internationalAddressLine1Placeofbirth') + .fill(declaration.birthLocation.addressLine1) + await page + .locator('#internationalAddressLine2Placeofbirth') + .fill(declaration.birthLocation.addressLine2) + await page + .locator('#internationalAddressLine3Placeofbirth') + .fill(declaration.birthLocation.addressLine3) + await page + .locator('#internationalPostalCodePlaceofbirth') + .fill(declaration.birthLocation.postcodeOrZip) + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.locator('#birthType').click() + await page + .getByText(declaration.birthType, { + exact: true + }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('6.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + await page.locator('#registrationEmail').fill(declaration.informantEmail) + + /* + * Expected result: should show additional fields: + * - Full Name + * - Date of birth + * - Nationality + * - Id + * - Usual place of residence + */ + await page + .locator('#firstNamesEng') + .fill(declaration.informant.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.informant.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.informant.age.toString()) + + await page.locator('#nationality').click() + await page + .getByText(declaration.informant.nationality, { exact: true }) + .click() + + await page.locator('#informantIdType').click() + await page + .getByText(declaration.informant.identifier.type, { exact: true }) + .click() + + await page.locator('#countryPrimaryInformant').click() + await page + .getByText(declaration.informant.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryInformant') + .fill(declaration.informant.address.state) + await page + .locator('#internationalDistrictPrimaryInformant') + .fill(declaration.informant.address.district) + await page + .locator('#internationalCityPrimaryInformant') + .fill(declaration.informant.address.town) + await page + .locator('#internationalAddressLine1PrimaryInformant') + .fill(declaration.informant.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryInformant') + .fill(declaration.informant.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryInformant') + .fill(declaration.informant.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryInformant') + .fill(declaration.informant.address.postcodeOrZip) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("6.1.3 Fill mother's details", async () => { + await page.getByLabel("Mother's details are not available").check() + await page.locator('#reasonNotApplying').fill(declaration.mother.reason) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("6.1.4 Fill father's details", async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.father.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.father.name.familyName) + + await page.getByLabel('Exact date of birth unknown').check() + + await page + .locator('#ageOfIndividualInYears') + .fill(declaration.father.age.toString()) + + await page.locator('#fatherIdType').click() + await page + .getByText(declaration.father.identifier.type, { exact: true }) + .click() + + await page.locator('#countryPrimaryFather').click() + await page + .getByText(declaration.father.address.country, { exact: true }) + .click() + await page + .locator('#internationalStatePrimaryFather') + .fill(declaration.father.address.state) + await page + .locator('#internationalDistrictPrimaryFather') + .fill(declaration.father.address.district) + await page + .locator('#internationalCityPrimaryFather') + .fill(declaration.father.address.town) + await page + .locator('#internationalAddressLine1PrimaryFather') + .fill(declaration.father.address.addressLine1) + await page + .locator('#internationalAddressLine2PrimaryFather') + .fill(declaration.father.address.addressLine2) + await page + .locator('#internationalAddressLine3PrimaryFather') + .fill(declaration.father.address.addressLine3) + await page + .locator('#internationalPostalCodePrimaryFather') + .fill(declaration.father.address.postcodeOrZip) + + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.father.maritalStatus, { exact: true }) + .click() + + await page.locator('#educationalAttainment').click() + await page + .getByText(declaration.father.levelOfEducation, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('6.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('6.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should include + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText( + declaration.child.gender + ) + + /* + * Expected result: should include + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText( + format( + new Date( + Number(declaration.child.birthDate.yyyy), + Number(declaration.child.birthDate.mm) - 1, + Number(declaration.child.birthDate.dd) + ), + 'dd MMMM yyyy' + ) + ) + + /* + * Expected result: should include + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + declaration.placeOfBirth + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.country + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.state + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.district + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.addressLine1 + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.addressLine2 + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.addressLine3 + ) + await expect(page.locator('#child-content #Place')).toContainText( + declaration.birthLocation.postcodeOrZip + ) + + /* + * Expected result: should include + * - Child's Attendant at birth + */ + await expect(page.locator('#child-content #Attendant')).toContainText( + declaration.attendantAtBirth + ) + + /* + * Expected result: should include + * - Child's Birth type + */ + await expect(page.locator('#child-content #Type')).toContainText( + declaration.birthType + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect( + page.locator('#informant-content #Relationship') + ).toContainText(declaration.informantType) + + /* + * Expected result: should include + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + declaration.informantEmail + ) + /* + * Expected result: should include + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.firstNames + ) + await expect(page.locator('#informant-content #Full')).toContainText( + declaration.informant.name.familyName + ) + + /* + * Expected result: should include + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Age')).toContainText( + declaration.informant.age + ' years' + ) + + /* + * Expected result: should include + * - Informant's Nationality + */ + await expect( + page.locator('#informant-content #Nationality') + ).toContainText(declaration.informant.nationality) + + /* + * Expected result: should include + * - Informant's address + */ + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.country + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.district + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.state + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.addressLine1 + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.addressLine2 + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.addressLine3 + ) + await expect(page.locator('#informant-content #Usual')).toContainText( + declaration.informant.address.postcodeOrZip + ) + + /* + * Expected result: should include + * - Informant's Type of Id + * - Informant's Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + declaration.informant.identifier.type + ) + + /* + * Expected result: should include + * - Mother's Details not available: true + * - Reason of why mother's details not available + */ + await expect(page.locator('#mother-content')).toContainText( + "Mother's details are not availableYes" + ) + await expect(page.locator('#mother-content #Reason')).toContainText( + declaration.mother.reason + ) + + /* + * Expected result: should include + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.firstNames + ) + await expect(page.locator('#father-content #Full')).toContainText( + declaration.father.name.familyName + ) + + /* + * Expected result: should include + * - Father's date of birth + */ + await expect(page.locator('#father-content #Age')).toContainText( + declaration.father.age + ' years' + ) + + /* + * Expected result: should include + * - Father's Nationality + */ + await expect(page.locator('#father-content #Nationality')).toContainText( + declaration.father.nationality + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + + /* + * Expected result: should include + * - Father's level of education + */ + await expect(page.locator('#father-content #Level')).toContainText( + declaration.father.levelOfEducation + ) + + /* + * Expected result: should include + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + declaration.father.identifier.type + ) + + /* + * Expected result: should include + * - Father's address + */ + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.country + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.district + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.state + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.town + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine1 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine2 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.addressLine3 + ) + await expect(page.locator('#father-content #Usual')).toContainText( + declaration.father.address.postcodeOrZip + ) + }) + + test('6.1.7 Register', async () => { + await page.getByRole('button', { name: 'Register' }).click() + await expect(page.getByText('Register the birth?')).toBeVisible() + await page.locator('#submit_confirm').click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Ready to print' }).click() + + /* + * Expected result: The declaration should be in Ready to print + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-7.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-7.spec.ts new file mode 100644 index 000000000..ceda780b0 --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-7.spec.ts @@ -0,0 +1,329 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' + +test.describe.serial('7. Birth declaration case - 7', () => { + let page: Page + const required = 'Required for registration' + const declaration = { + child: { + name: { + firstNames: faker.name.firstName(), + familyName: faker.name.lastName() + } + }, + attendantAtBirth: 'None', + informantType: 'Legal guardian', + mother: { + detailsDontExist: true + }, + father: { + detailsDontExist: true + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('7.1 Declaration started by FA', async () => { + test.beforeAll(async () => { + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('7.1.1 Fill child details', async () => { + await page + .locator('#firstNamesEng') + .fill(declaration.child.name.firstNames) + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + + await page.locator('#attendantAtBirth').click() + await page + .getByText(declaration.attendantAtBirth, { + exact: true + }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('7.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("7.1.3 Fill mother's details", async () => { + await page.getByLabel("Mother's details are not available").check() + await page.waitForTimeout(500) + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("7.1.4 Fill father's details", async () => { + await page.getByLabel("Father's details are not available").check() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test.skip('7.1.5 Add supporting documents', async () => { + goToSection(page, 'documents') + }) + + test('7.1.6 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('7.1.7 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + /* + * Expected result: should require + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's Type of Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Reason of why mother's details not available + */ + await expect(page.locator('#mother-content #Reason')).toContainText( + required + ) + + /* + * Expected result: should require + * - Reason of why father's details not available + */ + await expect(page.locator('#father-content #Reason')).toContainText( + required + ) + }) + + test('7.1.8 Send for review', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for review' }).click() + + /* + * Expected result: The declaration should be in sent for review + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('7.2 Declaration Review by RA', async () => { + test('7.2.1 Navigate to the declaration review page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'In Progress' }).click() + await page.getByRole('button', { name: 'Field Agents' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.firstNames} ${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Update', exact: true }).click() + }) + + test('7.2.2 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.firstNames + ) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + /* + * Expected result: should require + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's Type of Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Reason of why mother's details not available + */ + await expect(page.locator('#mother-content #Reason')).toContainText( + required + ) + + /* + * Expected result: should require + * - Reason of why father's details not available + */ + await expect(page.locator('#father-content #Reason')).toContainText( + required + ) + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-8.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-8.spec.ts new file mode 100644 index 000000000..49f6ea69e --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-8.spec.ts @@ -0,0 +1,429 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, goToSection, login } from '../../../helpers' + +test.describe.serial('8. Birth declaration case - 8', () => { + let page: Page + const required = 'Required for registration' + const declaration = { + informantType: 'Someone else', + informant: { + relation: 'Uncle' + }, + mother: { + maritalStatus: 'Not stated' + }, + father: { + maritalStatus: 'Not stated' + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('8.1 Declaration started by FA', async () => { + test.beforeAll(async () => { + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('8.1.1 Fill child details', async () => { + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('8.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + + /* + * Expected result: should show additional fields: + * - Relationship to child + */ + await page + .locator('#otherInformantType') + .fill(declaration.informant.relation) + + await page.waitForTimeout(500) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("8.1.3 Fill mother's details", async () => { + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.mother.maritalStatus, { exact: true }) + .click() + + page.waitForTimeout(1000) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("8.1.4 Fill father's details", async () => { + await page.locator('#maritalStatus').click() + await page + .getByText(declaration.father.maritalStatus, { exact: true }) + .click() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('8.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('8.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should require + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText(required) + await expect(page.locator('#child-content #Full')).toContainText(required) + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + await expect(page.locator('#informant-content')).toContainText( + declaration.informant.relation + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + required + ) + /* + * Expected result: should require + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's Type of Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + required + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should require + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + required + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + }) + + test('8.1.7 Send for review', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + }) + }) + + test.describe('8.2 Declaration Review by RA', async () => { + test('8.2.1 Navigate to the declaration review page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'In Progress' }).click() + await page.getByRole('button', { name: 'Field Agents' }).click() + + await expect(page.locator('#name_0')).toBeVisible() + const [firstButton] = await page + .getByRole('button', { + name: 'No name provided' + }) + .all() + await firstButton.click() + + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Update', exact: true }).click() + }) + + test('8.2.2 Verify informations in preview page', async () => { + /* + * Expected result: should require + * - Child's First Name + * - Child's Family Name + */ + await expect(page.locator('#child-content #Full')).toContainText(required) + await expect(page.locator('#child-content #Full')).toContainText(required) + + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + await expect(page.locator('#informant-content')).toContainText( + declaration.informant.relation + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + /* + * Expected result: should require + * - Informant's First Name + * - Informant's Family Name + */ + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + await expect(page.locator('#informant-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's date of birth + */ + await expect(page.locator('#informant-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Informant's Type of Id + */ + await expect(page.locator('#informant-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + required + ) + + /* + * Expected result: should include + * - Mother's Marital status + */ + await expect(page.locator('#mother-content #Marital')).toContainText( + declaration.mother.maritalStatus + ) + + /* + * Expected result: should require + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's First Name + * - Father's Family Name + */ + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + await expect(page.locator('#father-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's date of birth + */ + await expect(page.locator('#father-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Father's Type of Id + */ + await expect(page.locator('#father-content #Type')).toContainText( + required + ) + + /* + * Expected result: should include + * - Father's Marital status + */ + await expect(page.locator('#father-content #Marital')).toContainText( + declaration.father.maritalStatus + ) + }) + }) +}) diff --git a/e2e/testcases/birth/declarations/birth-declaration-9.spec.ts b/e2e/testcases/birth/declarations/birth-declaration-9.spec.ts new file mode 100644 index 000000000..9d80d0982 --- /dev/null +++ b/e2e/testcases/birth/declarations/birth-declaration-9.spec.ts @@ -0,0 +1,297 @@ +import { test, expect, type Page } from '@playwright/test' +import { createPIN, goToSection, login } from '../../../helpers' +import faker from '@faker-js/faker' + +test.describe.serial('9. Birth declaration case - 9', () => { + let page: Page + const required = 'Required for registration' + const declaration = { + child: { + name: { + familyName: faker.name.lastName() + } + }, + informantType: 'Mother', + mother: { + detailsDontExist: false + }, + father: { + detailsDontExist: true + } + } + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + }) + + test.afterAll(async () => { + await page.close() + }) + + test.describe('9.1 Declaration started by FA', async () => { + test.beforeAll(async () => { + await login(page, 'k.bwalya', 'test') + await createPIN(page) + await page.click('#header_new_event') + await page.getByLabel('Birth').click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('9.1.1 Fill child details', async () => { + await page + .locator('#familyNameEng') + .fill(declaration.child.name.familyName) + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('9.1.2 Fill informant details', async () => { + await page.waitForTimeout(500) + await page.locator('#informantType').click() + await page + .getByText(declaration.informantType, { + exact: true + }) + .click() + + await page.waitForTimeout(500) // Temporary measurement untill the bug is fixed. BUG: rerenders after selecting relation with child + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("9.1.3 Fill mother's details", async () => { + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test("9.1.4 Fill father's details", async () => { + await page.getByLabel("Father's details are not available").check() + + await page.getByRole('button', { name: 'Continue' }).click() + }) + + test('9.1.5 Go to preview', async () => { + goToSection(page, 'preview') + }) + + test('9.1.6 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's Family Name + * * should require + * - Child's First Name + */ + await expect(page.locator('#child-content #Full')).toContainText(required) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + + /* + * Expected result: should require + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Reason of why father's details not available + */ + await expect(page.locator('#father-content #Reason')).toContainText( + required + ) + }) + + test('9.1.7 Send for review', async () => { + await page.getByRole('button', { name: 'Send for review' }).click() + await expect(page.getByText('Send for review?')).toBeVisible() + await page.getByRole('button', { name: 'Confirm' }).click() + await expect(page.getByText('Farajaland CRS')).toBeVisible() + + /* + * Expected result: should redirect to registration home + */ + expect(page.url().includes('registration-home')) + + await expect(page.locator('#navigation_outbox')).not.toContainText('1', { + timeout: 1000 * 30 + }) + + await page.getByRole('button', { name: 'Sent for review' }).click() + + /* + * Expected result: The declaration should be in sent for review + */ + await expect( + page.getByRole('button', { + name: `${declaration.child.name.familyName}` + }) + ).toBeVisible() + }) + }) + + test.describe('9.2 Declaration Review by RA', async () => { + test('9.2.1 Navigate to the declaration review page', async () => { + await login(page, 'f.katongo', 'test') + await createPIN(page) + await page.getByRole('button', { name: 'In Progress' }).click() + await page.getByRole('button', { name: 'Field Agents' }).click() + await page + .getByRole('button', { + name: `${declaration.child.name.familyName}` + }) + .click() + await page.getByLabel('Assign record').click() + await page.getByRole('button', { name: 'Assign', exact: true }).click() + await page.getByRole('button', { name: 'Update', exact: true }).click() + }) + + test('9.2.2 Verify informations in preview page', async () => { + /* + * Expected result: should include + * - Child's Family Name + * * should require + * - Child's First Name + */ + await expect(page.locator('#child-content #Full')).toContainText(required) + await expect(page.locator('#child-content #Full')).toContainText( + declaration.child.name.familyName + ) + + /* + * Expected result: should require + * - Child's Gender + */ + await expect(page.locator('#child-content #Sex')).toContainText(required) + + /* + * Expected result: should require + * - Child's date of birth + */ + await expect(page.locator('#child-content #Date')).toContainText(required) + + /* + * Expected result: should require + * - Child's Place of birth type + * - Child's Place of birth details + */ + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + await expect(page.locator('#child-content #Place')).toContainText( + required + ) + + /* + * Expected result: should include + * - Informant's relation to child + */ + await expect(page.locator('#informant-content')).toContainText( + declaration.informantType + ) + + /* + * Expected result: should require + * - Informant's Email + */ + await expect(page.locator('#informant-content #Email')).toContainText( + 'Must be a valid email address' + ) + + /* + * Expected result: should require + * - Mother's First Name + * - Mother's Family Name + */ + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + await expect(page.locator('#mother-content #Full')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's date of birth + */ + await expect(page.locator('#mother-content #Date')).toContainText( + required + ) + + /* + * Expected result: should require + * - Mother's Type of Id + */ + await expect(page.locator('#mother-content #Type')).toContainText( + required + ) + + /* + * Expected result: should require + * - Reason of why father's details not available + */ + await expect(page.locator('#father-content #Reason')).toContainText( + required + ) + }) + }) +}) diff --git a/e2e/testcases/birth/helpers.ts b/e2e/testcases/birth/helpers.ts new file mode 100644 index 000000000..e41562188 --- /dev/null +++ b/e2e/testcases/birth/helpers.ts @@ -0,0 +1,257 @@ +import gql from 'graphql-tag' +import { print } from 'graphql/language/printer' +import { GATEWAY_HOST } from '../../constants' +import { BirthRegistrationInput } from '../../gateway' +import faker from '@faker-js/faker' + +import type testData from './data/1-both-mother-and-father.json' +import { readFileSync } from 'fs' +import uuid from 'uuid' +import { format, subDays, subYears } from 'date-fns' +import { Bundle } from 'typescript' +import { join } from 'path' + +export const CREATE_BIRTH_REGISTRATION = print(gql` + mutation createBirthRegistration($details: BirthRegistrationInput!) { + createBirthRegistration(details: $details) { + trackingId + compositionId + isPotentiallyDuplicate + __typename + } + } +`) + +type Details = { + informant: { + type: 'MOTHER' | 'FATHER' + } + child: { + firstNames: string + familyName: string + birthDate?: string + gender: 'male' | 'female' + birthType?: 'SINGLE' | 'MULTIPLE' + weightAtBirth?: number + } + mother: { + firstNames: string + familyName: string + birthDate?: string + maritalStatus?: 'SINGLE' | 'MARRIED' | 'DIVORCED' | 'WIDOWED' + } + father: { + firstNames: string + familyName: string + birthDate?: string + } + attendant: { + type: 'PHYSICIAN' | 'NURSE' | 'MIDWIFE' | 'OTHER' + } +} +async function getAllLocations( + type: 'ADMIN_STRUCTURE' | 'HEALTH_FACILITY' | 'CRVS_OFFICE' +) { + const locations = (await fetch(`${GATEWAY_HOST}/location?type=${type}`).then( + (res) => res.json() + )) as fhir.Bundle + + return locations.entry!.map((entry) => entry.resource as fhir.Location) +} + +function getLocationIdByName(locations: fhir.Location[], name: string) { + const location = locations.find((location) => location.name === name) + if (!location) { + throw new Error(`Location with name ${name} not found`) + } + return location.id +} +export async function createDeclaration(token: string, details: Details) { + const locations = await getAllLocations('ADMIN_STRUCTURE') + const facilities = await getAllLocations('HEALTH_FACILITY') + + const res = await fetch(`${GATEWAY_HOST}/graphql`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + query: CREATE_BIRTH_REGISTRATION, + variables: { + details: { + createdAt: new Date().toISOString(), + registration: { + status: [ + { + timestamp: new Date().toISOString(), + timeLoggedMS: 1031608 + } + ], + informantsSignature: + 'data:image/png;base64,' + + readFileSync( + join(__dirname, './data/assets/528KB-random.png') + ).toString('base64'), + informantType: details.informant.type, + contactPhoneNumber: '+260' + faker.random.numeric(9), + contactEmail: faker.internet.email(), + draftId: uuid.v4() + }, + child: { + name: [ + { + use: 'en', + firstNames: details.child.firstNames, + familyName: details.child.familyName + } + ], + gender: details.child.gender, + birthDate: + details.child.birthDate || + format( + subDays(new Date(), Math.ceil(50 * Math.random())), + 'yyyy-MM-dd' + ), + identifier: [] + }, + eventLocation: { + _fhirID: getLocationIdByName( + facilities, + 'Chikobo Rural Health Centre' + ) + }, + attendantAtBirth: details.attendant.type || 'PHYSICIAN', + birthType: details.child.birthType || 'SINGLE', + weightAtBirth: details.child.weightAtBirth || 2, + mother: { + name: [ + { + use: 'en', + firstNames: details.mother.firstNames, + familyName: details.mother.familyName + } + ], + birthDate: + details.mother.birthDate || + format( + subYears(new Date(), 16 + Math.ceil(10 * Math.random())), + 'yyyy-MM-dd' + ), + nationality: ['FAR'], + identifier: [ + { + id: faker.random.numeric(10), + type: 'NATIONAL_ID' + } + ], + address: [ + { + type: 'PRIMARY_ADDRESS', + line: [ + faker.address.buildingNumber(), + faker.address.streetName(), + faker.address.cityName(), + '', + '', + 'URBAN', + '', + '', + '', + '', + '', + '', + '', + '', + '' + ], + country: 'FAR', + state: getLocationIdByName(locations, 'Central'), + partOf: getLocationIdByName(locations, 'Ibombo'), + district: getLocationIdByName(locations, 'Ibombo'), + city: 'Example Town', + postalCode: '534534' + } + ], + maritalStatus: details.mother.maritalStatus || 'SINGLE', + educationalAttainment: 'NO_SCHOOLING' + }, + questionnaire: [ + { + fieldId: 'birth.mother.mother-view-group.motherIdType', + value: 'NATIONAL_ID' + }, + { + fieldId: 'birth.father.father-view-group.fatherIdType', + value: 'NATIONAL_ID' + } + ], + father: { + detailsExist: true, + name: [ + { + use: 'en', + firstNames: faker.name.findName(), + familyName: faker.name.lastName() + } + ], + birthDate: + details.father.birthDate || + format( + subYears(new Date(), 16 + Math.ceil(10 * Math.random())), + 'yyyy-MM-dd' + ), + nationality: ['FAR'], + identifier: [ + { + id: faker.random.numeric(10), + type: 'NATIONAL_ID' + } + ], + address: [ + { + type: 'PRIMARY_ADDRESS', + line: [ + '343', + 'Example Street', + 'Example Residential Area', + '', + '', + 'URBAN', + '', + '', + '', + '', + '', + '', + '', + '', + '' + ], + country: 'FAR', + state: getLocationIdByName(locations, 'Central'), + partOf: getLocationIdByName(locations, 'Ibombo'), + district: getLocationIdByName(locations, 'Ibombo'), + city: 'Example Town', + postalCode: '534534' + } + ], + maritalStatus: 'SINGLE', + educationalAttainment: 'NO_SCHOOLING' + } + } satisfies ConvertEnumsToStrings + } + }) + }) + return res.json().then((r) => r.data.createBirthRegistration) +} + +type ConvertEnumsToStrings = T extends (infer U)[] + ? ConvertEnumsToStrings[] + : T extends string + ? `${T}` + : T extends object + ? { + [K in keyof T]: ConvertEnumsToStrings + } + : T diff --git a/tsconfig.json b/tsconfig.json index 5865d236b..7fe113d1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "module": "commonjs", "outDir": "build/dist", "sourceMap": true, + "resolveJsonModule": true, "moduleResolution": "node", "rootDir": ".", "lib": [ From b00e14196f49f7ccbf61aa97bebfeaacd4f2ef76 Mon Sep 17 00:00:00 2001 From: Tomi Salo <54830004+Eezi@users.noreply.github.com> Date: Wed, 29 May 2024 12:26:30 +0300 Subject: [PATCH 36/46] e2e: marriage (#1013) * Added test for validating marriage event selection page * Added util function for repeated steps and did first versions of all steps for testcase 1 * I finished the last test steps and numbered the tests more clearly. * Update e2e/testcases/marriage/1-marriage-event-declaration.spec.ts Co-authored-by: Jamil * Update e2e/testcases/marriage/1-marriage-event-declaration.spec.ts Co-authored-by: Jamil * Update e2e/testcases/marriage/1-marriage-event-declaration.spec.ts Co-authored-by: Jamil * Changed the id selectors of the validateSectionButtons to the getByText * Changed the id selectors to the getByText * Apply suggestions from code review --------- Co-authored-by: Jamil --- e2e/helpers.ts | 7 + .../1-marriage-event-declaration.spec.ts | 183 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 e2e/testcases/marriage/1-marriage-event-declaration.spec.ts diff --git a/e2e/helpers.ts b/e2e/helpers.ts index cb9d5d144..0c266d2ed 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -76,3 +76,10 @@ export async function ensureLoginPageReady(page: Page) { return img && img.src && img.src.trim() !== '' }) } + +export async function validateSectionButtons(page: Page) { + await expect(page.getByText('Continue', { exact: true })).toBeVisible() + await expect(page.getByText('Exit', { exact: true })).toBeVisible() + await expect(page.getByText('Save & Exit', { exact: true })).toBeVisible() + await expect(page.locator('#eventToggleMenuToggleButton')).toBeVisible() +} diff --git a/e2e/testcases/marriage/1-marriage-event-declaration.spec.ts b/e2e/testcases/marriage/1-marriage-event-declaration.spec.ts new file mode 100644 index 000000000..6a67747b0 --- /dev/null +++ b/e2e/testcases/marriage/1-marriage-event-declaration.spec.ts @@ -0,0 +1,183 @@ +import { expect, test } from '@playwright/test' +import { createPIN, login } from '../../helpers' +import { validateSectionButtons } from '../../helpers' + +test.describe('1. Marriage event validation', () => { + test.beforeEach(async ({ page }) => { + await login(page, 'k.mweene', 'test') + await createPIN(page) + }) + + test('1.1. Navigate to the event declaration page', async ({ page }) => { + await page.click('#header_new_event') + await page.waitForSelector('#continue') + }) + + test('Testcase 1', async ({ page }) => { + await page.click('#header_new_event') + + await test.step('1.1.1 Validate the contents of the event type page', async () => { + await expect(page.getByText('Birth', { exact: true })).toBeVisible() + await expect(page.getByText('Death', { exact: true })).toBeVisible() + await expect(page.getByText('Marriage', { exact: true })).toBeVisible() + await expect(page.getByText('Exit', { exact: true })).toBeVisible() + await expect(page.getByText('Continue', { exact: true })).toBeVisible() + }) + + await test.step('1.1.2 Click the "Continue" button without selecting any event', async () => { + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText('Please select the type of event', { exact: true }) + ).toBeVisible() + }) + + await test.step('1.1.3 Select the "Marriage" event and click "Continue" button', async () => { + await page.getByText('Marriage', { exact: true }).click() + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText("Informant's details", { exact: true }) + ).toBeVisible() + }) + // 1.2. Is missing because marriage declaration does not have an "Introduction" page. + await test.step('1.3.1 Validate the content of the informant page', async () => { + await validateSectionButtons(page) + await expect( + page.locator('label', { hasText: 'Informant type' }) + ).toBeVisible() + await expect( + page.locator('label', { hasText: 'Phone number' }) + ).toBeVisible() + await expect(page.locator('label', { hasText: 'Email' })).toBeVisible() + }) + // 1.3.2 Is missing because "Informant details" page does not give an error if + // user click continue and nothing is selected. + await test.step('1.3.3 Select any option in Informant type > Click Continue', async () => { + await page.locator('#informantType').click() + await page.getByText('Groom', { exact: true }).click() + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText("Groom's details", { exact: true }) + ).toBeVisible() + }) + await test.step('1.4. Validate Groom Details page', async () => { + await validateSectionButtons(page) + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText("Bride's details", { exact: true }) + ).toBeVisible() + }) + await test.step('1.5. Validate Bridge Details page', async () => { + await validateSectionButtons(page) + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText('Marriage details', { exact: true }) + ).toBeVisible() + }) + await test.step('1.6. Validate Marriage Details page', async () => { + await validateSectionButtons(page) + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText('Witness 1 details', { exact: true }) + ).toBeVisible() + }) + await test.step('1.7. Validate witness 1 Details page', async () => { + await validateSectionButtons(page) + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText('Witness 2 details', { exact: true }) + ).toBeVisible() + }) + await test.step('1.8. Validate witness 2 Details page', async () => { + await validateSectionButtons(page) + await page.getByText('Continue', { exact: true }).click() + await expect( + page.getByText('Upload supporting documents', { exact: true }) + ).toBeVisible() + }) + await test.step('1.9. Validate Supporting document page', async () => { + await validateSectionButtons(page) + await page.getByText('Continue', { exact: true }).click() + page.getByText('Register event', { exact: true }) + }) + }) + test('1.11 Validate save and exit button', async ({ page }) => { + await page.click('#header_new_event') + await page.getByText('Marriage', { exact: true }).click() + await page.getByText('Continue', { exact: true }).click() + + await test.step('1.11.1. & 1.11.2. Validate "Save & Exit" button modal content and cancel', async () => { + await page.getByText('Save & Exit', { exact: true }).click() + await expect( + page.getByText( + 'All inputted data will be kept secure for future editing. Are you ready to save any changes to this declaration form?', + { exact: true } + ) + ).toBeVisible() + await expect(page.getByText('Cancel', { exact: true })).toBeVisible() + await expect(page.getByText('Confirm', { exact: true })).toBeVisible() + await page.getByText('Cancel', { exact: true }).click() + await expect(page.getByText('Save & exit?', { exact: true })).toBeHidden() + }) + + await test.step('1.11.3. Confirm "Save & Exit" button', async () => { + await page.getByText('Save & Exit', { exact: true }).click() + await page.getByText('Confirm', { exact: true }).click() + await expect( + page.getByRole('button', { name: 'In progress' }) + ).toBeVisible() + await expect( + page.getByText('No name provided', { exact: true }) + ).toBeVisible() + }) + }) + test('1.12 Validate exit button', async ({ page }) => { + await page.click('#header_new_event') + await page.getByText('Marriage', { exact: true }).click() + await page.getByText('Continue', { exact: true }).click() + + await test.step('1.12.1. & 1.12.2. Validate "Exit" button modal content and cancel', async () => { + await page.getByText('Exit', { exact: true }).click() + await expect( + page.getByText('Exit without saving changes?', { exact: true }) + ).toBeVisible() + await expect( + page.getByText( + 'You have unsaved changes on your declaration form. Are you sure you want to exit without saving?', + { exact: true } + ) + ).toBeVisible() + await expect(page.getByText('Cancel', { exact: true })).toBeVisible() + await expect(page.getByText('Confirm', { exact: true })).toBeVisible() + await page.getByText('Cancel', { exact: true }).click() + await expect( + page.getByText('Exit without saving changes?', { exact: true }) + ).toBeHidden() + }) + await test.step('1.12.3. Confirm "Exit" button', async () => { + await page.getByText('Exit', { exact: true }).click() + + await page.getByText('Confirm', { exact: true }).click() + await expect( + page.getByRole('button', { name: 'In progress' }) + ).toBeVisible() + await expect( + page.getByText('No records in progress', { exact: true }) + ).toBeVisible() + }) + }) + + test('1.13 Validate three dot menu button', async ({ page }) => { + await page.click('#header_new_event') + await page.getByText('Marriage', { exact: true }).click() + await page.getByText('Continue', { exact: true }).click() + + await test.step('1.13.3. Delete declaration from the 3 dot menu', async () => { + await page.click('#eventToggleMenuToggleButton') + await page.getByText('Delete declaration', { exact: true }).click() + await page.getByText('Confirm', { exact: true }).click() + await expect( + page.getByText('No records in progress', { exact: true }) + ).toBeVisible() + }) + }) +}) From 5042dc908c87d41558bc9d49206b124775564b77 Mon Sep 17 00:00:00 2001 From: Anamul Haque Date: Thu, 30 May 2024 12:16:50 +0600 Subject: [PATCH 37/46] fix: add translations for different informants in print, issue & correction flow (#127) * french translation added for informants * updated changelog.md --- CHANGELOG.md | 1 + src/translations/client.csv | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7a76a4d..a02e0d0c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Use image tag instead of patterns in certificate SVGs - Generate default address according to logged-in user's location - Remove authentication from dashboard queries route +- Added french translation of informant for print certificate flow, issue certificate flow & correction flow ## [1.4.1](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.4.0...v1.4.1) diff --git a/src/translations/client.csv b/src/translations/client.csv index b1f1f9398..ceac4fc70 100644 --- a/src/translations/client.csv +++ b/src/translations/client.csv @@ -383,7 +383,7 @@ constants.issueConfirmationMessage,Confirmation of issuance,Please confirm that constants.issueToBride,Issuing to bride,Issue to bride,Délivré à) la mariée constants.issueToFather,Issuing to father,Issue to father,Délivré au père constants.issueToGroom,Issuing to groom,Issue to groom,délivré au marié -constants.issueToInformant,Issuance of death to informant,Issue to informant ({informant}),Delivré à l'informateur +constants.issueToInformant,Issuance of death to informant,"Issue to informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Delivré à l'informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" constants.issueToMother,Issuing to mother,Issue to mother,Délivré à la mère constants.issueToSomeoneElse,Issuing to someone else,Issue to someone else,Délivrer à quelqu'un d'autre constants.issuedBy,The issued by sec text,Issued by,Délivré par @@ -520,7 +520,7 @@ correction.corrector.groom,Label for groom option in certificate correction form correction.corrector.idCheck,,Check proof of ID,Vérifiez la preuve d'identité. Correspond-elle aux détails suivants ? correction.corrector.idCheckVerify,,Yes,Oui correction.corrector.idCheckWithoutVerify,,No,Non -correction.corrector.informant,Label for informant option in certificate correction form,Informant ({informant}),Informateur +correction.corrector.informant,Label for informant option in certificate correction form,"Informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" correction.corrector.legalGuardian,Label for legal guardian option in certificate correction form,Legal guardian,Tuteur légal correction.corrector.me,Label for registrar option in certificate correction form,Me,Moi correction.corrector.mother,Label for mother option in certificate correction form,Mother,Mère @@ -904,7 +904,7 @@ form.field.label.ageOfInformant,,Age of informant,Âge de l'informateur form.field.label.ageOfMother,,Age of mother,Âge de la mère form.field.label.ageOfSpouse,,Age of spouse,Âge du conjoint form.field.label.app.certifyRecordTo.father,,Print and issue to father,Imprimer et remettre au père -form.field.label.app.certifyRecordTo.informant,,Print and issue to informant ({informant}),Imprimer et remettre à l'informateur ({informant}) +form.field.label.app.certifyRecordTo.informant,,"Print and issue to informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Imprimer et remettre à l'informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" form.field.label.app.certifyRecordTo.mother,,Print and issue to mother,Imprimer et remettre à la mère form.field.label.app.phoneVerWarn,,Check with the informant that the mobile phone number you have entered is correct,Vérifiez auprès de l'informateur que le numéro de téléphone mobile que vous avez indiqué est correct. form.field.label.app.whoContDet.app,,Informant,Informateur From 635774875176147a1f4f0a7bcfb2d5e6c805a0bc Mon Sep 17 00:00:00 2001 From: Tareq Date: Thu, 30 May 2024 16:31:11 +0600 Subject: [PATCH 38/46] Fix Correct item names in update modal for marriage declaration --- CHANGELOG.md | 3 ++- src/form/marriage/index.ts | 2 +- src/translations/client.csv | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02e0d0c9..c2ca59342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ - Use image tag instead of patterns in certificate SVGs - Generate default address according to logged-in user's location - Remove authentication from dashboard queries route -- Added french translation of informant for print certificate flow, issue certificate flow & correction flow +- Added french translation of informant for print certificate flow, issue certificate flow & correction flow +- Groom's and Bride's name, printIssue translation variables updated [#124](https://github.com/opencrvs/opencrvs-countryconfig/pull/124) ## [1.4.1](https://github.com/opencrvs/opencrvs-countryconfig/compare/v1.4.0...v1.4.1) diff --git a/src/form/marriage/index.ts b/src/form/marriage/index.ts index c88cab80b..213ffdeff 100644 --- a/src/form/marriage/index.ts +++ b/src/form/marriage/index.ts @@ -84,7 +84,7 @@ export const marriageForm: ISerializedForm = { { id: 'informant', viewType: 'form', - name: formMessageDescriptors.registrationName, + name: formMessageDescriptors.informantName, title: formMessageDescriptors.informantTitle, groups: [ { diff --git a/src/translations/client.csv b/src/translations/client.csv index ceac4fc70..b1ce67b03 100644 --- a/src/translations/client.csv +++ b/src/translations/client.csv @@ -903,7 +903,9 @@ form.field.label.ageOfGroom,,Age of groom,Âge du marié form.field.label.ageOfInformant,,Age of informant,Âge de l'informateur form.field.label.ageOfMother,,Age of mother,Âge de la mère form.field.label.ageOfSpouse,,Age of spouse,Âge du conjoint +form.field.label.app.certifyRecordTo.bride,,Print and issue to bride,Imprimer et envoyer à la mariée form.field.label.app.certifyRecordTo.father,,Print and issue to father,Imprimer et remettre au père +form.field.label.app.certifyRecordTo.groom,,Print and issue to groom,Imprimer et envoyer au marié form.field.label.app.certifyRecordTo.informant,,"Print and issue to informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Imprimer et remettre à l'informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" form.field.label.app.certifyRecordTo.mother,,Print and issue to mother,Imprimer et remettre à la mère form.field.label.app.phoneVerWarn,,Check with the informant that the mobile phone number you have entered is correct,Vérifiez auprès de l'informateur que le numéro de téléphone mobile que vous avez indiqué est correct. @@ -1290,7 +1292,7 @@ form.section.accountDetails,,Account details,Coordonnées du compte form.section.assignedRegistrationOffice,,Assign to a registration office,A quel bureau voulez-vous affecter un nouvel utilisateur ? form.section.assignedRegistrationOfficeGroupTitle,,Assigned registration office,Bureau d'enregistrement assigné form.section.bride.headOfBrideFamily,,Head of bride's family,Chef de la famille de la mariée -form.section.bride.name,,Print and issue to bride,Imprimer et envoyer à la mariée +form.section.bride.name,,Bride,Mariée form.section.bride.title,,Bride's details,Détails de la mariée form.section.causeOfDeath.name,,Cause of Death,Cause du décès form.section.causeOfDeath.title,,What is the medically certified cause of death?,Quelle est la cause de décès médicalement certifiée ? @@ -1327,7 +1329,7 @@ form.section.documents.uploadImage,,Upload a photo of the supporting document,T form.section.father.name,,Father,Père form.section.father.title,,Father's details,Information du père form.section.groom.headOfGroomFamily,,Head of groom's family,Chef de la famille du marié -form.section.groom.name,,Print and issue to groom,Imprimer et envoyer au marié +form.section.groom.name,,Groom,Marié form.section.groom.title,,Groom's details,Détails du marié form.section.informant.name,,Informant,Informateur form.section.informant.title,,Informant's details,information de l'informateur From 4027ae2667329ff980db3884864f55bd1e2b01ea Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Wed, 5 Jun 2024 13:57:00 +0300 Subject: [PATCH 39/46] don't require regLastLocation in Postman Event Notification (#115) --- postman/Event Notification.postman_collection.json | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/postman/Event Notification.postman_collection.json b/postman/Event Notification.postman_collection.json index ab3408626..bbe519238 100644 --- a/postman/Event Notification.postman_collection.json +++ b/postman/Event Notification.postman_collection.json @@ -12,7 +12,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"author\": [],\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Tatke\"\n ],\n \"given\": [\n \"Harney\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"3624667568\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghanland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Housewife\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"6848901132\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Madgeland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Businessman\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"FAR\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:45c68568-2ca0-4932-9731-535dd4180fe0\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"57722-1\",\n \"display\": \"Birth plurality of Pregnancy\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": \"SINGLE\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d2d3d5b8-658e-4c29-9ec5-cb2431b4ddf3\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"3141-9\",\n \"display\": \"Body weight Measured\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": 4,\n \"unit\": \"kg\",\n \"system\": \"http://unitsofmeasure.org\",\n \"code\": \"kg\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:706905cf-7e5d-4d9f-866a-a3795780a990\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"73764-3\",\n \"display\": \"Birth attendant title\"\n }\n ]\n },\n \"valueString\": \"PHYSICIAN\"\n }\n }\n ]\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"author\": [],\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Tatke\"\n ],\n \"given\": [\n \"Harney\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"3624667568\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghanland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Housewife\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"6848901132\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Madgeland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Businessman\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"FAR\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:45c68568-2ca0-4932-9731-535dd4180fe0\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"57722-1\",\n \"display\": \"Birth plurality of Pregnancy\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": \"SINGLE\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d2d3d5b8-658e-4c29-9ec5-cb2431b4ddf3\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"3141-9\",\n \"display\": \"Body weight Measured\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": 4,\n \"unit\": \"kg\",\n \"system\": \"http://unitsofmeasure.org\",\n \"code\": \"kg\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:706905cf-7e5d-4d9f-866a-a3795780a990\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"73764-3\",\n \"display\": \"Birth attendant title\"\n }\n ]\n },\n \"valueString\": \"PHYSICIAN\"\n }\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -38,7 +38,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Min\"\n ],\n \"given\": [\n \"Child\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n }\n ]\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Min\"\n ],\n \"given\": [\n \"Child\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -64,7 +64,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d3bdcf50-f6b9-40aa-a30b-d9515275becc\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Salam\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1975-12-12\",\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-11-12\",\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Anjum\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1985-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"false\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:59:51.196Z\"\n }\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d3bdcf50-f6b9-40aa-a30b-d9515275becc\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Salam\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1975-12-12\",\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-11-12\",\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Anjum\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1985-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"false\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:59:51.196Z\"\n }\n}", "options": { "raw": { "language": "json" @@ -90,7 +90,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:cbce7430-3c63-457e-a6d8-ed3b35a97a64\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"1234123421\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Bashir\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1971-12-12\",\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-12-12\",\n \"multipleBirthBoolean\": false,\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"4321234257\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Selena\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1980-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7b4fcd33-5b39-463a-bbd4-4ac2e41e5f99\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"uncertified-manner-of-death\",\n \"display\": \"Uncertified manner of death\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/manner-of-death\",\n \"code\": \"NATURAL_CAUSES\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:89c0605f-8eb8-4f58-98ff-317efe64e91f\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-method\",\n \"display\": \"Cause of death method\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-method\",\n \"code\": \"PHYSICIAN\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"true\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:50:26.175Z\"\n }\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:cbce7430-3c63-457e-a6d8-ed3b35a97a64\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"1234123421\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Bashir\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1971-12-12\",\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-12-12\",\n \"multipleBirthBoolean\": false,\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"4321234257\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Selena\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1980-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7b4fcd33-5b39-463a-bbd4-4ac2e41e5f99\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"uncertified-manner-of-death\",\n \"display\": \"Uncertified manner of death\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/manner-of-death\",\n \"code\": \"NATURAL_CAUSES\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:89c0605f-8eb8-4f58-98ff-317efe64e91f\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-method\",\n \"display\": \"Cause of death method\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-method\",\n \"code\": \"PHYSICIAN\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"true\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:50:26.175Z\"\n }\n}", "options": { "raw": { "language": "json" @@ -141,10 +141,6 @@ } ], "variable": [ - { - "key": "officeLocationId", - "value": "c24c0b72-11b5-4c1a-bbb7-61112fa6f481" - }, { "key": "officeId", "value": "0be1dc9b-5c8b-4d20-a88c-08cafc71c99a" From 61bad39395cd8f262c9f428c35aea6fc76e7085e Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Wed, 5 Jun 2024 13:57:20 +0300 Subject: [PATCH 40/46] fix: simplify hierarchy now that it can be queried through graphql (#116) --- src/form/index.ts | 12 ++++---- src/utils/users.ts | 74 ++++++++-------------------------------------- 2 files changed, 18 insertions(+), 68 deletions(-) diff --git a/src/form/index.ts b/src/form/index.ts index 4fc80de45..51323c305 100644 --- a/src/form/index.ts +++ b/src/form/index.ts @@ -14,15 +14,13 @@ import { birthForm } from './birth' import { deathForm } from './death' import { marriageForm } from './marriage' import { IForms, Event } from './types/types' -import { getUserOfficeLocationHierarchy } from '@countryconfig/utils/users' +import { fetchUserLocationHierarchy } from '@countryconfig/utils/users' export async function formHandler(req: Request): Promise { - const addressHierarchy = ( - await getUserOfficeLocationHierarchy( - req.headers.authorization, - req.auth.credentials.sub as string - ) - ).map(({ id }) => id) + const addressHierarchy = await fetchUserLocationHierarchy( + req.headers.authorization, + req.auth.credentials.sub as string + ) // ====================== NOTE REGARDING MIGRATING FROM OPNCRVS v1.2 OR EARLIER ====================== // SIMPLY RETURN A JSON OF YOUR FULL FORM HERE, WITH THE ADDITION OF THE NEW MARRIAGE AND VERSION PROP diff --git a/src/utils/users.ts b/src/utils/users.ts index c1ecea7a1..415c77bb5 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,20 +1,20 @@ -import { APPLICATION_CONFIG_URL, GATEWAY_URL } from '@countryconfig/constants' +import { GATEWAY_URL } from '@countryconfig/constants' import fetch from 'node-fetch' import gql from 'graphql-tag' import { print } from 'graphql/language/printer' import { URL } from 'url' type GetUser = { - primaryOffice?: { - id: string + primaryOffice: { + hierarchy: Array<{ + id: string + }> } } - -type Location = { - id: string -} - -async function getUser(token: string, userId: string): Promise { +export async function fetchUserLocationHierarchy( + token: string, + userId: string +) { const url = new URL('graphql', GATEWAY_URL) const getUsersRes = await fetch(url, { method: 'POST', @@ -30,36 +30,11 @@ async function getUser(token: string, userId: string): Promise { query: print(gql` query fetchUser($userId: String!) { getUser(userId: $userId) { - id - creationDate - username - practitionerId - mobile - systemRole - role { - _id - labels { - lang - label - __typename - } - __typename - } - status - name { - use - firstNames - familyName - __typename - } primaryOffice { - id - name - alias - status - __typename + hierarchy { + id + } } - __typename } } `) @@ -70,28 +45,5 @@ async function getUser(token: string, userId: string): Promise { data: { getUser: GetUser } } - return res.data.getUser -} - -async function getLocationHierarchy(locationId: string): Promise { - const url = new URL( - `location/${locationId}/hierarchy`, - APPLICATION_CONFIG_URL - ) - const res = await fetch(url) - if (!res.ok) { - throw new Error('Unable to retrieve location hierarchy') - } - return res.json() -} - -export async function getUserOfficeLocationHierarchy( - token: string, - userId: string -): Promise { - const user = await getUser(token, userId) - if (!user.primaryOffice) { - throw new Error('No primary office found for user') - } - return getLocationHierarchy(user.primaryOffice.id) + return res.data.getUser.primaryOffice.hierarchy.map(({ id }) => id) } From 83d9d9d1863bc7dacab5a10ef2e49fd76626d632 Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Wed, 5 Jun 2024 15:11:48 +0300 Subject: [PATCH 41/46] merge countryconfig to farajaland (#1021) * Code freeze 1.5.0 release * Update auth to go through gateway (#781) * fix auth api urls * add mongo url for search service * refactor: keep individual certificate endpoint only in dev * return composition id confirm registration endpoint in core (#797) * Add feature flags in application default config (#806) * chore: remove logrocket references (#811) * feature flag hotfix for client config prod * Upgrade node to 18 (#847) * Update node version in Dockerfile * Update node version in workflow yml files * Add os configuration option in package.json * Add engines configuration option in package.json * Feedback changes * Add node options NODE_OPTIONS dns-result-order `ipv4first` * Upgrade node version in workflow file * Remove matrix block * (state-transitions): remove openhim url and modify confirm registration url (#932) * Remove openhim url and modify confirm registration url * Remove openhim core and openhim console services * feat: enable gzip compression (#947) * feat: enable gzip compression * enable gzip for gateway and login * make SENTRY_DSN variable optional * fix: support node versions 18.x.x (#955) * build: use docker compose v2 in github workflow files (#956) * Merge pull request #937 from opencrvs/ocrvs-6410-mass-email-users * deps: remove openhim (#963) * Add smtp env vars in qa deploy config * Revert "Minor amend notification schema" This reverts commit b2c6e396f0756fb3ca5e2717ff2f9bc401be3f24. * fix: remove duplicate handlebars.js route * chore: update the certificate images with instead of the patterns (#977) * feat: generate default address according to user's location (#978) * feat: add GATEWAY_URL env variable * feat: generate default address from user location * chore: remove initialValue from dynamicOptions * [HOTFIX] Mass email subject & content styles (#983) * Update body content styles to use linebreaks * Forward subject from variables instead of template * fix: change gateway web url to internal swarm one (#990) * docs: update CHANGELOG * set owner for backup server authorized_keys to be the backup user * minor fix to how download script cleans backup directories before applying downloaded backups * chore!: move configuration options (#1005) * Remove authentication from dashboard queries endpoint and update traefik rules * fix: remove authentication from dashboard queries endpoint and update traefik rules (#120) * Update CHANGELOG.md * Remove unintended formatting changes * Update jq images to official ones to avoid deprecation breakage (#122) * Update Docker and checkout actions to get rid of warnings (#121) * fix: add translations for different informants in print, issue & correction flow (#127) * french translation added for informants * updated changelog.md * Fix Correct item names in update modal for marriage declaration * don't require regLastLocation in Postman Event Notification (#115) * fix: simplify hierarchy now that it can be queried through graphql (#116) --------- Co-authored-by: Riku Rouvila Co-authored-by: Md. Ashikul Alam <32668488+Nil20@users.noreply.github.com> Co-authored-by: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Co-authored-by: tahmidrahman-dsi Co-authored-by: Tameem Bin Haider Co-authored-by: Tameem Bin Haider Co-authored-by: tahmidrahman-dsi Co-authored-by: Niko Kurtti Co-authored-by: Anamul Haque Co-authored-by: Tareq --- .github/workflows/deploy-prod.yml | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/publish-release.yml | 4 +- .github/workflows/publish-to-dockerhub.yml | 6 +- .github/workflows/test.yml | 2 +- CHANGELOG.md | 107 +++++------------- infrastructure/backups/download.sh | 8 +- infrastructure/docker-compose.deploy.yml | 17 ++- .../docker-compose.staging-deploy.yml | 1 + .../monitoring/kibana/setup-config.sh | 4 +- infrastructure/server-setup/backups.yml | 2 + ...Event Notification.postman_collection.json | 12 +- src/form/index.ts | 13 +-- src/form/marriage/index.ts | 2 +- src/index.ts | 3 +- src/translations/client.csv | 12 +- src/utils/users.ts | 92 +++------------ 17 files changed, 92 insertions(+), 197 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 69189010c..8d75aa765 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -75,7 +75,7 @@ jobs: echo "KNOWN_HOSTS=" >> $GITHUB_ENV - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cc1e8e295..9708e9b20 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -102,7 +102,7 @@ jobs: echo "KNOWN_HOSTS=" >> $GITHUB_ENV - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b3d0fe56e..3ffcabf0c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -15,7 +15,7 @@ jobs: base: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: '${{ github.event.inputs.branch_name }}' @@ -47,7 +47,7 @@ jobs: custom_tag: ${{ env.TAG }} - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/publish-to-dockerhub.yml b/.github/workflows/publish-to-dockerhub.yml index 007a369a1..4aa44d796 100644 --- a/.github/workflows/publish-to-dockerhub.yml +++ b/.github/workflows/publish-to-dockerhub.yml @@ -17,20 +17,20 @@ jobs: push: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 if: github.event_name == 'workflow_dispatch' with: fetch-depth: 2 ref: '${{ github.event.inputs.branch_name }}' - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 if: github.event_name == 'push' - name: Get tags run: git fetch --tags origin - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index db701c4b8..288bffa17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Checking out git repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Use Node.js 18.19 uses: actions/setup-node@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa0f71f8..924a0e41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,26 @@ # Changelog -## 1.5.0 (TBD) +## [1.5.0] + +- Change auth URLs to access them via gateway +- Add hearth URL to search service +- Include an endpoint for serving individual certificates in development mode +- Include compositionId in confirm registration payload +- Move individual configuration options to feature flags +- Remove logrocket refrences +- Upgrade to node 18 +- Enable gzip compression in client & login +- Make SENTRY_DSN variable optional +- Use docker compose v2 in github workflows +- Mass email from national system admin - Remove dependency on openhim. The openhim db is kept for backwards compatibility reasons and will be removed in v1.6 -- Change condition of Number of previous births - -## [1.3.4](https://github.com/opencrvs/opencrvs-farajaland/compare/v1.3.3...v1.3.4) - -## Breaking changes - -## New features - -## Bug fixes - -- Fix typo in certificate handlebar names - -See [Releases](https://github.com/opencrvs/opencrvs-farajaland/releases) for release notes of older releases. +- Add smtp environment variables in qa compose file +- Use image tag instead of patterns in certificate SVGs +- Generate default address according to logged-in user's location +- Remove authentication from dashboard queries route +- Added french translation of informant for print certificate flow, issue certificate flow & correction flow +- Groom's and Bride's name, printIssue translation variables updated [#124](https://github.com/opencrvs/opencrvs-countryconfig/pull/124) +- Change condition of Number of previous births ## [1.4.1](https://github.com/opencrvs/opencrvs-farajaland/compare/v1.4.0...v1.4.1) @@ -70,76 +76,17 @@ In the next OpenCRVS release v1.5.0, there will be two significant changes: - The `infrastructure` directory and related pipelines will be moved to a new repository. - Both the new infrastructure repository and the OpenCRVS country resource package repositories will start following their own release cycles, mostly independent from the core's release cycle. From this release forward, both packages are released as "OpenCRVS minor compatible" releases, meaning that the OpenCRVS countryconfig 1.3.0- is compatible with OpenCRVS 1.3.0, 1.3.1, 1.3.2, etc. This allows for the release of new hotfix versions of the core without having to publish a new version of the infrastructure or countryconfig. -See [Releases](https://github.com/opencrvs/opencrvs-farajaland/releases) for release notes of older releases. - -## [1.3.3](https://github.com/opencrvs/opencrvs-farajaland/compare/v1.3.2...v1.3.3) - -### Breaking changes - -### New features - -- #### Greater customizability of location data in certificates - - The various admin level handlebars e.g. **statePlaceofbirth**, - **districtPrimaryMother** only contained the name of that location which was - not able to take advantage of all the information OpenCRVS had available - about the various admin levels e.g. the name of that location in the - secondary language. So we are introducing a new set of admin level - handlebars that would contain the **id** of that location which we can - resolve into a value of the shape - - ``` - { - name: string - alias: string - } - ``` - - using the new **"location"** handlebar helper. Here name is the primary - label of the location and alias being the secondary one. Currently only - these 2 fields are available but we will be adding more fields depending on - various countries requirements. If previously the certificate svg used to - contain `{{districtPlaceofbirth}}` then now we can replace it with - `{{location districtPlaceofbirthId 'name'}}`. To access alias, the `'name'` - needs to be replaced with `'alias'`. - - Below is a list of all the new handlebars that are meant to be used with the - "location" handlebar helper. - - - statePrimaryInformantId - - districtPrimaryInformantId - - statePlaceofbirthId - - districtPlaceofbirthId - - statePrimaryMotherId - - districtPrimaryMotherId - - statePrimaryFatherId - - districtPrimaryFatherId - - statePrimaryDeceasedId - - districtPrimaryDeceasedId - - statePlaceofdeathId - - districtPlaceofdeathId - - statePrimaryGroomId - - districtPrimaryGroomId - - statePrimaryBrideId - - districtPrimaryBrideId - - statePlaceofmarriageId - - districtPlaceofmarriageId - - registrar.stateId - - registrar.districtId - - registrar.officeId - - registrationAgent.stateId - - registrationAgent.districtId - - registrationAgent.officeId - - ##### We will be deprecating the counterpart of the above mentioned handlebars that contains only the label of the specified location in a future version so we highly recommend that implementers update their certificates to use these new ones. +## [1.3.4](https://github.com/opencrvs/opencrvs-farajaland/compare/v1.3.3...v1.3.4) ### Bug fixes +- Fix typo in certificate handlebar names + ## [1.3.3](https://github.com/opencrvs/opencrvs-farajaland/compare/v1.3.2...v1.3.3) -## Breaking changes +### Breaking changes -## New features +### New features - #### Greater customizability of location data in certificates @@ -207,7 +154,9 @@ See [Releases](https://github.com/opencrvs/opencrvs-farajaland/releases) for rel - #### Reason for late registration field The birth & death forms will include another custom field, **reasonForLateRegistration**, which makes use of "LATE_REGISTRATION_TARGET" configuration option in it's visibility conditional. -## Bug fixes +### Bug fixes - Updated translations for form introduction page and sending for approval to reflect the default notification method being email. - Remove hard-coded conditionals from "occupation" field to make it usable in the deceased form + +See [Releases](https://github.com/opencrvs/opencrvs-farajaland/releases) for release notes of older releases. diff --git a/infrastructure/backups/download.sh b/infrastructure/backups/download.sh index 5e4ca8391..0d87a1783 100644 --- a/infrastructure/backups/download.sh +++ b/infrastructure/backups/download.sh @@ -99,7 +99,13 @@ mkdir -p $BACKUP_RAW_FILES_DIR/extract tar -xvf $BACKUP_RAW_FILES_DIR/${LABEL}.tar.gz -C $BACKUP_RAW_FILES_DIR/extract # Delete previous days restore(s) and move the newly downloaded one in place -rm -rf /data/backups/* +for BACKUP_DIR in /data/backups/*; do + if [ -d "$BACKUP_DIR" ]; then + rm -rf $BACKUP_DIR/* + fi +done + + mv $BACKUP_RAW_FILES_DIR/extract/elasticsearch /data/backups/elasticsearch mv $BACKUP_RAW_FILES_DIR/extract/influxdb /data/backups/influxdb/${LABEL} diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index f0dbc1efa..e8122eefe 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -555,7 +555,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`)' + - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`) &!Path(`/dashboards/queries.json`)' - 'traefik.http.services.countryconfig.loadbalancer.server.port=3040' - 'traefik.http.routers.countryconfig.tls=true' - 'traefik.http.routers.countryconfig.tls.certresolver=certResolver' @@ -568,13 +568,15 @@ services: - 'traefik.http.middlewares.countryconfig.headers.stsseconds=31536000' - 'traefik.http.middlewares.countryconfig.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.countryconfig.headers.stspreload=true' - # This is an invalid IP range, effectively blocking all IPs from accessing /email path. + # This is an invalid IP range, effectively blocking all IPs from accessing below paths. # It's only meant to be accessed from the internal docker network. - - 'traefik.http.middlewares.block-email.ipwhitelist.sourcerange=255.255.255.255' + - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' - 'traefik.http.routers.block-email.rule=Host(`countryconfig.{{hostname}}`) && Path(`/email`)' - - 'traefik.http.routers.block-email.middlewares=block-email' + - 'traefik.http.routers.block-email.middlewares=block-internal-routes' - 'traefik.http.routers.block-notification.rule=Host(`countryconfig.{{hostname}}`) && Path(`/notification`)' - - 'traefik.http.routers.block-notification.middlewares=block-email' + - 'traefik.http.routers.block-notification.middlewares=block-internal-routes' + - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboards/queries.json`)' + - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 environment: - MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0 @@ -849,7 +851,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`)' + - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`) && !Path(`/dashboardQueries`)' - 'traefik.http.services.config.loadbalancer.server.port=2021' - 'traefik.http.routers.config.tls=true' - 'traefik.http.routers.config.tls.certresolver=certResolver' @@ -861,6 +863,9 @@ services: - 'traefik.http.middlewares.config.headers.stsseconds=31536000' - 'traefik.http.middlewares.config.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.config.headers.stspreload=true' + - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' + - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboardQueries`)' + - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 networks: - overlay_net diff --git a/infrastructure/docker-compose.staging-deploy.yml b/infrastructure/docker-compose.staging-deploy.yml index 783e2a598..826aa598f 100644 --- a/infrastructure/docker-compose.staging-deploy.yml +++ b/infrastructure/docker-compose.staging-deploy.yml @@ -159,6 +159,7 @@ services: depends_on: - mongo1 + mongo-on-update: environment: - REPLICAS=1 diff --git a/infrastructure/monitoring/kibana/setup-config.sh b/infrastructure/monitoring/kibana/setup-config.sh index 38f439bd7..a2f5cca78 100755 --- a/infrastructure/monitoring/kibana/setup-config.sh +++ b/infrastructure/monitoring/kibana/setup-config.sh @@ -23,7 +23,7 @@ if [ "$status_code" -ne 200 ]; then fi # Delete all alerts -$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net stedolan/jq -r '.data[].id' | while read -r id; do +$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net ghcr.io/jqlang/jq -r '.data[].id' | while read -r id; do $docker_command --connect-timeout 60 -X DELETE -H 'kbn-xsrf: true' -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "http://kibana:5601/api/alerting/rule/$id" done @@ -31,7 +31,7 @@ done $docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD -X POST "http://kibana:5601/api/saved_objects/_import?overwrite=true" -H 'kbn-xsrf: true' --form file=@/config.ndjson > /dev/null # Re-enable all alerts -$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net stedolan/jq -r '.data[].id' | while read -r id; do +$docker_command --connect-timeout 60 -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "$kibana_alerting_api_url" | docker run --rm -i --network=opencrvs_overlay_net ghcr.io/jqlang/jq -r '.data[].id' | while read -r id; do $docker_command --connect-timeout 60 -X POST -H 'kbn-xsrf: true' -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "http://kibana:5601/api/alerting/rule/$id/_disable" $docker_command --connect-timeout 60 -X POST -H 'kbn-xsrf: true' -u elastic:$ELASTICSEARCH_SUPERUSER_PASSWORD "http://kibana:5601/api/alerting/rule/$id/_enable" done diff --git a/infrastructure/server-setup/backups.yml b/infrastructure/server-setup/backups.yml index fa8717219..c690e2f8a 100644 --- a/infrastructure/server-setup/backups.yml +++ b/infrastructure/server-setup/backups.yml @@ -111,6 +111,8 @@ marker: '# {mark} ANSIBLE MANAGED BLOCK docker-manager-first {{ manager_hostname }}' create: yes mode: 0600 + owner: '{{ external_backup_server_user }}' + tags: - backups diff --git a/postman/Event Notification.postman_collection.json b/postman/Event Notification.postman_collection.json index 94cd2c6a4..d969556be 100644 --- a/postman/Event Notification.postman_collection.json +++ b/postman/Event Notification.postman_collection.json @@ -12,7 +12,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"author\": [],\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Tatke\"\n ],\n \"given\": [\n \"Harney\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"3624667568\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghanland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Housewife\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"6848901132\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Madgeland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Businessman\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"FAR\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:45c68568-2ca0-4932-9731-535dd4180fe0\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"57722-1\",\n \"display\": \"Birth plurality of Pregnancy\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": \"SINGLE\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d2d3d5b8-658e-4c29-9ec5-cb2431b4ddf3\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"3141-9\",\n \"display\": \"Body weight Measured\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": 4,\n \"unit\": \"kg\",\n \"system\": \"http://unitsofmeasure.org\",\n \"code\": \"kg\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:706905cf-7e5d-4d9f-866a-a3795780a990\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"73764-3\",\n \"display\": \"Birth attendant title\"\n }\n ]\n },\n \"valueString\": \"PHYSICIAN\"\n }\n }\n ]\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"author\": [],\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Tatke\"\n ],\n \"given\": [\n \"Harney\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"3624667568\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghanland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Housewife\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"use\": \"official\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n },\n \"value\": \"6848901132\"\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"telecom\": [\n {\n \"use\": \"mobile\",\n \"system\": \"phone\",\n \"value\": \"+260759205190\"\n }\n ],\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthInteger\": 2,\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Madgeland\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/patient-occupation\",\n \"valueString\": \"Businessman\"\n },\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"FAR\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/educational-attainment\",\n \"valueString\": \"POST_SECONDARY_ISCED_4\"\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:45c68568-2ca0-4932-9731-535dd4180fe0\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"57722-1\",\n \"display\": \"Birth plurality of Pregnancy\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": \"SINGLE\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d2d3d5b8-658e-4c29-9ec5-cb2431b4ddf3\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"3141-9\",\n \"display\": \"Body weight Measured\"\n }\n ]\n },\n \"valueQuantity\": {\n \"value\": 4,\n \"unit\": \"kg\",\n \"system\": \"http://unitsofmeasure.org\",\n \"code\": \"kg\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:706905cf-7e5d-4d9f-866a-a3795780a990\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"procedure\",\n \"display\": \"Procedure\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"73764-3\",\n \"display\": \"Birth attendant title\"\n }\n ]\n },\n \"valueString\": \"PHYSICIAN\"\n }\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -34,7 +34,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Min\"\n ],\n \"given\": [\n \"Child\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n }\n ]\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"meta\": {\n \"lastUpdated\": \"2022-08-14T14:43:47.000Z\"\n },\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"8f793c5a-3d53-4c9b-898b-1c04759716c6\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"final\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"birth-notification\"\n }\n ],\n \"text\": \"Birth Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n },\n \"date\": \"2022-08-14T14:43:47.000Z\",\n \"title\": \"Birth Notification\",\n \"section\": [\n {\n \"title\": \"Child details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"child-details\"\n }\n ],\n \"text\": \"Child details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\"\n }\n ]\n },\n {\n \"title\": \"Birth encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"birth-encounter\"\n }\n ],\n \"text\": \"Birth encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\"\n }\n ]\n },\n {\n \"title\": \"Mother's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"mother-details\"\n }\n ],\n \"text\": \"Mother's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n ]\n },\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\"\n }\n ]\n },\n {\n \"title\": \"Father's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"father-details\"\n }\n ],\n \"text\": \"Father's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8546aaf3-8a60-4150-bc24-ab5579bc0fa2\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"intent\": \"unknown\",\n \"identifier\": [],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"BIRTH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:37dd8e55-69c0-493d-b1a0-b7462a1d806a\"\n },\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"MOTHER\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260759205190\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:760c393e-4dc3-4572-83f6-b70765963ef1\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Min\"\n ],\n \"given\": [\n \"Child\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2022-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Mom\"\n ]\n }\n ],\n \"gender\": \"female\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:b74fbd0e-8536-4c11-833d-781e89a4b553\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"MOTHER\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:d9d3a8c8-6a47-47a1-be86-0493a4ec55a7\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:ad1e15bb-51da-449a-8a12-c7dae10728e4\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"family\": [\n \"Ratke\"\n ],\n \"given\": [\n \"Dad\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"2002-06-29\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7cb1d9cc-ea4b-4046-bea0-38bdf3082f56\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -56,7 +56,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\"\n }\n ]\n },\n {\n \"title\": \"Spouse's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"spouse-details\"\n }\n ],\n \"text\": \"Spouse's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d3bdcf50-f6b9-40aa-a30b-d9515275becc\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Salam\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1975-12-12\",\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-11-12\",\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Anjum\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1985-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"false\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:59:51.196Z\"\n }\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:d3bdcf50-f6b9-40aa-a30b-d9515275becc\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:56e7d617-1011-4ca1-863f-3b7262709329\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:9ae636db-f7ad-47aa-9e8a-5bb651482084\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:5c192954-d798-402f-a539-31aada34d005\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Salam\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1975-12-12\",\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-11-12\",\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:398afc2a-09c9-4b7b-bc45-2bef06501cf0\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Anjum\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1985-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:8de42635-6dac-4d59-96b4-943ceb40ad1e\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"false\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:59:51.196Z\"\n }\n}", "options": { "raw": { "language": "json" @@ -78,7 +78,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\"\n }\n ]\n },\n {\n \"title\": \"Spouse's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"spouse-details\"\n }\n ],\n \"text\": \"Spouse's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:cbce7430-3c63-457e-a6d8-ed3b35a97a64\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastLocation\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeLocationId}}\"\n }\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"1234123421\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Bashir\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1971-12-12\",\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-12-12\",\n \"multipleBirthBoolean\": false,\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"4321234257\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Selena\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1980-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7b4fcd33-5b39-463a-bbd4-4ac2e41e5f99\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"uncertified-manner-of-death\",\n \"display\": \"Uncertified manner of death\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/manner-of-death\",\n \"code\": \"NATURAL_CAUSES\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:89c0605f-8eb8-4f58-98ff-317efe64e91f\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-method\",\n \"display\": \"Cause of death method\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-method\",\n \"code\": \"PHYSICIAN\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"true\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:50:26.175Z\"\n }\n}", + "raw": "{\n \"resourceType\": \"Bundle\",\n \"type\": \"document\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\",\n \"resource\": {\n \"identifier\": {\n \"system\": \"urn:ietf:rfc:3986\",\n \"value\": \"7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"resourceType\": \"Composition\",\n \"status\": \"preliminary\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-types\",\n \"code\": \"death-notification\"\n }\n ],\n \"text\": \"Death Notification\"\n },\n \"class\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-classes\",\n \"code\": \"crvs-document\"\n }\n ],\n \"text\": \"CRVS Document\"\n },\n \"title\": \"Death Notification\",\n \"section\": [\n {\n \"title\": \"Informant's details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"informant-details\"\n }\n ],\n \"text\": \"Informant's details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\"\n }\n ]\n },\n {\n \"title\": \"Deceased details\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/doc-sections\",\n \"code\": \"deceased-details\"\n }\n ],\n \"text\": \"Deceased details\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\"\n }\n ]\n },\n {\n \"title\": \"Death encounter\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/sections\",\n \"code\": \"death-encounter\"\n }\n ],\n \"text\": \"Death encounter\"\n },\n \"entry\": [\n {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:cbce7430-3c63-457e-a6d8-ed3b35a97a64\",\n \"resource\": {\n \"resourceType\": \"Task\",\n \"status\": \"draft\",\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/types\",\n \"code\": \"DEATH\"\n }\n ]\n },\n \"focus\": {\n \"reference\": \"urn:uuid:7109f150-6262-4798-835e-55b3e4dd4cfb\"\n },\n \"identifier\": [],\n \"extension\": [\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person\",\n \"valueString\": \"SPOUSE\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-phone-number\",\n \"valueString\": \"+260712345679\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/contact-person-email\",\n \"valueString\": \"axon@gmail.com\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/timeLoggedMS\",\n \"valueInteger\": 0\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/in-complete-fields\",\n \"valueString\": \"N/A\"\n },\n {\n \"url\": \"http://opencrvs.org/specs/extension/regLastOffice\",\n \"valueReference\": {\n \"reference\": \"Location/{{officeId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:1363f62f-e34f-4c03-90f3-5fa1d40d0892\",\n \"resource\": {\n \"resourceType\": \"RelatedPerson\",\n \"relationship\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype\",\n \"code\": \"SPOUSE\"\n }\n ]\n },\n \"patient\": {\n \"reference\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\"\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:233b56c8-aa3d-4d32-9d42-7afe1200fc4f\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"1234123421\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Bashir\"\n ],\n \"family\": [\n \"Ahmed\"\n ]\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1971-12-12\",\n \"maritalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/StructureDefinition/marital-status\",\n \"code\": \"M\"\n }\n ],\n \"text\": \"MARRIED\"\n },\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"deceasedBoolean\": true,\n \"deceasedDateTime\": \"2021-12-12\",\n \"multipleBirthBoolean\": false,\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:42061dc8-0d97-493a-94e4-e60d0f3fc070\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"active\": true,\n \"identifier\": [\n {\n \"value\": \"4321234257\",\n \"type\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/identifier-type\",\n \"code\": \"NATIONAL_ID\"\n }\n ]\n }\n }\n ],\n \"name\": [\n {\n \"use\": \"en\",\n \"given\": [\n \"Selena\"\n ],\n \"family\": [\n \"Begum\"\n ]\n }\n ],\n \"birthDate\": \"1980-12-12\",\n \"deceasedBoolean\": false,\n \"multipleBirthBoolean\": false,\n \"address\": [\n {\n \"type\": \"PRIMARY_ADDRESS\",\n \"line\": [\n \"12\",\n \"Usual Street\",\n \"Usual Residental Area\",\n \"\",\n \"\",\n \"URBAN\"\n ],\n \"city\": \"Meghnan\",\n \"district\": \"{{districtId}}\",\n \"state\": \"{{stateId}}\",\n \"postalCode\": \"52275\",\n \"country\": \"{{countryCode}}\"\n }\n ],\n \"extension\": [\n {\n \"url\": \"http://hl7.org/fhir/StructureDefinition/patient-nationality\",\n \"extension\": [\n {\n \"url\": \"code\",\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"urn:iso:std:iso:3166\",\n \"code\": \"{{countryCode}}\"\n }\n ]\n }\n },\n {\n \"url\": \"period\",\n \"valuePeriod\": {\n \"start\": \"\",\n \"end\": \"\"\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\",\n \"resource\": {\n \"resourceType\": \"Encounter\",\n \"status\": \"finished\",\n \"location\": [\n {\n \"location\": {\n \"reference\": \"Location/{{facilityId}}\"\n }\n }\n ]\n }\n },\n {\n \"fullUrl\": \"urn:uuid:7b4fcd33-5b39-463a-bbd4-4ac2e41e5f99\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"uncertified-manner-of-death\",\n \"display\": \"Uncertified manner of death\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/manner-of-death\",\n \"code\": \"NATURAL_CAUSES\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:89c0605f-8eb8-4f58-98ff-317efe64e91f\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-method\",\n \"display\": \"Cause of death method\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-method\",\n \"code\": \"PHYSICIAN\"\n }\n ]\n }\n }\n },\n {\n \"fullUrl\": \"urn:uuid:6a0bd2db-4fa4-4665-ad62-24821dd01f2a\",\n \"resource\": {\n \"resourceType\": \"Observation\",\n \"status\": \"final\",\n \"context\": {\n \"reference\": \"urn:uuid:e9e011f5-78dc-4e2f-8ce2-ea80e04a510f\"\n },\n \"category\": [\n {\n \"coding\": [\n {\n \"system\": \"http://hl7.org/fhir/observation-category\",\n \"code\": \"vital-signs\",\n \"display\": \"Vital Signs\"\n }\n ]\n }\n ],\n \"code\": {\n \"coding\": [\n {\n \"system\": \"http://loinc.org\",\n \"code\": \"cause-of-death-established\",\n \"display\": \"Cause of death established\"\n }\n ]\n },\n \"valueCodeableConcept\": {\n \"coding\": [\n {\n \"system\": \"http://opencrvs.org/specs/cause-of-death-established\",\n \"code\": \"true\"\n }\n ]\n }\n }\n }\n ],\n \"meta\": {\n \"lastUpdated\": \"2022-11-30T05:50:26.175Z\"\n }\n}", "options": { "raw": { "language": "json" @@ -121,10 +121,6 @@ } ], "variable": [ - { - "key": "officeLocationId", - "value": "c24c0b72-11b5-4c1a-bbb7-61112fa6f481" - }, { "key": "officeId", "value": "0be1dc9b-5c8b-4d20-a88c-08cafc71c99a" diff --git a/src/form/index.ts b/src/form/index.ts index 4fc80de45..df9c4c8a3 100644 --- a/src/form/index.ts +++ b/src/form/index.ts @@ -14,15 +14,14 @@ import { birthForm } from './birth' import { deathForm } from './death' import { marriageForm } from './marriage' import { IForms, Event } from './types/types' -import { getUserOfficeLocationHierarchy } from '@countryconfig/utils/users' + +import { fetchUserLocationHierarchy } from '@countryconfig/utils/users' export async function formHandler(req: Request): Promise { - const addressHierarchy = ( - await getUserOfficeLocationHierarchy( - req.headers.authorization, - req.auth.credentials.sub as string - ) - ).map(({ id }) => id) + const addressHierarchy = await fetchUserLocationHierarchy( + req.headers.authorization, + req.auth.credentials.sub as string + ) // ====================== NOTE REGARDING MIGRATING FROM OPNCRVS v1.2 OR EARLIER ====================== // SIMPLY RETURN A JSON OF YOUR FULL FORM HERE, WITH THE ADDITION OF THE NEW MARRIAGE AND VERSION PROP diff --git a/src/form/marriage/index.ts b/src/form/marriage/index.ts index c88cab80b..213ffdeff 100644 --- a/src/form/marriage/index.ts +++ b/src/form/marriage/index.ts @@ -84,7 +84,7 @@ export const marriageForm: ISerializedForm = { { id: 'informant', viewType: 'form', - name: formMessageDescriptors.registrationName, + name: formMessageDescriptors.informantName, title: formMessageDescriptors.informantTitle, groups: [ { diff --git a/src/index.ts b/src/index.ts index e6166b8d0..b753400c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -375,6 +375,7 @@ export async function createServer() { handler: dashboardQueriesHandler, options: { tags: ['api'], + auth: false, description: 'Serves dashboard view refresher queries' } }) @@ -474,7 +475,7 @@ export async function createServer() { validate: { payload: emailSchema }, - description: 'Handles sending SMS' + description: 'Handles sending email using a predefined template file' } }) diff --git a/src/translations/client.csv b/src/translations/client.csv index 1cab2d872..bc9db2c32 100644 --- a/src/translations/client.csv +++ b/src/translations/client.csv @@ -383,7 +383,7 @@ constants.issueConfirmationMessage,Confirmation of issuance,Please confirm that constants.issueToBride,Issuing to bride,Issue to bride,Délivré à) la mariée constants.issueToFather,Issuing to father,Issue to father,Délivré au père constants.issueToGroom,Issuing to groom,Issue to groom,délivré au marié -constants.issueToInformant,Issuance of death to informant,Issue to informant ({informant}),Delivré à l'informateur +constants.issueToInformant,Issuance of death to informant,"Issue to informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Delivré à l'informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" constants.issueToMother,Issuing to mother,Issue to mother,Délivré à la mère constants.issueToSomeoneElse,Issuing to someone else,Issue to someone else,Délivrer à quelqu'un d'autre constants.issuedBy,The issued by sec text,Issued by,Délivré par @@ -524,7 +524,7 @@ correction.corrector.groom,Label for groom option in certificate correction form correction.corrector.idCheck,,Check proof of ID,Vérifiez la preuve d'identité. Correspond-elle aux détails suivants ? correction.corrector.idCheckVerify,,Yes,Oui correction.corrector.idCheckWithoutVerify,,No,Non -correction.corrector.informant,Label for informant option in certificate correction form,Informant ({informant}),Informateur +correction.corrector.informant,Label for informant option in certificate correction form,"Informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" correction.corrector.legalGuardian,Label for legal guardian option in certificate correction form,Legal guardian,Tuteur légal correction.corrector.me,Label for registrar option in certificate correction form,Me,Moi correction.corrector.mother,Label for mother option in certificate correction form,Mother,Mère @@ -910,8 +910,10 @@ form.field.label.ageOfGroom,,Age of groom,Âge du marié form.field.label.ageOfInformant,,Age of informant,Âge de l'informateur form.field.label.ageOfMother,,Age of mother,Âge de la mère form.field.label.ageOfSpouse,,Age of spouse,Âge du conjoint +form.field.label.app.certifyRecordTo.bride,,Print and issue to bride,Imprimer et envoyer à la mariée form.field.label.app.certifyRecordTo.father,,Print and issue to father,Imprimer et remettre au père -form.field.label.app.certifyRecordTo.informant,,Print and issue to informant ({informant}),Imprimer et remettre à l'informateur ({informant}) +form.field.label.app.certifyRecordTo.groom,,Print and issue to groom,Imprimer et envoyer au marié +form.field.label.app.certifyRecordTo.informant,,"Print and issue to informant ({informant, select, MOTHER {Mother} FATHER {Father} GRANDFATHER {Grandfather} GRANDMOTHER {Grandmother} BROTHER {Brother} SISTER {Sister} LEGAL_GUARDIAN {Legal guardian} BRIDE {Bride} GROOM {Groom} HEAD_OF_GROOM_FAMILY {Head of groom's family} HEAD_OF_BRIDE_FAMILY {Head of bride's family} SPOUSE {Spouse} SON {Son} DAUGHTER {Daughter} SON_IN_LAW {Son in law} DAUGHTER_IN_LAW {Daughter in law} GRANDSON {Grandson} GRANDDAUGHTER {Granddaughter} other {{informant}}})","Imprimer et remettre à l'informateur ({informant, select, MOTHER {Mère} FATHER {Père} GRANDFATHER {Grand-père} GRANDMOTHER {Grand-mère} BROTHER {Frère} SISTER {Sœur} LEGAL_GUARDIAN {Tuteur légal} BRIDE {Mariée} GROOM {Marié} HEAD_OF_GROOM_FAMILY {Chef de la famille du marié} HEAD_OF_BRIDE_FAMILY {Chef de la famille de la mariée} SPOUSE {Conjoint} SON {Fils} DAUGHTER {Fille} SON_IN_LAW {Beau-fils} DAUGHTER_IN_LAW {Belle-fille} GRANDSON {Petit-fils} GRANDDAUGHTER {Petite-fille} other {{informant}}})" form.field.label.app.certifyRecordTo.mother,,Print and issue to mother,Imprimer et remettre à la mère form.field.label.app.phoneVerWarn,,Check with the informant that the mobile phone number you have entered is correct,Vérifiez auprès de l'informateur que le numéro de téléphone mobile que vous avez indiqué est correct. form.field.label.app.whoContDet.app,,Informant,Informateur @@ -1296,7 +1298,7 @@ form.section.accountDetails,,Account details,Coordonnées du compte form.section.assignedRegistrationOffice,,Assign to a registration office,A quel bureau voulez-vous affecter un nouvel utilisateur ? form.section.assignedRegistrationOfficeGroupTitle,,Assigned registration office,Bureau d'enregistrement assigné form.section.bride.headOfBrideFamily,,Head of bride's family,Chef de la famille de la mariée -form.section.bride.name,,Print and issue to bride,Imprimer et envoyer à la mariée +form.section.bride.name,,Bride,Mariée form.section.bride.title,,Bride's details,Détails de la mariée form.section.causeOfDeath.name,,Cause of Death,Cause du décès form.section.causeOfDeath.title,,What is the medically certified cause of death?,Quelle est la cause de décès médicalement certifiée ? @@ -1333,7 +1335,7 @@ form.section.documents.uploadImage,,Upload a photo of the supporting document,T form.section.father.name,,Father,Père form.section.father.title,,Father's details,Information du père form.section.groom.headOfGroomFamily,,Head of groom's family,Chef de la famille du marié -form.section.groom.name,,Print and issue to groom,Imprimer et envoyer au marié +form.section.groom.name,,Groom,Marié form.section.groom.title,,Groom's details,Détails du marié form.section.informant.name,,Informant,Informateur form.section.informant.title,,Informant's details,information de l'informateur diff --git a/src/utils/users.ts b/src/utils/users.ts index cf0266c1b..415c77bb5 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,38 +1,20 @@ -import { APPLICATION_CONFIG_URL, GATEWAY_URL } from '@countryconfig/constants' +import { GATEWAY_URL } from '@countryconfig/constants' import fetch from 'node-fetch' import gql from 'graphql-tag' import { print } from 'graphql/language/printer' -import { - Maybe, - Scalars, - HumanName, - Role, - Status, - SystemRoleType, - Location -} from '@countryconfig/data-generator/gateway' import { URL } from 'url' type GetUser = { - __typename?: 'User' - id: Scalars['ID'] - creationDate: Scalars['String'] - username?: Maybe - practitionerId: Scalars['String'] - mobile?: Maybe - systemRole: SystemRoleType - role: Role - status: Status - name: Array - primaryOffice?: { - __typename?: 'Location' - id: Scalars['ID'] - name?: Maybe - alias?: Maybe> - status?: Maybe + primaryOffice: { + hierarchy: Array<{ + id: string + }> } } -async function getUser(token: string, userId: string): Promise { +export async function fetchUserLocationHierarchy( + token: string, + userId: string +) { const url = new URL('graphql', GATEWAY_URL) const getUsersRes = await fetch(url, { method: 'POST', @@ -48,36 +30,11 @@ async function getUser(token: string, userId: string): Promise { query: print(gql` query fetchUser($userId: String!) { getUser(userId: $userId) { - id - creationDate - username - practitionerId - mobile - systemRole - role { - _id - labels { - lang - label - __typename - } - __typename - } - status - name { - use - firstNames - familyName - __typename - } primaryOffice { - id - name - alias - status - __typename + hierarchy { + id + } } - __typename } } `) @@ -88,28 +45,5 @@ async function getUser(token: string, userId: string): Promise { data: { getUser: GetUser } } - return res.data.getUser -} - -async function getLocationHierarchy(locationId: string): Promise { - const url = new URL( - `location/${locationId}/hierarchy`, - APPLICATION_CONFIG_URL - ) - const res = await fetch(url) - if (!res.ok) { - throw new Error('Unable to retrieve location hierarchy') - } - return res.json() -} - -export async function getUserOfficeLocationHierarchy( - token: string, - userId: string -): Promise { - const user = await getUser(token, userId) - if (!user.primaryOffice) { - throw new Error('No primary office found for user') - } - return getLocationHierarchy(user.primaryOffice.id) + return res.data.getUser.primaryOffice.hierarchy.map(({ id }) => id) } From 81b1da8e154c9f7fe9768d733d1f8a248da55288 Mon Sep 17 00:00:00 2001 From: Tahmid Rahman <42269993+tahmidrahman-dsi@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:46:30 +0600 Subject: [PATCH 42/46] [OCRVS-7016] Fix typo in traefik router rule (#130) * fix: typo in countryconfig traefik rule * fix: remove duplicate traefik route definition in config --- infrastructure/docker-compose.deploy.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 82fdfe861..bce5ed74f 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -554,7 +554,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`) &!Path(`/dashboards/queries.json`)' + - 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`) && !Path(`/dashboards/queries.json`)' - 'traefik.http.services.countryconfig.loadbalancer.server.port=3040' - 'traefik.http.routers.countryconfig.tls=true' - 'traefik.http.routers.countryconfig.tls.certresolver=certResolver' @@ -850,7 +850,7 @@ services: deploy: labels: - 'traefik.enable=true' - - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`) && !Path(`/dashboardQueries`)' + - 'traefik.http.routers.config.rule=Host(`config.{{hostname}}`)' - 'traefik.http.services.config.loadbalancer.server.port=2021' - 'traefik.http.routers.config.tls=true' - 'traefik.http.routers.config.tls.certresolver=certResolver' @@ -862,9 +862,6 @@ services: - 'traefik.http.middlewares.config.headers.stsseconds=31536000' - 'traefik.http.middlewares.config.headers.stsincludesubdomains=true' - 'traefik.http.middlewares.config.headers.stspreload=true' - - 'traefik.http.middlewares.block-internal-routes.ipwhitelist.sourcerange=255.255.255.255' - - 'traefik.http.routers.block-dashboard-queries.rule=Host(`countryconfig.{{hostname}}`) && Path(`/dashboardQueries`)' - - 'traefik.http.routers.block-dashboard-queries.middlewares=block-internal-routes' replicas: 1 networks: - overlay_net From 69bbc49123d05a7d7022e1f92570944a0845f7fc Mon Sep 17 00:00:00 2001 From: Anamul Haque Date: Thu, 13 Jun 2024 17:10:19 +0600 Subject: [PATCH 43/46] fix: remove 'Other' dropdown when Informant is mother or father (#1008) --- src/form/birth/required-sections.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/form/birth/required-sections.ts b/src/form/birth/required-sections.ts index 3acd2022c..1169844ae 100644 --- a/src/form/birth/required-sections.ts +++ b/src/form/birth/required-sections.ts @@ -193,7 +193,7 @@ export const documentsSection = { { action: 'hide', expression: - "(draftData && draftData.registration && draftData.registration.informantType && selectedInformantAndContactType.selectedInformantType && (selectedInformantAndContactType.selectedInformantType === 'MOTHER' || selectedInformantAndContactType.selectedInformantType === 'FATHER'))" + "draftData?.informant?.informantType === 'MOTHER' || draftData?.informant?.informantType === 'FATHER'" } ], mapping: getFieldMapping('documents') From 33b3b535480944adfcac7ff084a6a473adfe8091 Mon Sep 17 00:00:00 2001 From: "Md. Ashikul Alam" <32668488+Nil20@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:36:10 +0600 Subject: [PATCH 44/46] fix: update same address field conditional (#1010) * fix: update same address field conditional * chore: update divider and address field conditionals * chore: convert conditional expression to array --- src/form/addresses/index.ts | 14 ++++++++------ src/form/birth/index.ts | 8 ++++++-- src/form/common/default-validation-conditionals.ts | 14 ++++++++++++++ src/form/death/index.ts | 13 ++++++++++--- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/form/addresses/index.ts b/src/form/addresses/index.ts index e8bc1efb1..d6b96d15e 100644 --- a/src/form/addresses/index.ts +++ b/src/form/addresses/index.ts @@ -19,7 +19,9 @@ import { informantNotMotherOrFather, isInformantSpouse, mothersDetailsDontExistOnOtherPage, - primaryAddressSameAsOtherPrimaryAddress /*, + primaryAddressSameAsOtherPrimaryAddress, + hideIfDeceasedAddressNotAvailable, + hideIfMotherAddressNotAvailable /*, SPOUSE_DETAILS_DONT_EXIST*/ } from '../common/default-validation-conditionals' import { formMessageDescriptors } from '../common/messages' @@ -121,14 +123,14 @@ export const defaultAddressConfiguration: IAddressConfiguration[] = [ { config: AddressSubsections.PRIMARY_ADDRESS_SUBSECTION, label: formMessageDescriptors.primaryAddress, - conditionalCase: `${FATHER_DETAILS_DONT_EXIST}` + conditionalCase: `${FATHER_DETAILS_DONT_EXIST} || ${hideIfMotherAddressNotAvailable[0].expression}` }, { config: AddressCopyConfigCases.PRIMARY_ADDRESS_SAME_AS_OTHER_PRIMARY, label: formMessageDescriptors.primaryAddressSameAsOtherPrimary, xComparisonSection: 'father', yComparisonSection: 'mother', - conditionalCase: `(${detailsDontExist} || ${mothersDetailsDontExistOnOtherPage})` + conditionalCase: `(${detailsDontExist} || ${mothersDetailsDontExistOnOtherPage}) || ${hideIfMotherAddressNotAvailable[0].expression}` }, { config: AddressCases.PRIMARY_ADDRESS, @@ -176,7 +178,7 @@ export const defaultAddressConfiguration: IAddressConfiguration[] = [ { config: AddressCopyConfigCases.PRIMARY_ADDRESS_SAME_AS_OTHER_PRIMARY, label: formMessageDescriptors.primaryAddressSameAsDeceasedsPrimary, - conditionalCase: `${isInformantSpouse}`, + conditionalCase: `${isInformantSpouse} || ${hideIfDeceasedAddressNotAvailable[0].expression}`, xComparisonSection: 'informant', yComparisonSection: 'deceased' }, @@ -242,14 +244,14 @@ export const defaultAddressConfiguration: IAddressConfiguration[] = [ { config: AddressSubsections.PRIMARY_ADDRESS_SUBSECTION, label: formMessageDescriptors.primaryAddress, - conditionalCase: `${SPOUSE_DETAILS_DONT_EXIST}` + conditionalCase: `${SPOUSE_DETAILS_DONT_EXIST} || ${hideIfDeceasedAddressNotAvailable[0].expression}` }, { config: AddressCopyConfigCases.PRIMARY_ADDRESS_SAME_AS_OTHER_PRIMARY, label: formMessageDescriptors.primaryAddressSameAsDeceasedsPrimary, xComparisonSection: 'spouse', yComparisonSection: 'deceased', - conditionalCase: `${detailsDontExist}` + conditionalCase: `${detailsDontExist} || ${hideIfDeceasedAddressNotAvailable[0].expression}` }, { config: AddressCases.PRIMARY_ADDRESS, diff --git a/src/form/birth/index.ts b/src/form/birth/index.ts index 1231ab35b..e623f07f1 100644 --- a/src/form/birth/index.ts +++ b/src/form/birth/index.ts @@ -60,7 +60,8 @@ import { informantNotMotherOrFather, detailsExistConditional, ageOfIndividualValidators, - ageOfParentsConditionals + ageOfParentsConditionals, + hideIfMotherAddressNotAvailable } from '../common/default-validation-conditionals' import { informantFirstNameConditionals, @@ -440,7 +441,10 @@ export const birthForm: ISerializedForm = { // preceding field of address fields divider('father-nid-seperator', detailsExist), // ADDRESS FIELDS WILL RENDER HERE - divider('father-address-seperator', detailsExist), + divider('father-address-seperator', [ + ...detailsExist, + ...hideIfMotherAddressNotAvailable + ]), getMaritalStatus(certificateHandlebars.fatherMaritalStatus, [ { action: 'hide', diff --git a/src/form/common/default-validation-conditionals.ts b/src/form/common/default-validation-conditionals.ts index 093c2b7a8..e3f3ed41d 100644 --- a/src/form/common/default-validation-conditionals.ts +++ b/src/form/common/default-validation-conditionals.ts @@ -112,6 +112,20 @@ export const hideIfInformantMotherOrFather = [ export const isInformantSpouse = '!values.informantType || values.informantType==="SPOUSE"' +export const hideIfDeceasedAddressNotAvailable = [ + { + action: 'hide', + expression: '!(draftData && draftData.deceased?.countryPrimaryDeceased)' + } +] + +export const hideIfMotherAddressNotAvailable = [ + { + action: 'hide', + expression: '!(draftData && draftData.mother?.countryPrimaryMother)' + } +] + export const hideIfInformantSpouse = [ { action: 'hide', diff --git a/src/form/death/index.ts b/src/form/death/index.ts index 48bec5381..07c8f6ba0 100644 --- a/src/form/death/index.ts +++ b/src/form/death/index.ts @@ -62,7 +62,8 @@ import { spouseFamilyNameConditionals, spouseFirstNameConditionals, hideIfInformantSpouse, - hideIfNidIntegrationEnabled + hideIfNidIntegrationEnabled, + hideIfDeceasedAddressNotAvailable } from '../common/default-validation-conditionals' import { documentsSection, registrationSection } from './required-sections' import { @@ -360,9 +361,15 @@ export const deathForm = { getIDType('death', 'spouse', detailsExist, true), ...getIDNumberFields('spouse', detailsExist, true), // preceding field of address fields - divider('spouse-nid-seperator', detailsExist), + divider('spouse-nid-seperator', [ + ...detailsExist, + ...hideIfDeceasedAddressNotAvailable + ]), // ADDRESS FIELDS WILL RENDER HERE - divider('spouse-address-separator') + divider( + 'spouse-address-separator', + hideIfDeceasedAddressNotAvailable + ) ], previewGroups: [spouseNameInEnglish] } From dd943f0f1dd2e29fcb6e5c0ef1f8be6fee5c7fa0 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Thu, 13 Jun 2024 17:41:06 +0600 Subject: [PATCH 45/46] docs: update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 924a0e41f..c94cd6b7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - Added french translation of informant for print certificate flow, issue certificate flow & correction flow - Groom's and Bride's name, printIssue translation variables updated [#124](https://github.com/opencrvs/opencrvs-countryconfig/pull/124) - Change condition of Number of previous births +- Remove 'Other' dropdown when informant is mother or father [#7011](https://github.com/opencrvs/opencrvs-core/issues/7011) +- Hide same as other primary address field if the other person's details not available [#7000](https://github.com/opencrvs/opencrvs-core/issues/7000) ## [1.4.1](https://github.com/opencrvs/opencrvs-farajaland/compare/v1.4.0...v1.4.1) From e67f4d6ab957941d1eedd146a3679557ba46035c Mon Sep 17 00:00:00 2001 From: Anamul Haque Date: Thu, 13 Jun 2024 17:42:59 +0600 Subject: [PATCH 46/46] fix: nid validation to prevent the informant, father, and mother from having the same id (#1011) * changed parametes like mother.id to motherNationalId * updated changelog.md * updated changelog.md --- CHANGELOG.md | 1 + .../common/default-validation-conditionals.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c94cd6b7f..8e43e6294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - Each environment now has a dedicated docker-compose--deploy.yml. Use `environment:init` to create a new environment and generate a corresponding file for customizable configurations. - 🔒 OpenHIM console is no longer exposed via HTTP. - Ansible playbooks are refactored into smaller task files. +- Resolved the issue of National ID validation to prevent the informant, father, and mother from having the same ID. ### New features diff --git a/src/form/common/default-validation-conditionals.ts b/src/form/common/default-validation-conditionals.ts index e3f3ed41d..0cd063d3a 100644 --- a/src/form/common/default-validation-conditionals.ts +++ b/src/form/common/default-validation-conditionals.ts @@ -317,7 +317,7 @@ export function getNationalIDValidators(configCase: string): Validator[] { }, { operation: 'duplicateIDNumber', - parameters: ['mother.iD'] + parameters: ['mother.motherNationalId'] } ] } else if (configCase === 'mother') { @@ -328,7 +328,7 @@ export function getNationalIDValidators(configCase: string): Validator[] { }, { operation: 'duplicateIDNumber', - parameters: ['father.iD'] + parameters: ['father.fatherNationalId'] } ] } else if (configCase === 'deceased') { @@ -339,7 +339,7 @@ export function getNationalIDValidators(configCase: string): Validator[] { }, { operation: 'duplicateIDNumber', - parameters: ['informant.informantID'] + parameters: ['informant.informantNationalId'] } ] } else if (configCase === 'groom') { @@ -350,7 +350,7 @@ export function getNationalIDValidators(configCase: string): Validator[] { }, { operation: 'duplicateIDNumber', - parameters: ['bride.iD'] + parameters: ['bride.brideNationalId'] } ] } else if (configCase === 'bride') { @@ -361,7 +361,7 @@ export function getNationalIDValidators(configCase: string): Validator[] { }, { operation: 'duplicateIDNumber', - parameters: ['groom.iD'] + parameters: ['groom.groomNationalId'] } ] } else { @@ -373,23 +373,23 @@ export function getNationalIDValidators(configCase: string): Validator[] { }, { operation: 'duplicateIDNumber', - parameters: ['deceased.deceasedID'] + parameters: ['deceased.deceasedNationalId'] }, { operation: 'duplicateIDNumber', - parameters: ['mother.iD'] + parameters: ['mother.motherNationalId'] }, { operation: 'duplicateIDNumber', - parameters: ['father.iD'] + parameters: ['father.fatherNationalId'] }, { operation: 'duplicateIDNumber', - parameters: ['groom.iD'] + parameters: ['groom.groomNationalId'] }, { operation: 'duplicateIDNumber', - parameters: ['bride.iD'] + parameters: ['bride.brideNationalId'] } ] }