From 9eee9b13f33fe1da7f8d8df7afe1c528983206d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Haapam=C3=A4ki?= Date: Fri, 23 Sep 2022 12:19:31 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20varahenkil=C3=B6n=20lis=C3=A4ys,=20rool?= =?UTF-8?q?ien=20k=C3=A4sittelyn=20muutos=20vaatimuksia=20vastaavaksi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/__snapshots__/api.test.ts.snap | 20 ++- backend/integrationtest/api/api.test.ts | 2 +- .../records/HYVAKSYMISPAATOS_APPROVED.json | 31 +++- .../api/records/NAHTAVILLAOLO.json | 29 +++- backend/integrationtest/api/testUtil/tests.ts | 54 ++++-- .../personSearch/personSearchClient.test.ts | 1 - .../asiakirja/suunnittelunAloitus/Kutsu20.ts | 4 +- .../suunnittelunAloitus/KutsuAdapter.ts | 6 +- backend/src/database/model/projekti.ts | 13 +- .../email/lahetekirje/LahetekirjeAdapter.ts | 10 +- backend/src/handler/tila/TilaManager.ts | 6 +- backend/src/personSearch/kayttajas.ts | 9 +- .../lambda/personSearchAdapter.ts | 10 -- backend/src/personSearch/personAdapter.ts | 9 +- .../adaptToDB/adaptKayttajatunnusList.ts | 2 +- ...aptStandardiYhteystiedotByAddingProjari.ts | 6 +- backend/src/projekti/kayttoOikeudetManager.ts | 87 ++++++---- backend/src/projekti/projektiHandler.ts | 58 +++++-- .../projektiSearch/projektiSearchAdapter.ts | 4 +- backend/src/user/index.ts | 1 - backend/src/user/userService.ts | 70 +++----- .../src/util/adaptStandardiYhteystiedot.ts | 6 +- backend/src/util/userUtil.ts | 20 +++ backend/src/velho/velhoAdapter.ts | 26 ++- backend/src/velho/velhoClient.ts | 40 ++--- .../__snapshots__/apiHandler.test.ts.snap | 81 +++++---- backend/test/apiHandler.test.ts | 94 +++++----- .../projektiDatabase.test.ts.snap | 3 +- backend/test/fixture/projektiFixture.ts | 20 +-- backend/test/fixture/userFixture.ts | 15 +- backend/test/handler/getCurrentUser.test.ts | 3 +- .../personSearch/lambda/personAdapter.test.ts | 3 - .../lambda/personSearchFixture.ts | 8 +- .../kayttoOikeudetManager.test.ts.snap | 71 ++++++++ .../projektiHandler.test.ts.snap | 31 +++- .../projekti/kayttoOikeudetManager.test.ts | 163 ++++++++++++++++++ backend/test/projekti/projektiHandler.test.ts | 17 +- .../__snapshots__/velhoAdapter.test.ts.snap | 46 +++-- backend/test/velho/velhoAdapter.test.ts | 4 +- .../2-perusta-projekti/2-perusta.spec.js | 1 + graphql/inputs.graphql | 3 +- graphql/types.graphql | 21 +-- package-lock.json | 108 +++++------- package.json | 1 - .../projekti/KayttoOikeusHallinta.tsx | 30 +--- .../KuulutuksenYhteystiedot.tsx | 6 +- .../KuulutuksessaEsitettavatYhteystiedot.tsx | 6 +- .../KuulutuksessaEsitettavatYhteystiedot.tsx | 6 +- .../EsitettavatYhteystiedot.tsx | 6 +- src/hooks/useProjekti.tsx | 4 +- src/pages/yllapito/perusta/[oid].tsx | 8 +- .../yllapito/projekti/[oid]/henkilot.tsx | 7 +- src/schemas/kayttoOikeudet.ts | 60 +++---- src/schemas/projekti.ts | 8 +- src/services/api/fragmentTypes.json | 18 +- 55 files changed, 830 insertions(+), 546 deletions(-) create mode 100644 backend/src/util/userUtil.ts create mode 100644 backend/test/projekti/__snapshots__/kayttoOikeudetManager.test.ts.snap create mode 100644 backend/test/projekti/kayttoOikeudetManager.test.ts diff --git a/backend/integrationtest/api/__snapshots__/api.test.ts.snap b/backend/integrationtest/api/__snapshots__/api.test.ts.snap index e405070516..52de8b3b29 100644 --- a/backend/integrationtest/api/__snapshots__/api.test.ts.snap +++ b/backend/integrationtest/api/__snapshots__/api.test.ts.snap @@ -1297,12 +1297,28 @@ Object { }, Object { "__typename": "ProjektiKayttajaJulkinen", - "email": "mikko.haapamaki@cgi.com", + "email": "mikko.haapamaki02@cgi.com", "id": "1", + "nimi": "Hassu, A-tunnus3", + "organisaatio": "CGI Suomi Oy", + "puhelinnumero": "123", + }, + Object { + "__typename": "ProjektiKayttajaJulkinen", + "email": "mikko.haapamaki@cgi.com", + "id": "2", "nimi": "Hassu, A-Tunnus", "organisaatio": "CGI Suomi Oy", "puhelinnumero": "123", }, + Object { + "__typename": "ProjektiKayttajaJulkinen", + "email": "mikko.haapamaki@cgi.com", + "id": "3", + "nimi": "Hassu, Testi1", + "organisaatio": "CGI Suomi Oy", + "puhelinnumero": "123", + }, ], "status": "ALOITUSKUULUTUS", "velho": Object { @@ -2544,6 +2560,7 @@ Object { "nimi": "HASSU AUTOMAATTITESTIPROJEKTI1", "suunnittelustaVastaavaViranomainen": "VAYLAVIRASTO", "tyyppi": "TIE", + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "vastuuhenkilonEmail": "mikko.haapamki@cgi.com", "vaylamuoto": Array [ "tie", @@ -3144,6 +3161,7 @@ Object { "nimi": "HASSU AUTOMAATTITESTIPROJEKTI1", "suunnittelustaVastaavaViranomainen": "VAYLAVIRASTO", "tyyppi": "TIE", + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "vastuuhenkilonEmail": "mikko.haapamki@cgi.com", "vaylamuoto": Array [ "tie", diff --git a/backend/integrationtest/api/api.test.ts b/backend/integrationtest/api/api.test.ts index 5ed4bc272c..b4810ed81a 100644 --- a/backend/integrationtest/api/api.test.ts +++ b/backend/integrationtest/api/api.test.ts @@ -113,7 +113,7 @@ describe("Api", () => { const projekti = await readProjektiFromVelho(); expect(oid).to.eq(projekti.oid); await cleanProjektiS3Files(oid); - const projektiPaallikko = await testProjektiHenkilot(projekti, oid); + const projektiPaallikko = await testProjektiHenkilot(projekti, oid, userFixture); await testProjektinTiedot(oid); await testAloitusKuulutusEsikatselu(oid); await testNullifyProjektiField(oid); diff --git a/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json b/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json index 7e6544e44c..64a817551f 100644 --- a/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json +++ b/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json @@ -21,6 +21,7 @@ "maakunnat": [ "Uusimaa" ], + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "kunnat": [ "Helsinki", " Vantaa" @@ -115,6 +116,7 @@ "maakunnat": [ "Uusimaa" ], + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "kunnat": [ "Helsinki", " Vantaa" @@ -142,6 +144,7 @@ "maakunnat": [ "Uusimaa" ], + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "kunnat": [ "Helsinki", " Vantaa" @@ -495,18 +498,37 @@ { "kayttajatunnus": "A000112", "nimi": "Hassu, A-tunnus1", - "rooli": "PROJEKTIPAALLIKKO", "organisaatio": "CGI Suomi Oy", + "tyyppi": "PROJEKTIPAALLIKKO", "puhelinnumero": "123", - "email": "mikko.haapamki@cgi.com" + "email": "mikko.haapamki@cgi.com", + "muokattavissa": false + }, + { + "kayttajatunnus": "A000114", + "nimi": "Hassu, A-tunnus3", + "organisaatio": "CGI Suomi Oy", + "tyyppi": "VARAHENKILO", + "puhelinnumero": "123", + "email": "mikko.haapamaki02@cgi.com", + "muokattavissa": false }, { "kayttajatunnus": "A000111", "nimi": "Hassu, A-Tunnus", - "rooli": "OMISTAJA", "organisaatio": "CGI Suomi Oy", + "__typename": "ProjektiKayttaja", "puhelinnumero": "123", - "email": "mikko.haapamaki@cgi.com" + "email": "mikko.haapamaki@cgi.com", + "muokattavissa": true + }, + { + "kayttajatunnus": "LX581241", + "nimi": "Hassu, Testi1", + "puhelinnumero": "123", + "organisaatio": "CGI Suomi Oy", + "email": "mikko.haapamaki@cgi.com", + "muokattavissa": true } ], "kasittelynTila": { @@ -582,6 +604,7 @@ "maakunnat": [ "Uusimaa" ], + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "kunnat": [ "Helsinki", " Vantaa" diff --git a/backend/integrationtest/api/records/NAHTAVILLAOLO.json b/backend/integrationtest/api/records/NAHTAVILLAOLO.json index f670bebd5d..533311668e 100644 --- a/backend/integrationtest/api/records/NAHTAVILLAOLO.json +++ b/backend/integrationtest/api/records/NAHTAVILLAOLO.json @@ -21,6 +21,7 @@ "maakunnat": [ "Uusimaa" ], + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "kunnat": [ "Helsinki", " Vantaa" @@ -115,6 +116,7 @@ "maakunnat": [ "Uusimaa" ], + "varahenkilonEmail": "mikko.haapamaki02@cgi.com", "kunnat": [ "Helsinki", " Vantaa" @@ -148,18 +150,37 @@ { "kayttajatunnus": "A000112", "nimi": "Hassu, A-tunnus1", - "rooli": "PROJEKTIPAALLIKKO", "organisaatio": "CGI Suomi Oy", + "tyyppi": "PROJEKTIPAALLIKKO", "puhelinnumero": "123", - "email": "mikko.haapamki@cgi.com" + "email": "mikko.haapamki@cgi.com", + "muokattavissa": false + }, + { + "kayttajatunnus": "A000114", + "nimi": "Hassu, A-tunnus3", + "organisaatio": "CGI Suomi Oy", + "tyyppi": "VARAHENKILO", + "puhelinnumero": "123", + "email": "mikko.haapamaki02@cgi.com", + "muokattavissa": false }, { "kayttajatunnus": "A000111", "nimi": "Hassu, A-Tunnus", - "rooli": "OMISTAJA", "organisaatio": "CGI Suomi Oy", + "__typename": "ProjektiKayttaja", "puhelinnumero": "123", - "email": "mikko.haapamaki@cgi.com" + "email": "mikko.haapamaki@cgi.com", + "muokattavissa": true + }, + { + "kayttajatunnus": "LX581241", + "nimi": "Hassu, Testi1", + "puhelinnumero": "123", + "organisaatio": "CGI Suomi Oy", + "email": "mikko.haapamaki@cgi.com", + "muokattavissa": true } ], "aloitusKuulutus": { diff --git a/backend/integrationtest/api/testUtil/tests.ts b/backend/integrationtest/api/testUtil/tests.ts index 85f2ba2a4e..98558960c4 100644 --- a/backend/integrationtest/api/testUtil/tests.ts +++ b/backend/integrationtest/api/testUtil/tests.ts @@ -1,12 +1,12 @@ import { AineistoInput, AsiakirjaTyyppi, + KayttajaTyyppi, Kieli, Projekti, ProjektiJulkinen, ProjektiKayttaja, ProjektiKayttajaInput, - ProjektiRooli, Status, TilasiirtymaToiminto, TilasiirtymaTyyppi, @@ -60,33 +60,39 @@ export async function loadProjektiJulkinenFromDatabase(oid: string, expectedStat return savedProjekti; } -export async function testProjektiHenkilot(projekti: Projekti, oid: string): Promise { +export async function testProjektiHenkilot(projekti: Projekti, oid: string, userFixture: UserFixture): Promise { await api.tallennaProjekti({ oid, - kayttoOikeudet: projekti.kayttoOikeudet?.map( - (value) => - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - ({ - rooli: value.rooli, - kayttajatunnus: value.kayttajatunnus, - // Emulate migration where the phone number may be empty - } as ProjektiKayttajaInput) - ), + kayttoOikeudet: projekti.kayttoOikeudet?.map((value) => { + const input: ProjektiKayttajaInput = { + kayttajatunnus: value.kayttajatunnus, + // Emulate migration where the phone number may be empty + puhelinnumero: undefined, + }; + return input; + }), }); const p = await loadProjektiFromDatabase(oid, Status.EI_JULKAISTU_PROJEKTIN_HENKILOT); // Expect that projektipaallikko is found const projektiPaallikko = p.kayttoOikeudet - ?.filter((kayttaja) => kayttaja.rooli === ProjektiRooli.PROJEKTIPAALLIKKO && kayttaja.email) + ?.filter((kayttaja) => kayttaja.tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO && kayttaja.email && kayttaja.muokattavissa === false) .pop(); expect(projektiPaallikko).is.not.empty; - const kayttoOikeudet = p.kayttoOikeudet?.map((value) => ({ - rooli: value.rooli, - kayttajatunnus: value.kayttajatunnus, + // Expect that varahenkilo from Velho is found + const varahenkilo = p.kayttoOikeudet + ?.filter((kayttaja) => kayttaja.tyyppi == KayttajaTyyppi.VARAHENKILO && kayttaja.muokattavissa === false) + .pop(); + expect(varahenkilo).is.not.empty; + + const kayttoOikeudet: ProjektiKayttajaInput[] = p.kayttoOikeudet?.map((value) => ({ + ...value, puhelinnumero: "123", })); + kayttoOikeudet.push({ kayttajatunnus: UserFixture.testi1Kayttaja.uid, puhelinnumero: "123" }); + // Save and load projekti await api.tallennaProjekti({ oid, @@ -94,10 +100,26 @@ export async function testProjektiHenkilot(projekti: Projekti, oid: string): Pro }); await loadProjektiFromDatabase(oid, Status.EI_JULKAISTU); + // Verify only omistaja can modify varahenkilo-field + try { + userFixture.loginAs(UserFixture.testi1Kayttaja); + const kayttoOikeudetWithVarahenkiloChanges = cloneDeep(kayttoOikeudet); + kayttoOikeudetWithVarahenkiloChanges + .filter((user) => user.kayttajatunnus == UserFixture.testi1Kayttaja.uid) + .forEach((user) => (user.tyyppi = KayttajaTyyppi.VARAHENKILO)); + await api.tallennaProjekti({ + oid, + kayttoOikeudet: kayttoOikeudetWithVarahenkiloChanges, + }); + fail("Vain omistajan pitää pystyä muokkaamaan varahenkilöyttä"); + } catch (e) { + expect(e.className).to.eq("IllegalAccessError"); + } + return { ...projektiPaallikko, puhelinnumero: "123" }; } -export async function tallennaLogo() { +export async function tallennaLogo(): Promise { const uploadProperties = await api.valmisteleTiedostonLataus("logo.png", "image/png"); expect(uploadProperties).to.not.be.empty; expect(uploadProperties.latausLinkki).to.not.be.undefined; diff --git a/backend/integrationtest/personSearch/personSearchClient.test.ts b/backend/integrationtest/personSearch/personSearchClient.test.ts index cdc1a9957e..05d35e5eaf 100644 --- a/backend/integrationtest/personSearch/personSearchClient.test.ts +++ b/backend/integrationtest/personSearch/personSearchClient.test.ts @@ -9,7 +9,6 @@ import log from "loglevel"; import sinon from "sinon"; export function expectNotEmptyKayttaja(kayttaja: Kayttaja): void { - expect(kayttaja.vaylaKayttajaTyyppi).to.not.be.empty; expect(kayttaja.etuNimi).to.not.be.empty; expect(kayttaja.sukuNimi).to.not.be.empty; expect(kayttaja.uid).to.not.be.empty; diff --git a/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts b/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts index 35703ef4ef..5499d2f63f 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts @@ -1,5 +1,5 @@ import { DBProjekti, DBVaylaUser, SuunnitteluSopimus, SuunnitteluVaihe, Vuorovaikutus, VuorovaikutusTilaisuus } from "../../database/model"; -import { Kieli, ProjektiTyyppi, VuorovaikutusTilaisuusTyyppi } from "../../../../common/graphql/apiModel"; +import { KayttajaTyyppi, Kieli, ProjektiTyyppi, VuorovaikutusTilaisuusTyyppi } from "../../../../common/graphql/apiModel"; import { formatProperNoun } from "../../../../common/util/formatProperNoun"; import dayjs from "dayjs"; import { linkSuunnitteluVaihe } from "../../../../common/links"; @@ -300,7 +300,7 @@ export class Kutsu20 extends CommonPdf { tilaisuus.esitettavatYhteystiedot?.yhteysHenkilot.forEach((kayttajatunnus) => { const user = this.kayttoOikeudet.filter((kayttaja) => kayttaja.kayttajatunnus == kayttajatunnus).pop(); if (user) { - const role = translate("rooli." + user.rooli, this.kieli); + const role = user.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO ? translate("rooli.PROJEKTIPAALLIKKO", this.kieli) : undefined; this.doc.text(safeConcatStrings(", ", [user.nimi, role, user.puhelinnumero])); } }); diff --git a/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts b/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts index e92861ca42..51e458e14d 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts @@ -1,4 +1,4 @@ -import { Kieli, ProjektiRooli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; +import { KayttajaTyyppi, Kieli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; import { DBVaylaUser, Kielitiedot, SuunnitteluSopimus, Velho, Vuorovaikutus, Yhteystieto } from "../../database/model"; import { AsiakirjanMuoto } from "../asiakirjaService"; import { translate } from "../../util/localization"; @@ -226,7 +226,7 @@ export class KutsuAdapter { throw new Error("BUG: Kayttöoikeudet pitää antaa jos yhteyshenkilöt on annettu."); } const yhteysHenkilotWithProjectManager = union( - this.kayttoOikeudet.filter((user) => user.rooli == ProjektiRooli.PROJEKTIPAALLIKKO).map((user) => user.kayttajatunnus), + this.kayttoOikeudet.filter((user) => user.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO).map((user) => user.kayttajatunnus), yhteysHenkilot ); this.getUsersForUsernames(yhteysHenkilotWithProjectManager).forEach((user) => { @@ -288,7 +288,7 @@ export class KutsuAdapter { return usernames ?.map((kayttajatunnus) => this.kayttoOikeudet - .filter((kayttaja) => kayttaja.kayttajatunnus == kayttajatunnus || kayttaja.rooli == ProjektiRooli.PROJEKTIPAALLIKKO) + .filter((kayttaja) => kayttaja.kayttajatunnus == kayttajatunnus || kayttaja.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO) .pop() ) .filter((o) => o); diff --git a/backend/src/database/model/projekti.ts b/backend/src/database/model/projekti.ts index eab5659f98..cc757124d0 100644 --- a/backend/src/database/model/projekti.ts +++ b/backend/src/database/model/projekti.ts @@ -1,17 +1,18 @@ -import { AloitusKuulutusTila, Kieli, ProjektiRooli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; +import { AloitusKuulutusTila, KayttajaTyyppi, Kieli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; import { SuunnitteluVaihe, Vuorovaikutus } from "./suunnitteluVaihe"; import { NahtavillaoloVaihe, NahtavillaoloVaiheJulkaisu } from "./nahtavillaoloVaihe"; import { HyvaksymisPaatosVaihe, HyvaksymisPaatosVaiheJulkaisu } from "./hyvaksymisPaatosVaihe"; -import { StandardiYhteystiedot, LocalizedMap, Yhteystieto, IlmoituksenVastaanottajat } from "./common"; +import { IlmoituksenVastaanottajat, LocalizedMap, StandardiYhteystiedot, Yhteystieto } from "./common"; export type DBVaylaUser = { - rooli: ProjektiRooli; email: string; kayttajatunnus: string; - puhelinnumero: string; + puhelinnumero?: string; organisaatio: string; nimi: string; + tyyppi?: KayttajaTyyppi; + muokattavissa?: boolean; }; export type AloitusKuulutus = { @@ -87,8 +88,8 @@ export type Velho = { toteuttavaOrganisaatio?: string | null; vastuuhenkilonNimi?: string | null; vastuuhenkilonEmail?: string | null; - varahenkiloNimi?: string | null; - varahenkiloEmail?: string | null; + varahenkilonNimi?: string | null; + varahenkilonEmail?: string | null; maakunnat?: string[] | null; kunnat?: string[] | null; linkki?: string | null; diff --git a/backend/src/email/lahetekirje/LahetekirjeAdapter.ts b/backend/src/email/lahetekirje/LahetekirjeAdapter.ts index dc11558bd9..a2095c57dd 100644 --- a/backend/src/email/lahetekirje/LahetekirjeAdapter.ts +++ b/backend/src/email/lahetekirje/LahetekirjeAdapter.ts @@ -1,5 +1,5 @@ import log from "loglevel"; -import { Kieli, ProjektiRooli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; +import { KayttajaTyyppi, Kieli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; import { AsiakirjanMuoto, determineAsiakirjaMuoto } from "../../asiakirja/asiakirjaService"; import { DBProjekti, Yhteystieto } from "../../database/model"; import { translate } from "../../util/localization"; @@ -56,8 +56,7 @@ export class LahetekirjeAdapter { protected get isElyTilaaja(): boolean { return ( - this.projekti?.velho?.suunnittelustaVastaavaViranomainen && - this.projekti?.velho?.suunnittelustaVastaavaViranomainen.endsWith("ELY") + this.projekti?.velho?.suunnittelustaVastaavaViranomainen && this.projekti?.velho?.suunnittelustaVastaavaViranomainen.endsWith("ELY") ); } @@ -195,9 +194,8 @@ export class LahetekirjeAdapter { kayttoOikeudet ?.filter( - ({ kayttajatunnus, rooli }) => - rooli === ProjektiRooli.PROJEKTIPAALLIKKO || - kuulutusYhteystiedot?.yhteysHenkilot?.find((yh) => yh === kayttajatunnus) + ({ kayttajatunnus, tyyppi }) => + tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO || kuulutusYhteystiedot?.yhteysHenkilot?.find((yh) => yh === kayttajatunnus) ) .forEach((oikeus) => { yt.push(vaylaUserToYhteystieto(oikeus)); diff --git a/backend/src/handler/tila/TilaManager.ts b/backend/src/handler/tila/TilaManager.ts index 50fec21b2c..01941d8b22 100644 --- a/backend/src/handler/tila/TilaManager.ts +++ b/backend/src/handler/tila/TilaManager.ts @@ -3,7 +3,7 @@ import { requirePermissionLuku, requirePermissionMuokkaa } from "../../user"; import { projektiDatabase } from "../../database/projektiDatabase"; import { emailHandler } from "../emailHandler"; import { DBProjekti } from "../../database/model"; -import { requireProjektiPaallikko } from "../../user/userService"; +import { requireOmistaja } from "../../user/userService"; export abstract class TilaManager { protected tyyppi: TilasiirtymaTyyppi; @@ -34,12 +34,12 @@ export abstract class TilaManager { } private async rejectInternal(projekti: DBProjekti, syy: string) { - requireProjektiPaallikko(projekti); + requireOmistaja(projekti); await this.reject(projekti, syy); } private async approveInternal(projekti: DBProjekti) { - const projektiPaallikko = requireProjektiPaallikko(projekti); + const projektiPaallikko = requireOmistaja(projekti); await this.approve(projekti, projektiPaallikko); } diff --git a/backend/src/personSearch/kayttajas.ts b/backend/src/personSearch/kayttajas.ts index 35f36fc246..30a8d468eb 100644 --- a/backend/src/personSearch/kayttajas.ts +++ b/backend/src/personSearch/kayttajas.ts @@ -1,9 +1,8 @@ -import { Kayttaja, VaylaKayttajaTyyppi } from "../../../common/graphql/apiModel"; +import { Kayttaja } from "../../../common/graphql/apiModel"; import { adaptPerson } from "./personAdapter"; import { log } from "../logger"; export type Person = { - vaylaKayttajaTyyppi?: VaylaKayttajaTyyppi | null; etuNimi: string; sukuNimi: string; organisaatio?: string; @@ -27,8 +26,9 @@ export class Kayttajas { } findByEmail(email: string): Kayttaja | undefined { + const lowercaseEmail = email.toLowerCase().trim(); for (const [uid, person] of Object.entries(this.personMap)) { - if (person.email.includes(email.toLowerCase().trim())) { + if (person.email.includes(lowercaseEmail)) { return adaptPerson(uid, person); } } @@ -41,11 +41,10 @@ export class Kayttajas { return new Kayttajas( kayttajas.reduce((map, kayttaja) => { if (kayttaja.uid) { - const person: Person = { + map[kayttaja.uid] = { ...kayttaja, email: [kayttaja.email], }; - map[kayttaja.uid] = person; } return map; }, {} as Record) diff --git a/backend/src/personSearch/lambda/personSearchAdapter.ts b/backend/src/personSearch/lambda/personSearchAdapter.ts index 842772c3f1..a78a503025 100644 --- a/backend/src/personSearch/lambda/personSearchAdapter.ts +++ b/backend/src/personSearch/lambda/personSearchAdapter.ts @@ -1,14 +1,5 @@ -import { VaylaKayttajaTyyppi } from "../../../../common/graphql/apiModel"; import { Person } from "../kayttajas"; -function adaptAccounttype(accountType: string) { - return { - "A-tunnus": VaylaKayttajaTyyppi.A_TUNNUS, - "L-tunnus": VaylaKayttajaTyyppi.L_TUNNUS, - "LX-tunnus": VaylaKayttajaTyyppi.LX_TUNNUS, - }[accountType]; -} - export function adaptPersonSearchResult(responseJson: any, kayttajas: Record): void { responseJson.person?.person?.forEach( (person: { @@ -31,7 +22,6 @@ export function adaptPersonSearchResult(responseJson: any, kayttajas: Record, account: Kayttaja): void { +export function mergeKayttaja(user: Partial, account: Kayttaja): DBVaylaUser { const { organisaatio, email } = account; const nimi = account.sukuNimi + ", " + account.etuNimi; const kayttajatunnus = account.uid; - mergeWith(user, { organisaatio, email, nimi, kayttajatunnus }); + return merge(user, { organisaatio, email, nimi, kayttajatunnus }); } export function adaptKayttaja(account: Kayttaja): DBVaylaUser { @@ -26,7 +26,6 @@ export function adaptPerson(uid: string, person: Person): Kayttaja { puhelinnumero: person.puhelinnumero, sukuNimi: person.sukuNimi, uid, - vaylaKayttajaTyyppi: person.vaylaKayttajaTyyppi, }) as Kayttaja; } diff --git a/backend/src/projekti/adapter/adaptToDB/adaptKayttajatunnusList.ts b/backend/src/projekti/adapter/adaptToDB/adaptKayttajatunnusList.ts index dff21db5c7..41dc0b28b0 100644 --- a/backend/src/projekti/adapter/adaptToDB/adaptKayttajatunnusList.ts +++ b/backend/src/projekti/adapter/adaptToDB/adaptKayttajatunnusList.ts @@ -22,7 +22,7 @@ export function adaptKayttajatunnusList( // Users with PROJEKTIPAALLIKKO role should always be in the kayttajaTunnusList // Push PROJEKTIPAALLIKKO into the list if not there already const projektipaallikkonTunnus = projekti.kayttoOikeudet?.find( - ({ rooli }) => rooli === API.ProjektiRooli.PROJEKTIPAALLIKKO + ({ tyyppi }) => tyyppi === API.KayttajaTyyppi.PROJEKTIPAALLIKKO )?.kayttajatunnus; if (!doNotForceProjektipaallikko && !unfilteredList.includes(projektipaallikkonTunnus)) { unfilteredList.push(projektipaallikkonTunnus); diff --git a/backend/src/projekti/adapter/common/adaptStandardiYhteystiedotByAddingProjari.ts b/backend/src/projekti/adapter/common/adaptStandardiYhteystiedotByAddingProjari.ts index 2255a67b5e..8f3b4d5dc4 100644 --- a/backend/src/projekti/adapter/common/adaptStandardiYhteystiedotByAddingProjari.ts +++ b/backend/src/projekti/adapter/common/adaptStandardiYhteystiedotByAddingProjari.ts @@ -1,6 +1,6 @@ import * as API from "../../../../../common/graphql/apiModel"; -import { ProjektiRooli } from "../../../../../common/graphql/apiModel"; -import { StandardiYhteystiedot, DBVaylaUser } from "../../../database/model"; +import { KayttajaTyyppi } from "../../../../../common/graphql/apiModel"; +import { DBVaylaUser, StandardiYhteystiedot } from "../../../database/model"; import { adaptYhteystiedotByAddingTypename } from "./lisaaTypename"; export function adaptStandardiYhteystiedotByAddingProjari( @@ -9,7 +9,7 @@ export function adaptStandardiYhteystiedotByAddingProjari( ): API.StandardiYhteystiedot { if (yhteystiedot) { const yhteysHenkilot = yhteystiedot.yhteysHenkilot || []; - const projari = kayttoOikeudet.find(({ rooli }) => rooli === ProjektiRooli.PROJEKTIPAALLIKKO); + const projari = kayttoOikeudet.find(({ tyyppi }) => tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO); if (!yhteysHenkilot.find((kayttajatunnus) => kayttajatunnus === projari.kayttajatunnus)) { yhteysHenkilot.push(projari.kayttajatunnus); } diff --git a/backend/src/projekti/kayttoOikeudetManager.ts b/backend/src/projekti/kayttoOikeudetManager.ts index 3569710604..1accd9cfbc 100644 --- a/backend/src/projekti/kayttoOikeudetManager.ts +++ b/backend/src/projekti/kayttoOikeudetManager.ts @@ -1,9 +1,9 @@ import { DBVaylaUser } from "../database/model"; -import { Kayttaja, ProjektiKayttaja, ProjektiKayttajaInput, ProjektiRooli } from "../../../common/graphql/apiModel"; +import { Kayttaja, KayttajaTyyppi, ProjektiKayttaja, ProjektiKayttajaInput } from "../../../common/graphql/apiModel"; import { SearchMode } from "../personSearch/personSearchClient"; import { log } from "../logger"; import differenceWith from "lodash/differenceWith"; -import { isAorL } from "../user"; +import remove from "lodash/remove"; import { mergeKayttaja } from "../personSearch/personAdapter"; import { Kayttajas } from "../personSearch/kayttajas"; @@ -20,18 +20,35 @@ export class KayttoOikeudetManager { if (!changes) { return; } - // Go through all users in database projekti and the input. - // Update existing ones, remove those that are missing in input, and add those that exist only in input. - const resultUsers: DBVaylaUser[] = this.users.reduce((resultingUsers: DBVaylaUser[], currentUser) => { + const resultUsers = this.modifyExistingUsers(changes); + + // Add new users + this.addNewUsers(changes, resultUsers); + this.users = resultUsers; + } + + /** + * Go through all users in database projekti and the input. + * Update existing ones, remove those that are missing in input, and add those that exist only in input. * @param changes + */ + private modifyExistingUsers(changes: ProjektiKayttajaInput[]) { + return this.users.reduce((resultingUsers: DBVaylaUser[], currentUser) => { const inputUser = changes.find((user) => user.kayttajatunnus === currentUser.kayttajatunnus); if (inputUser) { - // Update only puhelinnumero if projektipaallikko - if (inputUser.rooli === ProjektiRooli.PROJEKTIPAALLIKKO) { + if ( + currentUser.tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO || + (currentUser.tyyppi === KayttajaTyyppi.VARAHENKILO && currentUser.muokattavissa === false) + ) { + // Update only puhelinnumero if projektipaallikko or varahenkilö resultingUsers.push({ ...currentUser, puhelinnumero: inputUser.puhelinnumero, }); } else { + if (inputUser.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO) { + // Tyyppiä ei voi vaihtaa projektipäälliköksi + delete inputUser.tyyppi; + } // Update rest of fields resultingUsers.push({ ...currentUser, @@ -39,21 +56,23 @@ export class KayttoOikeudetManager { }); } } else { - // Remove user because it doesn't exist in input, except projektipaallikko which cannot be removed - if (currentUser.rooli === ProjektiRooli.PROJEKTIPAALLIKKO) { + // Remove user because it doesn't exist in input, except if muokattavissa===false + if (currentUser.muokattavissa === false) { resultingUsers.push(currentUser); } } return resultingUsers; }, []); + } - // Add new users + private addNewUsers(changes: ProjektiKayttajaInput[], resultUsers: DBVaylaUser[]) { const newUsers = differenceWith(changes, resultUsers, (u1, u2) => u1.kayttajatunnus === u2.kayttajatunnus); - newUsers.map((newUser) => { + newUsers.forEach((newUser) => { const userToAdd: Partial = { puhelinnumero: newUser.puhelinnumero, kayttajatunnus: newUser.kayttajatunnus, - rooli: newUser.rooli, + muokattavissa: true, + tyyppi: newUser.tyyppi == KayttajaTyyppi.VARAHENKILO ? KayttajaTyyppi.VARAHENKILO : undefined, }; try { const userWithAllInfo = this.fillInUserInfoFromUserManagement({ @@ -67,7 +86,6 @@ export class KayttoOikeudetManager { log.error(e); } }); - this.users = resultUsers; } getKayttoOikeudet(): DBVaylaUser[] { @@ -81,28 +99,40 @@ export class KayttoOikeudetManager { })); } - addProjektiPaallikkoFromEmail(vastuuhenkiloEmail: string): DBVaylaUser | undefined { - return this.resolveProjektiPaallikkoFromVelhoVastuuhenkilo(vastuuhenkiloEmail); - } - - resolveProjektiPaallikkoFromVelhoVastuuhenkilo(vastuuhenkiloEmail: string): DBVaylaUser | undefined { + addProjektiPaallikkoFromEmail(email: string): DBVaylaUser | undefined { // Replace or create new projektipaallikko - this.removeProjektiPaallikko(); const projektiPaallikko = this.fillInUserInfoFromUserManagement({ user: { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, - email: vastuuhenkiloEmail, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + email: email.toLowerCase(), + muokattavissa: false, }, searchMode: SearchMode.EMAIL, }); if (projektiPaallikko) { + // Remove existing PROJEKTIPAALLIKKO + remove(this.users, (aUser) => aUser.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO && !aUser.muokattavissa); this.users.push(projektiPaallikko); return projektiPaallikko; } } - addUserByKayttajatunnus(kayttajatunnus: string, rooli: ProjektiRooli): DBVaylaUser | undefined { - const partialUser: Partial = { kayttajatunnus, rooli }; + addVarahenkiloFromEmail(email: string | undefined): void { + if (email) { + const kayttajas = this.kayttajas; + const account: Kayttaja = kayttajas.findByEmail(email); + if (account) { + const user = mergeKayttaja({ tyyppi: KayttajaTyyppi.VARAHENKILO, muokattavissa: false }, account); + if (user) { + // Remove existing varahenkilo + remove(this.users, (aUser) => aUser.tyyppi == KayttajaTyyppi.VARAHENKILO && !aUser.muokattavissa); + this.users.push(user); + } + } + } + } + + addUser(partialUser: Partial): DBVaylaUser | undefined { const user = this.fillInUserInfoFromUserManagement({ user: partialUser, searchMode: SearchMode.UID, @@ -129,16 +159,7 @@ export class KayttoOikeudetManager { account = kayttajas.findByEmail(user.email); } if (account) { - // Projektipaallikko must be either L or A account - if (user.rooli === ProjektiRooli.PROJEKTIPAALLIKKO && !isAorL(account)) { - return; - } - mergeKayttaja(user, account); - return user as DBVaylaUser; + return mergeKayttaja({ ...user }, account); } } - - private removeProjektiPaallikko() { - this.users = this.users.filter((user) => user.rooli === ProjektiRooli.PROJEKTIPAALLIKKO); - } } diff --git a/backend/src/projekti/projektiHandler.ts b/backend/src/projekti/projektiHandler.ts index 4c9a6badeb..38315c6a3a 100644 --- a/backend/src/projekti/projektiHandler.ts +++ b/backend/src/projekti/projektiHandler.ts @@ -2,7 +2,7 @@ import { projektiDatabase } from "../database/projektiDatabase"; import { getVaylaUser, requirePermissionLuku, requirePermissionLuonti, requirePermissionMuokkaa, requireVaylaUser } from "../user"; import { velho } from "../velho/velhoClient"; import * as API from "../../../common/graphql/apiModel"; -import { NykyinenKayttaja, ProjektiRooli, TallennaProjektiInput, Velho } from "../../../common/graphql/apiModel"; +import { KayttajaTyyppi, NykyinenKayttaja, TallennaProjektiInput, Velho } from "../../../common/graphql/apiModel"; import { ProjektiAdaptationResult, projektiAdapter, ProjektiEventType, VuorovaikutusPublishedEvent } from "./adapter/projektiAdapter"; import { adaptVelhoByAddingTypename } from "./adapter/common"; import { auditLog, log } from "../logger"; @@ -13,7 +13,7 @@ import { fileService } from "../files/fileService"; import { personSearch } from "../personSearch/personSearchClient"; import { emailClient } from "../email/email"; import { createPerustamisEmail } from "../email/emailTemplates"; -import { requireAdmin } from "../user/userService"; +import { requireAdmin, requireOmistaja } from "../user/userService"; import { projektiArchive } from "../archive/projektiArchiveService"; import { NotFoundError } from "../error/NotFoundError"; import { projektiAdapterJulkinen } from "./adapter/projektiAdapterJulkinen"; @@ -68,16 +68,35 @@ export async function arkistoiProjekti(oid: string): Promise { return projektiArchive.archiveProjekti(oid); } -export async function createOrUpdateProjekti(input: TallennaProjektiInput): Promise { +function verifyTallennaProjektiPermissions(projektiInDB: DBProjekti, input: TallennaProjektiInput) { + requirePermissionMuokkaa(projektiInDB); if (input.kasittelynTila) { - requireAdmin("Hyvaksymispaatoksia voi tallentaa vain Hasssun yllapitaja"); + requireAdmin("Hyvaksymispaatoksia voi tallentaa vain Hassun yllapitaja"); + } + + // Vain omistaja voi muokata projektiPaallikonVarahenkilo-kenttää + if ( + // Etsi kentät, joita yritetään muokata + input.kayttoOikeudet + ?.filter((kayttoOikeus) => kayttoOikeus.tyyppi === null || kayttoOikeus.tyyppi === KayttajaTyyppi.VARAHENKILO) + // Suodata vain muokattavissa olevat käyttäjät + .filter((kayttoOikeus) => { + const dbVaylaUser = projektiInDB.kayttoOikeudet.filter((kayttaja) => kayttaja.kayttajatunnus == kayttoOikeus.kayttajatunnus).pop(); + return dbVaylaUser.muokattavissa === true; + }) + .pop() + ) { + requireOmistaja(projektiInDB); } +} + +export async function createOrUpdateProjekti(input: TallennaProjektiInput): Promise { requirePermissionLuku(); const oid = input.oid; const projektiInDB = await projektiDatabase.loadProjektiByOid(oid); if (projektiInDB) { // Save over existing one - requirePermissionMuokkaa(projektiInDB); + verifyTallennaProjektiPermissions(projektiInDB, input); auditLog.info("Tallenna projekti", { input }); await handleFiles(input); const projektiAdaptationResult = await projektiAdapter.adaptProjektiToSave(projektiInDB, input); @@ -86,6 +105,7 @@ export async function createOrUpdateProjekti(input: TallennaProjektiInput): Prom } else { requirePermissionLuonti(); const { projekti } = await createProjektiFromVelho(input.oid, requireVaylaUser(), input); + verifyTallennaProjektiPermissions(projekti, input); log.info("Creating projekti to Hassu", { oid }); await projektiDatabase.createProjekti(projekti); log.info("Created projekti to Hassu", { projekti }); @@ -107,11 +127,15 @@ export async function createProjektiFromVelho( ): Promise<{ projekti: DBProjekti; virhetiedot?: API.ProjektipaallikkoVirhe }> { try { log.info("Loading projekti from Velho", { oid }); - const { projekti, vastuuhenkilo } = await velho.loadProjekti(oid); - const result: { projekti: DBProjekti; virhetiedot?: API.ProjektipaallikkoVirhe } = { projekti }; + const projekti = await velho.loadProjekti(oid); + const vastuuhenkilonEmail = projekti.velho.vastuuhenkilonEmail; + const varahenkilonEmail = projekti.velho.varahenkilonEmail; const kayttoOikeudet = new KayttoOikeudetManager([], await personSearch.getKayttajas()); + const projektiPaallikko = kayttoOikeudet.addProjektiPaallikkoFromEmail(vastuuhenkilonEmail); + kayttoOikeudet.addVarahenkiloFromEmail(varahenkilonEmail); + const result: { projekti: DBProjekti; virhetiedot?: API.ProjektipaallikkoVirhe } = { projekti }; if (input) { // Saving a new projekti, so adjusting data based on the input const { muistiinpano } = input; @@ -119,22 +143,19 @@ export async function createProjektiFromVelho( // Add new users given as inputs kayttoOikeudet.applyChanges(input.kayttoOikeudet); } else { - // Loading a projekti from Velho for a first time - const projektiPaallikko = kayttoOikeudet.addProjektiPaallikkoFromEmail(vastuuhenkilo); - - if (!vastuuhenkilo) { + if (!vastuuhenkilonEmail) { result.virhetiedot = { __typename: "ProjektipaallikkoVirhe", tyyppi: API.ProjektiPaallikkoVirheTyyppi.PUUTTUU }; } else if (!projektiPaallikko) { result.virhetiedot = { __typename: "ProjektipaallikkoVirhe", tyyppi: API.ProjektiPaallikkoVirheTyyppi.EI_LOYDY, - sahkoposti: vastuuhenkilo, + sahkoposti: vastuuhenkilonEmail, }; } - // Prefill current user as sihteeri if it is different from project manager + // Prefill current user as varahenkilo if it is different from project manager if ((!projektiPaallikko || projektiPaallikko.kayttajatunnus !== vaylaUser.uid) && vaylaUser.uid) { - kayttoOikeudet.addUserByKayttajatunnus(vaylaUser.uid, ProjektiRooli.OMISTAJA); + kayttoOikeudet.addUser({ kayttajatunnus: vaylaUser.uid, muokattavissa: true, tyyppi: KayttajaTyyppi.VARAHENKILO }); } } @@ -153,7 +174,7 @@ export async function findUpdatesFromVelho(oid: string): Promise { requirePermissionMuokkaa(projektiFromDB); log.info("Loading projekti from Velho", { oid }); - const { projekti } = await velho.loadProjekti(oid); + const projekti = await velho.loadProjekti(oid); return adaptVelhoByAddingTypename(findUpdatedFields(projektiFromDB.velho, projekti.velho)); } catch (e) { @@ -169,13 +190,16 @@ export async function synchronizeUpdatesFromVelho(oid: string): Promise { requirePermissionMuokkaa(projektiFromDB); log.info("Loading projekti from Velho", { oid }); - const { projekti } = await velho.loadProjekti(oid); + const projekti = await velho.loadProjekti(oid); + const kayttoOikeudetManager = new KayttoOikeudetManager(projekti.kayttoOikeudet, await personSearch.getKayttajas()); + kayttoOikeudetManager.addProjektiPaallikkoFromEmail(projekti.velho.vastuuhenkilonEmail); + kayttoOikeudetManager.addVarahenkiloFromEmail(projekti.velho.varahenkilonEmail); const updatedFields = findUpdatedFields(projektiFromDB.velho, projekti.velho); const updatedVelhoValues = values(updatedFields); if (updatedVelhoValues.length > 0) { log.info("Muutoksia projektiin löytynyt Velhosta", { oid, updatedFields }); - await projektiDatabase.saveProjekti({ oid, velho: projekti.velho }); + await projektiDatabase.saveProjekti({ oid, velho: projekti.velho, kayttoOikeudet: kayttoOikeudetManager.getKayttoOikeudet() }); return adaptVelhoByAddingTypename(updatedFields); } else { log.info("Muutoksia projektiin löytynyt Velhosta", { oid }); diff --git a/backend/src/projektiSearch/projektiSearchAdapter.ts b/backend/src/projektiSearch/projektiSearchAdapter.ts index 792b7b4965..7d5770b152 100644 --- a/backend/src/projektiSearch/projektiSearchAdapter.ts +++ b/backend/src/projektiSearch/projektiSearchAdapter.ts @@ -1,9 +1,9 @@ import { DBProjekti, Kielitiedot } from "../database/model"; import { + KayttajaTyyppi, Kieli, ProjektiHakutulosDokumentti, ProjektiJulkinen, - ProjektiRooli, ProjektiTyyppi, Status, Viranomainen, @@ -43,7 +43,7 @@ export function adaptProjektiToIndex(projekti: DBProjekti): Partial value.rooli == ProjektiRooli.PROJEKTIPAALLIKKO) + .filter((value) => value.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO) .map((value) => safeTrim(value.nimi)) .pop(), paivitetty: projekti.paivitetty || dayjs().format(), diff --git a/backend/src/user/index.ts b/backend/src/user/index.ts index 8d47c606b2..9ee846ecb9 100644 --- a/backend/src/user/index.ts +++ b/backend/src/user/index.ts @@ -1,6 +1,5 @@ export * as userService from "./userService"; export { - isAorL, identifyUser, requirePermissionLuonti, requirePermissionLuku, diff --git a/backend/src/user/userService.ts b/backend/src/user/userService.ts index 140575008e..5f698b1522 100644 --- a/backend/src/user/userService.ts +++ b/backend/src/user/userService.ts @@ -3,10 +3,11 @@ import { validateJwtToken } from "./validatejwttoken"; import { config } from "../config"; import { log } from "../logger"; import { IllegalAccessError } from "../error/IllegalAccessError"; -import { NykyinenKayttaja, ProjektiRooli, VaylaKayttajaTyyppi } from "../../../common/graphql/apiModel"; -import { DBProjekti } from "../database/model/projekti"; +import { KayttajaTyyppi, NykyinenKayttaja } from "../../../common/graphql/apiModel"; +import { DBProjekti } from "../database/model"; import { createSignedCookies } from "./signedCookie"; import { apiConfig } from "../../../common/abstractApi"; +import { isAorL } from "../util/userUtil"; function parseRoles(roles: string): string[] | undefined { return roles @@ -25,24 +26,7 @@ function parseRoles(roles: string): string[] | undefined { : undefined; } -function adaptKayttajaTyyppi(roolit: string[] | null | undefined): VaylaKayttajaTyyppi | undefined { - const roleToTypeMap: Record = { - Atunnukset: VaylaKayttajaTyyppi.A_TUNNUS, - Ltunnukset: VaylaKayttajaTyyppi.L_TUNNUS, - LXtunnukset: VaylaKayttajaTyyppi.LX_TUNNUS, - }; - if (roolit) { - for (const role of roolit) { - const type = roleToTypeMap[role]; - if (type) { - return type; - } - } - } -} - export type KayttajaPermissions = { - vaylaKayttajaTyyppi?: VaylaKayttajaTyyppi | null; roolit?: string[] | null; }; @@ -81,7 +65,6 @@ export const identifyUser = async (event: AppSyncResolverEvent): Promis for (const identifyUserFunction of identifyUserFunctions) { const user = await identifyUserFunction(event); if (user) { - user.vaylaKayttajaTyyppi = adaptKayttajaTyyppi(user.roolit); (globalThis as any).currentUser = user; return; } @@ -130,10 +113,6 @@ function isHassuKayttaja(kayttaja: KayttajaPermissions) { return kayttaja.roolit?.includes("hassu_kayttaja") || isHassuAdmin(kayttaja); } -export function isAorL(account: KayttajaPermissions): boolean { - return isATunnus(account) || isLTunnus(account); -} - export function requirePermissionLuku(): NykyinenKayttaja { return requireVaylaUser(); } @@ -144,8 +123,8 @@ export function requirePermissionLuonti(): void { } } -export function hasPermissionLuonti(kayttaja: KayttajaPermissions = requireVaylaUser()): boolean { - return isHassuAdmin(kayttaja) || isAorL(kayttaja); +export function hasPermissionLuonti(kayttaja = requireVaylaUser()): boolean { + return !!kayttaja; } export function requirePermissionMuokkaa(projekti: DBProjekti): NykyinenKayttaja { @@ -153,15 +132,12 @@ export function requirePermissionMuokkaa(projekti: DBProjekti): NykyinenKayttaja if (isHassuAdmin(kayttaja)) { return kayttaja; } - if (!isAorL(kayttaja)) { - throw new IllegalAccessError("Vain L ja A tunnuksella voi muokata projekteja"); - } - // Current user must be added into the projekti with any role - const projektiUser = projekti.kayttoOikeudet.filter((user) => user.kayttajatunnus === kayttaja.uid).pop(); - if (!projektiUser) { + // Current user must be added into the projekti + if (projekti.kayttoOikeudet.filter((user) => user.kayttajatunnus === kayttaja.uid).pop()) { + return kayttaja; + } else { throw new IllegalAccessError("Sinulla ei ole käyttöoikeutta muokata projektia"); } - return kayttaja; } export function requireAdmin(description?: string): NykyinenKayttaja { @@ -172,25 +148,21 @@ export function requireAdmin(description?: string): NykyinenKayttaja { throw new IllegalAccessError("Sinulla ei ole admin-oikeuksia" + (description ? " (" + description + ")" : "")); } -export function requireProjektiPaallikko(projekti: DBProjekti): NykyinenKayttaja { +function isCurrentUserVirkamiesAndTypeOf(projekti: DBProjekti, tyyppi: KayttajaTyyppi): boolean { const kayttaja = requireVaylaUser(); if (isHassuAdmin(kayttaja)) { - return kayttaja; - } - // Current user must be added into the projekti with projektipaallikko role - const projektiUser = projekti.kayttoOikeudet - .filter((user) => user.kayttajatunnus === kayttaja.uid && user.rooli == ProjektiRooli.PROJEKTIPAALLIKKO) - .pop(); - if (!projektiUser) { - throw new IllegalAccessError("Sinulla ei ole käyttöoikeutta muokata projektia, koska et ole projektin projektipäällikkö."); + return true; } - return kayttaja; + const projektiUser = projekti.kayttoOikeudet.filter((user) => user.kayttajatunnus === kayttaja.uid && user.tyyppi == tyyppi).pop(); + return !!projektiUser && isAorL(projektiUser.kayttajatunnus); } -function isATunnus(account: KayttajaPermissions) { - return account?.vaylaKayttajaTyyppi === VaylaKayttajaTyyppi.A_TUNNUS; -} - -function isLTunnus(account: KayttajaPermissions) { - return account?.vaylaKayttajaTyyppi === VaylaKayttajaTyyppi.L_TUNNUS; +export function requireOmistaja(projekti: DBProjekti): NykyinenKayttaja { + const isOmistaja = + isCurrentUserVirkamiesAndTypeOf(projekti, KayttajaTyyppi.PROJEKTIPAALLIKKO) || + isCurrentUserVirkamiesAndTypeOf(projekti, KayttajaTyyppi.VARAHENKILO); + if (isOmistaja) { + return requireVaylaUser(); + } + throw new IllegalAccessError("Et ole projektin omistaja"); } diff --git a/backend/src/util/adaptStandardiYhteystiedot.ts b/backend/src/util/adaptStandardiYhteystiedot.ts index c5ea7dd908..07f3595350 100644 --- a/backend/src/util/adaptStandardiYhteystiedot.ts +++ b/backend/src/util/adaptStandardiYhteystiedot.ts @@ -1,6 +1,6 @@ import { DBProjekti, StandardiYhteystiedot } from "../database/model"; import * as API from "../../../common/graphql/apiModel"; -import { vaylaUserToYhteystieto } from "../util/vaylaUserToYhteystieto"; +import { vaylaUserToYhteystieto } from "./vaylaUserToYhteystieto"; export default function adaptStandardiYhteystiedot( dbProjekti: DBProjekti, @@ -10,8 +10,8 @@ export default function adaptStandardiYhteystiedot( const sahkopostit: string[] = []; dbProjekti.kayttoOikeudet .filter( - ({ kayttajatunnus, rooli }) => - rooli === API.ProjektiRooli.PROJEKTIPAALLIKKO || kuulutusYhteystiedot?.yhteysHenkilot?.find((yh) => yh === kayttajatunnus) + ({ kayttajatunnus, tyyppi }) => + tyyppi === API.KayttajaTyyppi.PROJEKTIPAALLIKKO || kuulutusYhteystiedot?.yhteysHenkilot?.find((yh) => yh === kayttajatunnus) ) .forEach((oikeus) => { yt.push({ __typename: "Yhteystieto", ...vaylaUserToYhteystieto(oikeus) }); diff --git a/backend/src/util/userUtil.ts b/backend/src/util/userUtil.ts new file mode 100644 index 0000000000..56c5d890ea --- /dev/null +++ b/backend/src/util/userUtil.ts @@ -0,0 +1,20 @@ +export function isAorL(uid: string): boolean { + if (uid) { + return isATunnus(uid) || isLTunnus(uid); + } + return false; +} + +function isATunnus(uid: string) { + if (uid) { + return !!uid.match(/^A[\d]+/)?.pop(); + } + return false; +} + +function isLTunnus(uid: string) { + if (uid) { + return !!uid.match(/^L[\d]+/)?.pop(); + } + return false; +} diff --git a/backend/src/velho/velhoAdapter.ts b/backend/src/velho/velhoAdapter.ts index 2687faddcf..b5954a4638 100644 --- a/backend/src/velho/velhoAdapter.ts +++ b/backend/src/velho/velhoAdapter.ts @@ -1,11 +1,11 @@ import { ProjektiTyyppi, VelhoHakuTulos, Viranomainen } from "../../../common/graphql/apiModel"; -import { DBProjekti, Velho } from "../database/model/projekti"; +import { DBProjekti, Velho } from "../database/model"; import { adaptKayttaja } from "../personSearch/personAdapter"; -import { userService } from "../user"; import { Kayttajas } from "../personSearch/kayttajas"; import { ProjektiProjekti, ProjektirekisteriApiV2ProjektiOminaisuudet, + ProjektirekisteriApiV2ProjektiOminaisuudetVarahenkilo, ProjektirekisteriApiV2ProjektiOminaisuudetVastuuhenkilo, ProjektirekisteriApiV2ProjektiOminaisuudetVaylamuotoEnum, } from "./projektirekisteri"; @@ -109,8 +109,7 @@ export function adaptSearchResults(searchResults: ProjektiSearchResult[], kaytta if (searchResults) { return searchResults.map((result) => { const projektiPaallikko = kayttajas.findByEmail(getVastuuhenkiloEmail(result.ominaisuudet.vastuuhenkilo)); - const projektiPaallikkoNimi = - projektiPaallikko && userService.hasPermissionLuonti(projektiPaallikko) ? adaptKayttaja(projektiPaallikko).nimi : undefined; + const projektiPaallikkoNimi = projektiPaallikko ? adaptKayttaja(projektiPaallikko).nimi : undefined; const tyyppi = getProjektiTyyppi(result.ominaisuudet.vaihe); const hakutulos: VelhoHakuTulos = { __typename: "VelhoHakuTulos", @@ -125,7 +124,9 @@ export function adaptSearchResults(searchResults: ProjektiSearchResult[], kaytta return []; } -function getVastuuhenkiloEmail(vastuuhenkilo: ProjektirekisteriApiV2ProjektiOminaisuudetVastuuhenkilo): string { +function getVastuuhenkiloEmail( + vastuuhenkilo: ProjektirekisteriApiV2ProjektiOminaisuudetVastuuhenkilo | ProjektirekisteriApiV2ProjektiOminaisuudetVarahenkilo +): string { if (vastuuhenkilo?.sahkoposti) { return vastuuhenkilo?.sahkoposti; } @@ -147,11 +148,12 @@ function getMaakunnat(data: ProjektiProjekti) { return data.ominaisuudet["muu-maakunta"]?.split(","); } -export function adaptProjekti(data: ProjektiProjekti): { projekti: DBProjekti; vastuuhenkilo: string } { +export function adaptProjekti(data: ProjektiProjekti): DBProjekti { const projektiTyyppi = getProjektiTyyppi(data.ominaisuudet.vaihe as any); const viranomainen = getViranomainen(data.ominaisuudet.tilaajaorganisaatio as any); - const vastuuhenkiloEmail = getVastuuhenkiloEmail(data.ominaisuudet.vastuuhenkilo); - const projekti: DBProjekti = { + const vastuuhenkilonEmail = getVastuuhenkiloEmail(data.ominaisuudet.vastuuhenkilo); + const varahenkilonEmail = getVastuuhenkiloEmail(data.ominaisuudet.varahenkilo); + return { oid: "" + data.oid, tyyppi: projektiTyyppi, velho: { @@ -162,17 +164,13 @@ export function adaptProjekti(data: ProjektiProjekti): { projekti: DBProjekti; v linkki: data.ominaisuudet.linkki, kunnat: getKunnat(data), maakunnat: getMaakunnat(data), - vastuuhenkilonEmail: vastuuhenkiloEmail, + vastuuhenkilonEmail, + varahenkilonEmail, asiatunnusVayla: data.ominaisuudet["asiatunnus-vaylavirasto"], asiatunnusELY: data.ominaisuudet["asiatunnus-ely"], }, kayttoOikeudet: [], }; - - return { - projekti, - vastuuhenkilo: vastuuhenkiloEmail, - }; } export function adaptDokumenttiTyyppi(dokumenttiTyyppi: string): { dokumenttiTyyppi: string; kategoria: string } { diff --git a/backend/src/velho/velhoClient.ts b/backend/src/velho/velhoClient.ts index ba7127e69f..7da143f0e5 100644 --- a/backend/src/velho/velhoClient.ts +++ b/backend/src/velho/velhoClient.ts @@ -7,7 +7,7 @@ import { VelhoAineisto, VelhoAineistoKategoria, VelhoHakuTulos } from "../../../ import { adaptDokumenttiTyyppi, adaptProjekti, adaptSearchResults, ProjektiSearchResult } from "./velhoAdapter"; import { VelhoError } from "../error/velhoError"; import { AxiosRequestConfig, AxiosResponse } from "axios"; -import { DBProjekti } from "../database/model/projekti"; +import { DBProjekti } from "../database/model"; import { personSearch } from "../personSearch/personSearchClient"; import dayjs from "dayjs"; import { aineistoKategoriat } from "../../../common/aineistoKategoriat"; @@ -74,12 +74,7 @@ axios.defaults.timeout = 28000; function checkResponseIsOK(response: AxiosResponse, message: string) { if (response.status >= 400) { throw new VelhoError( - "Error while communicating with Velho: " + - message + - " StatusCode:" + - response.status + - " Status:" + - response?.statusText + "Error while communicating with Velho: " + message + " StatusCode:" + response.status + " Status:" + response?.statusText ); } } @@ -130,11 +125,7 @@ export class VelhoClient { }, lauseke: [ "ja", - [ - "joukossa", - ["projekti/projekti", "ominaisuudet", "vaihe"], - ["vaihe/vaihe04", "vaihe/vaihe10", "vaihe/vaihe12"], - ], + ["joukossa", ["projekti/projekti", "ominaisuudet", "vaihe"], ["vaihe/vaihe04", "vaihe/vaihe10", "vaihe/vaihe12"]], ["yhtasuuri", ["projekti/projekti", "ominaisuudet", "tila"], "tila/tila15"], searchClause, ], @@ -155,7 +146,7 @@ export class VelhoClient { } } - public async loadProjekti(oid: string): Promise<{ projekti: DBProjekti; vastuuhenkilo: string }> { + public async loadProjekti(oid: string): Promise { const projektiApi = await this.createProjektiRekisteriApi(); let response; try { @@ -178,8 +169,7 @@ export class VelhoClient { // List aineistot belonging to one toimeksianto const aineistotResponse = await hakuApi.hakupalveluApiV1HakuAineistotLinkitOidGet(toimeksianto.oid); checkResponseIsOK(aineistotResponse, "hakuApi.hakupalveluApiV1HakuAineistotLinkitOidGet " + toimeksianto.oid); - const aineistoArray: AineistoPalvelu.AineistoAineisto[] = - aineistotResponse.data as AineistoPalvelu.AineistoAineisto[]; + const aineistoArray: AineistoPalvelu.AineistoAineisto[] = aineistotResponse.data as AineistoPalvelu.AineistoAineisto[]; const results = await resultPromise; const kategoria = toimeksianto.ominaisuudet.nimi.trim(); @@ -217,16 +207,12 @@ export class VelhoClient { public async getLinkForDocument(dokumenttiOid: string): Promise { const dokumenttiApi = await this.createDokumenttiApi(); - const dokumenttiResponse = await dokumenttiApi.aineistopalveluApiV1AineistoOidDokumenttiGet( - dokumenttiOid, - undefined, - { - maxRedirects: 0, - validateStatus(status) { - return status >= 200 && status < 400; - }, - } - ); + const dokumenttiResponse = await dokumenttiApi.aineistopalveluApiV1AineistoOidDokumenttiGet(dokumenttiOid, undefined, { + maxRedirects: 0, + validateStatus(status) { + return status >= 200 && status < 400; + }, + }); return dokumenttiResponse.headers.location; } @@ -277,9 +263,7 @@ export class VelhoClient { } private async createProjektiRekisteriApi() { - return new ProjektiRekisteri.ProjektiApi( - new ProjektiRekisteri.Configuration(await this.getVelhoApiConfiguration()) - ); + return new ProjektiRekisteri.ProjektiApi(new ProjektiRekisteri.Configuration(await this.getVelhoApiConfiguration())); } // private async createAineistoApi() { diff --git a/backend/test/__snapshots__/apiHandler.test.ts.snap b/backend/test/__snapshots__/apiHandler.test.ts.snap index 3d6ef1db56..2dffe2a8b8 100644 --- a/backend/test/__snapshots__/apiHandler.test.ts.snap +++ b/backend/test/__snapshots__/apiHandler.test.ts.snap @@ -8,17 +8,19 @@ Object { "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "__typename": "ProjektiKayttaja", "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", - "rooli": "OMISTAJA", + "tyyppi": "VARAHENKILO", }, ], "muistiinpano": "Testiprojekti 1:n muistiinpano", @@ -42,17 +44,19 @@ Array [ "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "__typename": "ProjektiKayttaja", "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", - "rooli": "OMISTAJA", + "tyyppi": "VARAHENKILO", }, ], ] @@ -66,18 +70,20 @@ Array [ Object { "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "22", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "muistiinpano": "Testiprojekti 1:n muistiinpano", @@ -102,19 +108,21 @@ Array [ "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "__typename": "ProjektiKayttaja", "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "22", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "muistiinpano": "Testiprojekti 1:n muistiinpano", @@ -144,10 +152,11 @@ Array [ Object { "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, ], "kielitiedot": undefined, @@ -173,10 +182,11 @@ Array [ "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, ], "muistiinpano": "Testiprojekti 1:n muistiinpano", @@ -196,7 +206,7 @@ Array [ exports[`apiHandler handleEvent tallennaProjekti should modify permissions from a project successfully 6`] = ` Array [ - "Save projekti having while adding omistaja back. There should be projektipaallikko and omistaja in the projekti now. Projektipaallikko cannot be removed, so it always stays there.", + "Save projekti having while adding other user back. There should be projektipaallikko and one user in the projekti now. Projektipaallikko cannot be removed, so it always stays there.", Object { "aloitusKuulutus": undefined, "euRahoitus": undefined, @@ -206,18 +216,20 @@ Array [ Object { "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "kielitiedot": undefined, @@ -235,7 +247,7 @@ Array [ exports[`apiHandler handleEvent tallennaProjekti should modify permissions from a project successfully 7`] = ` Array [ - "Loaded projekti having while adding omistaja back. There should be projektipaallikko and omistaja in the projekti now. Projektipaallikko cannot be removed, so it always stays there.", + "Loaded projekti having while adding other user back. There should be projektipaallikko and one user in the projekti now. Projektipaallikko cannot be removed, so it always stays there.", Object { "__typename": "Projekti", "kayttoOikeudet": Array [ @@ -243,19 +255,21 @@ Array [ "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "__typename": "ProjektiKayttaja", "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "muistiinpano": "Testiprojekti 1:n muistiinpano", @@ -308,26 +322,29 @@ Array [ Object { "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, Object { - "email": "A2@vayla.fi", + "email": "a2@vayla.fi", "kayttajatunnus": "A2", + "muokattavissa": true, "nimi": "SukunimiA2, EtunimiA2", "organisaatio": "Väylävirasto", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "kielitiedot": Object { @@ -391,28 +408,31 @@ Array [ "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "__typename": "ProjektiKayttaja", "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, Object { "__typename": "ProjektiKayttaja", - "email": "A2@vayla.fi", + "email": "a2@vayla.fi", "kayttajatunnus": "A2", + "muokattavissa": true, "nimi": "SukunimiA2, EtunimiA2", "organisaatio": "Väylävirasto", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "kielitiedot": Object { @@ -715,28 +735,31 @@ Object { "__typename": "ProjektiKayttaja", "email": "pekka.projari@vayla.fi", "kayttajatunnus": "A123", + "muokattavissa": false, "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "11", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "__typename": "ProjektiKayttaja", "email": "matti.meikalainen@vayla.fi", "kayttajatunnus": "A000111", + "muokattavissa": true, "nimi": "Meikäläinen, Matti", "organisaatio": "ELY", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, Object { "__typename": "ProjektiKayttaja", - "email": "A2@vayla.fi", + "email": "a2@vayla.fi", "kayttajatunnus": "A2", + "muokattavissa": true, "nimi": "SukunimiA2, EtunimiA2", "organisaatio": "Väylävirasto", "puhelinnumero": "123456789", - "rooli": "OMISTAJA", + "tyyppi": undefined, }, ], "kielitiedot": Object { @@ -871,7 +894,7 @@ Object { }, Object { "__typename": "ProjektiKayttajaJulkinen", - "email": "A2@vayla.fi", + "email": "a2@vayla.fi", "id": "2", "nimi": "SukunimiA2, EtunimiA2", "organisaatio": "Väylävirasto", diff --git a/backend/test/apiHandler.test.ts b/backend/test/apiHandler.test.ts index 5b16636631..01f77ac449 100644 --- a/backend/test/apiHandler.test.ts +++ b/backend/test/apiHandler.test.ts @@ -10,9 +10,9 @@ import { personSearch } from "../src/personSearch/personSearchClient"; import { userService } from "../src/user"; import { AloitusKuulutusTila, + KayttajaTyyppi, Kieli, Projekti, - ProjektiRooli, TallennaProjektiInput, TilasiirtymaToiminto, TilasiirtymaTyyppi, @@ -98,13 +98,9 @@ describe("apiHandler", () => { function mockLataaProjektiFromVelho() { loadProjektiByOidStub.resolves(); const velhoProjekti = fixture.velhoprojekti1(); + velhoProjekti.velho.vastuuhenkilonEmail = personSearchFixture.pekkaProjari.email; - loadVelhoProjektiByOidStub.callsFake(() => ({ - projekti: velhoProjekti, - vastuuhenkilo: personSearchFixture.pekkaProjari.email, - kayttoOikeudet: [], - })); - createProjektiStub.resolves(); + loadVelhoProjektiByOidStub.resolves(velhoProjekti); } describe("lataaProjekti", () => { @@ -123,11 +119,7 @@ describe("apiHandler", () => { it("should modify permissions from a project successfully", async () => { let mockedDatabaseProjekti: DBProjekti | undefined; - async function saveAndLoadProjekti( - p: Projekti, - description: string, - updatedValues: Partial - ) { + async function saveAndLoadProjekti(p: Projekti, description: string, updatedValues: Partial) { await api.tallennaProjekti({ oid: fixture.PROJEKTI1_OID, ...updatedValues, @@ -235,27 +227,26 @@ describe("apiHandler", () => { // Create stubs to keep state of the "database" so that it can be modified in the following steps mockDatabase(); - // Save projekti with the defaults. It should have both projektipaallikko and the current user as omistaja + // Save projekti with the defaults. It should have both projektipaallikko and the current user projekti = await saveAndLoadProjekti(projekti, "having both projektipaallikko and omistaja", { kayttoOikeudet: [ { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, kayttajatunnus: "A123", puhelinnumero: "11", }, { kayttajatunnus: "A000111", - rooli: ProjektiRooli.OMISTAJA, puhelinnumero: "22", }, ], }); - // Remove omistaja and save + // Remove the other user and save projekti = await saveAndLoadProjekti(projekti, "only projektipaallikko", { kayttoOikeudet: [ { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, kayttajatunnus: "A123", puhelinnumero: "11", }, @@ -266,13 +257,12 @@ describe("apiHandler", () => { userFixture.loginAs(UserFixture.pekkaProjari); projekti = await saveAndLoadProjekti( projekti, - "while adding omistaja back. There should be projektipaallikko and omistaja in the projekti now. Projektipaallikko cannot be removed, so it always stays there.", + "while adding other user back. There should be projektipaallikko and one user in the projekti now. Projektipaallikko cannot be removed, so it always stays there.", { kayttoOikeudet: [ { kayttajatunnus: "A000111", puhelinnumero: "123456789", - rooli: ProjektiRooli.OMISTAJA, }, ], } @@ -280,44 +270,38 @@ describe("apiHandler", () => { // Add one muokkaaja more and examine the results. Also test that fields can be removed from database persistFileToProjektiStub.resolves("/suunnittelusopimus/logo.gif"); - await saveAndLoadProjekti( - projekti, - "while adding one muokkaaja more. There should be three persons in the projekti now", - { - kayttoOikeudet: [ - { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, - kayttajatunnus: "A123", - puhelinnumero: "11", - }, - { - rooli: ProjektiRooli.OMISTAJA, - kayttajatunnus: "A000111", - puhelinnumero: "123456789", - }, - { - kayttajatunnus: "A2", - puhelinnumero: "123456789", - rooli: ProjektiRooli.OMISTAJA, - }, - ], - suunnitteluSopimus: { - email: "a@b.com", - puhelinnumero: "0291111", - kunta: "Nokia", - logo: "/suunnittelusopimus/logo.gif", - etunimi: "Etunimi", - sukunimi: "Sukunimi", + await saveAndLoadProjekti(projekti, "while adding one muokkaaja more. There should be three persons in the projekti now", { + kayttoOikeudet: [ + { + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + kayttajatunnus: "A123", + puhelinnumero: "11", }, - euRahoitus: false, // mandatory field for perustiedot - aloitusKuulutus: fixture.aloitusKuulutusInput, - kielitiedot: { - ensisijainenKieli: Kieli.SUOMI, - toissijainenKieli: Kieli.SAAME, - projektinNimiVieraskielella: "Projektin nimi saameksi", + { + kayttajatunnus: "A000111", + puhelinnumero: "123456789", }, - } - ); + { + kayttajatunnus: "A2", + puhelinnumero: "123456789", + }, + ], + suunnitteluSopimus: { + email: "a@b.com", + puhelinnumero: "0291111", + kunta: "Nokia", + logo: "/suunnittelusopimus/logo.gif", + etunimi: "Etunimi", + sukunimi: "Sukunimi", + }, + euRahoitus: false, // mandatory field for perustiedot + aloitusKuulutus: fixture.aloitusKuulutusInput, + kielitiedot: { + ensisijainenKieli: Kieli.SUOMI, + toissijainenKieli: Kieli.SAAME, + projektinNimiVieraskielella: "Projektin nimi saameksi", + }, + }); // Verify that projekti is not visible for anonymous users userFixture.logout(); diff --git a/backend/test/database/__snapshots__/projektiDatabase.test.ts.snap b/backend/test/database/__snapshots__/projektiDatabase.test.ts.snap index 0a754d25b2..9def04d702 100644 --- a/backend/test/database/__snapshots__/projektiDatabase.test.ts.snap +++ b/backend/test/database/__snapshots__/projektiDatabase.test.ts.snap @@ -43,7 +43,7 @@ Object { "nimi": "Projari, Pekka", "organisaatio": "Väylävirasto", "puhelinnumero": "123456789", - "rooli": "PROJEKTIPAALLIKKO", + "tyyppi": "PROJEKTIPAALLIKKO", }, Object { "email": "Matti.Meikalainen@vayla.fi", @@ -51,7 +51,6 @@ Object { "nimi": "Meikalainen, Matti", "organisaatio": "Väylävirasto", "puhelinnumero": "123456789", - "rooli": "MUOKKAAJA", }, ], ":kielitiedot": Object { diff --git a/backend/test/fixture/projektiFixture.ts b/backend/test/fixture/projektiFixture.ts index 15bb805548..f8558b0c36 100644 --- a/backend/test/fixture/projektiFixture.ts +++ b/backend/test/fixture/projektiFixture.ts @@ -7,11 +7,11 @@ import { IlmoitettavaViranomainen, IlmoituksenVastaanottajat, KaytettavaPalvelu, + KayttajaTyyppi, Kieli, NahtavillaoloVaiheTila, Projekti, ProjektiKayttaja, - ProjektiRooli, ProjektiTyyppi, Status, TallennaProjektiInput, @@ -99,7 +99,8 @@ export class ProjektiFixture { private static pekkaProjariProjektiKayttaja: ProjektiKayttaja = { __typename: "ProjektiKayttaja", kayttajatunnus: "A123", - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + muokattavissa: false, nimi: "Projari, Pekka", email: "pekka.projari@vayla.fi", organisaatio: "Väylävirasto", @@ -109,7 +110,7 @@ export class ProjektiFixture { private static mattiMeikalainenProjektiKayttaja: ProjektiKayttaja = { __typename: "ProjektiKayttaja", kayttajatunnus: "A000111", - rooli: ProjektiRooli.MUOKKAAJA, + muokattavissa: true, nimi: "Meikalainen, Matti", email: "Matti.Meikalainen@vayla.fi", organisaatio: "Väylävirasto", @@ -163,7 +164,8 @@ export class ProjektiFixture { kayttoOikeudet: [ { kayttajatunnus: "A123", - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + muokattavissa: false, nimi: "Projari, Pekka", email: "pekka.projari@vayla.fi", organisaatio: "Väylävirasto", @@ -187,7 +189,7 @@ export class ProjektiFixture { return { kayttoOikeudet: [ { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, email: ProjektiFixture.pekkaProjariProjektiKayttaja.email, kayttajatunnus: ProjektiFixture.pekkaProjariProjektiKayttaja.kayttajatunnus, nimi: ProjektiFixture.pekkaProjariProjektiKayttaja.nimi, @@ -195,7 +197,6 @@ export class ProjektiFixture { organisaatio: ProjektiFixture.pekkaProjariProjektiKayttaja.organisaatio, }, { - rooli: ProjektiRooli.MUOKKAAJA, email: ProjektiFixture.mattiMeikalainenProjektiKayttaja.email, kayttajatunnus: ProjektiFixture.mattiMeikalainenProjektiKayttaja.kayttajatunnus, nimi: ProjektiFixture.mattiMeikalainenProjektiKayttaja.nimi, @@ -253,7 +254,7 @@ export class ProjektiFixture { return { kayttoOikeudet: [ { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, email: ProjektiFixture.pekkaProjariProjektiKayttaja.email, kayttajatunnus: ProjektiFixture.pekkaProjariProjektiKayttaja.kayttajatunnus, nimi: ProjektiFixture.pekkaProjariProjektiKayttaja.nimi, @@ -261,7 +262,6 @@ export class ProjektiFixture { organisaatio: ProjektiFixture.pekkaProjariProjektiKayttaja.organisaatio, }, { - rooli: ProjektiRooli.MUOKKAAJA, email: ProjektiFixture.mattiMeikalainenProjektiKayttaja.email, kayttajatunnus: ProjektiFixture.mattiMeikalainenProjektiKayttaja.kayttajatunnus, nimi: ProjektiFixture.mattiMeikalainenProjektiKayttaja.nimi, @@ -412,7 +412,8 @@ export class ProjektiFixture { dbProjekti3: DBProjekti = { kayttoOikeudet: [ { - rooli: ProjektiRooli.PROJEKTIPAALLIKKO, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + muokattavissa: false, email: ProjektiFixture.pekkaProjariProjektiKayttaja.email, kayttajatunnus: ProjektiFixture.pekkaProjariProjektiKayttaja.kayttajatunnus, nimi: ProjektiFixture.pekkaProjariProjektiKayttaja.nimi, @@ -420,7 +421,6 @@ export class ProjektiFixture { organisaatio: ProjektiFixture.pekkaProjariProjektiKayttaja.organisaatio, }, { - rooli: ProjektiRooli.MUOKKAAJA, email: ProjektiFixture.mattiMeikalainenProjektiKayttaja.email, kayttajatunnus: ProjektiFixture.mattiMeikalainenProjektiKayttaja.kayttajatunnus, nimi: ProjektiFixture.mattiMeikalainenProjektiKayttaja.nimi, diff --git a/backend/test/fixture/userFixture.ts b/backend/test/fixture/userFixture.ts index 65c3d1a422..6166f3e95f 100644 --- a/backend/test/fixture/userFixture.ts +++ b/backend/test/fixture/userFixture.ts @@ -1,5 +1,5 @@ import sinon from "sinon"; -import { NykyinenKayttaja, ProjektiKayttaja, VaylaKayttajaTyyppi } from "../../../common/graphql/apiModel"; +import { NykyinenKayttaja, ProjektiKayttaja } from "../../../common/graphql/apiModel"; export class UserFixture { private sinonStub: sinon.SinonStub; @@ -20,7 +20,6 @@ export class UserFixture { __typename: "NykyinenKayttaja", uid: projektiKayttaja.kayttajatunnus, roolit: ["hassu_kayttaja", "Atunnukset"], - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }); } @@ -29,7 +28,6 @@ export class UserFixture { __typename: "NykyinenKayttaja", uid: "theadminuid", roolit: ["hassu_kayttaja", "hassu_admin"], - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }); } @@ -43,7 +41,6 @@ export class UserFixture { sukuNimi: "Projari", uid: "A123", roolit: ["Atunnukset", "role2"], - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }; static mattiMeikalainen: NykyinenKayttaja = { @@ -52,7 +49,6 @@ export class UserFixture { sukuNimi: "Meikalainen", uid: "A000111", roolit: ["hassu_kayttaja", "Atunnukset"], - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }; static manuMuokkaaja: NykyinenKayttaja = { @@ -61,6 +57,13 @@ export class UserFixture { sukuNimi: "Muokkaaja", uid: "LX1", roolit: ["role1", "role2"], - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.LX_TUNNUS, + }; + + static testi1Kayttaja: NykyinenKayttaja = { + __typename: "NykyinenKayttaja", + etuNimi: "Testi1", + sukuNimi: "Hassu", + uid: "LX581241", + roolit: ["hassu_kayttaja"], }; } diff --git a/backend/test/handler/getCurrentUser.test.ts b/backend/test/handler/getCurrentUser.test.ts index e562d6a660..fc387ad0e9 100644 --- a/backend/test/handler/getCurrentUser.test.ts +++ b/backend/test/handler/getCurrentUser.test.ts @@ -4,7 +4,7 @@ import * as sinon from "sinon"; import { getCurrentUser } from "../../src/handler/getCurrentUser"; import { userService } from "../../src/user"; import { UserFixture } from "../fixture/userFixture"; -import { NykyinenKayttaja, VaylaKayttajaTyyppi } from "../../../common/graphql/apiModel"; +import { NykyinenKayttaja } from "../../../common/graphql/apiModel"; const { expect } = require("chai"); @@ -28,7 +28,6 @@ describe("getCurrentUser", () => { etuNimi: "Matti", sukuNimi: "Meikalainen", uid: "A000111", - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, roolit: ["hassu_kayttaja", "Atunnukset"], } as NykyinenKayttaja); }); diff --git a/backend/test/personSearch/lambda/personAdapter.test.ts b/backend/test/personSearch/lambda/personAdapter.test.ts index e9c64035db..dcf6159f21 100644 --- a/backend/test/personSearch/lambda/personAdapter.test.ts +++ b/backend/test/personSearch/lambda/personAdapter.test.ts @@ -3,7 +3,6 @@ import { describe, it } from "mocha"; import { PersonSearchFixture } from "./personSearchFixture"; import { adaptPersonSearchResult } from "../../../src/personSearch/lambda/personSearchAdapter"; import { Person } from "../../../src/personSearch/kayttajas"; -import { VaylaKayttajaTyyppi } from "../../../../common/graphql/apiModel"; const { expect } = require("chai"); @@ -23,7 +22,6 @@ describe("personAdapter", () => { organisaatio: "Väylävirasto", puhelinnumero: "123456789", sukuNimi: "Projari", - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }; expect(kayttajas).to.eql({ A123: person, @@ -39,7 +37,6 @@ describe("personAdapter", () => { organisaatio: "ELY", puhelinnumero: "123456789", sukuNimi: "Meikäläinen", - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }; expect(kayttajas).to.eql({ A000111: person, diff --git a/backend/test/personSearch/lambda/personSearchFixture.ts b/backend/test/personSearch/lambda/personSearchFixture.ts index bee5998dd7..2ef3a41b57 100644 --- a/backend/test/personSearch/lambda/personSearchFixture.ts +++ b/backend/test/personSearch/lambda/personSearchFixture.ts @@ -1,4 +1,4 @@ -import { Kayttaja, VaylaKayttajaTyyppi } from "../../../../common/graphql/apiModel"; +import { Kayttaja } from "../../../../common/graphql/apiModel"; export class PersonSearchFixture { pekkaProjariSearchResult = { @@ -29,7 +29,6 @@ export class PersonSearchFixture { email: "pekka.projari@vayla.fi", organisaatio: "Väylävirasto", puhelinnumero: "123456789", - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }; mattiMeikalainenSearchResult = { @@ -60,7 +59,6 @@ export class PersonSearchFixture { organisaatio: "ELY", puhelinnumero: "123456789", uid: "A000111", - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.A_TUNNUS, }; manuMuokkaaja: Kayttaja = { @@ -71,19 +69,17 @@ export class PersonSearchFixture { organisaatio: "Väylävirasto", puhelinnumero: "123456789", uid: "LX1", - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.LX_TUNNUS, }; createKayttaja(uid: string): Kayttaja { return { __typename: "Kayttaja", - email: uid + "@vayla.fi", + email: (uid + "@vayla.fi").toLowerCase(), etuNimi: "Etunimi" + uid, sukuNimi: "Sukunimi" + uid, organisaatio: "Väylävirasto", puhelinnumero: "123456789", uid, - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi.L_TUNNUS, }; } } diff --git a/backend/test/projekti/__snapshots__/kayttoOikeudetManager.test.ts.snap b/backend/test/projekti/__snapshots__/kayttoOikeudetManager.test.ts.snap new file mode 100644 index 0000000000..9f8db64d70 --- /dev/null +++ b/backend/test/projekti/__snapshots__/kayttoOikeudetManager.test.ts.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KayttoOikeudetManager should manager projekti users successfully 1`] = ` +Array [ + Object { + "email": "a1@vayla.fi", + "kayttajatunnus": "A1", + "muokattavissa": false, + "nimi": "SukunimiA1, EtunimiA1", + "organisaatio": "Väylävirasto", + "tyyppi": "PROJEKTIPAALLIKKO", + }, + Object { + "email": "a2@vayla.fi", + "kayttajatunnus": "A2", + "muokattavissa": false, + "nimi": "SukunimiA2, EtunimiA2", + "organisaatio": "Väylävirasto", + "tyyppi": "VARAHENKILO", + }, + Object { + "email": "a3@vayla.fi", + "kayttajatunnus": "A3", + "muokattavissa": true, + "nimi": "SukunimiA3, EtunimiA3", + "organisaatio": "Väylävirasto", + "tyyppi": "VARAHENKILO", + }, +] +`; + +exports[`KayttoOikeudetManager should manager projekti users successfully 2`] = ` +Array [ + Object { + "email": "a1@vayla.fi", + "kayttajatunnus": "A1", + "muokattavissa": false, + "nimi": "SukunimiA1, EtunimiA1", + "organisaatio": "Väylävirasto", + "puhelinnumero": "123456789", + "tyyppi": "PROJEKTIPAALLIKKO", + }, + Object { + "email": "a2@vayla.fi", + "kayttajatunnus": "A2", + "muokattavissa": false, + "nimi": "SukunimiA2, EtunimiA2", + "organisaatio": "Väylävirasto", + "puhelinnumero": "123456789", + "tyyppi": "VARAHENKILO", + }, + Object { + "email": "a3@vayla.fi", + "kayttajatunnus": "A3", + "muokattavissa": true, + "nimi": "SukunimiA3, EtunimiA3", + "organisaatio": "Väylävirasto", + "puhelinnumero": "123456789", + "tyyppi": "VARAHENKILO", + }, + Object { + "email": "a4@vayla.fi", + "kayttajatunnus": "A4", + "muokattavissa": true, + "nimi": "SukunimiA4, EtunimiA4", + "organisaatio": "Väylävirasto", + "puhelinnumero": "123456789", + "tyyppi": undefined, + }, +] +`; diff --git a/backend/test/projekti/__snapshots__/projektiHandler.test.ts.snap b/backend/test/projekti/__snapshots__/projektiHandler.test.ts.snap index 6395e73bce..f8cf2f8821 100644 --- a/backend/test/projekti/__snapshots__/projektiHandler.test.ts.snap +++ b/backend/test/projekti/__snapshots__/projektiHandler.test.ts.snap @@ -4,7 +4,8 @@ exports[`projektiHandler should detect Velho changes successfully 1`] = ` Object { "__typename": "Velho", "nimi": "Uusi nimi", - "vastuuhenkilonEmail": "uusi@vayla.fi", + "varahenkilonEmail": "a2@vayla.fi", + "vastuuhenkilonEmail": "a1@vayla.fi", "vaylamuoto": Array [ "rata", ], @@ -13,6 +14,31 @@ Object { exports[`projektiHandler should update Velho changes successfully 1`] = ` Object { + "kayttoOikeudet": Array [ + Object { + "email": "Matti.Meikalainen@vayla.fi", + "kayttajatunnus": "A000111", + "nimi": "Meikalainen, Matti", + "organisaatio": "Väylävirasto", + "puhelinnumero": "123456789", + }, + Object { + "email": "a1@vayla.fi", + "kayttajatunnus": "A1", + "muokattavissa": false, + "nimi": "SukunimiA1, EtunimiA1", + "organisaatio": "Väylävirasto", + "tyyppi": "PROJEKTIPAALLIKKO", + }, + Object { + "email": "a2@vayla.fi", + "kayttajatunnus": "A2", + "muokattavissa": false, + "nimi": "SukunimiA2, EtunimiA2", + "organisaatio": "Väylävirasto", + "tyyppi": "VARAHENKILO", + }, + ], "oid": "1", "velho": Object { "asiatunnusVayla": "A1", @@ -27,7 +53,8 @@ Object { "nimi": "Uusi nimi", "suunnittelustaVastaavaViranomainen": "UUDENMAAN_ELY", "tyyppi": "TIE", - "vastuuhenkilonEmail": "uusi@vayla.fi", + "varahenkilonEmail": "a2@vayla.fi", + "vastuuhenkilonEmail": "a1@vayla.fi", "vaylamuoto": Array [ "rata", ], diff --git a/backend/test/projekti/kayttoOikeudetManager.test.ts b/backend/test/projekti/kayttoOikeudetManager.test.ts new file mode 100644 index 0000000000..24e0f05b26 --- /dev/null +++ b/backend/test/projekti/kayttoOikeudetManager.test.ts @@ -0,0 +1,163 @@ +import { describe, it } from "mocha"; +import { KayttoOikeudetManager } from "../../src/projekti/kayttoOikeudetManager"; +import { PersonSearchFixture } from "../personSearch/lambda/personSearchFixture"; +import { Kayttajas } from "../../src/personSearch/kayttajas"; +import { KayttajaTyyppi } from "../../../common/graphql/apiModel"; +import { DBVaylaUser } from "../../src/database/model"; + +const { expect } = require("chai"); + +describe("KayttoOikeudetManager", () => { + let kayttajas: Kayttajas; + + const personSearchFixture: PersonSearchFixture = new PersonSearchFixture(); + const kayttajaA1 = personSearchFixture.createKayttaja("A1"); + const kayttajaA2 = personSearchFixture.createKayttaja("A2"); + const kayttajaA3 = personSearchFixture.createKayttaja("A3"); + const kayttajaA4 = personSearchFixture.createKayttaja("A4"); + + beforeEach(() => { + kayttajas = Kayttajas.fromKayttajaList([kayttajaA1, kayttajaA2, kayttajaA3, kayttajaA4]); + }); + + function expectProjektiPaallikko(manager: KayttoOikeudetManager, uid: string) { + const projektiPaallikko = manager + .getKayttoOikeudet() + .filter((user) => user.tyyppi == KayttajaTyyppi.PROJEKTIPAALLIKKO) + .filter((user) => user.kayttajatunnus == uid) + .pop(); + expect(projektiPaallikko).to.exist; + expect(projektiPaallikko.muokattavissa).to.be.false; + } + + function expectVarahenkilo(manager: KayttoOikeudetManager, uid: string, muokattavissa: boolean) { + const varahenkilo = manager + .getKayttoOikeudet() + .filter((user) => user.tyyppi == KayttajaTyyppi.VARAHENKILO) + .filter((user) => user.kayttajatunnus == uid) + .pop(); + expect(varahenkilo, "varahenkilöä ei löydy: " + [uid, muokattavissa, JSON.stringify(manager.getKayttoOikeudet())].join(", ")).to.exist; + expect(varahenkilo.muokattavissa).to.eq(muokattavissa); + } + + function expectKayttaja(manager: KayttoOikeudetManager, uid: string, data: Partial) { + const varahenkilo = manager + .getKayttoOikeudet() + .filter((user) => user.kayttajatunnus == uid) + .pop(); + expect(varahenkilo, "henkilöä ei löydy: " + [uid, JSON.stringify(manager.getKayttoOikeudet())].join(", ")).to.exist; + expect(varahenkilo).to.include(data); + } + + it("should manager projekti users successfully", async () => { + const manager = new KayttoOikeudetManager([], kayttajas); + + // Lisää projektipäällikkö Velhosta + manager.addProjektiPaallikkoFromEmail(kayttajaA1.email); + expectProjektiPaallikko(manager, "A1"); + + // Lisää varahenkilö Velhosta + manager.addVarahenkiloFromEmail(kayttajaA2.email); + expectProjektiPaallikko(manager, "A1"); + expectVarahenkilo(manager, "A2", false); + + // Lisää nykyinen käyttäjä varahenkilöksi + manager.addUser({ kayttajatunnus: kayttajaA3.uid, muokattavissa: true, tyyppi: KayttajaTyyppi.VARAHENKILO }); + expectProjektiPaallikko(manager, "A1"); + expectVarahenkilo(manager, "A2", false); + expectVarahenkilo(manager, "A3", true); + + // Muokkaa sisältöä ja lisää käyttäjä käyttöliittymältä tulevan pyynnön perusteella + expect(manager.getKayttoOikeudet()).toMatchSnapshot(); + manager.applyChanges([ + { + kayttajatunnus: kayttajaA1.uid, + puhelinnumero: kayttajaA1.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA2.uid, + puhelinnumero: kayttajaA2.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA3.uid, + puhelinnumero: kayttajaA3.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA4.uid, + puhelinnumero: kayttajaA4.puhelinnumero, + }, + ]); + + expectKayttaja(manager, "A1", { puhelinnumero: kayttajaA1.puhelinnumero }); + expectKayttaja(manager, "A2", { puhelinnumero: kayttajaA2.puhelinnumero }); + expectKayttaja(manager, "A3", { puhelinnumero: kayttajaA3.puhelinnumero }); + expectKayttaja(manager, "A4", { puhelinnumero: kayttajaA4.puhelinnumero }); + expect(manager.getKayttoOikeudet()).to.have.length(4); + expect(manager.getKayttoOikeudet()).toMatchSnapshot(); + + // Vaihda käyttäjäA4 varahenkilöksi + manager.applyChanges([ + { + kayttajatunnus: kayttajaA1.uid, + puhelinnumero: kayttajaA1.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA2.uid, + puhelinnumero: kayttajaA2.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA3.uid, + puhelinnumero: kayttajaA3.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA4.uid, + puhelinnumero: kayttajaA4.puhelinnumero, + tyyppi: KayttajaTyyppi.VARAHENKILO, + }, + ]); + + expectKayttaja(manager, "A4", { tyyppi: KayttajaTyyppi.VARAHENKILO }); + + // Tarkista, ettei tyyppiä voi vaihtaa projektipäälliköksi + const kayttoOikeudetbefore = manager.getKayttoOikeudet(); + // Yritä vaihtaa käyttäjäA4 projektipäälliköksi + manager.applyChanges([ + { + kayttajatunnus: kayttajaA1.uid, + puhelinnumero: kayttajaA1.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA2.uid, + puhelinnumero: kayttajaA2.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA3.uid, + puhelinnumero: kayttajaA3.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA4.uid, + puhelinnumero: kayttajaA4.puhelinnumero, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + }, + ]); + // Tarkista, ettei mitään muuttunut + expect(manager.getKayttoOikeudet()).to.eql(kayttoOikeudetbefore); + manager.applyChanges(null); + expect(manager.getKayttoOikeudet()).to.eql(kayttoOikeudetbefore); + + // Yritä poistaa projektipäällikkö ja varahenkilö + manager.applyChanges([ + { + kayttajatunnus: kayttajaA3.uid, + puhelinnumero: kayttajaA3.puhelinnumero, + }, + { + kayttajatunnus: kayttajaA4.uid, + puhelinnumero: kayttajaA4.puhelinnumero, + tyyppi: KayttajaTyyppi.PROJEKTIPAALLIKKO, + }, + ]); + // Tarkista, ettei mitään muuttunut + expect(manager.getKayttoOikeudet()).to.eql(kayttoOikeudetbefore); + }); +}); diff --git a/backend/test/projekti/projektiHandler.test.ts b/backend/test/projekti/projektiHandler.test.ts index fb7c88aa53..565fbc815f 100644 --- a/backend/test/projekti/projektiHandler.test.ts +++ b/backend/test/projekti/projektiHandler.test.ts @@ -6,30 +6,37 @@ import { projektiDatabase } from "../../src/database/projektiDatabase"; import { velho } from "../../src/velho/velhoClient"; import { UserFixture } from "../fixture/userFixture"; import { userService } from "../../src/user"; +import { personSearch } from "../../src/personSearch/personSearchClient"; +import { PersonSearchFixture } from "../personSearch/lambda/personSearchFixture"; +import { Kayttajas } from "../../src/personSearch/kayttajas"; const { expect } = require("chai"); describe("projektiHandler", () => { let fixture: ProjektiFixture; - let loadProjektiByOidStub: sinon.SinonStub; let saveProjektiStub: sinon.SinonStub; let loadVelhoProjektiByOidStub: sinon.SinonStub; let userFixture: UserFixture; beforeEach(() => { - loadProjektiByOidStub = sinon.stub(projektiDatabase, "loadProjektiByOid"); saveProjektiStub = sinon.stub(projektiDatabase, "saveProjekti"); loadVelhoProjektiByOidStub = sinon.stub(velho, "loadProjekti"); + const personSearchFixture = new PersonSearchFixture(); + const a1User = personSearchFixture.createKayttaja("A1"); + const a2User = personSearchFixture.createKayttaja("A2"); + sinon.stub(personSearch, "getKayttajas").resolves(Kayttajas.fromKayttajaList([a1User, a2User])); + userFixture = new UserFixture(userService); fixture = new ProjektiFixture(); - loadProjektiByOidStub.resolves(fixture.dbProjekti1()); + sinon.stub(projektiDatabase, "loadProjektiByOid").resolves(fixture.dbProjekti1()); const updatedProjekti = fixture.dbProjekti1(); const velhoData = updatedProjekti.velho; velhoData.nimi = "Uusi nimi"; velhoData.vaylamuoto = ["rata"]; - velhoData.vastuuhenkilonEmail = "uusi@vayla.fi"; - loadVelhoProjektiByOidStub.resolves({ projekti: updatedProjekti, vastuuhenkilo: "TBD" }); + velhoData.vastuuhenkilonEmail = a1User.email; + velhoData.varahenkilonEmail = a2User.email; + loadVelhoProjektiByOidStub.resolves(updatedProjekti); }); afterEach(() => { diff --git a/backend/test/velho/__snapshots__/velhoAdapter.test.ts.snap b/backend/test/velho/__snapshots__/velhoAdapter.test.ts.snap index c31ed3b55d..64fec13583 100644 --- a/backend/test/velho/__snapshots__/velhoAdapter.test.ts.snap +++ b/backend/test/velho/__snapshots__/velhoAdapter.test.ts.snap @@ -2,32 +2,30 @@ exports[`VelhoAdapter should adapt project from Velho successfully 1`] = ` Object { - "projekti": Object { - "kayttoOikeudet": Array [], - "oid": "100", + "kayttoOikeudet": Array [], + "oid": "100", + "tyyppi": "TIE", + "velho": Object { + "asiatunnusELY": undefined, + "asiatunnusVayla": undefined, + "kunnat": Array [ + "Helsinki", + "Vantaa", + "Espoo", + ], + "linkki": "https://vayla.fi/vt-5-mikkeli-juva", + "maakunnat": Array [ + "Uusimaa", + ], + "nimi": "Kaupungin sisääntulotien (mt 123) parantaminen välillä valtatien 1 eritasoliittymä - Päätie, Helsinki", + "suunnittelustaVastaavaViranomainen": "PIRKANMAAN_ELY", "tyyppi": "TIE", - "velho": Object { - "asiatunnusELY": undefined, - "asiatunnusVayla": undefined, - "kunnat": Array [ - "Helsinki", - "Vantaa", - "Espoo", - ], - "linkki": "https://vayla.fi/vt-5-mikkeli-juva", - "maakunnat": Array [ - "Uusimaa", - ], - "nimi": "Kaupungin sisääntulotien (mt 123) parantaminen välillä valtatien 1 eritasoliittymä - Päätie, Helsinki", - "suunnittelustaVastaavaViranomainen": "PIRKANMAAN_ELY", - "tyyppi": "TIE", - "vastuuhenkilonEmail": "test@ely-keskus.fi", - "vaylamuoto": Array [ - "tie", - ], - }, + "varahenkilonEmail": null, + "vastuuhenkilonEmail": "test@ely-keskus.fi", + "vaylamuoto": Array [ + "tie", + ], }, - "vastuuhenkilo": "test@ely-keskus.fi", } `; diff --git a/backend/test/velho/velhoAdapter.test.ts b/backend/test/velho/velhoAdapter.test.ts index a4fb5ee4f5..e3d8099428 100644 --- a/backend/test/velho/velhoAdapter.test.ts +++ b/backend/test/velho/velhoAdapter.test.ts @@ -2,7 +2,7 @@ import { describe, it } from "mocha"; import { adaptProjekti, findUpdatedFields } from "../../src/velho/velhoAdapter"; import { default as velhoTieProjecti } from "./fixture/velhoTieProjekti.json"; import cloneDeep from "lodash/cloneDeep"; -import { Velho } from "../../src/database/model/projekti"; +import { Velho } from "../../src/database/model"; import { ProjektiProjekti } from "../../src/velho/projektirekisteri"; const { expect } = require("chai"); @@ -13,7 +13,7 @@ describe("VelhoAdapter", () => { }); it("should find updated Velho fields successfully", async () => { - const oldVelho: Velho = adaptProjekti(velhoTieProjecti.data as unknown as ProjektiProjekti).projekti.velho; + const oldVelho: Velho = adaptProjekti(velhoTieProjecti.data as unknown as ProjektiProjekti).velho; const newVelho: Velho = cloneDeep(oldVelho); newVelho.nimi = "Uusi nimi"; newVelho.vaylamuoto = ["rata"]; diff --git a/cypress/integration/2-perusta-projekti/2-perusta.spec.js b/cypress/integration/2-perusta-projekti/2-perusta.spec.js index d71575272c..388d84d96f 100644 --- a/cypress/integration/2-perusta-projekti/2-perusta.spec.js +++ b/cypress/integration/2-perusta-projekti/2-perusta.spec.js @@ -13,6 +13,7 @@ describe("Perusta projekti", () => { it("Perusta projekti", () => { cy.visit(Cypress.env("host") + "/yllapito/perusta/" + oid).get("main"); cy.get('input[name="kayttoOikeudet.0.puhelinnumero"').should("be.enabled").type("0291111111"); + cy.get('input[name="kayttoOikeudet.1.puhelinnumero"').should("be.enabled").type("0291111111"); cy.get("#save_and_open_projekti").click(); cy.url().should("contain", "/yllapito/projekti/"); }); diff --git a/graphql/inputs.graphql b/graphql/inputs.graphql index f69013a477..bfca34683e 100644 --- a/graphql/inputs.graphql +++ b/graphql/inputs.graphql @@ -187,10 +187,9 @@ input AineistoInput { } input ProjektiKayttajaInput { - rooli: ProjektiRooli! + tyyppi: KayttajaTyyppi kayttajatunnus: String! puhelinnumero: String! - esitetaanKuulutuksessa: Boolean } input ListaaKayttajatInput { diff --git a/graphql/types.graphql b/graphql/types.graphql index 35a15e24e0..8d50402a1a 100644 --- a/graphql/types.graphql +++ b/graphql/types.graphql @@ -1,9 +1,8 @@ # Huom: ala kayta skandinaavisia merkkeja tassa tiedostossa, koska muutoin CDK-asennus luulee joka kerta skeeman muuttuneen -enum ProjektiRooli { +enum KayttajaTyyppi { PROJEKTIPAALLIKKO - OMISTAJA - MUOKKAAJA + VARAHENKILO } enum Viranomainen { @@ -474,8 +473,8 @@ type Velho { toteuttavaOrganisaatio: String vastuuhenkilonNimi: String vastuuhenkilonEmail: String - varahenkiloNimi: String - varahenkiloEmail: String + varahenkilonNimi: String + varahenkilonEmail: String maakunnat: [String!] kunnat: [String!] linkki: String @@ -620,13 +619,13 @@ type SuunnitteluSopimus { } type ProjektiKayttaja { - rooli: ProjektiRooli! + tyyppi: KayttajaTyyppi kayttajatunnus: String! puhelinnumero: String email: String! organisaatio: String! nimi: String! - esitetaanKuulutuksessa: Boolean + muokattavissa: Boolean } type ProjektiKayttajaJulkinen { @@ -669,7 +668,6 @@ type VelhoAineisto { type NykyinenKayttaja { uid: String - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi etuNimi: String! sukuNimi: String! roolit: [String!] @@ -678,7 +676,6 @@ type NykyinenKayttaja { type Kayttaja { uid: String - vaylaKayttajaTyyppi: VaylaKayttajaTyyppi suomiFiKayttaja: Boolean etuNimi: String! sukuNimi: String! @@ -688,12 +685,6 @@ type Kayttaja { roolit: [String!] } -enum VaylaKayttajaTyyppi { - A_TUNNUS - L_TUNNUS - LX_TUNNUS -} - type PDF { nimi: String! sisalto: String! diff --git a/package-lock.json b/package-lock.json index 842759089c..e51abb6541 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,6 @@ "next-translate": "1.4.0", "node-cache": "5.1.2", "nodemailer": "6.7.2", - "omit-deep-lodash": "^1.1.6", "pdfkit": "0.12.3", "pino": "7.6.2", "react": "17.0.2", @@ -5633,13 +5632,13 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/aws-ecr-assets/node_modules/balanced-match": { "version": "1.0.2", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/aws-ecr-assets/node_modules/brace-expansion": { "version": "1.1.11", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -5649,13 +5648,13 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/aws-ecr-assets/node_modules/concat-map": { "version": "0.0.1", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/aws-ecr-assets/node_modules/minimatch": { "version": "3.0.4", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -5963,7 +5962,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { "version": "1.4.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -5972,7 +5971,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/lru-cache": { "version": "6.0.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -5984,7 +5983,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { "version": "7.3.5", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -5999,7 +5998,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cloud-assembly-schema/node_modules/yallist": { "version": "4.0.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC" }, @@ -6036,13 +6035,13 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/@balena/dockerignore": { "version": "1.0.2", - "dev": true, + "extraneous": true, "inBundle": true, "license": "Apache-2.0" }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/at-least-node": { "version": "1.0.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "engines": { @@ -6051,13 +6050,13 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/balanced-match": { "version": "1.0.2", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/brace-expansion": { "version": "1.1.11", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6067,13 +6066,13 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/concat-map": { "version": "0.0.1", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/fs-extra": { "version": "9.1.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6088,13 +6087,13 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/graceful-fs": { "version": "4.2.8", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC" }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/ignore": { "version": "5.1.9", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -6103,7 +6102,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/jsonfile": { "version": "6.1.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6115,7 +6114,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/minimatch": { "version": "3.0.4", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6127,7 +6126,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/core/node_modules/universalify": { "version": "2.0.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -6155,7 +6154,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cx-api/node_modules/lru-cache": { "version": "6.0.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6167,7 +6166,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cx-api/node_modules/semver": { "version": "7.3.5", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6182,7 +6181,7 @@ }, "node_modules/@aws-sdk/client-firehose/node_modules/@aws-cdk/cx-api/node_modules/yallist": { "version": "4.0.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "ISC" }, @@ -56082,17 +56081,6 @@ "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", "peer": true }, - "node_modules/omit-deep-lodash": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/omit-deep-lodash/-/omit-deep-lodash-1.1.6.tgz", - "integrity": "sha512-EyZUR25+cvpulxlNHinLyX+sCCajuf+w95GONvL5m4nu1MdiBkw6cvlub5v4PuStdtJng+cKoeLV+M8OUPn8/g==", - "dependencies": { - "lodash": "~4.17.21" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/on-exit-leak-free": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", @@ -70971,12 +70959,12 @@ "balanced-match": { "version": "1.0.2", "bundled": true, - "dev": true + "extraneous": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -70985,12 +70973,12 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "extraneous": true }, "minimatch": { "version": "3.0.4", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "brace-expansion": "^1.1.7" } @@ -71166,12 +71154,12 @@ "jsonschema": { "version": "1.4.0", "bundled": true, - "dev": true + "extraneous": true }, "lru-cache": { "version": "6.0.0", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "yallist": "^4.0.0" } @@ -71179,7 +71167,7 @@ "semver": { "version": "7.3.5", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "lru-cache": "^6.0.0" } @@ -71187,7 +71175,7 @@ "yallist": { "version": "4.0.0", "bundled": true, - "dev": true + "extraneous": true } } }, @@ -71210,22 +71198,22 @@ "@balena/dockerignore": { "version": "1.0.2", "bundled": true, - "dev": true + "extraneous": true }, "at-least-node": { "version": "1.0.0", "bundled": true, - "dev": true + "extraneous": true }, "balanced-match": { "version": "1.0.2", "bundled": true, - "dev": true + "extraneous": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -71234,12 +71222,12 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "extraneous": true }, "fs-extra": { "version": "9.1.0", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -71250,17 +71238,17 @@ "graceful-fs": { "version": "4.2.8", "bundled": true, - "dev": true + "extraneous": true }, "ignore": { "version": "5.1.9", "bundled": true, - "dev": true + "extraneous": true }, "jsonfile": { "version": "6.1.0", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" @@ -71269,7 +71257,7 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "brace-expansion": "^1.1.7" } @@ -71277,7 +71265,7 @@ "universalify": { "version": "2.0.0", "bundled": true, - "dev": true + "extraneous": true } } }, @@ -71294,7 +71282,7 @@ "lru-cache": { "version": "6.0.0", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "yallist": "^4.0.0" } @@ -71302,7 +71290,7 @@ "semver": { "version": "7.3.5", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "lru-cache": "^6.0.0" } @@ -71310,7 +71298,7 @@ "yallist": { "version": "4.0.0", "bundled": true, - "dev": true + "extraneous": true } } }, @@ -111511,14 +111499,6 @@ "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", "peer": true }, - "omit-deep-lodash": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/omit-deep-lodash/-/omit-deep-lodash-1.1.6.tgz", - "integrity": "sha512-EyZUR25+cvpulxlNHinLyX+sCCajuf+w95GONvL5m4nu1MdiBkw6cvlub5v4PuStdtJng+cKoeLV+M8OUPn8/g==", - "requires": { - "lodash": "~4.17.21" - } - }, "on-exit-leak-free": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", diff --git a/package.json b/package.json index 0ffe801735..a73a53812f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "next-translate": "1.4.0", "node-cache": "5.1.2", "nodemailer": "6.7.2", - "omit-deep-lodash": "^1.1.6", "pdfkit": "0.12.3", "pino": "7.6.2", "react": "17.0.2", diff --git a/src/components/projekti/KayttoOikeusHallinta.tsx b/src/components/projekti/KayttoOikeusHallinta.tsx index 30d282b0e7..c7583a093f 100644 --- a/src/components/projekti/KayttoOikeusHallinta.tsx +++ b/src/components/projekti/KayttoOikeusHallinta.tsx @@ -1,9 +1,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FieldArrayWithId, useFieldArray, useFormContext } from "react-hook-form"; -import { api, apiConfig, Kayttaja, ProjektiKayttajaInput, ProjektiRooli, TallennaProjektiInput } from "@services/api"; +import { api, apiConfig, Kayttaja, KayttajaTyyppi, ProjektiKayttajaInput, TallennaProjektiInput } from "@services/api"; import Autocomplete from "@components/form/Autocomplete"; import TextInput from "@components/form/TextInput"; -import Select from "@components/form/Select"; import IconButton from "@components/button/IconButton"; import Button from "@components/button/Button"; import useSWR, { KeyedMutator } from "swr"; @@ -26,18 +25,11 @@ interface Props { } export const defaultKayttaja: ProjektiKayttajaInput = { - // @ts-ignore By default rooli should be 'undefined' - rooli: "", + tyyppi: undefined, puhelinnumero: "", kayttajatunnus: "", }; -const rooliOptions = [ - { label: "Projektipäällikkö", value: "PROJEKTIPAALLIKKO", disabled: true }, - { label: "Omistaja", value: "OMISTAJA" }, - { label: "Muokkaaja", value: "MUOKKAAJA" }, -]; - function KayttoOikeusHallinta({ disableFields, onKayttajatUpdate }: Props) { const { control, @@ -61,7 +53,9 @@ function KayttoOikeusHallinta({ disableFields, onKayttajatUpdate }: Props) { muutHenkilot: FieldArrayWithId[]; }>( (acc, kayttoOikeus) => { - if (kayttoOikeus.rooli === ProjektiRooli.PROJEKTIPAALLIKKO) { + if (kayttoOikeus.tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO) { + acc.projektiPaallikot.push(kayttoOikeus); + } else if (kayttoOikeus.tyyppi === KayttajaTyyppi.VARAHENKILO /* TODO: && kayttoOikeus.muokattavissa === false*/) { acc.projektiPaallikot.push(kayttoOikeus); } else { acc.muutHenkilot.push(kayttoOikeus); @@ -184,7 +178,7 @@ const UserFields = ({ removeable, }: UserFieldProps) => { const kayttoOikeus = useMemo(() => kayttoOikeudet[index], [kayttoOikeudet, index]); - const isProjektiPaallikko = kayttoOikeus.rooli === ProjektiRooli.PROJEKTIPAALLIKKO; + const isProjektiPaallikko = kayttoOikeus.tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO; const kayttaja = kayttajat?.find(({ uid }) => uid === kayttoOikeus.kayttajatunnus); const getKayttajaNimi = useCallback((k: Kayttaja | null | undefined) => { return (k && `${k.sukuNimi}, ${k.etuNimi}`) || ""; @@ -236,18 +230,6 @@ const UserFields = ({ /> )} - {isProjektiPaallikko ? ( - rooli.value !== ProjektiRooli.PROJEKTIPAALLIKKO)} - disabled={disableFields || isProjektiPaallikko} - addEmptyOption - /> - )} )} - {projekti.kayttoOikeudet?.map(({ nimi, rooli, kayttajatunnus }, index) => { + {projekti.kayttoOikeudet?.map(({ nimi, tyyppi, kayttajatunnus }, index) => { const tunnuslista: string[] = value || []; return ( - {rooli === ProjektiRooli.PROJEKTIPAALLIKKO ? ( + {tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO ? ( ) : ( )} - {projekti.kayttoOikeudet?.map(({ nimi, rooli, kayttajatunnus }, index) => { + {projekti.kayttoOikeudet?.map(({ nimi, tyyppi, kayttajatunnus }, index) => { const tunnuslista: string[] = value || []; return ( - {rooli === ProjektiRooli.PROJEKTIPAALLIKKO ? ( + {tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO ? ( ) : ( )} - {projekti.kayttoOikeudet?.map(({ nimi, rooli, kayttajatunnus }, index) => { + {projekti.kayttoOikeudet?.map(({ nimi, tyyppi, kayttajatunnus }, index) => { const tunnuslista: string[] = value || []; return ( - {rooli === ProjektiRooli.PROJEKTIPAALLIKKO ? ( + {tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO ? ( ) : ( ( - {projekti.kayttoOikeudet?.map(({ nimi, rooli, kayttajatunnus }, index) => { + {projekti.kayttoOikeudet?.map(({ nimi, tyyppi, kayttajatunnus }, index) => { const tunnuslista = value || []; return ( - {rooli === ProjektiRooli.PROJEKTIPAALLIKKO ? ( + {tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO ? ( ) : ( !!kayttaja?.uid && !!projekti?.kayttoOikeudet?.find( - ({ kayttajatunnus, rooli }) => kayttaja.uid === kayttajatunnus && rooli === ProjektiRooli.PROJEKTIPAALLIKKO + ({ kayttajatunnus, tyyppi }) => kayttaja.uid === kayttajatunnus && tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO ); diff --git a/src/pages/yllapito/perusta/[oid].tsx b/src/pages/yllapito/perusta/[oid].tsx index ef430479e5..90b48055f1 100644 --- a/src/pages/yllapito/perusta/[oid].tsx +++ b/src/pages/yllapito/perusta/[oid].tsx @@ -64,11 +64,11 @@ const defaultFormValues = (projekti: ProjektiLisatiedolla) => ({ oid: projekti.oid, kayttoOikeudet: - projekti.kayttoOikeudet?.map(({ kayttajatunnus, puhelinnumero, rooli, esitetaanKuulutuksessa }) => ({ + projekti.kayttoOikeudet?.map(({ kayttajatunnus, puhelinnumero, tyyppi, muokattavissa }) => ({ kayttajatunnus, puhelinnumero: puhelinnumero || "", - rooli, - esitetaanKuulutuksessa, + tyyppi, + muokattavissa, })) || [], } as FormValues); @@ -131,7 +131,7 @@ const PerustaProjektiForm: FC = ({ projekti, projektiL }, [setFormContext] ); - + console.log(projekti); return ( <> diff --git a/src/pages/yllapito/projekti/[oid]/henkilot.tsx b/src/pages/yllapito/projekti/[oid]/henkilot.tsx index 3e6bc3eda8..e4aa43d743 100644 --- a/src/pages/yllapito/projekti/[oid]/henkilot.tsx +++ b/src/pages/yllapito/projekti/[oid]/henkilot.tsx @@ -69,11 +69,11 @@ function Henkilot({ projekti, projektiLoadError, reloadProjekti }: HenkilotFormP () => ({ oid: projekti.oid, kayttoOikeudet: - projekti.kayttoOikeudet?.map(({ kayttajatunnus, puhelinnumero, rooli, esitetaanKuulutuksessa }) => ({ + projekti.kayttoOikeudet?.map(({ kayttajatunnus, puhelinnumero, tyyppi, muokattavissa }) => ({ kayttajatunnus, puhelinnumero: puhelinnumero || "", - rooli, - esitetaanKuulutuksessa, + tyyppi, + muokattavissa, })) || [], }), [projekti] @@ -101,6 +101,7 @@ function Henkilot({ projekti, projektiLoadError, reloadProjekti }: HenkilotFormP const onSubmit = async (formData: FormValues) => { deleteFieldArrayIds(formData?.kayttoOikeudet); + // TODO: formData?.kayttoOikeudet.forEach(oikeus => delete oikeus.muokattavissa); setFormIsSubmitting(true); try { await api.tallennaProjekti(formData); diff --git a/src/schemas/kayttoOikeudet.ts b/src/schemas/kayttoOikeudet.ts index 58f6013b69..85a936ebb6 100644 --- a/src/schemas/kayttoOikeudet.ts +++ b/src/schemas/kayttoOikeudet.ts @@ -1,48 +1,32 @@ import * as Yup from "yup"; -import { Kayttaja, ProjektiKayttajaInput, ProjektiRooli, VaylaKayttajaTyyppi } from "../../common/graphql/apiModel"; +import { Kayttaja, KayttajaTyyppi, ProjektiKayttajaInput } from "../../common/graphql/apiModel"; import { addAgencyNumberTests, puhelinNumeroSchema } from "./puhelinNumero"; +import { isAorL } from "../../backend/src/util/userUtil"; export interface KayttoOikeudetSchemaContext { kayttajat: Kayttaja[]; } const kayttajaIsAorL = (kayttajat?: Kayttaja[] | null, kayttajatunnus?: string | null) => - kayttajat?.find( - ({ vaylaKayttajaTyyppi, uid }) => - uid === kayttajatunnus && - (VaylaKayttajaTyyppi.A_TUNNUS === vaylaKayttajaTyyppi || VaylaKayttajaTyyppi.L_TUNNUS === vaylaKayttajaTyyppi) - ); + kayttajat?.find(({ uid }) => uid && uid === kayttajatunnus && isAorL(uid)); -export const kayttoOikeudetSchema = Yup.array() - .of( - Yup.object() - .shape({ - rooli: Yup.mixed().oneOf(Object.values(ProjektiRooli), "Käyttäjälle on asetettava rooli"), - puhelinnumero: puhelinNumeroSchema.when(["$kayttajat", "kayttajatunnus"], { - is: kayttajaIsAorL, - then: addAgencyNumberTests, - }), - kayttajatunnus: Yup.string().required("Aseta käyttäjä"), - esitetaanKuulutuksessa: Yup.boolean().nullable().notRequired(), - id: Yup.string().nullable().notRequired(), - }) - .test("uniikki-kayttajatunnus", "Käyttäjä voi olla vain yhteen kertaan käyttöoikeuslistalla", function (current) { - const currentKayttaja = current as ProjektiKayttajaInput; - const kayttajaList = this.parent as ProjektiKayttajaInput[]; - const muutKayttajat = kayttajaList.filter((kayttaja) => kayttaja !== currentKayttaja); +export const kayttoOikeudetSchema = Yup.array().of( + Yup.object() + .shape({ + puhelinnumero: puhelinNumeroSchema.when(["$kayttajat", "kayttajatunnus"], { + is: kayttajaIsAorL, + then: addAgencyNumberTests, + }), + kayttajatunnus: Yup.string().required("Aseta käyttäjä"), + id: Yup.string().nullable().notRequired(), + tyyppi: Yup.mixed().oneOf([KayttajaTyyppi.PROJEKTIPAALLIKKO, KayttajaTyyppi.VARAHENKILO]).notRequired(), + }) + .test("uniikki-kayttajatunnus", "Käyttäjä voi olla vain yhteen kertaan käyttöoikeuslistalla", function (current) { + const currentKayttaja = current as ProjektiKayttajaInput; + const kayttajaList = this.parent as ProjektiKayttajaInput[]; + const muutKayttajat = kayttajaList.filter((kayttaja) => kayttaja !== currentKayttaja); - const isDuplicate = muutKayttajat.some( - (kayttaja) => kayttaja.kayttajatunnus === currentKayttaja.kayttajatunnus - ); - return isDuplicate ? this.createError({ path: `${this.path}.kayttajatunnus` }) : true; - }) - ) - .test("must-contain-projektipaallikko", "Projektille täytyy määrittää projektipäällikkö", (list) => { - return !!list && (list as ProjektiKayttajaInput[]).some(({ rooli }) => rooli === ProjektiRooli.PROJEKTIPAALLIKKO); - }) - .test("singular-projektipaallikko", "Projektilla voi olla vain yksi projektipäällikkö", (list) => { - return ( - !!list && - (list as ProjektiKayttajaInput[]).filter(({ rooli }) => rooli === ProjektiRooli.PROJEKTIPAALLIKKO).length < 2 - ); - }); + const isDuplicate = muutKayttajat.some((kayttaja) => kayttaja.kayttajatunnus === currentKayttaja.kayttajatunnus); + return isDuplicate ? this.createError({ path: `${this.path}.kayttajatunnus` }) : true; + }) +); diff --git a/src/schemas/projekti.ts b/src/schemas/projekti.ts index 6a04990ef4..0ce72c94c0 100644 --- a/src/schemas/projekti.ts +++ b/src/schemas/projekti.ts @@ -1,5 +1,5 @@ -import { ProjektiRooli } from "@services/api"; import * as yup from "yup"; +import { KayttajaTyyppi } from "../../common/graphql/apiModel"; export enum ProjektiTestType { PROJEKTI_IS_LOADED = "PROJEKTI_IS_LOADED", @@ -14,9 +14,7 @@ const projektiSchema = yup .shape({ tallennettu: yup.boolean().nullable(), kayttoOikeudet: yup.array().of( - yup.object().shape({ - rooli: yup.mixed().oneOf(Object.values(ProjektiRooli)), - }) + yup.object() ), }); @@ -41,7 +39,7 @@ const projektiTestMap = new Map([ ProjektiTestType.PROJEKTI_HAS_PAALLIKKO, "Projektille ei ole asetettu projektipäällikköä", (projekti) => - !projekti?.oid || !!projekti?.kayttoOikeudet?.some(({ rooli }) => rooli === ProjektiRooli.PROJEKTIPAALLIKKO) + !projekti?.oid || !!projekti?.kayttoOikeudet?.some(({ tyyppi }) => tyyppi === KayttajaTyyppi.PROJEKTIPAALLIKKO) ), ], [ diff --git a/src/services/api/fragmentTypes.json b/src/services/api/fragmentTypes.json index 6a41695e5b..0c1d1696b2 100644 --- a/src/services/api/fragmentTypes.json +++ b/src/services/api/fragmentTypes.json @@ -277,6 +277,12 @@ "possibleTypes": null, "__typename": "__Type" }, + { + "kind": "ENUM", + "name": "KayttajaTyyppi", + "possibleTypes": null, + "__typename": "__Type" + }, { "kind": "ENUM", "name": "Kieli", @@ -517,12 +523,6 @@ "possibleTypes": null, "__typename": "__Type" }, - { - "kind": "ENUM", - "name": "ProjektiRooli", - "possibleTypes": null, - "__typename": "__Type" - }, { "kind": "ENUM", "name": "ProjektiSarake", @@ -637,12 +637,6 @@ "possibleTypes": null, "__typename": "__Type" }, - { - "kind": "ENUM", - "name": "VaylaKayttajaTyyppi", - "possibleTypes": null, - "__typename": "__Type" - }, { "kind": "OBJECT", "name": "Velho",