From 814225a339869220e02abd91af3f8c62cce4106e Mon Sep 17 00:00:00 2001
From: Philipp Berger <philipp.berger@nexenio.com>
Date: Tue, 14 Sep 2021 20:31:07 +0200
Subject: [PATCH] chore: release v2.0.1

---
 .prettierrc                                   |  14 ++
 CHANGELOG.md                                  |   3 +
 e2e/package.json                              |   2 +-
 package.json                                  |  95 ++++++------
 scripts/updatePackageJsons.sh                 |  11 ++
 services/backend/.eslintrc.js                 |   2 +
 services/backend/package.json                 |   2 +-
 services/backend/src/constants/riskLevels.ts  |   4 +
 .../src/routes/v4/riskLevels.schemas.ts       |   6 +-
 services/backend/src/routes/v4/riskLevels.ts  |  70 +--------
 .../utils/notifications/notificationsV4.ts    |   2 +-
 services/contact-form/package.json            |   2 +-
 services/health-department/package.json       |   2 +-
 .../NotificationTrigger.react.js              |  35 ++++-
 .../GuestList/GuestList.react.js              |  24 +--
 .../GuestListTable/GuestListTable.react.js    |   6 +-
 .../ContactPersons/Notify/Notify.helper.js    |  11 --
 .../ContactPersons/Notify/Notify.react.js     |   6 +-
 .../App/modals/ModalArea/ModalArea.react.js   |   2 +-
 .../NotificationModal.helper.js               |  14 +-
 .../NotificationModal.react.js                | 138 ++++++++----------
 .../NotificationModal.styled.js               |   8 -
 .../src/constants/deviceTypes.js              |   4 +
 .../health-department/src/messages/de.json    |   8 +-
 .../health-department/src/messages/en.json    |  14 +-
 services/health-department/src/network/api.js |   9 --
 .../health-department/src/reducers/modals.js  |   8 +-
 services/locations/package.json               |   2 +-
 services/scanner/package.json                 |   2 +-
 services/webapp/package.json                  |   2 +-
 30 files changed, 232 insertions(+), 276 deletions(-)
 create mode 100644 .prettierrc
 create mode 100755 scripts/updatePackageJsons.sh
 create mode 100644 services/backend/src/constants/riskLevels.ts
 delete mode 100644 services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js
 create mode 100644 services/health-department/src/constants/deviceTypes.js

diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..a2c7a12
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,14 @@
+{
+  "printWidth": 80,
+  "tabWidth": 2,
+  "useTabs": false,
+  "semi": true,
+  "singleQuote": true,
+  "trailingComma": "es5",
+  "bracketSpacing": true,
+  "jsxBracketSameLine": false,
+  "arrowParens": "avoid",
+  "requirePragma": false,
+  "insertPragma": false,
+  "proseWrap": "always"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31046c7..6b7ae3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+### 2.0.1 (2021-09-14)
+* **backend:** feat: allow notification of individual users with level 2 notifications
+
 ### 2.0.0 (2021-09-10)
 * **backend:** fix: strict JWT schema check for signing app
 * **backend:** fix: netmask of ipv6 addresses
diff --git a/e2e/package.json b/e2e/package.json
index 6496b3b..4f5b8fa 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -1,6 +1,6 @@
 {
   "name": "e2e",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "main": "index.js",
   "private": true,
   "engines": {
diff --git a/package.json b/package.json
index 2ecaba7..15b9d38 100644
--- a/package.json
+++ b/package.json
@@ -1,49 +1,50 @@
 {
-    "name": "@lucaapp/web",
-    "version": "2.0.0",
-    "private": true,
-    "license": "Apache-2.0",
-    "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
-    "scripts": {
-        "latest": ". ./scripts/getGITEnvironmentVariables.sh && docker-compose -f docker-compose.yml",
-        "prod": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=prod docker-compose -f docker-compose.yml",
-        "dev": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=dev docker-compose -f docker-compose.yml -f docker-compose.dev.yml",
-        "test": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=test docker-compose -f docker-compose.yml -f docker-compose.test.yml",
-        "all": "./scripts/yarnAll.sh",
-        "prepare": "git lfs install && husky install && yarn generate:certs",
-        "generate:certs": "./scripts/generateCertificates.sh",
-        "lint:backend": "npm run lint --prefix services/backend",
-        "lint:contact-form": "npm run lint --prefix services/contact-form",
-        "lint:health-department": "npm run lint --prefix services/health-department",
-        "lint:locations": "npm run lint --prefix services/locations",
-        "lint:scanner": "npm run lint --prefix services/scanner",
-        "lint:webapp": "npm run lint --prefix services/webapp"
-    },
-    "lint-staged": {
-        "services/backend/**/*.{js,jsx}": [
-            "npm run --silent lint:backend"
-        ],
-        "services/contact-form/**/*.{js,jsx}": [
-            "npm run --silent lint:contact-form"
-        ],
-        "services/health-department/**/*.{js,jsx}": [
-            "npm run --silent lint:health-department"
-        ],
-        "services/locations/**/*.{js,jsx}": [
-            "npm run --silent lint:locations"
-        ],
-        "services/scanner/**/*.{js,jsx}": [
-            "npm run --silent lint:scanner"
-        ],
-        "services/webapp/**/*.{js,jsx}": [
-            "npm run --silent lint:webapp"
-        ]
-    },
-    "devDependencies": {
-        "husky": "6.0.0",
-        "lint-staged": "11.0.0"
-    },
-    "dependencies": {
-        "@types/lodash": "4.14.172"
-    }
+  "version": "2.0.1",
+  "name": "@lucaapp/web",
+  "private": true,
+  "license": "Apache-2.0",
+  "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
+  "scripts": {
+    "latest": ". ./scripts/getGITEnvironmentVariables.sh && docker-compose -f docker-compose.yml",
+    "prod": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=prod docker-compose -f docker-compose.yml",
+    "dev": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=dev docker-compose -f docker-compose.yml -f docker-compose.dev.yml",
+    "test": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=test docker-compose -f docker-compose.yml -f docker-compose.test.yml",
+    "all": "./scripts/yarnAll.sh",
+    "prepare": "git lfs install && husky install && yarn generate:certs",
+    "update:version": "./scripts/updatePackageJsons.sh",
+    "generate:certs": "./scripts/generateCertificates.sh",
+    "lint:backend": "npm run lint --prefix services/backend",
+    "lint:contact-form": "npm run lint --prefix services/contact-form",
+    "lint:health-department": "npm run lint --prefix services/health-department",
+    "lint:locations": "npm run lint --prefix services/locations",
+    "lint:scanner": "npm run lint --prefix services/scanner",
+    "lint:webapp": "npm run lint --prefix services/webapp"
+  },
+  "lint-staged": {
+    "services/backend/**/*.{js,jsx}": [
+      "npm run --silent lint:backend"
+    ],
+    "services/contact-form/**/*.{js,jsx}": [
+      "npm run --silent lint:contact-form"
+    ],
+    "services/health-department/**/*.{js,jsx}": [
+      "npm run --silent lint:health-department"
+    ],
+    "services/locations/**/*.{js,jsx}": [
+      "npm run --silent lint:locations"
+    ],
+    "services/scanner/**/*.{js,jsx}": [
+      "npm run --silent lint:scanner"
+    ],
+    "services/webapp/**/*.{js,jsx}": [
+      "npm run --silent lint:webapp"
+    ]
+  },
+  "devDependencies": {
+    "husky": "6.0.0",
+    "lint-staged": "11.0.0"
+  },
+  "dependencies": {
+    "@types/lodash": "4.14.172"
+  }
 }
diff --git a/scripts/updatePackageJsons.sh b/scripts/updatePackageJsons.sh
new file mode 100755
index 0000000..d0dcf2a
--- /dev/null
+++ b/scripts/updatePackageJsons.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+echo "Updating package.json files to version $1"
+
+npx json -I -f package.json -e "this.version=\"$1\""
+npx json -I -f e2e/package.json -e "this.version=\"$1\""
+npx json -I -f services/backend/package.json -e "this.version=\"$1\""
+npx json -I -f services/contact-form/package.json -e "this.version=\"$1\""
+npx json -I -f services/health-department/package.json -e "this.version=\"$1\""
+npx json -I -f services/locations/package.json -e "this.version=\"$1\""
+npx json -I -f services/scanner/package.json -e "this.version=\"$1\""
+npx json -I -f services/webapp/package.json -e "this.version=\"$1\""
\ No newline at end of file
diff --git a/services/backend/.eslintrc.js b/services/backend/.eslintrc.js
index dfd2dae..c28c818 100644
--- a/services/backend/.eslintrc.js
+++ b/services/backend/.eslintrc.js
@@ -27,6 +27,8 @@ module.exports = {
     ecmaVersion: 2020,
   },
   rules: {
+    'no-shadow': 'off',
+    '@typescript-eslint/no-shadow': ['error'],
     'max-lines': [2, { max: 250, skipBlankLines: true, skipComments: true }],
     complexity: 2,
     'no-await-in-loop': 0,
diff --git a/services/backend/package.json b/services/backend/package.json
index 927b022..5c5f072 100644
--- a/services/backend/package.json
+++ b/services/backend/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lucaapp/backend",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "private": true,
   "license": "Apache-2.0",
   "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
diff --git a/services/backend/src/constants/riskLevels.ts b/services/backend/src/constants/riskLevels.ts
new file mode 100644
index 0000000..aa12095
--- /dev/null
+++ b/services/backend/src/constants/riskLevels.ts
@@ -0,0 +1,4 @@
+export enum RiskLevel {
+  RISK_LEVEL_2 = 2,
+  RISK_LEVEL_3 = 3,
+}
diff --git a/services/backend/src/routes/v4/riskLevels.schemas.ts b/services/backend/src/routes/v4/riskLevels.schemas.ts
index d251b8d..7a8d9d5 100644
--- a/services/backend/src/routes/v4/riskLevels.schemas.ts
+++ b/services/backend/src/routes/v4/riskLevels.schemas.ts
@@ -1,8 +1,5 @@
 import { z } from 'utils/validation';
-
-export const addRiskLevelSchema = z.object({
-  locationTransferId: z.uuid(),
-});
+import { RiskLevel } from 'constants/riskLevels';
 
 export const getRiskLevelParameterSchema = z.object({
   locationTransferId: z.uuid(),
@@ -11,4 +8,5 @@ export const getRiskLevelParameterSchema = z.object({
 export const addRiskLevelTracesSchema = z.object({
   locationTransferId: z.uuid(),
   traceIds: z.array(z.traceId()),
+  riskLevel: z.nativeEnum(RiskLevel),
 });
diff --git a/services/backend/src/routes/v4/riskLevels.ts b/services/backend/src/routes/v4/riskLevels.ts
index 98f94fc..22f0f70 100644
--- a/services/backend/src/routes/v4/riskLevels.ts
+++ b/services/backend/src/routes/v4/riskLevels.ts
@@ -8,64 +8,12 @@ import {
 } from 'middlewares/validateSchema';
 import { requireHealthDepartmentEmployee } from 'middlewares/requireUser';
 import {
-  addRiskLevelSchema,
   getRiskLevelParameterSchema,
   addRiskLevelTracesSchema,
 } from './riskLevels.schemas';
 
-const RISK_LEVEL_2 = 2;
-const RISK_LEVEL_3 = 3;
-
 const router = Router();
 
-router.post(
-  '/',
-  requireHealthDepartmentEmployee,
-  validateSchema(addRiskLevelSchema),
-  async (request, response) => {
-    const user = request.user as IHealthDepartmentEmployee;
-
-    const locationTransfer = await database.LocationTransfer.findOne({
-      where: {
-        uuid: request.body.locationTransferId,
-        departmentId: user.departmentId,
-      },
-    });
-
-    if (!locationTransfer) return response.sendStatus(status.NOT_FOUND);
-
-    const locationTransferTraces = await database.LocationTransferTrace.findAll(
-      {
-        where: { locationTransferId: locationTransfer.uuid },
-        include: {
-          model: database.RiskLevel,
-        },
-      }
-    );
-
-    const duplicateRiskLevel = locationTransferTraces.some(
-      // @ts-ignore - any until models are typed
-      locationTransferTrace =>
-        locationTransferTrace.RiskLevels.some(
-          // @ts-ignore - any until models are typed
-          riskLevel => riskLevel.level === RISK_LEVEL_2
-        )
-    );
-
-    if (duplicateRiskLevel) return response.sendStatus(status.CONFLICT);
-
-    // @ts-ignore - any until models are typed
-    const riskLevels = locationTransferTraces.map(locationTransferTrace => ({
-      level: RISK_LEVEL_2,
-      locationTransferTraceId: locationTransferTrace.uuid,
-    }));
-
-    await database.RiskLevel.bulkCreate(riskLevels);
-
-    return response.sendStatus(status.NO_CONTENT);
-  }
-);
-
 router.get(
   '/:locationTransferId',
   requireHealthDepartmentEmployee,
@@ -127,30 +75,16 @@ router.post(
           locationTransferId: locationTransfer.uuid,
           traceId: { [Op.in]: request.body.traceIds },
         },
-        include: {
-          model: database.RiskLevel,
-        },
       }
     );
 
-    const duplicateRiskLevel = locationTransferTraces.some(
-      // @ts-ignore - any until models are typed
-      locationTransferTrace =>
-        locationTransferTrace.RiskLevels.some(
-          // @ts-ignore - any until models are typed
-          riskLevel => riskLevel.level === RISK_LEVEL_3
-        )
-    );
-
-    if (duplicateRiskLevel) return response.sendStatus(status.CONFLICT);
-
     // @ts-ignore - any until models are typed
     const riskLevels = locationTransferTraces.map(locationTransferTrace => ({
-      level: RISK_LEVEL_3,
+      level: request.body.riskLevel,
       locationTransferTraceId: locationTransferTrace.uuid,
     }));
 
-    database.RiskLevel.bulkCreate(riskLevels);
+    database.RiskLevel.bulkCreate(riskLevels, { ignoreDuplicates: true });
 
     return response.sendStatus(status.NO_CONTENT);
   }
diff --git a/services/backend/src/utils/notifications/notificationsV4.ts b/services/backend/src/utils/notifications/notificationsV4.ts
index 4c360d2..361b60d 100644
--- a/services/backend/src/utils/notifications/notificationsV4.ts
+++ b/services/backend/src/utils/notifications/notificationsV4.ts
@@ -138,7 +138,7 @@ export const generateChunk = async () => {
 
   const dummyTraces = await database.DummyTrace.findAll({
     attributes: ['healthDepartmentId', 'traceId'],
-    where: { createdAt: { [Op.gt]: startTime } },
+    where: { updatedAt: { [Op.gt]: startTime } },
   });
 
   const completedHashHexStrings: string[] = completedLocationTransfers.flatMap(
diff --git a/services/contact-form/package.json b/services/contact-form/package.json
index bd429e1..4de2cb1 100644
--- a/services/contact-form/package.json
+++ b/services/contact-form/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lucaapp/contact-form",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "private": true,
   "license": "Apache-2.0",
   "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
diff --git a/services/health-department/package.json b/services/health-department/package.json
index d7e81bb..5fe9765 100644
--- a/services/health-department/package.json
+++ b/services/health-department/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lucaapp/health-department",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "private": true,
   "license": "Apache-2.0",
   "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
diff --git a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js
index 4668ec1..7622dec 100644
--- a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js
+++ b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js
@@ -3,7 +3,8 @@ import { useIntl } from 'react-intl';
 import { Tooltip } from 'antd';
 import { useQuery } from 'react-query';
 
-import { RISK_LEVEL_2 } from 'constants/riskLevels';
+import { RISK_LEVEL_2, RISK_LEVEL_3 } from 'constants/riskLevels';
+import { NOTIFIEABLE_DEVICE_TYPES } from 'constants/deviceTypes';
 
 import {
   getWarningLevelsForLocationTransfer,
@@ -16,6 +17,27 @@ import { NotificationModal } from 'components/App/modals/NotificationModal';
 
 import { BellIcon, ButtonWrapper } from './NotificationTrigger.styled';
 
+const checkIfAnyContactPersonsAreNotifyable = (contactPersons, riskLevels) => {
+  const allowedDeviceTypes = new Set([
+    NOTIFIEABLE_DEVICE_TYPES.IOS,
+    NOTIFIEABLE_DEVICE_TYPES.ANDROID,
+  ]);
+  const riskLevelsToCheck = [RISK_LEVEL_2, RISK_LEVEL_3];
+
+  const tracesOfNotifyableDevices = contactPersons.traces.filter(
+    contactPerson => allowedDeviceTypes.has(contactPerson.deviceType)
+  );
+  return tracesOfNotifyableDevices.some(({ traceId }) => {
+    const riskLevelsForTrace = riskLevels.find(
+      riskLevel => riskLevel.traceId === traceId
+    );
+    if (!riskLevelsForTrace.riskLevels.length) return true;
+    return riskLevelsToCheck.some(
+      level => !riskLevelsForTrace.riskLevels.includes(level)
+    );
+  });
+};
+
 export const NotificationTrigger = ({ location }) => {
   const intl = useIntl();
   const [openModal] = useModal();
@@ -71,16 +93,17 @@ export const NotificationTrigger = ({ location }) => {
   )
     return null;
 
-  const isNotificationDisabled =
-    contactPersons.traces.length === 0 ||
-    riskLevels.some(traceRisk => traceRisk.riskLevels.includes(RISK_LEVEL_2));
+  const isNotificationEnabled = checkIfAnyContactPersonsAreNotifyable(
+    contactPersons,
+    riskLevels
+  );
 
   return (
     <ButtonWrapper
-      onClick={isNotificationDisabled ? () => {} : openNotificationModal}
+      onClick={isNotificationEnabled ? openNotificationModal : () => {}}
     >
       <Tooltip title={intl.formatMessage({ id: 'modal.notification.button' })}>
-        <BellIcon disabled={isNotificationDisabled} />
+        <BellIcon disabled={!isNotificationEnabled} />
       </Tooltip>
     </ButtonWrapper>
   );
diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js
index deafd1e..0c5b1d8 100644
--- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js
+++ b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js
@@ -1,6 +1,7 @@
 import React, { useState, useEffect } from 'react';
 import { useIntl } from 'react-intl';
 import { message } from 'antd';
+import { useQuery } from 'react-query';
 
 import { decryptTrace } from 'utils/cryptoOperations';
 import { INITIAL_OVERLAP_VALUE } from 'constants/timeOverlap';
@@ -18,12 +19,12 @@ export const GuestList = ({
   location,
 }) => {
   const intl = useIntl();
-  const [traces, setTraces] = useState(null);
   const [filteredTraces, setFilteredTraces] = useState(null);
   const [minTimeOverlap, setMinTimeOverlap] = useState(INITIAL_OVERLAP_VALUE);
 
-  useEffect(() => {
-    async function decryptTraces() {
+  const { data: traces } = useQuery(
+    `decryptedTraces${location.transferId}`,
+    async () => {
       const hideMessage = message.loading(
         intl.formatMessage({ id: 'message.decryptingData' }),
         0
@@ -38,18 +39,17 @@ export const GuestList = ({
           })
         )
       );
-
-      const validDecrypedTraces = decryptedTraces.filter(
+      hideMessage();
+      return decryptedTraces.filter(
         user => !(user.isInvalid || user.isUnregistered)
       );
+    },
+    { staleTime: Number.POSITIVE_INFINITY }
+  );
 
-      setTraces(validDecrypedTraces);
-      setDecryptedTraces(validDecrypedTraces);
-      hideMessage();
-    }
-
-    decryptTraces();
-  }, [encryptedTraces, intl, setDecryptedTraces]);
+  useEffect(() => {
+    if (traces) setDecryptedTraces(traces);
+  }, [traces, setDecryptedTraces]);
 
   useEffect(() => {
     if (!traces) return;
diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js
index 6f3ef53..5fc4d1b 100644
--- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js
+++ b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js
@@ -5,7 +5,7 @@ import { getWarningLevelsForLocationTransfer, getMe } from 'network/api';
 
 import { useQuery } from 'react-query';
 
-import { DEVICE_TYPES } from '../../Notify/Notify.helper';
+import { NOTIFIEABLE_DEVICE_TYPES } from 'constants/deviceTypes';
 import { AdditionalData } from './AdditionalData';
 import { Notified } from './Notified';
 import { Name } from './Name';
@@ -98,8 +98,8 @@ export const GuestListTable = ({
           !healthDepartmentEmployee ||
           !healthDepartmentEmployee.notificationsEnabled ||
           !(
-            notificationTrace.deviceType === DEVICE_TYPES.IOS ||
-            notificationTrace.deviceType === DEVICE_TYPES.ANDROID
+            notificationTrace.deviceType === NOTIFIEABLE_DEVICE_TYPES.IOS ||
+            notificationTrace.deviceType === NOTIFIEABLE_DEVICE_TYPES.ANDROID
           )
         )
           return null;
diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js
deleted file mode 100644
index 88ce93f..0000000
--- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export const DEVICE_TYPES = {
-  IOS: 0,
-  ANDROID: 1,
-};
-
-export const filterByDeviceType = contactPersons =>
-  contactPersons.filter(
-    contact =>
-      contact.deviceType === DEVICE_TYPES.IOS ||
-      contact.deviceType === DEVICE_TYPES.ANDROID
-  );
diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js
index 4763f6e..eac6eef 100644
--- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js
+++ b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js
@@ -5,7 +5,6 @@ import { useModal } from 'components/hooks/useModal';
 import { getMe } from 'network/api';
 import { NotificationModal } from 'components/App/modals/NotificationModal';
 import { StyledLink, Wrapper, BellOutlinedIcon } from './Notify.styled';
-import { filterByDeviceType } from './Notify.helper';
 
 export const Notify = ({ traces, location }) => {
   const intl = useIntl();
@@ -23,9 +22,6 @@ export const Notify = ({ traces, location }) => {
 
   if (!traces || traces.length === 0) return null;
 
-  const selectedFilteredTraces = filterByDeviceType(traces);
-  const selectedTracesIds = selectedFilteredTraces.map(trace => trace.traceId);
-
   const openNotificationModal = () => {
     openModal({
       title: intl.formatMessage({
@@ -36,7 +32,7 @@ export const Notify = ({ traces, location }) => {
           locationId={locationId}
           locationName={locationName}
           locationTransferId={locationTransferId}
-          traceIds={selectedTracesIds}
+          traces={traces}
           time={time}
           departmentId={healthDepartmentEmployee.departmentId}
         />
diff --git a/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js b/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js
index b1ef718..b61abb6 100644
--- a/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js
+++ b/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js
@@ -22,7 +22,7 @@ export const ModalArea = () => {
 
   // Use a copy of the modal content in order to complete the closing animation of the modal with content
   useEffect(() => {
-    if (stateModal) setModalContent(stateModal);
+    if (stateModal) setModalContent([...stateModal].pop());
   }, [stateModal]);
 
   if (!modalContent) return null;
diff --git a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js
index 54348b7..8a1ab91 100644
--- a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js
+++ b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js
@@ -1,11 +1,17 @@
-import { RISK_LEVEL_3 } from 'constants/riskLevels';
+import { NOTIFIEABLE_DEVICE_TYPES } from 'constants/deviceTypes';
 
-export const filterLevel3RiskLevels = (traceIdsToFilter, riskLevels) =>
+export const filterByDeviceType = contactPersons =>
+  contactPersons.filter(
+    contact =>
+      contact.deviceType === NOTIFIEABLE_DEVICE_TYPES.IOS ||
+      contact.deviceType === NOTIFIEABLE_DEVICE_TYPES.ANDROID
+  );
+
+export const filterRiskLevels = (traceIdsToFilter, riskLevels, level) =>
   traceIdsToFilter.filter(traceId =>
     riskLevels.some(
       riskLevel =>
-        riskLevel.traceId === traceId &&
-        !riskLevel.riskLevels.includes(RISK_LEVEL_3)
+        riskLevel.traceId === traceId && !riskLevel.riskLevels.includes(level)
     )
   );
 
diff --git a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js
index d483e3e..cdbcf29 100644
--- a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js
+++ b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js
@@ -3,7 +3,6 @@ import { useIntl } from 'react-intl';
 import { Popconfirm, notification } from 'antd';
 import { useQuery, useQueryClient } from 'react-query';
 import {
-  notifyLocationGuests,
   notifyLocationTracesGuests,
   getNotificationConfig,
   getContactPersons,
@@ -13,7 +12,6 @@ import { formattedTimeLabel } from 'utils/time';
 import { useModal } from 'components/hooks/useModal';
 import { PrimaryButton } from 'components/general';
 import { RISK_LEVEL_2, RISK_LEVEL_3 } from 'constants/riskLevels';
-import { filterByDeviceType } from '../ContactPersonsModal/ContactPersons/Notify/Notify.helper';
 import {
   Wrapper,
   SectionTitle,
@@ -22,25 +20,25 @@ import {
   SwitchDescription,
   StyledSwitch,
   SwitchWrapper,
-  Warning,
 } from './NotificationModal.styled';
 import {
-  filterLevel3RiskLevels,
+  filterRiskLevels,
   getLocaleObject,
+  filterByDeviceType,
 } from './NotificationModal.helper';
 
 // eslint-disable-next-line complexity
 export const NotificationModal = ({
   locationName,
   locationTransferId,
-  traceIds,
+  traces,
   time,
   departmentId,
 }) => {
   const intl = useIntl();
   const queryClient = useQueryClient();
   const [, closeModal] = useModal();
-  const [level, setLevel] = useState(traceIds ? RISK_LEVEL_3 : RISK_LEVEL_2);
+  const [level, setLevel] = useState(RISK_LEVEL_2);
 
   const setRiskLevel2 = checked =>
     checked ? setLevel(RISK_LEVEL_2) : setLevel(RISK_LEVEL_3);
@@ -82,17 +80,29 @@ export const NotificationModal = ({
 
   if (!config || !contactPersons || !riskLevels) return null;
 
-  const contactPersonFiltered = filterByDeviceType(contactPersons.traces);
+  const triggeredWithoutSelection = !traces;
+  const filteredContactPersons = filterByDeviceType(
+    triggeredWithoutSelection ? contactPersons.traces : traces
+  );
+  const localeObject = getLocaleObject(config, departmentId, level, intl);
+  const traceIdsToNotify = filterRiskLevels(
+    filteredContactPersons.map(trace => trace.traceId),
+    riskLevels,
+    level
+  );
+  const completeTracesLength = triggeredWithoutSelection
+    ? contactPersons.traces.length
+    : traces.length;
+  const amountOfNotifyableTraces = traceIdsToNotify.length;
+  const amountOfNonNotifyableTraces =
+    completeTracesLength - amountOfNotifyableTraces;
 
   const notify = () => {
-    const notificationRequest =
-      level === RISK_LEVEL_2
-        ? notifyLocationGuests(locationTransferId)
-        : notifyLocationTracesGuests({
-            traceIds:
-              traceIds || contactPersonFiltered.map(trace => trace.traceId),
-            locationTransferId,
-          });
+    const notificationRequest = notifyLocationTracesGuests({
+      traceIds: traceIdsToNotify,
+      locationTransferId,
+      riskLevel: level,
+    });
 
     notificationRequest
       .then(response => {
@@ -113,17 +123,6 @@ export const NotificationModal = ({
       .catch(() => triggerNotificationError());
   };
 
-  const localeObject = getLocaleObject(config, departmentId, level, intl);
-  const level3TraceIds = filterLevel3RiskLevels(
-    traceIds || contactPersonFiltered.map(trace => trace.traceId),
-    riskLevels
-  );
-  const wasLevel2Triggered = riskLevels.some(traceRisk =>
-    traceRisk.riskLevels.includes(RISK_LEVEL_2)
-  );
-  const isButtonDisabled =
-    level3TraceIds.length === 0 && level === RISK_LEVEL_3;
-
   return (
     <Wrapper>
       <SectionTitle>
@@ -136,38 +135,30 @@ export const NotificationModal = ({
         {intl.formatMessage({ id: 'modal.notification.section1' })}
       </Section>
 
-      {!wasLevel2Triggered && (
-        <Section>
-          <SwitchWrapper>
-            <StyledSwitch
-              checked={level === RISK_LEVEL_2}
-              onChange={setRiskLevel2}
-            />
-            <SwitchDescription>
-              {intl.formatMessage({
-                id: 'modal.notification.selection.potentialInfectionRisk',
-              })}
-            </SwitchDescription>
-          </SwitchWrapper>
-          <SwitchWrapper>
-            <StyledSwitch
-              checked={level === RISK_LEVEL_3}
-              onChange={setRiskLevel3}
-            />
-            <SwitchDescription>
-              {intl.formatMessage({
-                id: 'modal.notification.selection.elevatedInfectionRisk',
-              })}
-            </SwitchDescription>
-          </SwitchWrapper>
-        </Section>
-      )}
-
-      {traceIds && level === RISK_LEVEL_2 && (
-        <Warning>
-          {intl.formatMessage({ id: 'modal.notification.selectionWarning' })}
-        </Warning>
-      )}
+      <Section>
+        <SwitchWrapper>
+          <StyledSwitch
+            checked={level === RISK_LEVEL_2}
+            onChange={setRiskLevel2}
+          />
+          <SwitchDescription>
+            {intl.formatMessage({
+              id: 'modal.notification.selection.potentialInfectionRisk',
+            })}
+          </SwitchDescription>
+        </SwitchWrapper>
+        <SwitchWrapper>
+          <StyledSwitch
+            checked={level === RISK_LEVEL_3}
+            onChange={setRiskLevel3}
+          />
+          <SwitchDescription>
+            {intl.formatMessage({
+              id: 'modal.notification.selection.elevatedInfectionRisk',
+            })}
+          </SwitchDescription>
+        </SwitchWrapper>
+      </Section>
 
       <SectionTitle>
         {intl.formatMessage({ id: 'modal.notification.section2.title' })}
@@ -199,7 +190,6 @@ export const NotificationModal = ({
           }
         )}
       </Section>
-
       <SectionTitle>
         {intl.formatMessage({ id: 'modal.notification.section3.title' })}
       </SectionTitle>
@@ -218,23 +208,22 @@ export const NotificationModal = ({
         {intl.formatMessage(
           { id: 'modal.notification.section4.title' },
           {
-            guestCount:
-              level === RISK_LEVEL_2
-                ? contactPersonFiltered.length
-                : level3TraceIds.length,
-          }
-        )}
-      </SectionTitle>
-      <SectionTitle>
-        {intl.formatMessage(
-          { id: 'modal.notification.selection.countNoNotification' },
-          {
-            amount: contactPersons.traces.length - contactPersonFiltered.length,
+            guestCount: amountOfNotifyableTraces,
           }
         )}
       </SectionTitle>
+      {amountOfNonNotifyableTraces !== 0 && (
+        <SectionTitle>
+          {intl.formatMessage(
+            { id: 'modal.notification.selection.countNoNotification' },
+            {
+              amount: amountOfNonNotifyableTraces,
+            }
+          )}
+        </SectionTitle>
+      )}
       <ButtonWrapper>
-        {isButtonDisabled ? (
+        {amountOfNotifyableTraces === 0 ? (
           <PrimaryButton disabled>
             {intl.formatMessage({
               id: 'modal.notification.button.alreadyNotified',
@@ -249,10 +238,7 @@ export const NotificationModal = ({
                 id: 'modal.notification.confirmation',
               },
               {
-                guestCount:
-                  level === RISK_LEVEL_2
-                    ? contactPersonFiltered.length
-                    : level3TraceIds.length,
+                guestCount: traceIdsToNotify.length,
               }
             )}
             okText={intl.formatMessage({
diff --git a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js
index cd1054b..03599fa 100644
--- a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js
+++ b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js
@@ -6,14 +6,6 @@ export const Wrapper = styled.div`
   flex-direction: column;
 `;
 
-export const Warning = styled.div`
-  color: rgb(241, 103, 4);
-  font-family: Montserrat-Medium, sans-serif;
-  font-size: 16px;
-  font-weight: 500;
-  margin-bottom: 24px;
-`;
-
 export const SectionTitle = styled.div`
   font-family: Montserrat-Bold, sans-serif;
   font-size: 16px;
diff --git a/services/health-department/src/constants/deviceTypes.js b/services/health-department/src/constants/deviceTypes.js
new file mode 100644
index 0000000..076bbec
--- /dev/null
+++ b/services/health-department/src/constants/deviceTypes.js
@@ -0,0 +1,4 @@
+export const NOTIFIEABLE_DEVICE_TYPES = {
+  IOS: 0,
+  ANDROID: 1,
+};
diff --git a/services/health-department/src/messages/de.json b/services/health-department/src/messages/de.json
index b6d6a35..e721a71 100644
--- a/services/health-department/src/messages/de.json
+++ b/services/health-department/src/messages/de.json
@@ -382,15 +382,15 @@
     "notification.notification.success": "Benachrichtigungen gesendet.",
     "notification.notification.error": "Benachrichtigungen konnten nicht gesendet werden.",
     "ContactPerson.notify.title": "AUSWAHL BENACHRICHTIGEN",
-    "profile.contactInformation.required": "Bitte gib wenigstens eine Telefonnummer und/oder eine E-Mail-Adresse an.",
+    "profile.contactInformation.required": "Bitte gib eine Telefonnummer und/oder eine E-Mail-Adresse an.",
     "modal.notification.section3": "Alle luca App-Nutzer:innen, die in diesem Zeitraum bei {locationName} eingecheckt waren, erhalten die Benachrichtigung.\n{br}Von: {from}\n{br}Bis: {till}",
     "modal.notification.section3.title": "Zeitraum",
     "processDetails.note.invalidSignature": "Die Signatur der Notiz ist falsch!",
     "modal.notification.notSpecified": "Keine Angabe",
     "modal.notification.selection.potentialInfectionRisk": "Mögliches Infektionsrisiko",
     "modal.notification.selection.elevatedInfectionRisk": "Erhöhtes Infektionsrisiko",
-    "modal.notification.button.alreadyNotified": "Bereits informiert",
+    "modal.notification.button.alreadyNotified": "Bereits benachrichtigt",
     "contactperson.notified": "Hinweis ausgelöst",
-    "modal.notification.selectionWarning": "Diese Warnmeldung kann nur an alle Personen in der Kontaktliste ausgespielt werden.",
-    "modal.notification.selection.countNoNotification": "{amount} Personen werden nicht benachrichtigt, da der Check-in via Schlüsselanhänger, Kontaktformular oder Web App erfolgte"
+    "modal.notification.selectionWarning": "Wichtige Information: Der Hinweis \"Mögliches Infektionsrisiko\" wird an alle Personen ausgespielt, die im jeweiligen Zeitrahmen in der Location eingecheckt waren. Ihre Auswahl aus dem vorherigen Schritt ist nicht mehr gültig.",
+    "modal.notification.selection.countNoNotification": "{amount} Personen werden nicht benachrichtigt, da der Check-in via Schlüsselanhänger, Kontaktformular oder Web App erfolgte oder weil diese Person bereits benachrichtigt wurde."
 }
\ No newline at end of file
diff --git a/services/health-department/src/messages/en.json b/services/health-department/src/messages/en.json
index 990acae..1825577 100644
--- a/services/health-department/src/messages/en.json
+++ b/services/health-department/src/messages/en.json
@@ -59,7 +59,7 @@
     "modal.trackInfection.confirmation": "Are you sure you want to track a new infection chain? This action cannot be undone.",
     "modal.trackInfection.confirmButton": "Start tracking",
     "modal.trackInfection.declineButton": "Cancel",
-    "modal.searchGroup.title": "Find groups",
+    "modal.searchGroup.title": "Find locations",
     "modal.addEmployee.title": "Add Employee",
     "navigation.profile": "Profile",
     "navigation.tracking": "Tracking",
@@ -94,12 +94,12 @@
     "history.contactInformation": "Contact person",
     "history.locations": "Locations",
     "history.confirmed": "CONFIRMED",
-    "manualSearch.button": "Search for Groups",
-    "groupSearch.form.group.placeholder": "Search for groups by name",
+    "manualSearch.button": "Search for locations",
+    "groupSearch.form.group.placeholder": "Search for locations by name",
     "groupSearch.form.button": "Search",
     "groupSearch.form.request": "Request data",
     "groupSearch.form.error.minLength": "Please enter at least 3 characters",
-    "groupSearch.table.empty": "No groups found",
+    "groupSearch.table.empty": "No locations found",
     "groupSearch.requestData.info": "Please select a timespan to request data from {group}.",
     "userManagement.email": "Email",
     "userManagement.firstName": "Firstname",
@@ -382,9 +382,9 @@
     "notification.notification.success": "Notifications sent.",
     "notification.notification.error": "Notifications could not be sent.",
     "ContactPerson.notify.title": "NOTIFY SELECTION",
-    "profile.contactInformation.required": "Please provide at least a phone number and/or an email address.",
+    "profile.contactInformation.required": "Please provide a phone number and/or an email address.",
     "modal.notification.section3": "All luca app users who were checked into {locationName} during this timeframe will receive this notification.\n{br}From: {from}\n{br}To: {till}",
-    "modal.notification.section3.title": "Timeframe",
+    "modal.notification.section3.title": "Time period",
     "processDetails.note.invalidSignature": "Note signature is wrong!",
     "modal.notification.notSpecified": "Not specified",
     "modal.notification.selection.potentialInfectionRisk": "Potential infection risk",
@@ -392,5 +392,5 @@
     "modal.notification.button.alreadyNotified": "Already notified",
     "contactperson.notified": "Notification sent",
     "modal.notification.selectionWarning": "This warning can only be triggered for all contact persons.",
-    "modal.notification.selection.countNoNotification": "{amount} people won't receive this notification as the check-in occured via badge, contact form or web app."
+    "modal.notification.selection.countNoNotification": "{amount} persons won't receive this notification as the check-in occured via badge, contact form or web app or because they have already been notified."
 }
\ No newline at end of file
diff --git a/services/health-department/src/network/api.js b/services/health-department/src/network/api.js
index c8085f4..46b57b7 100644
--- a/services/health-department/src/network/api.js
+++ b/services/health-department/src/network/api.js
@@ -294,15 +294,6 @@ export const createUserTransfer = payload => {
 };
 
 // NOTIFICATIONS
-export const notifyLocationGuests = locationTransferId =>
-  fetch(`${API_PATH}/v4/riskLevels`, {
-    method: 'POST',
-    body: JSON.stringify({
-      locationTransferId,
-    }),
-    headers,
-  });
-
 export const notifyLocationTracesGuests = payload =>
   fetch(`${API_PATH}/v4/riskLevels/traces`, {
     method: 'POST',
diff --git a/services/health-department/src/reducers/modals.js b/services/health-department/src/reducers/modals.js
index e2dc3a5..1dec546 100644
--- a/services/health-department/src/reducers/modals.js
+++ b/services/health-department/src/reducers/modals.js
@@ -1,14 +1,16 @@
 // Actions
 import { OPEN_MODAL, CLOSE_MODAL } from 'actions/modals';
 
-const initialState = null;
+const initialState = [];
 
 export const modalsReducer = (state = initialState, action) => {
   switch (action.type) {
     case OPEN_MODAL:
-      return action.payload;
+      state.push(action.payload);
+      return [...state];
     case CLOSE_MODAL:
-      return null;
+      state.pop();
+      return [...state];
     default:
       return state;
   }
diff --git a/services/locations/package.json b/services/locations/package.json
index 6abd860..f18697d 100644
--- a/services/locations/package.json
+++ b/services/locations/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lucaapp/locations",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "private": true,
   "license": "Apache-2.0",
   "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
diff --git a/services/scanner/package.json b/services/scanner/package.json
index 890951f..9e119e1 100644
--- a/services/scanner/package.json
+++ b/services/scanner/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lucaapp/scanner",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "private": true,
   "license": "Apache-2.0",
   "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
diff --git a/services/webapp/package.json b/services/webapp/package.json
index 5d75248..cb8e455 100644
--- a/services/webapp/package.json
+++ b/services/webapp/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lucaapp/webapp",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "private": true,
   "license": "Apache-2.0",
   "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
-- 
GitLab