From c499f43564826fb1dd3f73390f3ab3a230d53c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Haapama=CC=88ki?= Date: Mon, 19 Sep 2022 13:18:50 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20ep=C3=A4aktiivinen=20tila=20hyv=C3=A4ks?= =?UTF-8?q?ymisp=C3=A4=C3=A4t=C3=B6skuulutuksen=20j=C3=A4lkeen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aloitusKuulutusHandler.test.ts | 16 +- backend/integrationtest/api/api.test.ts | 29 +- .../api/hyvaksymisPaatosHyvaksytty.test.ts | 108 ++ backend/integrationtest/api/palaute.test.ts | 11 +- .../records/HYVAKSYMISPAATOS_APPROVED.json | 924 ++++++++++++++++++ .../api/records/NAHTAVILLAOLO.json | 6 +- .../api/testFixtureRecorder.ts | 19 +- .../api/testUtil/hyvaksymisPaatosVaihe.ts | 18 +- backend/integrationtest/api/testUtil/tests.ts | 13 +- .../personSearch/personSearchClient.test.ts | 8 +- .../projektiSearchService.test.ts | 4 +- backend/src/archive/projektiArchiveService.ts | 14 +- backend/src/config.ts | 2 - backend/src/database/model/projekti.ts | 5 + backend/src/database/projektiDatabase.ts | 80 +- backend/src/files/fileService.ts | 62 +- backend/src/handler/asiakirjaAdapter.ts | 17 +- backend/src/handler/tila/TilaManager.ts | 7 +- .../tila/hyvaksymisPaatosVaiheTilaManager.ts | 17 +- .../ilmoitustauluSyoteService.ts | 5 +- .../adaptToAPI/adaptAloitusKuulutus.ts | 10 +- .../adaptToAPI/adaptNahtavillaoloVaihe.ts | 45 +- .../adaptToAPI/adaptSuunnitteluVaihe.ts | 21 +- .../adaptToDB/adaptAloitusKuulutusToSave.ts | 4 +- .../adaptHyvaksymisPaatosVaiheToSave.ts | 6 +- .../adaptNahtavillaoloVaiheToSave.ts | 13 +- .../adaptToDB/adaptSuunnitteluVaiheToSave.ts | 18 +- .../adaptToDB/adaptVuorovaikutusToSave.ts | 12 +- .../src/projekti/adapter/adaptToDB/common.ts | 11 +- .../common/adaptIlmoituksenVastaanottajat.ts | 3 +- .../projekti/adapter/common/lisaaTypename.ts | 4 +- backend/src/projekti/adapter/common/util.ts | 12 +- .../src/projekti/adapter/projektiAdapter.ts | 138 +-- .../adapter/projektiAdapterJulkinen.ts | 174 +--- backend/src/projekti/projektiHandler.ts | 27 +- backend/src/projekti/projektiUtil.ts | 7 + .../status/projektiJulkinenStatusHandler.ts | 71 ++ .../projekti/status/projektiStatusHandler.ts | 144 +++ backend/src/projekti/status/statusHandler.ts | 67 ++ backend/src/util/dateUtil.ts | 24 +- backend/test/apiHandler.test.ts | 46 +- backend/test/fixture/userFixture.ts | 2 +- .../test/muistutus/muistutusHandler.test.ts | 1 - backend/test/setup.js | 2 - .../9-hyvaksyntavaihe.spec.js | 4 +- deployment/bin/hassu.ts | 1 - deployment/bin/setupEnvironment.ts | 1 - deployment/lib/hassu-backend.ts | 49 +- deployment/lib/hassu-database.ts | 24 +- graphql/inputs.graphql | 2 + graphql/operations.graphql | 2 +- graphql/types.graphql | 16 +- .../kansalaisenEtusivu/Hakutulokset.tsx | 2 - .../kuulutuksenTiedot/Lukunakyma.tsx | 15 +- .../hyvaksyminen/kuulutuksenTiedot/index.tsx | 4 +- .../ProjektiJulkinenPageLayout.tsx | 5 +- .../KuulutuksessaEsitettavatYhteystiedot.tsx | 8 +- src/locales/fi/projekti.json | 9 +- src/locales/sv/projekti.json | 9 +- .../hyvaksymispaatosmenneisyyteen.dev.ts | 17 + .../[oid]/nahtavillaolomenneisyyteen.dev.ts | 23 +- src/pages/yllapito/index.tsx | 1 - src/services/api/commonApi.ts | 2 +- src/services/api/fragmentTypes.json | 6 - src/util/apiUtil.dev.ts | 30 + 65 files changed, 1717 insertions(+), 740 deletions(-) create mode 100644 backend/integrationtest/api/hyvaksymisPaatosHyvaksytty.test.ts create mode 100644 backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json create mode 100644 backend/src/projekti/projektiUtil.ts create mode 100644 backend/src/projekti/status/projektiJulkinenStatusHandler.ts create mode 100644 backend/src/projekti/status/projektiStatusHandler.ts create mode 100644 backend/src/projekti/status/statusHandler.ts create mode 100644 src/pages/api/test/[oid]/hyvaksymispaatosmenneisyyteen.dev.ts create mode 100644 src/util/apiUtil.dev.ts diff --git a/backend/integrationtest/aloitusKuulutus/aloitusKuulutusHandler.test.ts b/backend/integrationtest/aloitusKuulutus/aloitusKuulutusHandler.test.ts index 586922df8..080c98d19 100644 --- a/backend/integrationtest/aloitusKuulutus/aloitusKuulutusHandler.test.ts +++ b/backend/integrationtest/aloitusKuulutus/aloitusKuulutusHandler.test.ts @@ -14,8 +14,6 @@ import { emailHandler } from "../../src/handler/emailHandler"; const { expect } = require("chai"); -const sandbox = sinon.createSandbox(); - async function takeSnapshot(oid: string) { const dbProjekti = await projektiDatabase.loadProjektiByOid(oid); expect({ @@ -31,29 +29,25 @@ describe("AloitusKuulutus", () => { let sendEmailsByToimintoStub: sinon.SinonStub; before(async () => { - readUsersFromSearchUpdaterLambda = sandbox.stub(personSearchUpdaterClient, "readUsersFromSearchUpdaterLambda"); + readUsersFromSearchUpdaterLambda = sinon.stub(personSearchUpdaterClient, "readUsersFromSearchUpdaterLambda"); readUsersFromSearchUpdaterLambda.callsFake(async () => { return await personSearchUpdaterHandler.handleEvent(); }); - publishProjektiFileStub = sandbox.stub(fileService, "publishProjektiFile"); + publishProjektiFileStub = sinon.stub(fileService, "publishProjektiFile"); publishProjektiFileStub.resolves(); - sendEmailsByToimintoStub = sandbox.stub(emailHandler, "sendEmailsByToiminto"); + sendEmailsByToimintoStub = sinon.stub(emailHandler, "sendEmailsByToiminto"); sendEmailsByToimintoStub.resolves(); }); afterEach(() => { userFixture.logout(); - sandbox.reset(); - sandbox.restore(); - sinon.reset(); sinon.restore(); }); - beforeEach("Initialize test database!", async () => await setupLocalDatabase()); - - beforeEach(() => { + beforeEach(async () => { + await setupLocalDatabase(); userFixture = new UserFixture(userService); }); diff --git a/backend/integrationtest/api/api.test.ts b/backend/integrationtest/api/api.test.ts index f5f39fe9e..fe2f731a3 100644 --- a/backend/integrationtest/api/api.test.ts +++ b/backend/integrationtest/api/api.test.ts @@ -1,5 +1,5 @@ import { describe, it } from "mocha"; -import { replaceAWSDynamoDBWithLocalstack, setupLocalDatabase } from "../util/databaseUtil"; +import { setupLocalDatabase } from "../util/databaseUtil"; import { Status } from "../../../common/graphql/apiModel"; import * as sinon from "sinon"; import { personSearchUpdaterClient } from "../../src/personSearch/personSearchUpdaterClient"; @@ -15,7 +15,7 @@ import { getCloudFront, produce } from "../../src/aws/client"; import { cleanProjektiS3Files } from "../util/s3Util"; import { emailClient } from "../../src/email/email"; import { - archiveProjekti, + deleteProjekti, julkaiseSuunnitteluvaihe, julkaiseVuorovaikutus, loadProjektiFromDatabase, @@ -51,7 +51,6 @@ import { } from "./testUtil/hyvaksymisPaatosVaihe"; import { FixtureName, recordProjektiTestFixture } from "./testFixtureRecorder"; -const sandbox = sinon.createSandbox(); const { expect } = require("chai"); const oid = "1.2.246.578.5.1.2978288874.2711575506"; @@ -65,10 +64,7 @@ describe("Api", () => { after(() => { userFixture.logout(); - sandbox.restore(); - sandbox.reset(); sinon.restore(); - sinon.reset(); AWSMock.restore(); }); @@ -77,31 +73,31 @@ describe("Api", () => { before(async () => { await setupLocalDatabase(); userFixture = new UserFixture(userService); - readUsersFromSearchUpdaterLambda = sandbox.stub(personSearchUpdaterClient, "readUsersFromSearchUpdaterLambda"); + readUsersFromSearchUpdaterLambda = sinon.stub(personSearchUpdaterClient, "readUsersFromSearchUpdaterLambda"); readUsersFromSearchUpdaterLambda.callsFake(async () => { return await personSearchUpdaterHandler.handleEvent(); }); - sandbox.stub(openSearchClientYllapito, "query").resolves({ status: 200 }); - sandbox.stub(openSearchClientYllapito, "deleteDocument"); - sandbox.stub(openSearchClientYllapito, "putDocument"); + sinon.stub(openSearchClientYllapito, "query").resolves({ status: 200 }); + sinon.stub(openSearchClientYllapito, "deleteDocument"); + sinon.stub(openSearchClientYllapito, "putDocument"); - importAineistoStub = sandbox.stub(aineistoImporterClient, "importAineisto"); + importAineistoStub = sinon.stub(aineistoImporterClient, "importAineisto"); importAineistoStub.callsFake(async (event) => { fakeAineistoImportQueue.push({ Records: [{ body: JSON.stringify(event) } as SQSRecord] }); }); - awsCloudfrontInvalidationStub = sandbox.stub(); + awsCloudfrontInvalidationStub = sinon.stub(); awsCloudfrontInvalidationStub.resolves({}); AWSMock.setSDKInstance(AWS); produce("cloudfront", () => undefined, true); AWSMock.mock("CloudFront", "createInvalidation", awsCloudfrontInvalidationStub); getCloudFront(); - emailClientStub = sandbox.stub(emailClient, "sendEmail"); + emailClientStub = sinon.stub(emailClient, "sendEmail"); try { - await archiveProjekti(oid); + await deleteProjekti(oid); } catch (ignored) { // ignored } @@ -172,10 +168,7 @@ describe("Api", () => { await processQueue(fakeAineistoImportQueue); await takePublicS3Snapshot(oid, "Hyvaksymispaatos approved", "hyvaksymispaatos"); verifyEmailsSent(emailClientStub); - }); - it.skip("should archive projekti", async function () { - replaceAWSDynamoDBWithLocalstack(); - await archiveProjekti(oid); + await recordProjektiTestFixture(FixtureName.HYVAKSYMISPAATOS_APPROVED, oid); }); }); diff --git a/backend/integrationtest/api/hyvaksymisPaatosHyvaksytty.test.ts b/backend/integrationtest/api/hyvaksymisPaatosHyvaksytty.test.ts new file mode 100644 index 000000000..b722c2ddf --- /dev/null +++ b/backend/integrationtest/api/hyvaksymisPaatosHyvaksytty.test.ts @@ -0,0 +1,108 @@ +/* tslint:disable:only-arrow-functions no-unused-expression */ +import { describe, it } from "mocha"; +import { FixtureName, MOCKED_TIMESTAMP, useProjektiTestFixture } from "./testFixtureRecorder"; +import { setupLocalDatabase } from "../util/databaseUtil"; +import { deleteProjekti, loadProjektiFromDatabase, loadProjektiJulkinenFromDatabase } from "./testUtil/tests"; +import { UserFixture } from "../../test/fixture/userFixture"; +import { userService } from "../../src/user"; +import sinon from "sinon"; +import { projektiDatabase } from "../../src/database/projektiDatabase"; +import dayjs from "dayjs"; +import { Status } from "../../../common/graphql/apiModel"; +import { assert, expect } from "chai"; +import { ISO_DATE_FORMAT } from "../../src/util/dateUtil"; + +const oid = "1.2.246.578.5.1.2978288874.2711575506"; + +describe("Hyväksytyn hyväksymispäätöskuulutuksen jälkeen", () => { + let userFixture: UserFixture; + + before(async () => { + userFixture = new UserFixture(userService); + + await setupLocalDatabase(); + try { + await deleteProjekti(oid); + } catch (_ignore) { + // ignore + } + await useProjektiTestFixture(FixtureName.HYVAKSYMISPAATOS_APPROVED); + }); + + after(() => { + userFixture.logout(); + sinon.restore(); + }); + + async function setKuulutusVaihePaattyyPaivaToYesterday() { + const dbProjekti = await projektiDatabase.loadProjektiByOid(oid); + const julkaisu = dbProjekti.hyvaksymisPaatosVaiheJulkaisut[0]; + // Päättymispäivä yli vuosi menneisyyteen, jotta projekti menee epäaktiiviseksi + julkaisu.kuulutusVaihePaattyyPaiva = dayjs().add(-1, "day").format(ISO_DATE_FORMAT); + await projektiDatabase.updateHyvaksymisPaatosVaiheJulkaisu(dbProjekti, julkaisu); + } + + async function setKuulutusVaihePaattyyPaivaToOverOneYearAgo() { + const dbProjekti = await projektiDatabase.loadProjektiByOid(oid); + const julkaisu = dbProjekti.hyvaksymisPaatosVaiheJulkaisut[0]; + // Päättymispäivä yli vuosi menneisyyteen, jotta projekti menee epäaktiiviseksi + julkaisu.kuulutusVaihePaattyyPaiva = dayjs().add(-1, "year").add(-1, "day").format(ISO_DATE_FORMAT); + await projektiDatabase.updateHyvaksymisPaatosVaiheJulkaisu(dbProjekti, julkaisu); + } + + async function expectYllapitoProjektiStatus(expectedStatus: Status) { + userFixture.loginAs(UserFixture.mattiMeikalainen); + await loadProjektiFromDatabase(oid, expectedStatus); // Verify status in yllapito + } + + async function expectJulkinenProjektiStatus(expectedStatus: Status) { + userFixture.logout(); + try { + await loadProjektiJulkinenFromDatabase(oid, expectedStatus); + } catch (e) { + console.log(e); + assert.fail("Could not load julkinen projekti from API"); + } + userFixture.loginAs(UserFixture.mattiMeikalainen); + } + + async function expectJulkinenNotFound() { + userFixture.logout(); + try { + await loadProjektiJulkinenFromDatabase(oid); + assert.fail("Projektilla on julkista sisältöä vaikka ei pitäisi"); + } catch (e) { + // expected + } + userFixture.loginAs(UserFixture.mattiMeikalainen); + } + + it("should get epäaktiivinen and jatkopäätös1 statuses successfully", async () => { + userFixture.loginAs(UserFixture.mattiMeikalainen); + await setKuulutusVaihePaattyyPaivaToYesterday(); + await expectJulkinenProjektiStatus(Status.HYVAKSYTTY); + await setKuulutusVaihePaattyyPaivaToOverOneYearAgo(); + await expectYllapitoProjektiStatus(Status.EPAAKTIIVINEN); + await expectJulkinenNotFound(); + + const epaAktiivinenProjekti1 = await projektiDatabase.loadProjektiByOid(oid); + expect(epaAktiivinenProjekti1.ajastettuTarkistus).to.eql("2101-01-01T23:59:00+02:00"); // MOCKED_TIMESTAMP + 1 year + // TODO aineistojen ajastettu poisto tässä kohtaa + + await projektiDatabase.saveProjekti({ + oid, + kasittelynTila: { + ...epaAktiivinenProjekti1.kasittelynTila, + ensimmainenJatkopaatos: { paatoksenPvm: MOCKED_TIMESTAMP, asianumero: "jatkopaatos1_asianumero" }, + }, + }); + + await expectYllapitoProjektiStatus(Status.JATKOPAATOS_1); + await expectJulkinenNotFound(); + + // TODO hyväksytty jatkopäätös1 + // TODO hyväksytty epäaktiivinen2 + // TODO hyväksytty jatkopäätös2 + // TODO hyväksytty epäaktiivinen2 + }); +}); diff --git a/backend/integrationtest/api/palaute.test.ts b/backend/integrationtest/api/palaute.test.ts index ed7d10029..6d35a2d5e 100644 --- a/backend/integrationtest/api/palaute.test.ts +++ b/backend/integrationtest/api/palaute.test.ts @@ -2,7 +2,7 @@ import { describe, it } from "mocha"; import { FixtureName, useProjektiTestFixture } from "./testFixtureRecorder"; import { setupLocalDatabase } from "../util/databaseUtil"; -import { tallennaLogo, verifyEmailsSent } from "./testUtil/tests"; +import { deleteProjekti, tallennaLogo, verifyEmailsSent } from "./testUtil/tests"; import { UserFixture } from "../../test/fixture/userFixture"; import { userService } from "../../src/user"; import { api } from "./apiClient"; @@ -10,9 +10,7 @@ import { expectToMatchSnapshot, takeYllapitoS3Snapshot } from "./testUtil/util"; import { cleanupGeneratedIdAndTimestampFromFeedbacks } from "./testUtil/cleanUpFunctions"; import * as sinon from "sinon"; import { emailClient } from "../../src/email/email"; -import { projektiArchive } from "../../src/archive/projektiArchiveService"; -const sandbox = sinon.createSandbox(); const oid = "1.2.246.578.5.1.2978288874.2711575506"; describe("Palaute", () => { @@ -21,11 +19,11 @@ describe("Palaute", () => { before(async () => { userFixture = new UserFixture(userService); - emailClientStub = sandbox.stub(emailClient, "sendEmail"); + emailClientStub = sinon.stub(emailClient, "sendEmail"); await setupLocalDatabase(); try { - await projektiArchive.archiveProjekti(oid); + await deleteProjekti(oid); } catch (_ignore) { // ignore } @@ -34,8 +32,7 @@ describe("Palaute", () => { after(() => { userFixture.logout(); - sandbox.restore(); - sandbox.reset(); + sinon.restore(); }); it("should insert and manage feedback", async () => { diff --git a/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json b/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json new file mode 100644 index 000000000..7b1e047f6 --- /dev/null +++ b/backend/integrationtest/api/records/HYVAKSYMISPAATOS_APPROVED.json @@ -0,0 +1,924 @@ +{ + "aloitusKuulutusJulkaisut": [ + { + "aloituskuulutusPDFt": { + "SUOMI": { + "aloituskuulutusIlmoitusPDFPath": "/aloituskuulutus/T412_1 Ilmoitus aloituskuulutuksesta.pdf", + "aloituskuulutusPDFPath": "/aloituskuulutus/T412 Aloituskuulutus.pdf" + } + }, + "__typename": "AloitusKuulutus", + "hankkeenKuvaus": { + "SUOMI": "Lorem Ipsum", + "SAAME": "Saameksi", + "RUOTSI": "På Svenska", + "__typename": "HankkeenKuvaukset" + }, + "velho": { + "suunnittelustaVastaavaViranomainen": "VAYLAVIRASTO", + "nimi": "HASSU AUTOMAATTITESTIPROJEKTI1", + "tyyppi": "TIE", + "maakunnat": [ + "Uusimaa" + ], + "kunnat": [ + "Helsinki", + " Vantaa" + ], + "vastuuhenkilonEmail": "mikko.haapamki@cgi.com", + "vaylamuoto": [ + "tie" + ], + "linkki": null + }, + "suunnitteluSopimus": { + "sukunimi": "Jossain", + "logo": "/suunnittelusopimus/logo.png", + "kunta": "Nokia", + "puhelinnumero": "123", + "email": "Joku.Jossain@vayla.fi", + "etunimi": "Joku" + }, + "siirtyySuunnitteluVaiheeseen": "2022-01-01", + "kielitiedot": { + "ensisijainenKieli": "SUOMI" + }, + "kuulutusPaiva": "2022-01-02", + "muokkaaja": "A000112", + "hyvaksyja": "A000112", + "yhteystiedot": [ + { + "sukunimi": "Hassu", + "sahkoposti": "mikko.haapamki@cgi.com", + "puhelinnumero": "123", + "organisaatio": "CGI Suomi Oy", + "etunimi": "A-tunnus1" + }, + { + "sukunimi": "Koi", + "sahkoposti": "markku.koi@koi.com", + "organisaatio": "Kajaani", + "puhelinnumero": "0293121213", + "etunimi": "Marko", + "__typename": "Yhteystieto" + } + ], + "id": 1, + "tila": "HYVAKSYTTY" + } + ], + "salt": "salt123", + "tyyppi": "TIE", + "suunnitteluSopimus": { + "sukunimi": "Jossain", + "logo": "/suunnittelusopimus/logo.png", + "kunta": "Nokia", + "puhelinnumero": "123", + "email": "Joku.Jossain@vayla.fi", + "etunimi": "Joku" + }, + "velho": { + "suunnittelustaVastaavaViranomainen": "VAYLAVIRASTO", + "nimi": "HASSU AUTOMAATTITESTIPROJEKTI1", + "tyyppi": "TIE", + "maakunnat": [ + "Uusimaa" + ], + "kunnat": [ + "Helsinki", + " Vantaa" + ], + "vastuuhenkilonEmail": "mikko.haapamki@cgi.com", + "vaylamuoto": [ + "tie" + ], + "linkki": null + }, + "oid": "1.2.246.578.5.1.2978288874.2711575506", + "nahtavillaoloVaiheJulkaisut": [ + { + "kuulutusYhteysHenkilot": [ + "A000112" + ], + "hankkeenKuvaus": { + "SUOMI": "Lorem Ipsum nahtavillaoloVaihe", + "SAAME": "Saameksi nahtavillaoloVaihe" + }, + "velho": { + "suunnittelustaVastaavaViranomainen": "VAYLAVIRASTO", + "nimi": "HASSU AUTOMAATTITESTIPROJEKTI1", + "tyyppi": "TIE", + "maakunnat": [ + "Uusimaa" + ], + "kunnat": [ + "Helsinki", + " Vantaa" + ], + "vastuuhenkilonEmail": "mikko.haapamki@cgi.com", + "vaylamuoto": [ + "tie" + ], + "linkki": null + }, + "aineistoNahtavilla": [ + { + "kategoriaId": "T2xx", + "nimi": "T222 Meluesteiden periaatekuvat.txt", + "jarjestys": 1, + "tiedosto": "/nahtavillaolo/1/T222 Meluesteiden periaatekuvat.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2281391320.546836829", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T213 Teiden hallinnolsten järjestelyjen kartat.txt", + "jarjestys": 2, + "tiedosto": "/nahtavillaolo/1/T213 Teiden hallinnolsten järjestelyjen kartat.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2400417287.1486592341", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T224 Siltataulukko.txt", + "jarjestys": 3, + "tiedosto": "/nahtavillaolo/1/T224 Siltataulukko.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2661373170.3358971896", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T211 Piirustusmerkinnät.txt", + "jarjestys": 4, + "tiedosto": "/nahtavillaolo/1/T211 Piirustusmerkinnät.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2717425363.3158441393", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T212 Yleiskartta.txt", + "jarjestys": 5, + "tiedosto": "/nahtavillaolo/1/T212 Yleiskartta.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2723303646.77167902", + "tila": "VALMIS" + } + ], + "kuulutusYhteystiedot": [ + { + "sukunimi": "Sukunimi", + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "puhelinnumero": "0293121213", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi" + }, + { + "sukunimi": "Jokunen", + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "puhelinnumero": "02998765", + "titteli": "Konsultti", + "etunimi": "Joku" + } + ], + "nahtavillaoloPDFt": { + "SUOMI": { + "nahtavillaoloIlmoitusPDFPath": "/nahtavillaolo/T414_1 Ilmoitus suunnitelman nahtavillaolo.pdf", + "nahtavillaoloPDFPath": "/nahtavillaolo/T414 Kuulutus suunnitelman nahtavillaolo.pdf", + "nahtavillaoloIlmoitusKiinteistonOmistajallePDFPath": "/nahtavillaolo/31T Ilmoitus kiinteistonomistajat nahtaville asettaminen.pdf" + } + }, + "kielitiedot": { + "ensisijainenKieli": "SUOMI" + }, + "ilmoituksenVastaanottajat": { + "__typename": "IlmoituksenVastaanottajat", + "kunnat": [ + { + "sahkoposti": "mikkeli@mikke.li", + "lahetetty": "2022-03-11T14:54", + "nimi": "Mikkeli", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "juva@ju.va", + "lahetetty": "2022-03-11T14:54", + "nimi": " Juva", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "savonlinna@savonlin.na", + "lahetetty": "2022-03-11T14:54", + "nimi": " Savonlinna", + "__typename": "KuntaVastaanottaja" + } + ], + "viranomaiset": [ + { + "sahkoposti": "kirjaamo.etela-savo@ely-keskus.fi", + "lahetetty": "2022-03-11T14:54", + "nimi": "ETELA_SAVO_ELY", + "__typename": "ViranomaisVastaanottaja" + } + ] + }, + "lisaAineisto": [ + { + "dokumenttiOid": "1.2.246.578.5.100.2303050596.2160380418", + "nimi": "tokatiedosto_toka.pdf", + "jarjestys": 1, + "tila": "VALMIS", + "tiedosto": "/nahtavillaolo/1/tokatiedosto_toka.pdf", + "tuotu": "2020-01-01T00:00:00+02:00" + } + ], + "kuulutusPaiva": "2022-06-07", + "muokkaaja": "A000112", + "hyvaksyja": "A000112", + "kuulutusVaihePaattyyPaiva": "2020-01-01T00:00:00+02:00", + "id": 1, + "tila": "HYVAKSYTTY", + "muistutusoikeusPaattyyPaiva": "2042-06-08" + } + ], + "ajastettuTarkistus": "2101-01-01T23:59:00+02:00", + "euRahoitus": false, + "suunnitteluVaihe": { + "hankkeenKuvaus": { + "SUOMI": "Lorem Ipsum suunnitteluvaihe", + "__typename": "HankkeenKuvaukset", + "SAAME": "Saameksi suunnitteluvaihe" + }, + "palautteidenVastaanottajat": [ + "A000111" + ], + "suunnittelunEteneminenJaKesto": "suunnitelma etenee aikataulussa ja valmistuu vuoden 2022 aikana", + "julkinen": true, + "arvioSeuraavanVaiheenAlkamisesta": "huomenna" + }, + "kielitiedot": { + "ensisijainenKieli": "SUOMI" + }, + "hyvaksymisPaatosVaihe": { + "ilmoituksenVastaanottajat": { + "__typename": "IlmoituksenVastaanottajat", + "kunnat": [ + { + "sahkoposti": "mikkeli@mikke.li", + "lahetetty": "2022-03-11T14:54", + "nimi": "Mikkeli", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "juva@ju.va", + "lahetetty": "2022-03-11T14:54", + "nimi": " Juva", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "savonlinna@savonlin.na", + "lahetetty": "2022-03-11T14:54", + "nimi": " Savonlinna", + "__typename": "KuntaVastaanottaja" + } + ], + "viranomaiset": [ + { + "sahkoposti": "kirjaamo.etela-savo@ely-keskus.fi", + "lahetetty": "2022-03-11T14:54", + "nimi": "ETELA_SAVO_ELY", + "__typename": "ViranomaisVastaanottaja" + } + ] + }, + "hallintoOikeus": "HAMEENLINNA", + "hyvaksymisPaatos": [ + { + "dokumenttiOid": "1.2.246.578.5.100.2147637429.4251089044", + "nimi": "TYHJÄ.txt", + "jarjestys": 1, + "tila": "VALMIS", + "tiedosto": "/hyvaksymispaatos/1/paatos/TYHJÄ.txt", + "tuotu": "2020-01-01T00:00:00+02:00" + } + ], + "kuulutusPaiva": "2022-06-09", + "kuulutusYhteysHenkilot": [ + "A000112" + ], + "kuulutusVaihePaattyyPaiva": "2020-01-01T00:00:00+02:00", + "id": 1, + "aineistoNahtavilla": [ + { + "kategoriaId": "T1xx", + "nimi": "T113 TS Esite.txt", + "jarjestys": 1, + "tiedosto": "/hyvaksymispaatos/1/T113 TS Esite.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2160495081.1282714556", + "tila": "VALMIS" + } + ], + "kuulutusYhteystiedot": [ + { + "sukunimi": "Sukunimi", + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "puhelinnumero": "0293121213", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi" + }, + { + "sukunimi": "Jokunen", + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "puhelinnumero": "02998765", + "titteli": "Konsultti", + "etunimi": "Joku" + } + ] + }, + "nahtavillaoloVaihe": { + "ilmoituksenVastaanottajat": { + "__typename": "IlmoituksenVastaanottajat", + "kunnat": [ + { + "sahkoposti": "mikkeli@mikke.li", + "lahetetty": "2022-03-11T14:54", + "nimi": "Mikkeli", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "juva@ju.va", + "lahetetty": "2022-03-11T14:54", + "nimi": " Juva", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "savonlinna@savonlin.na", + "lahetetty": "2022-03-11T14:54", + "nimi": " Savonlinna", + "__typename": "KuntaVastaanottaja" + } + ], + "viranomaiset": [ + { + "sahkoposti": "kirjaamo.etela-savo@ely-keskus.fi", + "lahetetty": "2022-03-11T14:54", + "nimi": "ETELA_SAVO_ELY", + "__typename": "ViranomaisVastaanottaja" + } + ] + }, + "lisaAineisto": [ + { + "dokumenttiOid": "1.2.246.578.5.100.2303050596.2160380418", + "nimi": "tokatiedosto_toka.pdf", + "jarjestys": 1, + "tila": "VALMIS", + "tiedosto": "/nahtavillaolo/1/tokatiedosto_toka.pdf", + "tuotu": "2020-01-01T00:00:00+02:00" + } + ], + "kuulutusPaiva": "2022-06-07", + "kuulutusYhteysHenkilot": [ + "A000112" + ], + "hankkeenKuvaus": { + "SUOMI": "Lorem Ipsum nahtavillaoloVaihe", + "SAAME": "Saameksi nahtavillaoloVaihe" + }, + "kuulutusVaihePaattyyPaiva": "2020-01-01T00:00:00+02:00", + "id": 1, + "aineistoNahtavilla": [ + { + "kategoriaId": "T2xx", + "nimi": "T222 Meluesteiden periaatekuvat.txt", + "jarjestys": 1, + "tiedosto": "/nahtavillaolo/1/T222 Meluesteiden periaatekuvat.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2281391320.546836829", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T213 Teiden hallinnolsten järjestelyjen kartat.txt", + "jarjestys": 2, + "tiedosto": "/nahtavillaolo/1/T213 Teiden hallinnolsten järjestelyjen kartat.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2400417287.1486592341", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T224 Siltataulukko.txt", + "jarjestys": 3, + "tiedosto": "/nahtavillaolo/1/T224 Siltataulukko.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2661373170.3358971896", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T211 Piirustusmerkinnät.txt", + "jarjestys": 4, + "tiedosto": "/nahtavillaolo/1/T211 Piirustusmerkinnät.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2717425363.3158441393", + "tila": "VALMIS" + }, + { + "kategoriaId": "T2xx", + "nimi": "T212 Yleiskartta.txt", + "jarjestys": 5, + "tiedosto": "/nahtavillaolo/1/T212 Yleiskartta.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2723303646.77167902", + "tila": "VALMIS" + } + ], + "kuulutusYhteystiedot": [ + { + "sukunimi": "Sukunimi", + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "puhelinnumero": "0293121213", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi" + }, + { + "sukunimi": "Jokunen", + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "puhelinnumero": "02998765", + "titteli": "Konsultti", + "etunimi": "Joku" + } + ], + "muistutusoikeusPaattyyPaiva": "2042-06-08" + }, + "paivitetty": "2020-01-01T00:00:00+02:00", + "kayttoOikeudet": [ + { + "kayttajatunnus": "A000112", + "esitetaanKuulutuksessa": true, + "nimi": "Hassu, A-tunnus1", + "rooli": "PROJEKTIPAALLIKKO", + "organisaatio": "CGI Suomi Oy", + "puhelinnumero": "123", + "email": "mikko.haapamki@cgi.com" + }, + { + "kayttajatunnus": "A000111", + "nimi": "Hassu, A-Tunnus", + "rooli": "OMISTAJA", + "organisaatio": "CGI Suomi Oy", + "puhelinnumero": "123", + "email": "mikko.haapamaki@cgi.com" + } + ], + "kasittelynTila": { + "hyvaksymispaatos": { + "asianumero": "asianro123", + "paatoksenPvm": "2022-06-09" + } + }, + "aloitusKuulutus": { + "hankkeenKuvaus": { + "SUOMI": "Lorem Ipsum", + "SAAME": "Saameksi", + "RUOTSI": "På Svenska", + "__typename": "HankkeenKuvaukset" + }, + "kuulutusPaiva": "2022-01-02", + "siirtyySuunnitteluVaiheeseen": "2022-01-01", + "kuulutusYhteystiedot": { + "yhteysHenkilot": [], + "yhteysTiedot": [ + { + "sukunimi": "Koi", + "sahkoposti": "markku.koi@koi.com", + "organisaatio": "Kajaani", + "puhelinnumero": "0293121213", + "etunimi": "Marko", + "__typename": "Yhteystieto" + } + ], + "__typename": "KuulutusYhteystiedot" + }, + "__typename": "AloitusKuulutus" + }, + "hyvaksymisPaatosVaiheJulkaisut": [ + { + "kuulutusYhteysHenkilot": [ + "A000112" + ], + "velho": { + "suunnittelustaVastaavaViranomainen": "VAYLAVIRASTO", + "nimi": "HASSU AUTOMAATTITESTIPROJEKTI1", + "tyyppi": "TIE", + "maakunnat": [ + "Uusimaa" + ], + "kunnat": [ + "Helsinki", + " Vantaa" + ], + "vastuuhenkilonEmail": "mikko.haapamki@cgi.com", + "vaylamuoto": [ + "tie" + ], + "linkki": null + }, + "aineistoNahtavilla": [ + { + "kategoriaId": "T1xx", + "nimi": "T113 TS Esite.txt", + "jarjestys": 1, + "tiedosto": "/hyvaksymispaatos/1/T113 TS Esite.txt", + "tuotu": "2020-01-01T00:00:00+02:00", + "dokumenttiOid": "1.2.246.578.5.100.2160495081.1282714556", + "tila": "VALMIS" + } + ], + "kuulutusYhteystiedot": [ + { + "sukunimi": "Sukunimi", + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "puhelinnumero": "0293121213", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi" + }, + { + "sukunimi": "Jokunen", + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "puhelinnumero": "02998765", + "titteli": "Konsultti", + "etunimi": "Joku" + } + ], + "kielitiedot": { + "ensisijainenKieli": "SUOMI" + }, + "ilmoituksenVastaanottajat": { + "__typename": "IlmoituksenVastaanottajat", + "kunnat": [ + { + "sahkoposti": "mikkeli@mikke.li", + "lahetetty": "2022-03-11T14:54", + "nimi": "Mikkeli", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "juva@ju.va", + "lahetetty": "2022-03-11T14:54", + "nimi": " Juva", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "savonlinna@savonlin.na", + "lahetetty": "2022-03-11T14:54", + "nimi": " Savonlinna", + "__typename": "KuntaVastaanottaja" + } + ], + "viranomaiset": [ + { + "sahkoposti": "kirjaamo.etela-savo@ely-keskus.fi", + "lahetetty": "2022-03-11T14:54", + "nimi": "ETELA_SAVO_ELY", + "__typename": "ViranomaisVastaanottaja" + } + ] + }, + "hallintoOikeus": "HAMEENLINNA", + "hyvaksymisPaatos": [ + { + "dokumenttiOid": "1.2.246.578.5.100.2147637429.4251089044", + "nimi": "TYHJÄ.txt", + "jarjestys": 1, + "tila": "VALMIS", + "tiedosto": "/hyvaksymispaatos/1/paatos/TYHJÄ.txt", + "tuotu": "2020-01-01T00:00:00+02:00" + } + ], + "kuulutusPaiva": "2022-06-09", + "muokkaaja": "A000112", + "hyvaksyja": "A000112", + "kuulutusVaihePaattyyPaiva": "2020-01-01T00:00:00+02:00", + "id": 1, + "tila": "HYVAKSYTTY", + "hyvaksymisPaatosVaihePDFt": { + "SUOMI": { + "hyvaksymisKuulutusPDFPath": "/hyvaksymispaatos/T431 Kuulutus hyvaksymispaatoksen nahtavillaolo.pdf", + "ilmoitusHyvaksymispaatoskuulutuksestaKunnillePDFPath": "/hyvaksymispaatos/T431_1 Ilmoitus hyvaksymispaatoksesta kunnalle ja ELYlle.pdf", + "hyvaksymisIlmoitusLausunnonantajillePDFPath": "/hyvaksymispaatos/T431_3 Ilmoitus hyvaksymispaatoksesta lausunnon antajille.pdf", + "hyvaksymisIlmoitusMuistuttajillePDFPath": "/hyvaksymispaatos/T431_4 Ilmoitus hyvaksymispaatoksesta muistuttajille.pdf", + "ilmoitusHyvaksymispaatoskuulutuksestaToiselleViranomaisellePDFPath": "/hyvaksymispaatos/T431_2 Ilmoitus hyvaksymispaatoksen kuulutuksesta.pdf" + } + } + } + ], + "vuorovaikutukset": [ + { + "esittelyaineistot": [ + { + "dokumenttiOid": "1.2.246.578.5.100.2830496143.3575999363", + "nimi": "new ekatiedosto_eka.pdf", + "jarjestys": 12, + "tila": "VALMIS", + "tiedosto": "/suunnitteluvaihe/vuorovaikutus_1/aineisto/ekatiedosto_eka.pdf", + "tuotu": "2020-01-01T00:00:00+02:00" + } + ], + "__typename": "Vuorovaikutus", + "videot": [ + { + "nimi": "Esittely 1", + "url": "https://video", + "__typename": "Linkki" + } + ], + "kysymyksetJaPalautteetViimeistaan": "2022-03-23T23:48", + "vuorovaikutusYhteysHenkilot": [ + "A000112" + ], + "julkinen": true, + "esitettavatYhteystiedot": [ + { + "sukunimi": "Koi", + "sahkoposti": "markku.koi@koi.com", + "organisaatio": "Kajaani", + "puhelinnumero": "0293121213", + "etunimi": "Marko", + "__typename": "Yhteystieto" + } + ], + "ilmoituksenVastaanottajat": { + "__typename": "IlmoituksenVastaanottajat", + "kunnat": [ + { + "sahkoposti": "mikkeli@mikke.li", + "lahetetty": "2022-03-11T14:54", + "nimi": "Mikkeli", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "juva@ju.va", + "lahetetty": "2022-03-11T14:54", + "nimi": " Juva", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "savonlinna@savonlin.na", + "lahetetty": "2022-03-11T14:54", + "nimi": " Savonlinna", + "__typename": "KuntaVastaanottaja" + } + ], + "viranomaiset": [ + { + "sahkoposti": "kirjaamo.etela-savo@ely-keskus.fi", + "lahetetty": "2022-03-11T14:54", + "nimi": "ETELA_SAVO_ELY", + "__typename": "ViranomaisVastaanottaja" + } + ] + }, + "vuorovaikutusTilaisuudet": [ + { + "alkamisAika": "15:00", + "nimi": "Lorem ipsum 1", + "paivamaara": "2022-03-04", + "paattymisAika": "16:00", + "tyyppi": "VERKOSSA", + "kaytettavaPalvelu": "TEAMS", + "__typename": "VuorovaikutusTilaisuus", + "linkki": "https://linkki_tilaisuuteen" + }, + { + "alkamisAika": "10:00", + "nimi": "Lorem ipsum two 1", + "paivamaara": "2022-04-05", + "paattymisAika": "11:00", + "tyyppi": "PAIKALLA", + "postinumero": "00100", + "__typename": "VuorovaikutusTilaisuus", + "postitoimipaikka": "Helsinki", + "osoite": "Katu 123", + "paikka": "Kunnantalo", + "Saapumisohjeet": "Ensimmäinen ovi vasemmalla" + }, + { + "projektiYhteysHenkilot": [ + "A000112" + ], + "alkamisAika": "10:00", + "nimi": "Soittoaikatilaisuuden nimi tässä", + "paivamaara": "2022-04-05", + "paattymisAika": "11:00", + "tyyppi": "SOITTOAIKA", + "__typename": "VuorovaikutusTilaisuus", + "esitettavatYhteystiedot": [ + { + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi", + "__typename": "Yhteystieto", + "sukunimi": "Sukunimi", + "puhelinnumero": "0293121213" + }, + { + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "titteli": "Konsultti", + "etunimi": "Joku", + "__typename": "Yhteystieto", + "sukunimi": "Jokunen", + "puhelinnumero": "02998765" + } + ] + }, + { + "alkamisAika": "12:00", + "nimi": "Toisen soittoaikatilaisuuden nimi tässä", + "paivamaara": "2033-04-05", + "paattymisAika": "13:00", + "tyyppi": "SOITTOAIKA", + "__typename": "VuorovaikutusTilaisuus", + "esitettavatYhteystiedot": [ + { + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi", + "__typename": "Yhteystieto", + "sukunimi": "Sukunimi", + "puhelinnumero": "0293121213" + }, + { + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "titteli": "Konsultti", + "etunimi": "Joku", + "__typename": "Yhteystieto", + "sukunimi": "Jokunen", + "puhelinnumero": "02998765" + } + ] + } + ], + "vuorovaikutusPDFt": { + "SUOMI": { + "kutsuPDFPath": "/suunnitteluvaihe/vuorovaikutus_1/kutsu/TS Tie Yleisotilaisuus kutsu.pdf" + } + }, + "suunnitelmaluonnokset": [], + "vuorovaikutusJulkaisuPaiva": "2022-03-24", + "vuorovaikutusNumero": 1 + }, + { + "ilmoituksenVastaanottajat": { + "__typename": "IlmoituksenVastaanottajat", + "kunnat": [ + { + "sahkoposti": "mikkeli@mikke.li", + "lahetetty": "2022-03-11T14:54", + "nimi": "Mikkeli", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "juva@ju.va", + "lahetetty": "2022-03-11T14:54", + "nimi": " Juva", + "__typename": "KuntaVastaanottaja" + }, + { + "sahkoposti": "savonlinna@savonlin.na", + "lahetetty": "2022-03-11T14:54", + "nimi": " Savonlinna", + "__typename": "KuntaVastaanottaja" + } + ], + "viranomaiset": [ + { + "sahkoposti": "kirjaamo.etela-savo@ely-keskus.fi", + "lahetetty": "2022-03-11T14:54", + "nimi": "ETELA_SAVO_ELY", + "__typename": "ViranomaisVastaanottaja" + } + ] + }, + "vuorovaikutusTilaisuudet": [ + { + "alkamisAika": "15:00", + "nimi": "Lorem ipsum 2", + "paivamaara": "2022-03-04", + "paattymisAika": "16:00", + "tyyppi": "VERKOSSA", + "kaytettavaPalvelu": "TEAMS", + "linkki": "https://linkki_tilaisuuteen" + }, + { + "alkamisAika": "10:00", + "nimi": "Lorem ipsum two 2", + "paivamaara": "2022-04-05", + "paattymisAika": "11:00", + "tyyppi": "PAIKALLA", + "postinumero": "00100", + "postitoimipaikka": "Helsinki", + "osoite": "Katu 123", + "paikka": "Kunnantalo", + "Saapumisohjeet": "Ensimmäinen ovi vasemmalla" + }, + { + "projektiYhteysHenkilot": [ + "A000112", + "A000111" + ], + "alkamisAika": "10:00", + "nimi": "Soittoaikatilaisuuden nimi tässä", + "paivamaara": "2022-04-05", + "paattymisAika": "11:00", + "tyyppi": "SOITTOAIKA", + "esitettavatYhteystiedot": [ + { + "sukunimi": "Sukunimi", + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "puhelinnumero": "0293121213", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi" + }, + { + "sukunimi": "Jokunen", + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "puhelinnumero": "02998765", + "titteli": "Konsultti", + "etunimi": "Joku" + } + ] + }, + { + "alkamisAika": "12:00", + "nimi": "Toisen soittoaikatilaisuuden nimi tässä", + "paivamaara": "2033-04-05", + "paattymisAika": "13:00", + "tyyppi": "SOITTOAIKA", + "esitettavatYhteystiedot": [ + { + "sukunimi": "Sukunimi", + "sahkoposti": "Etunimi.Sukunimi@vayla.fi", + "organisaatio": "", + "puhelinnumero": "0293121213", + "titteli": "Projektipäällikkö", + "etunimi": "Etunimi" + }, + { + "sukunimi": "Jokunen", + "sahkoposti": "Joku.Jokunen@vayla.fi", + "organisaatio": "", + "puhelinnumero": "02998765", + "titteli": "Konsultti", + "etunimi": "Joku" + } + ] + } + ], + "esittelyaineistot": [], + "suunnitelmaluonnokset": [], + "vuorovaikutusJulkaisuPaiva": "2022-03-23", + "videot": [ + { + "nimi": "Esittely 2", + "url": "https://video" + } + ], + "vuorovaikutusNumero": 2, + "kysymyksetJaPalautteetViimeistaan": "2022-03-23T23:48", + "vuorovaikutusYhteysHenkilot": [ + "A000112", + "A000111" + ], + "esitettavatYhteystiedot": [ + { + "sukunimi": "Koi", + "sahkoposti": "markku.koi@koi.com", + "organisaatio": "Kajaani", + "puhelinnumero": "0293121213", + "etunimi": "Marko", + "__typename": "Yhteystieto" + } + ] + } + ] +} \ No newline at end of file diff --git a/backend/integrationtest/api/records/NAHTAVILLAOLO.json b/backend/integrationtest/api/records/NAHTAVILLAOLO.json index 0f6298cdc..6f6b447c5 100644 --- a/backend/integrationtest/api/records/NAHTAVILLAOLO.json +++ b/backend/integrationtest/api/records/NAHTAVILLAOLO.json @@ -112,7 +112,7 @@ "kielitiedot": { "ensisijainenKieli": "SUOMI" }, - "paivitetty": "2020-01-01T00:00:00+03:00", + "paivitetty": "2020-01-01T00:00:00+02:00", "kayttoOikeudet": [ { "kayttajatunnus": "A000112", @@ -281,7 +281,7 @@ "jarjestys": 12, "tila": "VALMIS", "tiedosto": "/suunnitteluvaihe/vuorovaikutus_1/aineisto/ekatiedosto_eka.pdf", - "tuotu": "2020-01-01T00:00:00+03:00" + "tuotu": "2020-01-01T00:00:00+02:00" } ], "__typename": "Vuorovaikutus", @@ -291,7 +291,7 @@ "nimi": "new karttakuvalla_tiedosto.pdf", "jarjestys": 11, "tiedosto": "/suunnitteluvaihe/vuorovaikutus_1/aineisto/karttakuvalla_tiedosto.pdf", - "tuotu": "2020-01-01T00:00:00+03:00", + "tuotu": "2020-01-01T00:00:00+02:00", "dokumenttiOid": "1.2.246.578.5.100.3084966615.4150346995", "tila": "VALMIS" } diff --git a/backend/integrationtest/api/testFixtureRecorder.ts b/backend/integrationtest/api/testFixtureRecorder.ts index 66136dd6a..76b3c6e77 100644 --- a/backend/integrationtest/api/testFixtureRecorder.ts +++ b/backend/integrationtest/api/testFixtureRecorder.ts @@ -6,24 +6,35 @@ import { localDocumentClient } from "../util/databaseUtil"; export enum FixtureName { NAHTAVILLAOLO = "NAHTAVILLAOLO", + HYVAKSYMISPAATOS_APPROVED = "HYVAKSYMISPAATOS_APPROVED", } +export const MOCKED_TIMESTAMP = "2020-01-01T00:00:00+02:00"; + export async function recordProjektiTestFixture(fixtureName: string | FixtureName, oid: string): Promise { const dbProjekti = await projektiDatabase.loadProjektiByOid(oid); - replaceFieldsByName(dbProjekti, "2020-01-01T00:00:00+03:00", "tuotu", "paivitetty"); + replaceFieldsByName(dbProjekti, MOCKED_TIMESTAMP, "tuotu", "paivitetty", "kuulutusVaihePaattyyPaiva"); replaceFieldsByName(dbProjekti, "salt123", "salt"); const oldValue = readRecord(fixtureName); const currentValue = JSON.stringify(dbProjekti, null, 2); // Prevent updating file timestamp so that running tests with "watch" don't get into infinite loop if (!oldValue || oldValue !== currentValue) { - fs.writeFileSync(__dirname + "/records/" + fixtureName + ".json", currentValue); + fs.writeFileSync(createRecordFileName(fixtureName), currentValue); } } function readRecord(fixtureName: string | FixtureName) { - const buffer = fs.readFileSync(__dirname + "/records/" + fixtureName + ".json"); - return buffer.toString("utf-8"); + try { + const buffer = fs.readFileSync(createRecordFileName(fixtureName)); + return buffer.toString("utf-8"); + } catch (e) { + // ignored + } +} + +function createRecordFileName(fixtureName: string | FixtureName) { + return __dirname + "/records/" + fixtureName + ".json"; } export async function useProjektiTestFixture(fixtureName: string | FixtureName): Promise { diff --git a/backend/integrationtest/api/testUtil/hyvaksymisPaatosVaihe.ts b/backend/integrationtest/api/testUtil/hyvaksymisPaatosVaihe.ts index bc64a83ab..0040cffcc 100644 --- a/backend/integrationtest/api/testUtil/hyvaksymisPaatosVaihe.ts +++ b/backend/integrationtest/api/testUtil/hyvaksymisPaatosVaihe.ts @@ -15,18 +15,14 @@ import { expect } from "chai"; import { api } from "../apiClient"; import { adaptAineistoToInput, expectToMatchSnapshot } from "./util"; import { apiTestFixture } from "../apiTestFixture"; -import { - cleanupHyvaksymisPaatosVaiheJulkaisuJulkinenTimestamps, - cleanupHyvaksymisPaatosVaiheTimestamps, -} from "./cleanUpFunctions"; +import { cleanupHyvaksymisPaatosVaiheJulkaisuJulkinenTimestamps, cleanupHyvaksymisPaatosVaiheTimestamps } from "./cleanUpFunctions"; +import dayjs from "dayjs"; -export async function testHyvaksymisPaatosVaiheHyvaksymismenettelyssa( - oid: string, - userFixture: UserFixture -): Promise { +export async function testHyvaksymisPaatosVaiheHyvaksymismenettelyssa(oid: string, userFixture: UserFixture): Promise { const dbProjekti = await projektiDatabase.loadProjektiByOid(oid); const julkaisu = dbProjekti.nahtavillaoloVaiheJulkaisut[0]; - julkaisu.kuulutusVaihePaattyyPaiva = "2022-06-08"; + // Päättymispäivä alle vuosi menneisyyteen, jottei projekti mene epäaktiiviseksi + julkaisu.kuulutusVaihePaattyyPaiva = dayjs().add(-2, "day").format(); await projektiDatabase.updateNahtavillaoloVaiheJulkaisu(dbProjekti, julkaisu); userFixture.loginAsAdmin(); @@ -99,9 +95,7 @@ export async function testHyvaksymisPaatosVaiheApproval( const projektiHyvaksyttavaksi = await loadProjektiFromDatabase(oid, Status.HYVAKSYMISMENETTELYSSA); expect(projektiHyvaksyttavaksi.hyvaksymisPaatosVaiheJulkaisut).to.have.length(1); - expect(projektiHyvaksyttavaksi.hyvaksymisPaatosVaiheJulkaisut[0].tila).to.eq( - HyvaksymisPaatosVaiheTila.ODOTTAA_HYVAKSYNTAA - ); + expect(projektiHyvaksyttavaksi.hyvaksymisPaatosVaiheJulkaisut[0].tila).to.eq(HyvaksymisPaatosVaiheTila.ODOTTAA_HYVAKSYNTAA); await api.siirraTila({ oid, diff --git a/backend/integrationtest/api/testUtil/tests.ts b/backend/integrationtest/api/testUtil/tests.ts index a9f08bfec..3edbd486c 100644 --- a/backend/integrationtest/api/testUtil/tests.ts +++ b/backend/integrationtest/api/testUtil/tests.ts @@ -20,7 +20,6 @@ import fs from "fs"; import { UserFixture } from "../../../test/fixture/userFixture"; import { detailedDiff } from "deep-object-diff"; import { parseDate } from "../../../src/util/dateUtil"; -import { projektiArchive } from "../../../src/archive/projektiArchiveService"; import { cleanupVuorovaikutusTimestamps } from "./cleanUpFunctions"; import Sinon from "sinon"; import * as log from "loglevel"; @@ -30,6 +29,8 @@ import { expectApiError, expectToMatchSnapshot } from "./util"; import { handleEvent } from "../../../src/aineisto/aineistoImporterLambda"; import { SQSEvent } from "aws-lambda/trigger/sqs"; import cloneDeep from "lodash/cloneDeep"; +import { projektiDatabase } from "../../../src/database/projektiDatabase"; +import { fileService } from "../../../src/files/fileService"; const { expect } = require("chai"); @@ -404,10 +405,6 @@ export async function testPublicAccessToProjekti( expectToMatchSnapshot("publicProjekti" + (description || ""), actual); } -export async function archiveProjekti(oid: string): Promise { - await projektiArchive.archiveProjekti(oid); -} - export async function searchProjectsFromVelhoAndPickFirst(): Promise { const searchResult = await api.getVelhoSuunnitelmasByName("HASSU AUTOMAATTITESTIPROJEKTI1"); // tslint:disable-next-line:no-unused-expression @@ -458,3 +455,9 @@ export async function processQueue(fakeAineistoImportQueue: SQSEvent[]): Promise } fakeAineistoImportQueue.splice(0, fakeAineistoImportQueue.length); // Clear the queue } + +export async function deleteProjekti(oid: string): Promise { + await projektiDatabase.deleteProjektiByOid(oid); + + await fileService.deleteProjekti(oid); +} diff --git a/backend/integrationtest/personSearch/personSearchClient.test.ts b/backend/integrationtest/personSearch/personSearchClient.test.ts index 9baff8c70..cdc1a9957 100644 --- a/backend/integrationtest/personSearch/personSearchClient.test.ts +++ b/backend/integrationtest/personSearch/personSearchClient.test.ts @@ -5,10 +5,8 @@ import { personSearch } from "../../src/personSearch/personSearchClient"; import { Kayttaja } from "../../../common/graphql/apiModel"; import { personSearchUpdaterClient } from "../../src/personSearch/personSearchUpdaterClient"; import * as personSearchUpdaterHandler from "../../src/personSearch/lambda/personSearchUpdaterHandler"; -import * as sinon from "sinon"; import log from "loglevel"; - -const sandbox = sinon.createSandbox(); +import sinon from "sinon"; export function expectNotEmptyKayttaja(kayttaja: Kayttaja): void { expect(kayttaja.vaylaKayttajaTyyppi).to.not.be.empty; @@ -24,14 +22,14 @@ describe("PersonSearchClient", () => { let readUsersFromSearchUpdaterLambda: sinon.SinonStub; beforeEach(() => { - readUsersFromSearchUpdaterLambda = sandbox.stub(personSearchUpdaterClient, "readUsersFromSearchUpdaterLambda"); + readUsersFromSearchUpdaterLambda = sinon.stub(personSearchUpdaterClient, "readUsersFromSearchUpdaterLambda"); readUsersFromSearchUpdaterLambda.callsFake(async () => { return await personSearchUpdaterHandler.handleEvent(); }); }); afterEach(() => { - sandbox.restore(); + sinon.restore(); }); it("should list users", async function () { diff --git a/backend/integrationtest/projektiSearch/projektiSearchService.test.ts b/backend/integrationtest/projektiSearch/projektiSearchService.test.ts index 7ea2d6709..280dd9a59 100644 --- a/backend/integrationtest/projektiSearch/projektiSearchService.test.ts +++ b/backend/integrationtest/projektiSearch/projektiSearchService.test.ts @@ -3,11 +3,11 @@ import { describe, it } from "mocha"; import { ProjektiFixture } from "../../test/fixture/projektiFixture"; import { projektiSearchService } from "../../src/projektiSearch/projektiSearchService"; import { Kieli, ListaaProjektitInput, ProjektiTyyppi, Status, Viranomainen } from "../../../common/graphql/apiModel"; -import { DBProjekti } from "../../src/database/model/projekti"; +import { DBProjekti } from "../../src/database/model"; import dayjs from "dayjs"; import { UserFixture } from "../../test/fixture/userFixture"; import { userService } from "../../src/user"; -import * as sinon from "sinon"; +import sinon from "sinon"; const { expect } = require("chai"); diff --git a/backend/src/archive/projektiArchiveService.ts b/backend/src/archive/projektiArchiveService.ts index 094ba8cc1..b7aadc1bc 100644 --- a/backend/src/archive/projektiArchiveService.ts +++ b/backend/src/archive/projektiArchiveService.ts @@ -1,16 +1,12 @@ -import dayjs from "dayjs"; -import { ArchivedProjektiKey, projektiDatabase } from "../database/projektiDatabase"; +import { projektiDatabase } from "../database/projektiDatabase"; import { fileService } from "../files/fileService"; class ProjektiArchiveService { - async archiveProjekti(oid: string): Promise { - const timestamp = dayjs().format(); - const archivedProjektiKey = { oid, timestamp }; - await projektiDatabase.archiveProjektiByOid(archivedProjektiKey); + async archiveProjekti(oid: string): Promise { + await projektiDatabase.deleteProjektiByOid(oid); + await fileService.deleteProjekti(oid); - await fileService.archiveProjekti(archivedProjektiKey); - - return archivedProjektiKey; + return oid; } } diff --git a/backend/src/config.ts b/backend/src/config.ts index d151e9b7c..cc6258edc 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -6,7 +6,6 @@ const BaseConfig = require("../../common/BaseConfig.js").BaseConfig; const config = { projektiTableName: process.env.TABLE_PROJEKTI, feedbackTableName: process.env.TABLE_FEEDBACK, - projektiArchiveTableName: process.env.TABLE_PROJEKTI_ARCHIVE, cognitoURL: process.env.COGNITO_URL, velhoAuthURL: process.env.VELHO_AUTH_URL, velhoApiURL: process.env.VELHO_API_URL, @@ -34,7 +33,6 @@ const config = { yllapitoBucketName: process.env.YLLAPITO_BUCKET_NAME, publicBucketName: process.env.PUBLIC_BUCKET_NAME, internalBucketName: process.env.INTERNAL_BUCKET_NAME || "unset", - archiveBucketName: process.env.ARCHIVE_BUCKET_NAME, smtpKeyId: process.env.SMTP_KEY_ID, smtpSecret: process.env.SMTP_SECRET, diff --git a/backend/src/database/model/projekti.ts b/backend/src/database/model/projekti.ts index 78581a2b1..0bdc5e915 100644 --- a/backend/src/database/model/projekti.ts +++ b/backend/src/database/model/projekti.ts @@ -126,12 +126,17 @@ export type DBProjekti = { nahtavillaoloVaiheJulkaisut?: NahtavillaoloVaiheJulkaisu[] | null; hyvaksymisPaatosVaihe?: HyvaksymisPaatosVaihe | null; hyvaksymisPaatosVaiheJulkaisut?: HyvaksymisPaatosVaiheJulkaisu[] | null; + jatkoPaatos1Vaihe?: HyvaksymisPaatosVaihe | null; + jatkoPaatos1VaiheJulkaisut?: HyvaksymisPaatosVaiheJulkaisu[] | null; + jatkoPaatos2Vaihe?: HyvaksymisPaatosVaihe | null; + jatkoPaatos2VaiheJulkaisut?: HyvaksymisPaatosVaiheJulkaisu[] | null; uusiaPalautteita?: number; // false, jos projekti ladattiin Velhosta, mutta ei ole vielä tallennettu tietokantaan tallennettu?: boolean; kayttoOikeudet: DBVaylaUser[]; paivitetty?: string; + ajastettuTarkistus?: string; // Secret salt to use when generating lisaaineisto links within this projekti salt?: string; kasittelynTila?: KasittelynTila | null; diff --git a/backend/src/database/projektiDatabase.ts b/backend/src/database/projektiDatabase.ts index 6b29f7f7b..21f4f8643 100644 --- a/backend/src/database/projektiDatabase.ts +++ b/backend/src/database/projektiDatabase.ts @@ -1,10 +1,5 @@ import { log } from "../logger"; -import { - AloitusKuulutusJulkaisu, - DBProjekti, - HyvaksymisPaatosVaiheJulkaisu, - NahtavillaoloVaiheJulkaisu, -} from "./model"; +import { AloitusKuulutusJulkaisu, DBProjekti, HyvaksymisPaatosVaiheJulkaisu, NahtavillaoloVaiheJulkaisu } from "./model"; import { config } from "../config"; import { getDynamoDBDocumentClient } from "./dynamoDB"; import { DocumentClient } from "aws-sdk/lib/dynamodb/document_client"; @@ -13,12 +8,6 @@ import { Response } from "aws-sdk/lib/response"; import dayjs from "dayjs"; const projektiTableName: string = config.projektiTableName || "missing"; -const archiveTableName: string = config.projektiArchiveTableName || "missing"; - -export type ArchivedProjektiKey = { - oid: string; - timestamp: string; -}; async function createProjekti(projekti: DBProjekti): Promise { const params: DocumentClient.PutItemInput = { @@ -149,43 +138,23 @@ async function saveProjekti(dbProjekti: Partial): Promise { - const client = getDynamoDBDocumentClient(); +/** + * Only for integration testing + */ +async function deleteProjektiByOid(oid: string): Promise { + if (config.env !== "prod") { + const client = getDynamoDBDocumentClient(); - // Load projekti to be archived - const data: DocumentClient.GetItemOutput = await client - .get({ - TableName: projektiTableName, - Key: { oid }, - ConsistentRead: true, - }) - .promise(); - const item = data.Item as ArchivedProjektiKey; - if (!item) { - throw new Error("Arkistointi ei onnistunut, koska arkistoitavaa projektia ei löytynyt tietokannasta."); + const removeResult = await client + .delete({ + TableName: projektiTableName, + Key: { + oid, + }, + }) + .promise(); + checkAndRaiseError(removeResult.$response, "Arkistointi ei onnistunut"); } - - // Assign sort key - item.timestamp = timestamp; - - // Write the copied projekti to archive table - const putParams: DocumentClient.PutItemInput = { - TableName: archiveTableName, - Item: item, - }; - const putResult = await client.put(putParams).promise(); - checkAndRaiseError(putResult.$response, "Arkistointi ei onnistunut"); - - // Delete the archived projekti - const removeResult = await client - .delete({ - TableName: projektiTableName, - Key: { - oid, - }, - }) - .promise(); - checkAndRaiseError(removeResult.$response, "Arkistointi ei onnistunut"); } function checkAndRaiseError(response: Response, msg: string) { @@ -286,12 +255,9 @@ export const projektiDatabase = { saveProjekti, scanProjektit, loadProjektiByOid, - archiveProjektiByOid, + deleteProjektiByOid, - async insertAloitusKuulutusJulkaisu( - oid: string, - julkaisu: AloitusKuulutusJulkaisu - ): Promise { + async insertAloitusKuulutusJulkaisu(oid: string, julkaisu: AloitusKuulutusJulkaisu): Promise { return insertJulkaisuToList(oid, "aloitusKuulutusJulkaisut", julkaisu, "AloitusKuulutusJulkaisu"); }, @@ -355,10 +321,7 @@ export const projektiDatabase = { return result; }, - async insertNahtavillaoloVaiheJulkaisu( - oid: string, - julkaisu: NahtavillaoloVaiheJulkaisu - ): Promise { + async insertNahtavillaoloVaiheJulkaisu(oid: string, julkaisu: NahtavillaoloVaiheJulkaisu): Promise { return insertJulkaisuToList(oid, "nahtavillaoloVaiheJulkaisut", julkaisu, "NahtavillaoloVaiheJulkaisu"); }, @@ -399,10 +362,7 @@ export const projektiDatabase = { ); }, - async updateHyvaksymisPaatosVaiheJulkaisu( - projekti: DBProjekti, - julkaisu: HyvaksymisPaatosVaiheJulkaisu - ): Promise { + async updateHyvaksymisPaatosVaiheJulkaisu(projekti: DBProjekti, julkaisu: HyvaksymisPaatosVaiheJulkaisu): Promise { await updateJulkaisuToList( projekti.oid, "hyvaksymisPaatosVaiheJulkaisut", diff --git a/backend/src/files/fileService.ts b/backend/src/files/fileService.ts index e8f521723..856a8aa8b 100644 --- a/backend/src/files/fileService.ts +++ b/backend/src/files/fileService.ts @@ -2,7 +2,6 @@ import { config } from "../config"; import { log } from "../logger"; import { NotFoundError } from "../error/NotFoundError"; import { uuid } from "../util/uuid"; -import { ArchivedProjektiKey } from "../database/projektiDatabase"; import { Dayjs } from "dayjs"; import { uriEscapePath } from "aws-sdk/lib/util"; import { ListObjectsV2Output } from "aws-sdk/clients/s3"; @@ -84,9 +83,7 @@ export class FileService { MetadataDirective: "REPLACE", }) .promise(); - log.info( - `Copied uploaded file (${sourceFileProperties.ContentType}) ${sourceFileProperties.CopySource} to ${targetBucketPath}` - ); + log.info(`Copied uploaded file (${sourceFileProperties.ContentType}) ${sourceFileProperties.CopySource} to ${targetBucketPath}`); return targetPath; } catch (e) { log.error(e); @@ -127,12 +124,7 @@ export class FileService { } } - private static async putFile( - bucket: string, - param: CreateFileProperties, - targetPath: string, - metadata: { [p: string]: string } - ) { + private static async putFile(bucket: string, param: CreateFileProperties, targetPath: string, metadata: { [p: string]: string }) { await getS3() .putObject({ Body: param.contents, @@ -154,13 +146,9 @@ export class FileService { return new ProjektiPaths(oid).publicPath; } - async getUploadedSourceFileInformation( - uploadedFileSource: string - ): Promise<{ ContentType: string; CopySource: string }> { + async getUploadedSourceFileInformation(uploadedFileSource: string): Promise<{ ContentType: string; CopySource: string }> { try { - const headObject = await getS3() - .headObject({ Bucket: config.uploadBucketName, Key: uploadedFileSource }) - .promise(); + const headObject = await getS3().headObject({ Bucket: config.uploadBucketName, Key: uploadedFileSource }).promise(); return { ContentType: headObject.ContentType, CopySource: uriEscapePath(config.uploadBucketName + "/" + uploadedFileSource), @@ -179,25 +167,20 @@ export class FileService { return uploadedFileSource; } - async archiveProjekti({ oid, timestamp }: ArchivedProjektiKey): Promise { - const yllapitoProjektiDirectory = FileService.getYllapitoProjektiDirectory(oid); - await this.moveFilesRecursively( - config.yllapitoBucketName, - yllapitoProjektiDirectory, - config.archiveBucketName, - yllapitoProjektiDirectory + "/" + timestamp - ); - - const publicProjektiDirectory = FileService.getPublicProjektiDirectory(oid); - await this.moveFilesRecursively( - config.publicBucketName, - publicProjektiDirectory, - config.archiveBucketName, - publicProjektiDirectory + "/" + timestamp - ); + /** + * Only for integration testing + */ + async deleteProjekti(oid: string): Promise { + if (config.env == "localstack") { + const yllapitoProjektiDirectory = FileService.getYllapitoProjektiDirectory(oid); + await this.deleteFilesRecursively(config.yllapitoBucketName, yllapitoProjektiDirectory); + + const publicProjektiDirectory = FileService.getPublicProjektiDirectory(oid); + await this.deleteFilesRecursively(config.publicBucketName, publicProjektiDirectory); + } } - private async moveFilesRecursively(sourceBucket: string, sourcePrefix: string, targetBucket, targetPrefix: string) { + private async deleteFilesRecursively(sourceBucket: string, sourcePrefix: string) { const s3 = getS3(); let ContinuationToken = undefined; do { @@ -216,7 +199,7 @@ export class FileService { while (sourceKeys.length) { const key = sourceKeys.pop(); if (key) { - await FileService.moveFile(sourceBucket, key, targetBucket, key.replace(sourcePrefix, targetPrefix)); + await FileService.deleteFile(sourceBucket, key); } } }) @@ -226,18 +209,9 @@ export class FileService { } while (ContinuationToken); } - private static async moveFile(sourceBucket: string, sourceKey: string, targetBucket: string, targetKey: string) { + private static async deleteFile(sourceBucket: string, sourceKey: string) { const s3 = getS3(); - await s3 - .copyObject({ - Bucket: targetBucket, - Key: targetKey, - CopySource: uriEscapePath(`${sourceBucket}/${sourceKey}`), - MetadataDirective: "REPLACE", - }) - .promise(); - await s3 .deleteObject({ Bucket: sourceBucket, diff --git a/backend/src/handler/asiakirjaAdapter.ts b/backend/src/handler/asiakirjaAdapter.ts index d4fa3f6da..bee4ae616 100644 --- a/backend/src/handler/asiakirjaAdapter.ts +++ b/backend/src/handler/asiakirjaAdapter.ts @@ -16,6 +16,7 @@ import { } from "../../../common/graphql/apiModel"; import { deepClone } from "aws-cdk/lib/util"; import { vaylaUserToYhteystieto } from "../util/vaylaUserToYhteystieto"; +import { findJulkaisuWithTila } from "../projekti/projektiUtil"; function createNextAloitusKuulutusJulkaisuID(dbProjekti: DBProjekti) { if (!dbProjekti.aloitusKuulutusJulkaisut) { @@ -80,33 +81,25 @@ export class AsiakirjaAdapter { findAloitusKuulutusWaitingForApproval(projekti: DBProjekti): AloitusKuulutusJulkaisu | undefined { if (projekti.aloitusKuulutusJulkaisut) { - return projekti.aloitusKuulutusJulkaisut - .filter((julkaisu) => julkaisu.tila == AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA) - .pop(); + return findJulkaisuWithTila(projekti.aloitusKuulutusJulkaisut, AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA); } } findNahtavillaoloWaitingForApproval(projekti: DBProjekti): NahtavillaoloVaiheJulkaisu | undefined { if (projekti.nahtavillaoloVaiheJulkaisut) { - return projekti.nahtavillaoloVaiheJulkaisut - .filter((julkaisu) => julkaisu.tila == NahtavillaoloVaiheTila.ODOTTAA_HYVAKSYNTAA) - .pop(); + return findJulkaisuWithTila(projekti.nahtavillaoloVaiheJulkaisut, NahtavillaoloVaiheTila.ODOTTAA_HYVAKSYNTAA); } } findHyvaksymisPaatosVaiheWaitingForApproval(projekti: DBProjekti): HyvaksymisPaatosVaiheJulkaisu | undefined { if (projekti.hyvaksymisPaatosVaiheJulkaisut) { - return projekti.hyvaksymisPaatosVaiheJulkaisut - .filter((julkaisu) => julkaisu.tila == HyvaksymisPaatosVaiheTila.ODOTTAA_HYVAKSYNTAA) - .pop(); + return findJulkaisuWithTila(projekti.hyvaksymisPaatosVaiheJulkaisut, HyvaksymisPaatosVaiheTila.ODOTTAA_HYVAKSYNTAA); } } findAloitusKuulutusLastApproved(projekti: DBProjekti): AloitusKuulutusJulkaisu | undefined { if (projekti.aloitusKuulutusJulkaisut) { - return projekti.aloitusKuulutusJulkaisut - .filter((julkaisu) => julkaisu.tila == AloitusKuulutusTila.HYVAKSYTTY) - .pop(); + return findJulkaisuWithTila(projekti.aloitusKuulutusJulkaisut, AloitusKuulutusTila.HYVAKSYTTY); } } } diff --git a/backend/src/handler/tila/TilaManager.ts b/backend/src/handler/tila/TilaManager.ts index aaa0ea0e0..50fec21b2 100644 --- a/backend/src/handler/tila/TilaManager.ts +++ b/backend/src/handler/tila/TilaManager.ts @@ -1,4 +1,4 @@ -import { NykyinenKayttaja, TilaSiirtymaInput, TilasiirtymaToiminto } from "../../../../common/graphql/apiModel"; +import { NykyinenKayttaja, TilaSiirtymaInput, TilasiirtymaToiminto, TilasiirtymaTyyppi } from "../../../../common/graphql/apiModel"; import { requirePermissionLuku, requirePermissionMuokkaa } from "../../user"; import { projektiDatabase } from "../../database/projektiDatabase"; import { emailHandler } from "../emailHandler"; @@ -6,8 +6,11 @@ import { DBProjekti } from "../../database/model"; import { requireProjektiPaallikko } from "../../user/userService"; export abstract class TilaManager { - public async siirraTila({ oid, syy, toiminto }: TilaSiirtymaInput): Promise { + protected tyyppi: TilasiirtymaTyyppi; + + public async siirraTila({ oid, syy, toiminto, tyyppi }: TilaSiirtymaInput): Promise { requirePermissionLuku(); + this.tyyppi = tyyppi; const projekti = await projektiDatabase.loadProjektiByOid(oid); if (toiminto == TilasiirtymaToiminto.LAHETA_HYVAKSYTTAVAKSI) { diff --git a/backend/src/handler/tila/hyvaksymisPaatosVaiheTilaManager.ts b/backend/src/handler/tila/hyvaksymisPaatosVaiheTilaManager.ts index 132adae15..30b8153e9 100644 --- a/backend/src/handler/tila/hyvaksymisPaatosVaiheTilaManager.ts +++ b/backend/src/handler/tila/hyvaksymisPaatosVaiheTilaManager.ts @@ -12,8 +12,14 @@ import { projektiDatabase } from "../../database/projektiDatabase"; import { aineistoService } from "../../aineisto/aineistoService"; import { fileService } from "../../files/fileService"; import { asiakirjaService, HyvaksymisPaatosKuulutusAsiakirjaTyyppi } from "../../asiakirja/asiakirjaService"; -import { parseDate } from "../../util/dateUtil"; +import { parseAndAddDate, parseDate } from "../../util/dateUtil"; import { ProjektiPaths } from "../../files/ProjektiPath"; +import { + HYVAKSYMISPAATOS_DURATION_UNIT, + HYVAKSYMISPAATOS_DURATION_VALUE, + JATKOPAATOS_DURATION_UNIT, + JATKOPAATOS_DURATION_VALUE, +} from "../../projekti/status/statusHandler"; async function createPDF( asiakirjaTyyppi: HyvaksymisPaatosKuulutusAsiakirjaTyyppi, @@ -83,10 +89,19 @@ class HyvaksymisPaatosVaiheTilaManager extends TilaManager { julkaisu.tila = HyvaksymisPaatosVaiheTila.HYVAKSYTTY; julkaisu.hyvaksyja = projektiPaallikko.uid; + await projektiDatabase.saveProjekti({ oid: projekti.oid, ajastettuTarkistus: this.getNextAjastettuTarkistus(julkaisu, true) }); + await projektiDatabase.updateHyvaksymisPaatosVaiheJulkaisu(projekti, julkaisu); await aineistoService.publishHyvaksymisPaatosVaihe(projekti.oid, julkaisu.id); } + private getNextAjastettuTarkistus(julkaisu: HyvaksymisPaatosVaiheJulkaisu, isHyvaksymisPaatos) { + if (isHyvaksymisPaatos) { + return parseAndAddDate(julkaisu.kuulutusVaihePaattyyPaiva, HYVAKSYMISPAATOS_DURATION_VALUE, HYVAKSYMISPAATOS_DURATION_UNIT).format(); + } + return parseAndAddDate(julkaisu.kuulutusVaihePaattyyPaiva, JATKOPAATOS_DURATION_VALUE, JATKOPAATOS_DURATION_UNIT).format(); + } + async reject(projekti: DBProjekti, syy: string): Promise { const julkaisu = asiakirjaAdapter.findHyvaksymisPaatosVaiheWaitingForApproval(projekti); if (!julkaisu) { diff --git a/backend/src/ilmoitustauluSyote/ilmoitustauluSyoteService.ts b/backend/src/ilmoitustauluSyote/ilmoitustauluSyoteService.ts index 8f3ef6054..3be7dee36 100644 --- a/backend/src/ilmoitustauluSyote/ilmoitustauluSyoteService.ts +++ b/backend/src/ilmoitustauluSyote/ilmoitustauluSyoteService.ts @@ -1,6 +1,7 @@ import { AloitusKuulutusTila, Kieli, ProjektiJulkinen } from "../../../common/graphql/apiModel"; import { openSearchClientIlmoitustauluSyote } from "../projektiSearch/openSearchClient"; import { ilmoitusKuulutusAdapter } from "./ilmoitustauluSyoteAdapter"; +import { findJulkaisutWithTila } from "../projekti/projektiUtil"; class IlmoitustauluSyoteService { async index(projekti: ProjektiJulkinen) { @@ -12,9 +13,7 @@ class IlmoitustauluSyoteService { } private async indexAloitusKuulutusJulkaisut(projekti: ProjektiJulkinen, kielet: Kieli[], oid: string) { - const aloitusKuulutusJulkaisut = projekti.aloitusKuulutusJulkaisut?.filter( - (julkaisu) => julkaisu.tila == AloitusKuulutusTila.HYVAKSYTTY - ); + const aloitusKuulutusJulkaisut = findJulkaisutWithTila(projekti.aloitusKuulutusJulkaisut, AloitusKuulutusTila.HYVAKSYTTY); if (aloitusKuulutusJulkaisut) { for (const aloitusKuulutusJulkaisu of aloitusKuulutusJulkaisut) { for (const kieli of kielet) { diff --git a/backend/src/projekti/adapter/adaptToAPI/adaptAloitusKuulutus.ts b/backend/src/projekti/adapter/adaptToAPI/adaptAloitusKuulutus.ts index e0d7f08a2..895a93be0 100644 --- a/backend/src/projekti/adapter/adaptToAPI/adaptAloitusKuulutus.ts +++ b/backend/src/projekti/adapter/adaptToAPI/adaptAloitusKuulutus.ts @@ -45,10 +45,7 @@ export function adaptAloitusKuulutusJulkaisut( return undefined; } -function adaptJulkaisuPDFPaths( - oid: string, - aloitusKuulutusPDFS: LocalizedMap -): API.AloitusKuulutusPDFt | undefined { +function adaptJulkaisuPDFPaths(oid: string, aloitusKuulutusPDFS: LocalizedMap): API.AloitusKuulutusPDFt | undefined { if (!aloitusKuulutusPDFS) { return undefined; } @@ -56,10 +53,7 @@ function adaptJulkaisuPDFPaths( const result = {}; for (const kieli in aloitusKuulutusPDFS) { result[kieli] = { - aloituskuulutusPDFPath: fileService.getYllapitoPathForProjektiFile( - oid, - aloitusKuulutusPDFS[kieli].aloituskuulutusPDFPath - ), + aloituskuulutusPDFPath: fileService.getYllapitoPathForProjektiFile(oid, aloitusKuulutusPDFS[kieli].aloituskuulutusPDFPath), aloituskuulutusIlmoitusPDFPath: fileService.getYllapitoPathForProjektiFile( oid, aloitusKuulutusPDFS[kieli].aloituskuulutusIlmoitusPDFPath diff --git a/backend/src/projekti/adapter/adaptToAPI/adaptNahtavillaoloVaihe.ts b/backend/src/projekti/adapter/adaptToAPI/adaptNahtavillaoloVaihe.ts index 1868a17f1..4c23d2fe3 100644 --- a/backend/src/projekti/adapter/adaptToAPI/adaptNahtavillaoloVaihe.ts +++ b/backend/src/projekti/adapter/adaptToAPI/adaptNahtavillaoloVaihe.ts @@ -1,10 +1,4 @@ -import { - DBProjekti, - LocalizedMap, - NahtavillaoloPDF, - NahtavillaoloVaihe, - NahtavillaoloVaiheJulkaisu, -} from "../../../database/model"; +import { DBProjekti, LocalizedMap, NahtavillaoloPDF, NahtavillaoloVaihe, NahtavillaoloVaiheJulkaisu } from "../../../database/model"; import * as API from "../../../../../common/graphql/apiModel"; import { adaptAineistot, @@ -17,33 +11,19 @@ import { import { fileService } from "../../../files/fileService"; import { lisaAineistoService } from "../../../aineisto/lisaAineistoService"; -export function adaptNahtavillaoloVaihe( - dbProjekti: DBProjekti, - nahtavillaoloVaihe: NahtavillaoloVaihe -): API.NahtavillaoloVaihe { +export function adaptNahtavillaoloVaihe(dbProjekti: DBProjekti, nahtavillaoloVaihe: NahtavillaoloVaihe): API.NahtavillaoloVaihe { if (!nahtavillaoloVaihe) { return undefined; } - const { - aineistoNahtavilla, - lisaAineisto, - kuulutusYhteystiedot, - ilmoituksenVastaanottajat, - hankkeenKuvaus, - nahtavillaoloPDFt, - ...rest - } = nahtavillaoloVaihe; + const { aineistoNahtavilla, lisaAineisto, kuulutusYhteystiedot, ilmoituksenVastaanottajat, hankkeenKuvaus, nahtavillaoloPDFt, ...rest } = + nahtavillaoloVaihe; return { __typename: "NahtavillaoloVaihe", ...rest, nahtavillaoloPDFt: adaptNahtavillaoloPDFPaths(dbProjekti.oid, nahtavillaoloPDFt), aineistoNahtavilla: adaptAineistot(aineistoNahtavilla), lisaAineisto: adaptAineistot(lisaAineisto), - lisaAineistoParametrit: lisaAineistoService.generateListingParams( - dbProjekti.oid, - nahtavillaoloVaihe.id, - dbProjekti.salt - ), + lisaAineistoParametrit: lisaAineistoService.generateListingParams(dbProjekti.oid, nahtavillaoloVaihe.id, dbProjekti.salt), kuulutusYhteystiedot: adaptYhteystiedotByAddingTypename(kuulutusYhteystiedot), ilmoituksenVastaanottajat: adaptIlmoituksenVastaanottajat(ilmoituksenVastaanottajat), hankkeenKuvaus: adaptHankkeenKuvaus(hankkeenKuvaus), @@ -85,10 +65,7 @@ export function adaptNahtavillaoloVaiheJulkaisut( return undefined; } -function adaptNahtavillaoloPDFPaths( - oid: string, - nahtavillaoloPDFs: LocalizedMap -): API.NahtavillaoloPDFt | undefined { +function adaptNahtavillaoloPDFPaths(oid: string, nahtavillaoloPDFs: LocalizedMap): API.NahtavillaoloPDFt | undefined { if (!nahtavillaoloPDFs) { return undefined; } @@ -96,14 +73,8 @@ function adaptNahtavillaoloPDFPaths( const result = {}; for (const kieli in nahtavillaoloPDFs) { result[kieli] = { - nahtavillaoloPDFPath: fileService.getYllapitoPathForProjektiFile( - oid, - nahtavillaoloPDFs[kieli].nahtavillaoloPDFPath - ), - nahtavillaoloIlmoitusPDFPath: fileService.getYllapitoPathForProjektiFile( - oid, - nahtavillaoloPDFs[kieli].nahtavillaoloIlmoitusPDFPath - ), + nahtavillaoloPDFPath: fileService.getYllapitoPathForProjektiFile(oid, nahtavillaoloPDFs[kieli].nahtavillaoloPDFPath), + nahtavillaoloIlmoitusPDFPath: fileService.getYllapitoPathForProjektiFile(oid, nahtavillaoloPDFs[kieli].nahtavillaoloIlmoitusPDFPath), nahtavillaoloIlmoitusKiinteistonOmistajallePDFPath: fileService.getYllapitoPathForProjektiFile( oid, nahtavillaoloPDFs[kieli].nahtavillaoloIlmoitusKiinteistonOmistajallePDFPath diff --git a/backend/src/projekti/adapter/adaptToAPI/adaptSuunnitteluVaihe.ts b/backend/src/projekti/adapter/adaptToAPI/adaptSuunnitteluVaihe.ts index cca9d234b..8558ece88 100644 --- a/backend/src/projekti/adapter/adaptToAPI/adaptSuunnitteluVaihe.ts +++ b/backend/src/projekti/adapter/adaptToAPI/adaptSuunnitteluVaihe.ts @@ -1,11 +1,4 @@ -import { - LocalizedMap, - Palaute, - SuunnitteluVaihe, - Vuorovaikutus, - VuorovaikutusPDF, - VuorovaikutusTilaisuus, -} from "../../../database/model"; +import { LocalizedMap, Palaute, SuunnitteluVaihe, Vuorovaikutus, VuorovaikutusPDF, VuorovaikutusTilaisuus } from "../../../database/model"; import * as API from "../../../../../common/graphql/apiModel"; import { adaptAineistot, @@ -23,8 +16,7 @@ export function adaptSuunnitteluVaihe( palautteet: Array ): API.SuunnitteluVaihe { if (suunnitteluVaihe) { - const { julkinen, arvioSeuraavanVaiheenAlkamisesta, suunnittelunEteneminenJaKesto, palautteidenVastaanottajat } = - suunnitteluVaihe; + const { julkinen, arvioSeuraavanVaiheenAlkamisesta, suunnittelunEteneminenJaKesto, palautteidenVastaanottajat } = suunnitteluVaihe; return { julkinen, arvioSeuraavanVaiheenAlkamisesta, @@ -57,9 +49,7 @@ function adaptVuorovaikutukset(oid: string, vuorovaikutukset: Array -): API.VuorovaikutusTilaisuus[] { +function adaptVuorovaikutusTilaisuudet(vuorovaikutusTilaisuudet: Array): API.VuorovaikutusTilaisuus[] { if (vuorovaikutusTilaisuudet) { return vuorovaikutusTilaisuudet.map((vuorovaikutusTilaisuus) => ({ ...vuorovaikutusTilaisuus, @@ -70,10 +60,7 @@ function adaptVuorovaikutusTilaisuudet( return vuorovaikutusTilaisuudet as undefined; } -function adaptVuorovaikutusPDFPaths( - oid: string, - pdfs: LocalizedMap -): API.VuorovaikutusPDFt | undefined { +function adaptVuorovaikutusPDFPaths(oid: string, pdfs: LocalizedMap): API.VuorovaikutusPDFt | undefined { if (!pdfs) { return undefined; } diff --git a/backend/src/projekti/adapter/adaptToDB/adaptAloitusKuulutusToSave.ts b/backend/src/projekti/adapter/adaptToDB/adaptAloitusKuulutusToSave.ts index 354254afe..ce54070ff 100644 --- a/backend/src/projekti/adapter/adaptToDB/adaptAloitusKuulutusToSave.ts +++ b/backend/src/projekti/adapter/adaptToDB/adaptAloitusKuulutusToSave.ts @@ -3,9 +3,7 @@ import { AloitusKuulutus } from "../../../database/model"; import { adaptHankkeenKuvausToSave, adaptIlmoituksenVastaanottajatToSave } from "./common"; import { adaptKuulutusYhteystiedotByAddingTypename } from "../common"; -export function adaptAloitusKuulutusToSave( - aloitusKuulutus: API.AloitusKuulutusInput -): AloitusKuulutus | null | undefined { +export function adaptAloitusKuulutusToSave(aloitusKuulutus: API.AloitusKuulutusInput): AloitusKuulutus | null | undefined { if (aloitusKuulutus) { const { hankkeenKuvaus, ilmoituksenVastaanottajat, kuulutusYhteystiedot, ...rest } = aloitusKuulutus; return { diff --git a/backend/src/projekti/adapter/adaptToDB/adaptHyvaksymisPaatosVaiheToSave.ts b/backend/src/projekti/adapter/adaptToDB/adaptHyvaksymisPaatosVaiheToSave.ts index b14d9e8fd..505f06ce5 100644 --- a/backend/src/projekti/adapter/adaptToDB/adaptHyvaksymisPaatosVaiheToSave.ts +++ b/backend/src/projekti/adapter/adaptToDB/adaptHyvaksymisPaatosVaiheToSave.ts @@ -30,11 +30,7 @@ export function adaptHyvaksymisPaatosVaiheToSave( projektiAdaptationResult ); - const hyvaksymisPaatos = adaptAineistotToSave( - dbHyvaksymisPaatosVaihe?.hyvaksymisPaatos, - hyvaksymisPaatosInput, - projektiAdaptationResult - ); + const hyvaksymisPaatos = adaptAineistotToSave(dbHyvaksymisPaatosVaihe?.hyvaksymisPaatos, hyvaksymisPaatosInput, projektiAdaptationResult); let id = dbHyvaksymisPaatosVaihe?.id; if (!id) { diff --git a/backend/src/projekti/adapter/adaptToDB/adaptNahtavillaoloVaiheToSave.ts b/backend/src/projekti/adapter/adaptToDB/adaptNahtavillaoloVaiheToSave.ts index 88c99c6b4..0b2bba35b 100644 --- a/backend/src/projekti/adapter/adaptToDB/adaptNahtavillaoloVaiheToSave.ts +++ b/backend/src/projekti/adapter/adaptToDB/adaptNahtavillaoloVaiheToSave.ts @@ -1,12 +1,7 @@ import { NahtavillaoloVaihe } from "../../../database/model"; import * as API from "../../../../../common/graphql/apiModel"; import { ProjektiAdaptationResult } from "../projektiAdapter"; -import { - adaptAineistotToSave, - adaptHankkeenKuvausToSave, - adaptIlmoituksenVastaanottajatToSave, - adaptYhteystiedotToSave, -} from "./common"; +import { adaptAineistotToSave, adaptHankkeenKuvausToSave, adaptIlmoituksenVastaanottajatToSave, adaptYhteystiedotToSave } from "./common"; import mergeWith from "lodash/mergeWith"; export function adaptNahtavillaoloVaiheToSave( @@ -36,11 +31,7 @@ export function adaptNahtavillaoloVaiheToSave( projektiAdaptationResult ); - const lisaAineisto = adaptAineistotToSave( - dbNahtavillaoloVaihe?.lisaAineisto, - lisaAineistoInput, - projektiAdaptationResult - ); + const lisaAineisto = adaptAineistotToSave(dbNahtavillaoloVaihe?.lisaAineisto, lisaAineistoInput, projektiAdaptationResult); let id = dbNahtavillaoloVaihe?.id; if (!id) { diff --git a/backend/src/projekti/adapter/adaptToDB/adaptSuunnitteluVaiheToSave.ts b/backend/src/projekti/adapter/adaptToDB/adaptSuunnitteluVaiheToSave.ts index 64e23644f..2f6629367 100644 --- a/backend/src/projekti/adapter/adaptToDB/adaptSuunnitteluVaiheToSave.ts +++ b/backend/src/projekti/adapter/adaptToDB/adaptSuunnitteluVaiheToSave.ts @@ -3,10 +3,7 @@ import * as API from "../../../../../common/graphql/apiModel"; import { IllegalArgumentError } from "../../../error/IllegalArgumentError"; import { adaptHankkeenKuvaus, findPublishedAloitusKuulutusJulkaisu } from "../common"; -export function adaptSuunnitteluVaiheToSave( - dbProjekti: DBProjekti, - suunnitteluVaihe: API.SuunnitteluVaiheInput -): SuunnitteluVaihe { +export function adaptSuunnitteluVaiheToSave(dbProjekti: DBProjekti, suunnitteluVaihe: API.SuunnitteluVaiheInput): SuunnitteluVaihe { function validateSuunnitteluVaihePublishing() { const isSuunnitteluVaiheBeingPublished = !dbProjekti.suunnitteluVaihe?.julkinen && suunnitteluVaihe.julkinen; if (isSuunnitteluVaiheBeingPublished) { @@ -19,19 +16,12 @@ export function adaptSuunnitteluVaiheToSave( if ( suunnitteluVaihe && - (suunnitteluVaihe.arvioSeuraavanVaiheenAlkamisesta || - suunnitteluVaihe.hankkeenKuvaus || - suunnitteluVaihe.suunnittelunEteneminenJaKesto) + (suunnitteluVaihe.arvioSeuraavanVaiheenAlkamisesta || suunnitteluVaihe.hankkeenKuvaus || suunnitteluVaihe.suunnittelunEteneminenJaKesto) ) { validateSuunnitteluVaihePublishing(); - const { - arvioSeuraavanVaiheenAlkamisesta, - suunnittelunEteneminenJaKesto, - hankkeenKuvaus, - julkinen, - palautteidenVastaanottajat, - } = suunnitteluVaihe; + const { arvioSeuraavanVaiheenAlkamisesta, suunnittelunEteneminenJaKesto, hankkeenKuvaus, julkinen, palautteidenVastaanottajat } = + suunnitteluVaihe; return { arvioSeuraavanVaiheenAlkamisesta, suunnittelunEteneminenJaKesto, diff --git a/backend/src/projekti/adapter/adaptToDB/adaptVuorovaikutusToSave.ts b/backend/src/projekti/adapter/adaptToDB/adaptVuorovaikutusToSave.ts index 86b0f1b01..0926de45a 100644 --- a/backend/src/projekti/adapter/adaptToDB/adaptVuorovaikutusToSave.ts +++ b/backend/src/projekti/adapter/adaptToDB/adaptVuorovaikutusToSave.ts @@ -1,12 +1,7 @@ import { DBProjekti, Vuorovaikutus, VuorovaikutusTilaisuus } from "../../../database/model"; import * as API from "../../../../../common/graphql/apiModel"; import { findVuorovaikutusByNumber } from "../../../util/findVuorovaikutusByNumber"; -import { - AineistoChangedEvent, - ProjektiAdaptationResult, - ProjektiEventType, - VuorovaikutusPublishedEvent, -} from "../projektiAdapter"; +import { AineistoChangedEvent, ProjektiAdaptationResult, ProjektiEventType, VuorovaikutusPublishedEvent } from "../projektiAdapter"; import { IllegalArgumentError } from "../../../error/IllegalArgumentError"; import { adaptKayttajatunnusList } from "./adaptKayttajatunnusList"; import { adaptAineistotToSave, adaptIlmoituksenVastaanottajatToSave, adaptYhteystiedotToSave } from "./common"; @@ -35,10 +30,7 @@ export function adaptVuorovaikutusToSave( projektiAdaptationResult ); - const vuorovaikutusTilaisuudet = adaptVuorovaikutusTilaisuudetToSave( - projekti, - vuorovaikutusInput.vuorovaikutusTilaisuudet - ); + const vuorovaikutusTilaisuudet = adaptVuorovaikutusTilaisuudetToSave(projekti, vuorovaikutusInput.vuorovaikutusTilaisuudet); // Vuorovaikutus must have at least one vuorovaikutustilaisuus if (!vuorovaikutusTilaisuudet) { throw new IllegalArgumentError("Vuorovaikutuksella pitää olla ainakin yksi vuorovaikutustilaisuus"); diff --git a/backend/src/projekti/adapter/adaptToDB/common.ts b/backend/src/projekti/adapter/adaptToDB/common.ts index 80061af81..eb27381fb 100644 --- a/backend/src/projekti/adapter/adaptToDB/common.ts +++ b/backend/src/projekti/adapter/adaptToDB/common.ts @@ -10,8 +10,7 @@ export function adaptIlmoituksenVastaanottajatToSave( if (!vastaanottajat) { return vastaanottajat as null | undefined; } - const kunnat: API.KuntaVastaanottaja[] = - vastaanottajat?.kunnat?.map((kunta) => ({ __typename: "KuntaVastaanottaja", ...kunta })) || null; + const kunnat: API.KuntaVastaanottaja[] = vastaanottajat?.kunnat?.map((kunta) => ({ __typename: "KuntaVastaanottaja", ...kunta })) || null; if (!vastaanottajat?.viranomaiset || vastaanottajat.viranomaiset.length === 0) { throw new IllegalArgumentError("Viranomaisvastaanottajia pitää olla vähintään yksi."); } @@ -23,9 +22,7 @@ export function adaptIlmoituksenVastaanottajatToSave( return { __typename: "IlmoituksenVastaanottajat", kunnat, viranomaiset }; } -export function adaptYhteystiedotToSave( - yhteystietoInputs: Array -): API.YhteystietoInput[] | undefined { +export function adaptYhteystiedotToSave(yhteystietoInputs: Array): API.YhteystietoInput[] | undefined { return yhteystietoInputs?.length > 0 ? yhteystietoInputs.map((yt) => ({ ...yt })) : undefined; } @@ -33,9 +30,7 @@ export function adaptYhteysHenkilotToSave(yhteystiedot: string[]): string[] { return yhteystiedot.filter((yt, index) => yhteystiedot.indexOf(yt) === index); } -export function adaptKuulutusYhteystiedot( - kuulutusYhteystiedot: API.KuulutusYhteystiedotInput -): API.KuulutusYhteystiedotInput { +export function adaptKuulutusYhteystiedot(kuulutusYhteystiedot: API.KuulutusYhteystiedotInput): API.KuulutusYhteystiedotInput { return { yhteysTiedot: adaptYhteystiedotToSave(kuulutusYhteystiedot.yhteysTiedot), yhteysHenkilot: adaptYhteysHenkilotToSave(kuulutusYhteystiedot.yhteysHenkilot), diff --git a/backend/src/projekti/adapter/common/adaptIlmoituksenVastaanottajat.ts b/backend/src/projekti/adapter/common/adaptIlmoituksenVastaanottajat.ts index 1a123e21a..246bf7282 100644 --- a/backend/src/projekti/adapter/common/adaptIlmoituksenVastaanottajat.ts +++ b/backend/src/projekti/adapter/common/adaptIlmoituksenVastaanottajat.ts @@ -6,8 +6,7 @@ export function adaptIlmoituksenVastaanottajat( if (!vastaanottajat) { return vastaanottajat as null | undefined; } - const kunnat: API.KuntaVastaanottaja[] = - vastaanottajat?.kunnat?.map((kunta) => ({ __typename: "KuntaVastaanottaja", ...kunta })) || null; + const kunnat: API.KuntaVastaanottaja[] = vastaanottajat?.kunnat?.map((kunta) => ({ __typename: "KuntaVastaanottaja", ...kunta })) || null; const viranomaiset: API.ViranomaisVastaanottaja[] = vastaanottajat?.viranomaiset?.map((viranomainen) => ({ __typename: "ViranomaisVastaanottaja", diff --git a/backend/src/projekti/adapter/common/lisaaTypename.ts b/backend/src/projekti/adapter/common/lisaaTypename.ts index ef1b92b87..05eed5f41 100644 --- a/backend/src/projekti/adapter/common/lisaaTypename.ts +++ b/backend/src/projekti/adapter/common/lisaaTypename.ts @@ -1,9 +1,7 @@ import { Kielitiedot, KuulutusYhteystiedot, Linkki, Suunnitelma, Velho, Yhteystieto } from "../../../database/model"; import * as API from "../../../../../common/graphql/apiModel"; -export function adaptLiittyvatSuunnitelmatByAddingTypename( - suunnitelmat?: Suunnitelma[] | null -): API.Suunnitelma[] | undefined | null { +export function adaptLiittyvatSuunnitelmatByAddingTypename(suunnitelmat?: Suunnitelma[] | null): API.Suunnitelma[] | undefined | null { if (suunnitelmat) { const liittyvatSuunnitelmat = suunnitelmat.map( (suunnitelma) => diff --git a/backend/src/projekti/adapter/common/util.ts b/backend/src/projekti/adapter/common/util.ts index 08ec1ba65..78deb0d48 100644 --- a/backend/src/projekti/adapter/common/util.ts +++ b/backend/src/projekti/adapter/common/util.ts @@ -1,18 +1,12 @@ import { AloitusKuulutusJulkaisu } from "../../../database/model"; import * as API from "../../../../../common/graphql/apiModel"; +import { findJulkaisuWithTila } from "../../projektiUtil"; export function findPublishedAloitusKuulutusJulkaisu( aloitusKuulutusJulkaisut: AloitusKuulutusJulkaisu[] ): AloitusKuulutusJulkaisu | undefined { return ( - findJulkaisuByStatus(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.HYVAKSYTTY) || - findJulkaisuByStatus(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.MIGROITU) + findJulkaisuWithTila(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.HYVAKSYTTY) || + findJulkaisuWithTila(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.MIGROITU) ); } - -export function findJulkaisuByStatus( - aloitusKuulutusJulkaisut: T[], - tila: API.AloitusKuulutusTila -): T | undefined { - return aloitusKuulutusJulkaisut?.filter((j) => j.tila == tila).pop(); -} diff --git a/backend/src/projekti/adapter/projektiAdapter.ts b/backend/src/projekti/adapter/projektiAdapter.ts index 1de442643..304815345 100644 --- a/backend/src/projekti/adapter/projektiAdapter.ts +++ b/backend/src/projekti/adapter/projektiAdapter.ts @@ -1,17 +1,10 @@ import { DBProjekti } from "../../database/model"; import * as API from "../../../../common/graphql/apiModel"; -import { NahtavillaoloVaiheTila } from "../../../../common/graphql/apiModel"; import mergeWith from "lodash/mergeWith"; import { KayttoOikeudetManager } from "../kayttoOikeudetManager"; import { personSearch } from "../../personSearch/personSearchClient"; import pickBy from "lodash/pickBy"; -import { perustiedotValidationSchema } from "../../../../src/schemas/perustiedot"; -import { ValidationError } from "yup"; -import { log } from "../../logger"; -import dayjs from "dayjs"; -import { kayttoOikeudetSchema } from "../../../../src/schemas/kayttoOikeudet"; import { lisaAineistoService } from "../../aineisto/lisaAineistoService"; -import { ISO_DATE_FORMAT, parseDate } from "../../util/dateUtil"; import { adaptKielitiedotByAddingTypename, adaptLiittyvatSuunnitelmatByAddingTypename } from "./common"; import { adaptAloitusKuulutus, @@ -32,6 +25,7 @@ import { adaptSuunnitteluVaiheToSave, adaptVuorovaikutusToSave, } from "./adaptToDB"; +import { applyProjektiStatus } from "../status/projektiStatusHandler"; export enum ProjektiEventType { VUOROVAIKUTUS_PUBLISHED = "VUOROVAIKUTUS_PUBLISHED", @@ -65,10 +59,7 @@ export class ProjektiAdaptationResult { return this.dbProjekti; } - async onEvent( - eventType: ProjektiEventType, - eventHandler: (event: ProjektiEvent, projekti: DBProjekti) => Promise - ): Promise { + async onEvent(eventType: ProjektiEventType, eventHandler: (event: ProjektiEvent, projekti: DBProjekti) => Promise): Promise { for (const event of this.events) { if (event.eventType == eventType) { await eventHandler(event, this.projekti); @@ -93,6 +84,10 @@ export class ProjektiAdapter { nahtavillaoloVaiheJulkaisut, hyvaksymisPaatosVaihe, hyvaksymisPaatosVaiheJulkaisut, + jatkoPaatos1Vaihe, + jatkoPaatos1VaiheJulkaisut, + jatkoPaatos2Vaihe, + jatkoPaatos2VaiheJulkaisut, salt: _salt, kasittelynTila, ...fieldsToCopyAsIs @@ -115,22 +110,30 @@ export class ProjektiAdapter { suunnitteluVaihe: adaptSuunnitteluVaihe(dbProjekti.oid, suunnitteluVaihe, vuorovaikutukset, undefined), nahtavillaoloVaihe: adaptNahtavillaoloVaihe(dbProjekti, nahtavillaoloVaihe), nahtavillaoloVaiheJulkaisut: adaptNahtavillaoloVaiheJulkaisut(dbProjekti.oid, nahtavillaoloVaiheJulkaisut), - hyvaksymisPaatosVaihe: adaptHyvaksymisPaatosVaihe( - dbProjekti, - hyvaksymisPaatosVaihe, - dbProjekti.kasittelynTila?.hyvaksymispaatos - ), + hyvaksymisPaatosVaihe: adaptHyvaksymisPaatosVaihe(dbProjekti, hyvaksymisPaatosVaihe, dbProjekti.kasittelynTila?.hyvaksymispaatos), hyvaksymisPaatosVaiheJulkaisut: adaptHyvaksymisPaatosVaiheJulkaisut( dbProjekti.oid, dbProjekti.kasittelynTila?.hyvaksymispaatos, hyvaksymisPaatosVaiheJulkaisut ), + jatkoPaatos1Vaihe: adaptHyvaksymisPaatosVaihe(dbProjekti, jatkoPaatos1Vaihe, dbProjekti.kasittelynTila?.ensimmainenJatkopaatos), + jatkoPaatos1VaiheJulkaisut: adaptHyvaksymisPaatosVaiheJulkaisut( + dbProjekti.oid, + dbProjekti.kasittelynTila?.ensimmainenJatkopaatos, + jatkoPaatos1VaiheJulkaisut + ), + jatkoPaatos2Vaihe: adaptHyvaksymisPaatosVaihe(dbProjekti, jatkoPaatos2Vaihe, dbProjekti.kasittelynTila?.toinenJatkopaatos), + jatkoPaatos2VaiheJulkaisut: adaptHyvaksymisPaatosVaiheJulkaisut( + dbProjekti.oid, + dbProjekti.kasittelynTila?.toinenJatkopaatos, + jatkoPaatos2VaiheJulkaisut + ), virhetiedot, kasittelynTila: adaptKasittelynTila(kasittelynTila), ...fieldsToCopyAsIs, }) as API.Projekti; if (apiProjekti.tallennettu) { - this.applyStatus(apiProjekti); + applyProjektiStatus(apiProjekti); } return apiProjekti; } @@ -139,10 +142,7 @@ export class ProjektiAdapter { return mergeWith(projekti, (await this.adaptProjektiToSave(projekti, changes)).projekti); } - async adaptProjektiToSave( - projekti: DBProjekti, - changes: API.TallennaProjektiInput - ): Promise { + async adaptProjektiToSave(projekti: DBProjekti, changes: API.TallennaProjektiInput): Promise { // Pick only fields that are relevant to DB const { oid, @@ -161,11 +161,7 @@ export class ProjektiAdapter { const projektiAdaptationResult: ProjektiAdaptationResult = new ProjektiAdaptationResult(); const kayttoOikeudetManager = new KayttoOikeudetManager(projekti.kayttoOikeudet, await personSearch.getKayttajas()); kayttoOikeudetManager.applyChanges(kayttoOikeudet); - const vuorovaikutukset = adaptVuorovaikutusToSave( - projekti, - projektiAdaptationResult, - suunnitteluVaihe?.vuorovaikutus - ); + const vuorovaikutukset = adaptVuorovaikutusToSave(projekti, projektiAdaptationResult, suunnitteluVaihe?.vuorovaikutus); const aloitusKuulutusToSave = adaptAloitusKuulutusToSave(aloitusKuulutus); const dbProjekti = mergeWith( {}, @@ -199,100 +195,10 @@ export class ProjektiAdapter { projektiAdaptationResult.setProjekti(dbProjekti); return projektiAdaptationResult; } - - /** - * Function to determine the status of the projekti - * @param projekti - */ - private applyStatus(projekti: API.Projekti): API.Projekti { - function checkPerustiedot() { - try { - kayttoOikeudetSchema.validateSync(projekti.kayttoOikeudet); - } catch (e) { - if (e instanceof ValidationError) { - log.info("Käyttöoikeudet puutteelliset", e); - projekti.status = API.Status.EI_JULKAISTU_PROJEKTIN_HENKILOT; - return true; // This is the final status - } else { - throw e; - } - } - try { - perustiedotValidationSchema.validateSync(projekti); - } catch (e) { - if (e instanceof ValidationError) { - log.info("Perustiedot puutteelliset", e.errors); - return true; // This is the final status - } else { - throw e; - } - } - - if (!projekti.aloitusKuulutus) { - projekti.aloitusKuulutus = { __typename: "AloitusKuulutus" }; - } - projekti.status = API.Status.ALOITUSKUULUTUS; - } - - function checkSuunnittelu() { - if (projekti.aloitusKuulutusJulkaisut) { - projekti.status = API.Status.SUUNNITTELU; - } - } - - function checkNahtavillaolo() { - if (projekti.suunnitteluVaihe?.julkinen) { - projekti.status = API.Status.NAHTAVILLAOLO; - } - } - - function checkHyvaksymisMenettelyssa() { - const hyvaksymisPaatos = projekti.kasittelynTila?.hyvaksymispaatos; - const hasHyvaksymisPaatos = hyvaksymisPaatos && hyvaksymisPaatos.asianumero && hyvaksymisPaatos.paatoksenPvm; - - const nahtavillaoloVaihe = projekti.nahtavillaoloVaiheJulkaisut - ?.filter((julkaisu) => julkaisu.tila == NahtavillaoloVaiheTila.HYVAKSYTTY) - .pop(); - const nahtavillaoloKuulutusPaattyyInThePast = isDateInThePast(nahtavillaoloVaihe?.kuulutusVaihePaattyyPaiva); - - if (hasHyvaksymisPaatos && nahtavillaoloKuulutusPaattyyInThePast) { - projekti.status = API.Status.HYVAKSYMISMENETTELYSSA; - } - } - - // Perustiedot is available if the projekti has been saved - projekti.tallennettu = true; - projekti.status = API.Status.EI_JULKAISTU; - - // Aloituskuulutus is available, if projekti has all basic information set - if (checkPerustiedot()) { - return projekti; - } - - checkSuunnittelu(); - - checkNahtavillaolo(); - - checkHyvaksymisMenettelyssa(); - - return projekti; - } } function removeUndefinedFields(object: API.Projekti): Partial { return pickBy(object, (value) => value !== undefined); } -export function isDateInThePast(kuulutusVaihePaattyyPaiva: string | undefined): boolean { - if (kuulutusVaihePaattyyPaiva) { - // Support times as well for testing, so do not set the time if it was already provided - let date = parseDate(kuulutusVaihePaattyyPaiva); - if (kuulutusVaihePaattyyPaiva.length == ISO_DATE_FORMAT.length) { - date = date.set("hour", 23).set("minute", 59); - } - return date.isBefore(dayjs()); - } - return false; -} - export const projektiAdapter = new ProjektiAdapter(); diff --git a/backend/src/projekti/adapter/projektiAdapterJulkinen.ts b/backend/src/projekti/adapter/projektiAdapterJulkinen.ts index 90687a99b..946832c16 100644 --- a/backend/src/projekti/adapter/projektiAdapterJulkinen.ts +++ b/backend/src/projekti/adapter/projektiAdapterJulkinen.ts @@ -14,13 +14,9 @@ import { VuorovaikutusTilaisuus, } from "../../database/model"; import * as API from "../../../../common/graphql/apiModel"; -import { - HyvaksymisPaatosVaiheJulkaisuJulkinen, - NahtavillaoloVaiheJulkaisuJulkinen, -} from "../../../../common/graphql/apiModel"; +import { HyvaksymisPaatosVaiheJulkaisuJulkinen, NahtavillaoloVaiheJulkaisuJulkinen, Status } from "../../../../common/graphql/apiModel"; import pickBy from "lodash/pickBy"; import dayjs, { Dayjs } from "dayjs"; -import { isDateInThePast } from "./projektiAdapter"; import { fileService } from "../../files/fileService"; import { log } from "../../logger"; import { parseDate } from "../../util/dateUtil"; @@ -31,69 +27,12 @@ import { adaptLinkkiByAddingTypename, adaptLinkkiListByAddingTypename, adaptYhteystiedotByAddingTypename, - findJulkaisuByStatus, findPublishedAloitusKuulutusJulkaisu, } from "./common"; +import { findJulkaisuWithTila } from "../projektiUtil"; +import { applyProjektiJulkinenStatus } from "../status/projektiJulkinenStatusHandler"; class ProjektiAdapterJulkinen { - private applyStatus(projekti: API.ProjektiJulkinen) { - function checkAloituskuulutus() { - if (projekti.aloitusKuulutusJulkaisut) { - const julkisetAloituskuulutukset = projekti.aloitusKuulutusJulkaisut.filter((julkaisu) => { - return julkaisu.kuulutusPaiva && parseDate(julkaisu.kuulutusPaiva).isBefore(dayjs()); - }); - - if (julkisetAloituskuulutukset?.length > 0) { - projekti.status = API.Status.ALOITUSKUULUTUS; - } - } - } - - function checkSuunnittelu() { - // Valiaikainen ui kehitysta varten, kunnes suunnitteluvaihe tietomallissa - if (projekti.suunnitteluVaihe) { - projekti.status = API.Status.SUUNNITTELU; - } - } - - function checkNahtavillaolo() { - const kuulutusPaiva = projekti.nahtavillaoloVaihe?.kuulutusPaiva; - if (kuulutusPaiva && parseDate(kuulutusPaiva).isBefore(dayjs())) { - projekti.status = API.Status.NAHTAVILLAOLO; - } - } - - function checkHyvaksymisMenettelyssa() { - const nahtavillaoloVaihe = projekti.nahtavillaoloVaihe; - if (isDateInThePast(nahtavillaoloVaihe?.kuulutusVaihePaattyyPaiva)) { - projekti.status = API.Status.HYVAKSYMISMENETTELYSSA; - } - } - - function checkHyvaksytty() { - const hyvaksymisPaatosVaihe = projekti.hyvaksymisPaatosVaihe; - if (isDateInThePast(hyvaksymisPaatosVaihe?.kuulutusPaiva)) { - projekti.status = API.Status.HYVAKSYTTY; - } - } - - projekti.status = API.Status.EI_JULKAISTU; - - checkAloituskuulutus(); - - checkSuunnittelu(); - - checkNahtavillaolo(); - - checkHyvaksymisMenettelyssa(); - - checkHyvaksytty(); - - // checkLainvoima(); - - return projekti; - } - public adaptProjekti(dbProjekti: DBProjekti): API.ProjektiJulkinen | undefined { const aloitusKuulutusJulkaisut = this.adaptAloitusKuulutusJulkaisut( dbProjekti.oid, @@ -113,7 +52,21 @@ class ProjektiAdapterJulkinen { } const nahtavillaoloVaihe = ProjektiAdapterJulkinen.adaptNahtavillaoloVaiheJulkaisu(dbProjekti, projektiHenkilot); - const hyvaksymisPaatosVaihe = ProjektiAdapterJulkinen.adaptHyvaksymisPaatosVaihe(dbProjekti, projektiHenkilot); + const hyvaksymisPaatosVaihe = ProjektiAdapterJulkinen.adaptHyvaksymisPaatosVaihe( + dbProjekti, + projektiHenkilot, + dbProjekti.hyvaksymisPaatosVaiheJulkaisut + ); + const jatkoPaatos1Vaihe = ProjektiAdapterJulkinen.adaptHyvaksymisPaatosVaihe( + dbProjekti, + projektiHenkilot, + dbProjekti.jatkoPaatos1VaiheJulkaisut + ); + const jatkoPaatos2Vaihe = ProjektiAdapterJulkinen.adaptHyvaksymisPaatosVaihe( + dbProjekti, + projektiHenkilot, + dbProjekti.jatkoPaatos2VaiheJulkaisut + ); const projekti: API.ProjektiJulkinen = { __typename: "ProjektiJulkinen", @@ -127,9 +80,14 @@ class ProjektiAdapterJulkinen { projektiHenkilot: Object.values(projektiHenkilot), nahtavillaoloVaihe, hyvaksymisPaatosVaihe, + jatkoPaatos1Vaihe, + jatkoPaatos2Vaihe, }; const projektiJulkinen = removeUndefinedFields(projekti) as API.ProjektiJulkinen; - return this.applyStatus(projektiJulkinen); + applyProjektiJulkinenStatus(projektiJulkinen); + if (projektiJulkinen.status != Status.EI_JULKAISTU && projektiJulkinen.status != Status.EPAAKTIIVINEN) { + return projektiJulkinen; + } } adaptAloitusKuulutusJulkaisut( @@ -163,10 +121,7 @@ class ProjektiAdapterJulkinen { return undefined; } - adaptSuunnitteluSopimus( - oid: string, - suunnitteluSopimus?: SuunnitteluSopimus | null - ): API.SuunnitteluSopimus | undefined | null { + adaptSuunnitteluSopimus(oid: string, suunnitteluSopimus?: SuunnitteluSopimus | null): API.SuunnitteluSopimus | undefined | null { if (suunnitteluSopimus) { return { __typename: "SuunnitteluSopimus", @@ -177,10 +132,7 @@ class ProjektiAdapterJulkinen { return suunnitteluSopimus as undefined | null; } - adaptJulkaisuPDFPaths( - oid: string, - aloitusKuulutusPDFS: LocalizedMap - ): API.AloitusKuulutusPDFt | undefined { + adaptJulkaisuPDFPaths(oid: string, aloitusKuulutusPDFS: LocalizedMap): API.AloitusKuulutusPDFt | undefined { if (!aloitusKuulutusPDFS) { return undefined; } @@ -188,10 +140,7 @@ class ProjektiAdapterJulkinen { const result = {}; for (const kieli in aloitusKuulutusPDFS) { result[kieli] = { - aloituskuulutusPDFPath: fileService.getPublicPathForProjektiFile( - oid, - aloitusKuulutusPDFS[kieli].aloituskuulutusPDFPath - ), + aloituskuulutusPDFPath: fileService.getPublicPathForProjektiFile(oid, aloitusKuulutusPDFS[kieli].aloituskuulutusPDFPath), aloituskuulutusIlmoitusPDFPath: fileService.getPublicPathForProjektiFile( oid, aloitusKuulutusPDFS[kieli].aloituskuulutusIlmoitusPDFPath @@ -201,12 +150,8 @@ class ProjektiAdapterJulkinen { return { __typename: "AloitusKuulutusPDFt", SUOMI: result[API.Kieli.SUOMI], ...result }; } - private static adaptSuunnitteluVaihe( - dbProjekti: DBProjekti, - projektiHenkilot: ProjektiHenkilot - ): API.SuunnitteluVaiheJulkinen { - const { hankkeenKuvaus, arvioSeuraavanVaiheenAlkamisesta, suunnittelunEteneminenJaKesto } = - dbProjekti.suunnitteluVaihe; + private static adaptSuunnitteluVaihe(dbProjekti: DBProjekti, projektiHenkilot: ProjektiHenkilot): API.SuunnitteluVaiheJulkinen { + const { hankkeenKuvaus, arvioSeuraavanVaiheenAlkamisesta, suunnittelunEteneminenJaKesto } = dbProjekti.suunnitteluVaihe; return { __typename: "SuunnitteluVaiheJulkinen", hankkeenKuvaus: adaptHankkeenKuvaus(hankkeenKuvaus), @@ -256,9 +201,10 @@ class ProjektiAdapterJulkinen { private static adaptHyvaksymisPaatosVaihe( dbProjekti: DBProjekti, - projektiHenkilot: ProjektiHenkilot + projektiHenkilot: ProjektiHenkilot, + paatosVaiheJulkaisut: HyvaksymisPaatosVaiheJulkaisu[] ): API.HyvaksymisPaatosVaiheJulkaisuJulkinen { - const julkaisu = pickExactlyOneHyvaksymisPaatosVaihe(dbProjekti.hyvaksymisPaatosVaiheJulkaisut); + const julkaisu = findApprovedHyvaksymisPaatosVaihe(paatosVaiheJulkaisut); if (julkaisu) { const { hyvaksymisPaatos, @@ -310,13 +256,13 @@ function pickExactlyOneNahtavillaoloVaiheJulkaisu( } } -function pickExactlyOneHyvaksymisPaatosVaihe( +function findApprovedHyvaksymisPaatosVaihe( hyvaksymisPaatosVaiheJulkaisut: HyvaksymisPaatosVaiheJulkaisu[] ): HyvaksymisPaatosVaiheJulkaisu | undefined { const julkaisut = hyvaksymisPaatosVaiheJulkaisut?.filter(isHyvaksymisPaatosVaihePublic); if (julkaisut) { if (julkaisut.length > 1) { - throw new Error("Bug: vain yksi HyvaksymisPaatosVaiheJulkaisu voi olla julkinen kerrallaan"); + throw new Error("Bug: löytyi liian monta julkaisua"); } return julkaisut.pop(); } @@ -369,10 +315,7 @@ function adaptUsernamesToProjektiHenkiloIds(usernames: Array, projektiHe return usernames?.map((username) => projektiHenkilot[username].id); } -function adaptVuorovaikutukset( - dbProjekti: DBProjekti, - projektiHenkilot: ProjektiHenkilot -): API.VuorovaikutusJulkinen[] { +function adaptVuorovaikutukset(dbProjekti: DBProjekti, projektiHenkilot: ProjektiHenkilot): API.VuorovaikutusJulkinen[] { const vuorovaikutukset = dbProjekti.vuorovaikutukset; if (vuorovaikutukset && vuorovaikutukset.length > 0) { return vuorovaikutukset @@ -383,24 +326,11 @@ function adaptVuorovaikutukset( return { ...vuorovaikutus, __typename: "VuorovaikutusJulkinen", - vuorovaikutusTilaisuudet: adaptVuorovaikutusTilaisuudet( - vuorovaikutus.vuorovaikutusTilaisuudet, - projektiHenkilot - ), + vuorovaikutusTilaisuudet: adaptVuorovaikutusTilaisuudet(vuorovaikutus.vuorovaikutusTilaisuudet, projektiHenkilot), videot: adaptLinkkiListByAddingTypename(vuorovaikutus.videot), suunnittelumateriaali: adaptLinkkiByAddingTypename(vuorovaikutus.suunnittelumateriaali), - esittelyaineistot: adaptAineistotJulkinen( - dbProjekti.oid, - vuorovaikutus.esittelyaineistot, - undefined, - julkaisuPaiva - ), - suunnitelmaluonnokset: adaptAineistotJulkinen( - dbProjekti.oid, - vuorovaikutus.suunnitelmaluonnokset, - undefined, - julkaisuPaiva - ), + esittelyaineistot: adaptAineistotJulkinen(dbProjekti.oid, vuorovaikutus.esittelyaineistot, undefined, julkaisuPaiva), + suunnitelmaluonnokset: adaptAineistotJulkinen(dbProjekti.oid, vuorovaikutus.suunnitelmaluonnokset, undefined, julkaisuPaiva), vuorovaikutusYhteystiedot: adaptAndMergeYhteystiedot(dbProjekti, vuorovaikutus), vuorovaikutusYhteysHenkilot: adaptUsernamesToProjektiHenkiloIds(usernames, projektiHenkilot), vuorovaikutusPDFt: adaptVuorovaikutusPDFPaths(dbProjekti.oid, vuorovaikutus.vuorovaikutusPDFt), @@ -420,10 +350,7 @@ function adaptVuorovaikutusTilaisuudet( if (vuorovaikutusTilaisuudet) { return vuorovaikutusTilaisuudet.map((vuorovaikutusTilaisuus) => ({ ...vuorovaikutusTilaisuus, - projektiYhteysHenkilot: adaptUsernamesToProjektiHenkiloIds( - vuorovaikutusTilaisuus.projektiYhteysHenkilot, - projektiHenkilot - ), + projektiYhteysHenkilot: adaptUsernamesToProjektiHenkiloIds(vuorovaikutusTilaisuus.projektiYhteysHenkilot, projektiHenkilot), esitettavatYhteystiedot: adaptYhteystiedotByAddingTypename(vuorovaikutusTilaisuus.esitettavatYhteystiedot), __typename: "VuorovaikutusTilaisuus", })); @@ -431,15 +358,13 @@ function adaptVuorovaikutusTilaisuudet( return vuorovaikutusTilaisuudet as undefined; } -function checkIfAloitusKuulutusJulkaisutIsPublic( - aloitusKuulutusJulkaisut: API.AloitusKuulutusJulkaisuJulkinen[] -): boolean { +function checkIfAloitusKuulutusJulkaisutIsPublic(aloitusKuulutusJulkaisut: API.AloitusKuulutusJulkaisuJulkinen[]): boolean { if (!(aloitusKuulutusJulkaisut && aloitusKuulutusJulkaisut.length == 1)) { log.info("Projektilla ei ole hyväksyttyä aloituskuulutusta"); return false; } - const hyvaksyttyJulkaisu = findJulkaisuByStatus(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.HYVAKSYTTY); + const hyvaksyttyJulkaisu = findJulkaisuWithTila(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.HYVAKSYTTY); if (hyvaksyttyJulkaisu) { if (hyvaksyttyJulkaisu.kuulutusPaiva && parseDate(hyvaksyttyJulkaisu.kuulutusPaiva).isAfter(dayjs())) { log.info("Projektin aloituskuulutuksen kuulutuspäivä on tulevaisuudessa", { @@ -448,7 +373,7 @@ function checkIfAloitusKuulutusJulkaisutIsPublic( }); return false; } - } else if (!findJulkaisuByStatus(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.MIGROITU)) { + } else if (!findJulkaisuWithTila(aloitusKuulutusJulkaisut, API.AloitusKuulutusTila.MIGROITU)) { // If there are no HYVAKSYTTY or MIGROITU aloitusKuulutusJulkaisu, hide projekti return false; } @@ -472,16 +397,10 @@ function isKuulutusNahtavillaVaiheOver( | HyvaksymisPaatosVaiheJulkaisu | HyvaksymisPaatosVaiheJulkaisuJulkinen ): boolean { - return ( - !nahtavillaoloVaihe.kuulutusVaihePaattyyPaiva || - parseDate(nahtavillaoloVaihe.kuulutusVaihePaattyyPaiva).isBefore(dayjs()) - ); + return !nahtavillaoloVaihe.kuulutusVaihePaattyyPaiva || parseDate(nahtavillaoloVaihe.kuulutusVaihePaattyyPaiva).isBefore(dayjs()); } -function adaptVuorovaikutusPDFPaths( - oid: string, - pdfs: LocalizedMap -): API.VuorovaikutusPDFt | undefined { +function adaptVuorovaikutusPDFPaths(oid: string, pdfs: LocalizedMap): API.VuorovaikutusPDFt | undefined { if (!pdfs) { return undefined; } @@ -508,10 +427,7 @@ function adaptAndMergeYhteystiedot(dbProjekti: DBProjekti, vuorovaikutus: Vuorov return vuorovaikutusYhteystiedot; } -function adaptYhteystiedotFromUsernames( - dbProjekti: DBProjekti, - usernames?: Array -): API.Yhteystieto[] | undefined { +function adaptYhteystiedotFromUsernames(dbProjekti: DBProjekti, usernames?: Array): API.Yhteystieto[] | undefined { if (!usernames || usernames.length == 0) { return undefined; } diff --git a/backend/src/projekti/projektiHandler.ts b/backend/src/projekti/projektiHandler.ts index 0bc3033d5..4c9a6bade 100644 --- a/backend/src/projekti/projektiHandler.ts +++ b/backend/src/projekti/projektiHandler.ts @@ -1,26 +1,9 @@ import { projektiDatabase } from "../database/projektiDatabase"; -import { - getVaylaUser, - requirePermissionLuku, - requirePermissionLuonti, - requirePermissionMuokkaa, - requireVaylaUser, -} from "../user"; +import { getVaylaUser, requirePermissionLuku, requirePermissionLuonti, requirePermissionMuokkaa, requireVaylaUser } from "../user"; import { velho } from "../velho/velhoClient"; import * as API from "../../../common/graphql/apiModel"; -import { - ArkistointiTunnus, - NykyinenKayttaja, - ProjektiRooli, - TallennaProjektiInput, - Velho, -} from "../../../common/graphql/apiModel"; -import { - ProjektiAdaptationResult, - projektiAdapter, - ProjektiEventType, - VuorovaikutusPublishedEvent, -} from "./adapter/projektiAdapter"; +import { NykyinenKayttaja, ProjektiRooli, TallennaProjektiInput, Velho } from "../../../common/graphql/apiModel"; +import { ProjektiAdaptationResult, projektiAdapter, ProjektiEventType, VuorovaikutusPublishedEvent } from "./adapter/projektiAdapter"; import { adaptVelhoByAddingTypename } from "./adapter/common"; import { auditLog, log } from "../logger"; import { KayttoOikeudetManager } from "./kayttoOikeudetManager"; @@ -80,9 +63,9 @@ export async function loadProjektiJulkinen(oid: string): Promise { +export async function arkistoiProjekti(oid: string): Promise { requireAdmin(); - return { __typename: "ArkistointiTunnus", ...(await projektiArchive.archiveProjekti(oid)) }; + return projektiArchive.archiveProjekti(oid); } export async function createOrUpdateProjekti(input: TallennaProjektiInput): Promise { diff --git a/backend/src/projekti/projektiUtil.ts b/backend/src/projekti/projektiUtil.ts new file mode 100644 index 000000000..c5d40e7e3 --- /dev/null +++ b/backend/src/projekti/projektiUtil.ts @@ -0,0 +1,7 @@ +export function findJulkaisutWithTila(julkaisut: (J & { tila?: T })[] | undefined | null, tila: T): J[] | undefined { + return julkaisut?.filter((julkaisu) => julkaisu.tila == tila); +} + +export function findJulkaisuWithTila(julkaisut: (J & { tila?: T })[] | undefined | null, tila: T): J | undefined { + return findJulkaisutWithTila(julkaisut, tila)?.pop(); +} diff --git a/backend/src/projekti/status/projektiJulkinenStatusHandler.ts b/backend/src/projekti/status/projektiJulkinenStatusHandler.ts new file mode 100644 index 000000000..292c1491f --- /dev/null +++ b/backend/src/projekti/status/projektiJulkinenStatusHandler.ts @@ -0,0 +1,71 @@ +import * as API from "../../../../common/graphql/apiModel"; +import { isDateInThePast, parseDate } from "../../util/dateUtil"; +import dayjs from "dayjs"; +import { AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler, StatusHandler } from "./statusHandler"; + +export function applyProjektiJulkinenStatus(projekti: API.ProjektiJulkinen): void { + const aloituskuulutus = new (class extends StatusHandler { + handle(p: API.ProjektiJulkinen) { + if (projekti.aloitusKuulutusJulkaisut) { + const julkisetAloituskuulutukset = projekti.aloitusKuulutusJulkaisut.filter((julkaisu) => { + return julkaisu.kuulutusPaiva && parseDate(julkaisu.kuulutusPaiva).isBefore(dayjs()); + }); + + if (julkisetAloituskuulutukset?.length > 0) { + projekti.status = API.Status.ALOITUSKUULUTUS; + super.handle(p); // Continue evaluating next rules + } + } + } + })(); + + const suunnittelu = new (class extends StatusHandler { + handle(p: API.ProjektiJulkinen) { + if (projekti.suunnitteluVaihe) { + projekti.status = API.Status.SUUNNITTELU; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + const nahtavillaOlo = new (class extends StatusHandler { + handle(p: API.ProjektiJulkinen) { + const kuulutusPaiva = projekti.nahtavillaoloVaihe?.kuulutusPaiva; + if (kuulutusPaiva && parseDate(kuulutusPaiva).isBefore(dayjs())) { + projekti.status = API.Status.NAHTAVILLAOLO; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + const hyvaksymisMenettelyssa = new (class extends StatusHandler { + handle(p: API.ProjektiJulkinen) { + const nahtavillaoloVaihe = projekti.nahtavillaoloVaihe; + if (isDateInThePast(nahtavillaoloVaihe?.kuulutusVaihePaattyyPaiva)) { + projekti.status = API.Status.HYVAKSYMISMENETTELYSSA; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + const hyvaksytty = new (class extends StatusHandler { + handle(p: API.ProjektiJulkinen) { + const hyvaksymisPaatosVaihe = projekti.hyvaksymisPaatosVaihe; + if (isDateInThePast(hyvaksymisPaatosVaihe?.kuulutusPaiva)) { + projekti.status = API.Status.HYVAKSYTTY; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + const epaAktiivinen1 = new (class extends AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler { + getPaatosVaihe(p: API.ProjektiJulkinen): { kuulutusVaihePaattyyPaiva?: string | null } { + return p.hyvaksymisPaatosVaihe; + } + })(true); + + projekti.status = API.Status.EI_JULKAISTU; + aloituskuulutus.setNext(suunnittelu).setNext(nahtavillaOlo).setNext(hyvaksymisMenettelyssa).setNext(hyvaksytty).setNext(epaAktiivinen1); + + aloituskuulutus.handle(projekti); +} diff --git a/backend/src/projekti/status/projektiStatusHandler.ts b/backend/src/projekti/status/projektiStatusHandler.ts new file mode 100644 index 000000000..e4c09c431 --- /dev/null +++ b/backend/src/projekti/status/projektiStatusHandler.ts @@ -0,0 +1,144 @@ +import * as API from "../../../../common/graphql/apiModel"; +import { HyvaksymisPaatosVaiheTila, NahtavillaoloVaiheTila } from "../../../../common/graphql/apiModel"; +import { kayttoOikeudetSchema } from "../../../../src/schemas/kayttoOikeudet"; +import { ValidationError } from "yup"; +import { log } from "../../logger"; +import { perustiedotValidationSchema } from "../../../../src/schemas/perustiedot"; +import { findJulkaisutWithTila, findJulkaisuWithTila } from "../projektiUtil"; +import { AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler, StatusHandler } from "./statusHandler"; +import { isDateInThePast } from "../../util/dateUtil"; + +export function applyProjektiStatus(projekti: API.Projekti): void { + const perustiedot = new (class extends StatusHandler { + handle(p: API.Projekti) { + // Initial state + p.tallennettu = true; + p.status = API.Status.EI_JULKAISTU; + + try { + kayttoOikeudetSchema.validateSync(p.kayttoOikeudet); + } catch (e) { + if (e instanceof ValidationError) { + log.info("Käyttöoikeudet puutteelliset", e); + p.status = API.Status.EI_JULKAISTU_PROJEKTIN_HENKILOT; + return; // This is the final status + } else { + throw e; + } + } + try { + perustiedotValidationSchema.validateSync(p); + } catch (e) { + if (e instanceof ValidationError) { + log.info("Perustiedot puutteelliset", e.errors); + return; // This is the final status + } else { + throw e; + } + } + + if (!p.aloitusKuulutus) { + p.aloitusKuulutus = { __typename: "AloitusKuulutus" }; + } + p.status = API.Status.ALOITUSKUULUTUS; + super.handle(p); // Continue evaluating next rules + } + })(); + + const suunnittelu = new (class extends StatusHandler { + handle(p: API.Projekti) { + if (p.aloitusKuulutusJulkaisut) { + p.status = API.Status.SUUNNITTELU; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + const nahtavillaOlo = new (class extends StatusHandler { + handle(p: API.Projekti) { + if (p.suunnitteluVaihe?.julkinen) { + p.status = API.Status.NAHTAVILLAOLO; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + const hyvaksymisMenettelyssa = new (class extends StatusHandler { + handle(p: API.Projekti) { + const hyvaksymisPaatos = p.kasittelynTila?.hyvaksymispaatos; + const hasHyvaksymisPaatos = hyvaksymisPaatos && hyvaksymisPaatos.asianumero && hyvaksymisPaatos.paatoksenPvm; + + const nahtavillaoloVaihe = findJulkaisuWithTila(p.nahtavillaoloVaiheJulkaisut, NahtavillaoloVaiheTila.HYVAKSYTTY); + const nahtavillaoloKuulutusPaattyyInThePast = isDateInThePast(nahtavillaoloVaihe?.kuulutusVaihePaattyyPaiva); + + if (hasHyvaksymisPaatos && nahtavillaoloKuulutusPaattyyInThePast) { + p.status = API.Status.HYVAKSYMISMENETTELYSSA; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + /** + * Jos hyväksymispäätöskuulutuksen päättymispäivästä on kulunut vuosi, niin tila on epäaktiivinen + */ + const epaAktiivinen1 = new (class extends AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler { + getPaatosVaihe(p: API.Projekti): { kuulutusVaihePaattyyPaiva?: string | null } { + return findJulkaisutWithTila(p.hyvaksymisPaatosVaiheJulkaisut, HyvaksymisPaatosVaiheTila.HYVAKSYTTY)?.pop(); + } + })(true); + + /** + * Ensimmäisen jatkopäätöksen päivämäärä ja asiatunnus annettu + */ + const jatkoPaatos1 = new (class extends StatusHandler { + handle(p: API.Projekti) { + const jatkoPaatos = p.kasittelynTila?.ensimmainenJatkopaatos; + if (jatkoPaatos && jatkoPaatos.asianumero && jatkoPaatos.paatoksenPvm) { + p.status = API.Status.JATKOPAATOS_1; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + /** + * Jos jatkopäätöksen kuulutuksen päättymispäivästä on 6kk, niin tila on epäaktiivinen + */ + const epaAktiivinen2 = new (class extends AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler { + getPaatosVaihe(p: API.Projekti): { kuulutusVaihePaattyyPaiva?: string | null } { + return findJulkaisutWithTila(p.jatkoPaatos1VaiheJulkaisut, HyvaksymisPaatosVaiheTila.HYVAKSYTTY)?.pop(); + } + })(false); + + /** + * Ensimmäisen jatkopäätöksen päivämäärä ja asiatunnus annettu + */ + const jatkoPaatos2 = new (class extends StatusHandler { + handle(p: API.Projekti) { + const jatkoPaatos = p.kasittelynTila?.toinenJatkopaatos; + if (jatkoPaatos && jatkoPaatos.asianumero && jatkoPaatos.paatoksenPvm) { + p.status = API.Status.JATKOPAATOS_2; + super.handle(p); // Continue evaluating next rules + } + } + })(); + + /** + * Jos toisen jatkopäätöksen kuulutuksen päättymispäivästä on 6kk, niin tila on epäaktiivinen + */ + const epaAktiivinen3 = new (class extends AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler { + getPaatosVaihe(p: API.Projekti): { kuulutusVaihePaattyyPaiva?: string | null } { + return findJulkaisutWithTila(p.jatkoPaatos2VaiheJulkaisut, HyvaksymisPaatosVaiheTila.HYVAKSYTTY).pop(); + } + })(false); + + perustiedot + .setNext(suunnittelu) + .setNext(nahtavillaOlo) + .setNext(hyvaksymisMenettelyssa) + .setNext(epaAktiivinen1) + .setNext(jatkoPaatos1) + .setNext(epaAktiivinen2) + .setNext(jatkoPaatos2) + .setNext(epaAktiivinen3); + perustiedot.handle(projekti); +} diff --git a/backend/src/projekti/status/statusHandler.ts b/backend/src/projekti/status/statusHandler.ts new file mode 100644 index 000000000..00f25fc1a --- /dev/null +++ b/backend/src/projekti/status/statusHandler.ts @@ -0,0 +1,67 @@ +import * as API from "../../../../common/graphql/apiModel"; +import { isDateInThePast } from "../../util/dateUtil"; + +export const HYVAKSYMISPAATOS_DURATION_VALUE = 1; +export const HYVAKSYMISPAATOS_DURATION_UNIT = "year"; +export const JATKOPAATOS_DURATION_VALUE = 1; +export const JATKOPAATOS_DURATION_UNIT = "year"; + +// Chain of responsibilites pattern to determine projekti status +export abstract class StatusHandler { + private nextHandler: StatusHandler; + + public setNext(handler: StatusHandler): StatusHandler { + this.nextHandler = handler; + return handler; + } + + public handle(p: T): void { + if (this.nextHandler) { + this.nextHandler.handle(p); + } + } +} + +/* + * Handler to determine if given hyväksymispäätöskuulutusvaihe ended a year or 6 months ago + */ +export abstract class AbstractHyvaksymisPaatosEpaAktiivinenStatusHandler< + T extends API.Projekti | API.ProjektiJulkinen +> extends StatusHandler { + private isHyvaksymisPaatos: boolean; + + constructor(isHyvaksymisPaatos: boolean) { + super(); + this.isHyvaksymisPaatos = isHyvaksymisPaatos; + } + + abstract getPaatosVaihe(p: T): { kuulutusVaihePaattyyPaiva?: string | null }; + + handle(p: T): void { + const hyvaksymisPaatosVaihe = this.getPaatosVaihe(p); + + // Kuulutusvaiheen päättymisestä pitää olla vuosi + const kuulutusVaihePaattyyPaiva = hyvaksymisPaatosVaihe?.kuulutusVaihePaattyyPaiva; + if (kuulutusVaihePaattyyPaiva) { + let hyvaksymisPaatosKuulutusPaattyyInThePast: boolean; + if (this.isHyvaksymisPaatos) { + hyvaksymisPaatosKuulutusPaattyyInThePast = isDateInThePast( + kuulutusVaihePaattyyPaiva, + HYVAKSYMISPAATOS_DURATION_VALUE, + HYVAKSYMISPAATOS_DURATION_UNIT + ); + } else { + hyvaksymisPaatosKuulutusPaattyyInThePast = isDateInThePast( + kuulutusVaihePaattyyPaiva, + JATKOPAATOS_DURATION_VALUE, + JATKOPAATOS_DURATION_UNIT + ); + } + + if (hyvaksymisPaatosKuulutusPaattyyInThePast) { + p.status = API.Status.EPAAKTIIVINEN; + } + super.handle(p); // Continue evaluating next rules + } + } +} diff --git a/backend/src/util/dateUtil.ts b/backend/src/util/dateUtil.ts index b6eeb33eb..96d2bb06a 100644 --- a/backend/src/util/dateUtil.ts +++ b/backend/src/util/dateUtil.ts @@ -1,4 +1,4 @@ -import dayjs, { Dayjs } from "dayjs"; +import dayjs, { Dayjs, ManipulateType } from "dayjs"; import tz from "dayjs/plugin/timezone"; import customParseFormat from "dayjs/plugin/customParseFormat"; import utc from "dayjs/plugin/utc"; @@ -39,3 +39,25 @@ export function dateTimeToString(date: Dayjs): string { export function localDateTimeString(): string { return dayjs().tz().format(DATE_TIME_FORMAT); } + +export function isDateInThePast(dateString: string | undefined, value?: number, unit?: ManipulateType): boolean { + const date = parseAndAddDate(dateString, value, unit); + if (date) { + return date.isBefore(dayjs()); + } + return false; +} + +export function parseAndAddDate(dateString: string | undefined, value?: number, unit?: ManipulateType): Dayjs { + if (dateString) { + // Support times as well for testing, so do not set the time if it was already provided + let date = parseDate(dateString); + if (value && unit) { + date = date.add(value, unit); + } + if (dateString.length == ISO_DATE_FORMAT.length) { + date = date.set("hour", 23).set("minute", 59); + } + return date; + } +} diff --git a/backend/test/apiHandler.test.ts b/backend/test/apiHandler.test.ts index ad2fa0092..5b1663663 100644 --- a/backend/test/apiHandler.test.ts +++ b/backend/test/apiHandler.test.ts @@ -27,34 +27,30 @@ import { NotFoundError } from "../src/error/NotFoundError"; import { emailClient } from "../src/email/email"; import AWSMock from "aws-sdk-mock"; import AWS from "aws-sdk"; +import { findJulkaisuWithTila } from "../src/projekti/projektiUtil"; const { expect, assert } = require("chai"); -const sandbox = sinon.createSandbox(); - describe("apiHandler", () => { let userFixture: UserFixture; let awsStub: sinon.SinonStub; afterEach(() => { - sandbox.reset(); - sinon.reset(); - sinon.restore(); - sandbox.restore(); userFixture.logout(); + sinon.restore(); AWSMock.restore(); }); beforeEach(() => { userFixture = new UserFixture(userService); AWSMock.setSDKInstance(AWS); - awsStub = sandbox.stub(); + awsStub = sinon.stub(); awsStub.resolves({}); AWSMock.mock("S3", "putObject", awsStub); AWSMock.mock("S3", "copyObject", awsStub); AWSMock.mock("S3", "getObject", awsStub); - const headObjectStub = sandbox.stub(); + const headObjectStub = sinon.stub(); AWSMock.mock("S3", "headObject", headObjectStub); headObjectStub.resolves({ Metadata: {} }); }); @@ -75,16 +71,16 @@ describe("apiHandler", () => { let sendEmailStub: sinon.SinonStub; beforeEach(() => { - createProjektiStub = sandbox.stub(projektiDatabase, "createProjekti"); - getKayttajasStub = sandbox.stub(personSearch, "getKayttajas"); - saveProjektiStub = sandbox.stub(projektiDatabase, "saveProjekti"); - loadProjektiByOidStub = sandbox.stub(projektiDatabase, "loadProjektiByOid"); - insertAloitusKuulutusJulkaisuStub = sandbox.stub(projektiDatabase, "insertAloitusKuulutusJulkaisu"); - updateAloitusKuulutusJulkaisuStub = sandbox.stub(projektiDatabase, "updateAloitusKuulutusJulkaisu"); - deleteAloitusKuulutusJulkaisuStub = sandbox.stub(projektiDatabase, "deleteAloitusKuulutusJulkaisu"); - loadVelhoProjektiByOidStub = sandbox.stub(velho, "loadProjekti"); - persistFileToProjektiStub = sandbox.stub(fileService, "persistFileToProjekti"); - sendEmailStub = sandbox.stub(emailClient, "sendEmail"); + createProjektiStub = sinon.stub(projektiDatabase, "createProjekti"); + getKayttajasStub = sinon.stub(personSearch, "getKayttajas"); + saveProjektiStub = sinon.stub(projektiDatabase, "saveProjekti"); + loadProjektiByOidStub = sinon.stub(projektiDatabase, "loadProjektiByOid"); + insertAloitusKuulutusJulkaisuStub = sinon.stub(projektiDatabase, "insertAloitusKuulutusJulkaisu"); + updateAloitusKuulutusJulkaisuStub = sinon.stub(projektiDatabase, "updateAloitusKuulutusJulkaisu"); + deleteAloitusKuulutusJulkaisuStub = sinon.stub(projektiDatabase, "deleteAloitusKuulutusJulkaisu"); + loadVelhoProjektiByOidStub = sinon.stub(velho, "loadProjekti"); + persistFileToProjektiStub = sinon.stub(fileService, "persistFileToProjekti"); + sendEmailStub = sinon.stub(emailClient, "sendEmail"); fixture = new ProjektiFixture(); personSearchFixture = new PersonSearchFixture(); @@ -118,8 +114,8 @@ describe("apiHandler", () => { const projekti = await api.lataaProjekti(fixture.PROJEKTI1_OID); expect(projekti).toMatchSnapshot(); - sandbox.assert.calledOnce(loadProjektiByOidStub); - sandbox.assert.calledOnce(loadVelhoProjektiByOidStub); + sinon.assert.calledOnce(loadProjektiByOidStub); + sinon.assert.calledOnce(loadVelhoProjektiByOidStub); }); }); @@ -212,17 +208,17 @@ describe("apiHandler", () => { const p = await api.lataaProjekti(oid); if (expectedState == AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA) { expect(p.aloitusKuulutusJulkaisut).not.be.empty; - const julkaisu = findAloitusKuulutusJulkaisuByState(p, AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA); + const julkaisu = findJulkaisuWithTila(p.aloitusKuulutusJulkaisut, AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA); expect(julkaisu).to.not.be.empty; expect(!!p.aloitusKuulutus?.palautusSyy); // null or undefined } else if (expectedState == AloitusKuulutusTila.HYVAKSYTTY) { expect(p.aloitusKuulutusJulkaisut).not.be.empty; - const julkaisu = findAloitusKuulutusJulkaisuByState(p, AloitusKuulutusTila.HYVAKSYTTY); + const julkaisu = findJulkaisuWithTila(p.aloitusKuulutusJulkaisut, AloitusKuulutusTila.HYVAKSYTTY); expect(julkaisu).to.not.be.empty; expect(!!p.aloitusKuulutus?.palautusSyy); // null or undefined } else { // Either rejected or inital state - const julkaisu = findAloitusKuulutusJulkaisuByState(p, AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA); + const julkaisu = findJulkaisuWithTila(p.aloitusKuulutusJulkaisut, AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA); expect(julkaisu).to.be.undefined; if (syy) { expect(p.aloitusKuulutus?.palautusSyy).to.eq(syy); @@ -407,7 +403,3 @@ describe("apiHandler", () => { }); }); }); - -function findAloitusKuulutusJulkaisuByState(p: Projekti, tila: AloitusKuulutusTila) { - return p.aloitusKuulutusJulkaisut?.filter((j) => j.tila == tila).pop(); -} diff --git a/backend/test/fixture/userFixture.ts b/backend/test/fixture/userFixture.ts index aadd22342..65c3d1a42 100644 --- a/backend/test/fixture/userFixture.ts +++ b/backend/test/fixture/userFixture.ts @@ -1,4 +1,4 @@ -import * as sinon from "sinon"; +import sinon from "sinon"; import { NykyinenKayttaja, ProjektiKayttaja, VaylaKayttajaTyyppi } from "../../../common/graphql/apiModel"; export class UserFixture { diff --git a/backend/test/muistutus/muistutusHandler.test.ts b/backend/test/muistutus/muistutusHandler.test.ts index 9a0af750f..380884c1a 100644 --- a/backend/test/muistutus/muistutusHandler.test.ts +++ b/backend/test/muistutus/muistutusHandler.test.ts @@ -18,7 +18,6 @@ describe("apiHandler", () => { let userFixture: UserFixture; afterEach(() => { - sinon.reset(); sinon.restore(); userFixture.logout(); }); diff --git a/backend/test/setup.js b/backend/test/setup.js index 03db2c9f0..8b88b3947 100644 --- a/backend/test/setup.js +++ b/backend/test/setup.js @@ -22,14 +22,12 @@ process.env.USE_PINO_PRETTY = "true"; process.env.TABLE_PROJEKTI = "Projekti-localstack"; process.env.TABLE_FEEDBACK = "Palaute-localstack"; -process.env.TABLE_PROJEKTI_ARCHIVE = "Projekti-arkisto-localstack"; process.env.AWS_REGION = "eu-west-1"; process.env.UPLOAD_BUCKET_NAME = "hassu-localstack-upload"; process.env.YLLAPITO_BUCKET_NAME = "hassu-localstack-yllapito"; process.env.PUBLIC_BUCKET_NAME = "hassu-localstack-public"; process.env.INTERNAL_BUCKET_NAME = "hassu-localstack-internal"; -process.env.ARCHIVE_BUCKET_NAME = "hassu-localstack-archive"; process.env.S3_ENDPOINT = "http://localhost:4566"; // Credentials must be test/test in order to get localstack pre-signed urls to work diff --git a/cypress/integration/2-perusta-projekti/9-hyvaksyntavaihe.spec.js b/cypress/integration/2-perusta-projekti/9-hyvaksyntavaihe.spec.js index 4da37e2a2..6c2e45bee 100644 --- a/cypress/integration/2-perusta-projekti/9-hyvaksyntavaihe.spec.js +++ b/cypress/integration/2-perusta-projekti/9-hyvaksyntavaihe.spec.js @@ -19,10 +19,10 @@ describe("Projektin nahtavillaolovaiheen kuulutustiedot", () => { const today = dayjs().format("YYYY-MM-DDTHH:mm"); - cy.get('[name="kasittelynTila.hyvaksymispaatos.paatoksenPvm"]').should("be.enabled").type(today, { + cy.get('[name="kasittelynTila.hyvaksymispaatos.paatoksenPvm"]').should("be.enabled").clear().type(today, { waitForAnimations: true, }); - cy.get('[name="kasittelynTila.hyvaksymispaatos.asianumero"]').type(asianumero); + cy.get('[name="kasittelynTila.hyvaksymispaatos.asianumero"]').clear().type(asianumero); cy.get("#save").click(); cy.contains("Tallennus onnistui").wait(2000); // extra wait added because somehow the next test brings blank page otherwise diff --git a/deployment/bin/hassu.ts b/deployment/bin/hassu.ts index 1801b4c70..06b4ef5eb 100644 --- a/deployment/bin/hassu.ts +++ b/deployment/bin/hassu.ts @@ -20,7 +20,6 @@ async function main() { uploadBucket: hassuDatabaseStack.uploadBucket, yllapitoBucket: hassuDatabaseStack.yllapitoBucket, internalBucket: hassuDatabaseStack.internalBucket, - archiveBucket: hassuDatabaseStack.archiveBucket, publicBucket: hassuDatabaseStack.publicBucket, }); await hassuBackendStack.process().catch((e) => { diff --git a/deployment/bin/setupEnvironment.ts b/deployment/bin/setupEnvironment.ts index a69ce4320..65e484441 100644 --- a/deployment/bin/setupEnvironment.ts +++ b/deployment/bin/setupEnvironment.ts @@ -209,7 +209,6 @@ async function main() { writeEnvFile(".env.local", { REACT_APP_API_URL: backendStackOutputs.AppSyncAPIURL, INTERNAL_BUCKET_NAME: Config.internalBucketName, - ARCHIVE_BUCKET_NAME: Config.archiveBucketName, FRONTEND_DOMAIN_NAME: frontendStackOutputs.CloudfrontPrivateDNSName, SONARQUBE_HOST_URL: variables.SonarQubeHostURL, SONARQUBE_ACCESS_TOKEN: variables.SonarQubeAccessToken, diff --git a/deployment/lib/hassu-backend.ts b/deployment/lib/hassu-backend.ts index 07cb2c480..f079fab28 100644 --- a/deployment/lib/hassu-backend.ts +++ b/deployment/lib/hassu-backend.ts @@ -18,11 +18,7 @@ import * as events from "@aws-cdk/aws-events"; import { IDomain } from "@aws-cdk/aws-opensearchservice"; import { Effect, ManagedPolicy, PolicyStatement } from "@aws-cdk/aws-iam"; import { Bucket } from "@aws-cdk/aws-s3"; -import { - getEnvironmentVariablesFromSSM, - readAccountStackOutputs, - readFrontendStackOutputs -} from "../bin/setupEnvironment"; +import { getEnvironmentVariablesFromSSM, readAccountStackOutputs, readFrontendStackOutputs } from "../bin/setupEnvironment"; import { LambdaInsightsVersion } from "@aws-cdk/aws-lambda/lib/lambda-insights"; import { RuleTargetInput } from "@aws-cdk/aws-events/lib/input"; import { EmailEventType } from "../../backend/src/email/emailEvent"; @@ -37,7 +33,6 @@ export type HassuBackendStackProps = { uploadBucket: Bucket; yllapitoBucket: Bucket; internalBucket: Bucket; - archiveBucket: Bucket; publicBucket: Bucket; }; @@ -72,11 +67,7 @@ export class HassuBackendStack extends cdk.Stack { const personSearchUpdaterLambda = await this.createPersonSearchUpdaterLambda(commonEnvironmentVariables); const aineistoSQS = await this.createAineistoImporterQueue(); const emailSQS = await this.createEmailQueueSystem(); - const backendLambda = await this.createBackendLambda( - commonEnvironmentVariables, - personSearchUpdaterLambda, - aineistoSQS - ); + const backendLambda = await this.createBackendLambda(commonEnvironmentVariables, personSearchUpdaterLambda, aineistoSQS); this.attachDatabaseToLambda(backendLambda); HassuBackendStack.mapApiResolversToLambda(api, backendLambda); @@ -100,11 +91,7 @@ export class HassuBackendStack extends cdk.Stack { } } - private static configureOpenSearchAccess( - projektiSearchIndexer: NodejsFunction, - backendLambda: NodejsFunction, - searchDomain: IDomain - ) { + private static configureOpenSearchAccess(projektiSearchIndexer: NodejsFunction, backendLambda: NodejsFunction, searchDomain: IDomain) { // Grant write access to the app-search index searchDomain.grantIndexWrite("projekti-" + Config.env + "-*", projektiSearchIndexer); searchDomain.grantIndexReadWrite("projekti-" + Config.env + "-*", backendLambda); @@ -225,9 +212,7 @@ export class HassuBackendStack extends cdk.Stack { commandHooks: { beforeBundling(inputDir: string, outputDir: string): string[] { return [ - `${path.normalize( - "./node_modules/.bin/copyfiles" - )} -f -u 1 ${inputDir}/backend/src/asiakirja/files/* ${outputDir}/files`, + `${path.normalize("./node_modules/.bin/copyfiles")} -f -u 1 ${inputDir}/backend/src/asiakirja/files/* ${outputDir}/files`, ]; }, afterBundling(): string[] { @@ -247,9 +232,7 @@ export class HassuBackendStack extends cdk.Stack { tracing: Tracing.PASS_THROUGH, insightsVersion: LambdaInsightsVersion.VERSION_1_0_98_0, }); - backendLambda.addToRolePolicy( - new PolicyStatement({ effect: Effect.ALLOW, actions: ["ssm:GetParameter"], resources: ["*"] }) - ); + backendLambda.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: ["ssm:GetParameter"], resources: ["*"] })); backendLambda.addToRolePolicy( new PolicyStatement({ effect: Effect.ALLOW, @@ -257,9 +240,7 @@ export class HassuBackendStack extends cdk.Stack { resources: [personSearchUpdaterLambda.functionArn], }) ); - backendLambda.role?.addManagedPolicy( - ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLambdaInsightsExecutionRolePolicy") - ); + backendLambda.role?.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLambdaInsightsExecutionRolePolicy")); aineistoSQS.grantSendMessages(backendLambda); @@ -268,7 +249,6 @@ export class HassuBackendStack extends cdk.Stack { this.props.yllapitoBucket.grantReadWrite(backendLambda); this.props.internalBucket.grantReadWrite(backendLambda); this.props.publicBucket.grantReadWrite(backendLambda); - this.props.archiveBucket.grantReadWrite(backendLambda); return backendLambda; } @@ -327,12 +307,7 @@ export class HassuBackendStack extends cdk.Stack { new PolicyStatement({ effect: Effect.ALLOW, actions: ["cloudfront:CreateInvalidation"], - resources: [ - "arn:aws:cloudfront::" + - cdk.Aws.ACCOUNT_ID + - ":distribution/" + - frontendStackOutputs?.CloudfrontDistributionId, - ], + resources: ["arn:aws:cloudfront::" + cdk.Aws.ACCOUNT_ID + ":distribution/" + frontendStackOutputs?.CloudfrontDistributionId], }) ); } @@ -342,10 +317,7 @@ export class HassuBackendStack extends cdk.Stack { return importer; } - private async createEmailQueueLambda( - commonEnvironmentVariables: Record, - emailSQS: Queue - ): Promise { + private async createEmailQueueLambda(commonEnvironmentVariables: Record, emailSQS: Queue): Promise { const importer = new NodejsFunction(this, "EmailQueueLambda", { functionName: "hassu-email-" + Config.env, runtime: lambda.Runtime.NODEJS_14_X, @@ -388,10 +360,6 @@ export class HassuBackendStack extends cdk.Stack { projektiTable.grantFullAccess(backendFn); backendFn.addEnvironment("TABLE_PROJEKTI", projektiTable.tableName); - const archiveTable = this.props.projektiTable; - archiveTable.grantFullAccess(backendFn); - backendFn.addEnvironment("TABLE_PROJEKTI_ARCHIVE", archiveTable.tableName); - const feedbackTable = this.props.feedbackTable; feedbackTable.grantFullAccess(backendFn); backendFn.addEnvironment("TABLE_FEEDBACK", feedbackTable.tableName); @@ -413,7 +381,6 @@ export class HassuBackendStack extends cdk.Stack { YLLAPITO_BUCKET_NAME: this.props.yllapitoBucket.bucketName, PUBLIC_BUCKET_NAME: this.props.publicBucket.bucketName, INTERNAL_BUCKET_NAME: this.props.internalBucket.bucketName, - ARCHIVE_BUCKET_NAME: this.props.archiveBucket.bucketName, }; } diff --git a/deployment/lib/hassu-database.ts b/deployment/lib/hassu-database.ts index e8ab37c8b..05b762f0b 100644 --- a/deployment/lib/hassu-database.ts +++ b/deployment/lib/hassu-database.ts @@ -122,14 +122,7 @@ export class HassuDatabaseStack extends cdk.Stack { }, }); - // TODO: uncomment after cdk-construct+aws-cdk version upgrade - // const cfnTable = table.node.defaultChild as CfnTable; - // cfnTable.tableClass = "STANDARD_INFREQUENT_ACCESS"; - - if (Config.isPermanentEnvironment()) { - HassuDatabaseStack.enableBackup(table); - table.applyRemovalPolicy(RemovalPolicy.RETAIN); - } + table.applyRemovalPolicy(RemovalPolicy.DESTROY); return table; } @@ -161,15 +154,16 @@ export class HassuDatabaseStack extends cdk.Stack { const bucket = new Bucket(this, "ArchiveBucket", { bucketName: Config.archiveBucketName, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, - removalPolicy: RemovalPolicy.RETAIN, + removalPolicy: RemovalPolicy.DESTROY, versioned: true, }); - if (Config.isPermanentEnvironment()) { - HassuDatabaseStack.enableBackup(bucket); - } else { - // Do not keep archived data in developer environments - bucket.addLifecycleRule({ id: this.stackName + "-upload-delete-after-48h", expiration: Duration.hours(48) }); - } + + // These exports added here so that the references from backend stack can be removed first. This code and archive bucket can be deleted after this code has been deployed once to every environment + this.exportValue(bucket.bucketArn); + this.exportValue(bucket.bucketName); + + // Do not keep archived data in developer environments + bucket.addLifecycleRule({ id: this.stackName + "-upload-delete-after-48h", expiration: Duration.hours(24) }); return bucket; } diff --git a/graphql/inputs.graphql b/graphql/inputs.graphql index e61a572ac..3089a77b1 100644 --- a/graphql/inputs.graphql +++ b/graphql/inputs.graphql @@ -10,6 +10,8 @@ input TallennaProjektiInput { suunnitteluVaihe: SuunnitteluVaiheInput nahtavillaoloVaihe: NahtavillaoloVaiheInput hyvaksymisPaatosVaihe: HyvaksymisPaatosVaiheInput + jatkoPaatos1Vaihe: HyvaksymisPaatosVaiheInput + jatkoPaatos2Vaihe: HyvaksymisPaatosVaiheInput kasittelynTila: KasittelyntilaInput } diff --git a/graphql/operations.graphql b/graphql/operations.graphql index 6bad88fc8..9b8b526b4 100644 --- a/graphql/operations.graphql +++ b/graphql/operations.graphql @@ -36,5 +36,5 @@ type Mutation { synkronoiProjektiMuutoksetVelhosta(oid: String!): Velho - arkistoiProjekti(oid: String!): ArkistointiTunnus + arkistoiProjekti(oid: String!): String } diff --git a/graphql/types.graphql b/graphql/types.graphql index 79d7966f5..0f5291968 100644 --- a/graphql/types.graphql +++ b/graphql/types.graphql @@ -57,8 +57,9 @@ enum Status { NAHTAVILLAOLO HYVAKSYMISMENETTELYSSA HYVAKSYTTY - LAINVOIMA - ARKISTOITU + EPAAKTIIVINEN + JATKOPAATOS_1 + JATKOPAATOS_2 } enum AsiakirjaTyyppi { @@ -109,6 +110,10 @@ type Projekti implements IProjekti { nahtavillaoloVaiheJulkaisut: [NahtavillaoloVaiheJulkaisu!] hyvaksymisPaatosVaihe: HyvaksymisPaatosVaihe hyvaksymisPaatosVaiheJulkaisut: [HyvaksymisPaatosVaiheJulkaisu!] + jatkoPaatos1Vaihe: HyvaksymisPaatosVaihe + jatkoPaatos1VaiheJulkaisut: [HyvaksymisPaatosVaiheJulkaisu!] + jatkoPaatos2Vaihe: HyvaksymisPaatosVaihe + jatkoPaatos2VaiheJulkaisut: [HyvaksymisPaatosVaiheJulkaisu!] velho: Velho! kayttoOikeudet: [ProjektiKayttaja!] """ @@ -482,6 +487,8 @@ type ProjektiJulkinen implements IProjekti { paivitetty: String nahtavillaoloVaihe: NahtavillaoloVaiheJulkaisuJulkinen hyvaksymisPaatosVaihe: HyvaksymisPaatosVaiheJulkaisuJulkinen + jatkoPaatos1Vaihe: HyvaksymisPaatosVaiheJulkaisuJulkinen + jatkoPaatos2Vaihe: HyvaksymisPaatosVaiheJulkaisuJulkinen } type Suunnitelma { @@ -696,11 +703,6 @@ type Kielitiedot { projektinNimiVieraskielella: String } -type ArkistointiTunnus { - oid: String! - timestamp: String! -} - interface IProjektiHakutulos { tulokset: [ProjektiHakutulosDokumentti!] } diff --git a/src/components/kansalaisenEtusivu/Hakutulokset.tsx b/src/components/kansalaisenEtusivu/Hakutulokset.tsx index 4c492c4ba..73b192e55 100644 --- a/src/components/kansalaisenEtusivu/Hakutulokset.tsx +++ b/src/components/kansalaisenEtusivu/Hakutulokset.tsx @@ -34,8 +34,6 @@ function getSivuTilanPerusteella(tila: Status | null | undefined) { return "hyvaksymismenettelyssa"; case Status.HYVAKSYTTY: return "hyvaksymispaatos"; - case Status.LAINVOIMA: - return "lainvoima"; default: return ""; } diff --git a/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/Lukunakyma.tsx b/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/Lukunakyma.tsx index b03881947..1651a52f8 100644 --- a/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/Lukunakyma.tsx +++ b/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/Lukunakyma.tsx @@ -13,6 +13,7 @@ import { splitFilePath } from "../../../../util/fileUtil"; import { Link } from "@mui/material"; import lowerCase from "lodash/lowerCase"; import IlmoituksenVastaanottajatLukutila from "./IlmoituksenVastaanottajatLukutila"; +import ButtonFlatWithIcon from "@components/button/ButtonFlat"; interface Props { hyvaksymisPaatosVaiheJulkaisu?: HyvaksymisPaatosVaiheJulkaisu | null; @@ -61,9 +62,21 @@ export default function HyvaksymisKuulutusLukunakyma({ hyvaksymisPaatosVaiheJulk

Kuulutuspäivä

Kuulutusvaihe päättyy

{kuulutusPaiva}

-

+

+ {process.env.ENVIRONMENT != "prod" && ( + { + e.preventDefault(); + window.location.assign(`/api/test/${projekti.oid}/hyvaksymispaatosmenneisyyteen`); + }} + > + Siirrä menneisyyteen (TESTAAJILLE) + + )}

Päätöksen päivä

diff --git a/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/index.tsx b/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/index.tsx index 7edbba71d..4b3269e82 100644 --- a/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/index.tsx +++ b/src/components/projekti/hyvaksyminen/kuulutuksenTiedot/index.tsx @@ -1,8 +1,8 @@ import { yupResolver } from "@hookform/resolvers/yup"; -import { TallennaProjektiInput, KirjaamoOsoite, HyvaksymisPaatosVaiheInput } from "@services/api"; +import { HyvaksymisPaatosVaiheInput, KirjaamoOsoite, TallennaProjektiInput } from "@services/api"; import Notification, { NotificationType } from "@components/notification/Notification"; import React, { ReactElement, useEffect, useMemo } from "react"; -import { UseFormProps, useForm, FormProvider } from "react-hook-form"; +import { FormProvider, useForm, UseFormProps } from "react-hook-form"; import { ProjektiLisatiedolla, useProjekti } from "src/hooks/useProjekti"; import { hyvaksymispaatosKuulutusSchema } from "src/schemas/hyvaksymispaatosKuulutus"; import Painikkeet from "./Painikkeet"; diff --git a/src/components/projekti/kansalaisnakyma/ProjektiJulkinenPageLayout.tsx b/src/components/projekti/kansalaisnakyma/ProjektiJulkinenPageLayout.tsx index c9e4d08e2..948cb80ca 100644 --- a/src/components/projekti/kansalaisnakyma/ProjektiJulkinenPageLayout.tsx +++ b/src/components/projekti/kansalaisnakyma/ProjektiJulkinenPageLayout.tsx @@ -31,8 +31,9 @@ export default function ProjektiPageLayout({ children, title, selectedStep }: Pr NAHTAVILLAOLO: 2, HYVAKSYMISMENETTELYSSA: 3, HYVAKSYTTY: 4, - LAINVOIMA: 4, // Lainvoima-vaihetta ei kansalaisella. Poistuu samalla kun Statuksesta, jos ei tarvetta virkamiespuolellakaan - ARKISTOITU: -1, + JATKOPAATOS_1: 5, + JATKOPAATOS_2: 6, + EPAAKTIIVINEN: -1, }; return ( diff --git a/src/components/projekti/nahtavillaolo/kuulutuksentiedot/KuulutuksessaEsitettavatYhteystiedot.tsx b/src/components/projekti/nahtavillaolo/kuulutuksentiedot/KuulutuksessaEsitettavatYhteystiedot.tsx index fa4422ecd..942dfccaf 100644 --- a/src/components/projekti/nahtavillaolo/kuulutuksentiedot/KuulutuksessaEsitettavatYhteystiedot.tsx +++ b/src/components/projekti/nahtavillaolo/kuulutuksentiedot/KuulutuksessaEsitettavatYhteystiedot.tsx @@ -15,6 +15,7 @@ import capitalize from "lodash/capitalize"; import replace from "lodash/replace"; import { useProjekti } from "src/hooks/useProjekti"; import { KuulutuksenTiedotFormValues } from "./KuulutuksenTiedot"; +import { findJulkaisutWithTila } from "../../../../../backend/src/projekti/projektiUtil"; const defaultYhteystieto: YhteystietoInput = { etunimi: "", @@ -27,12 +28,7 @@ const defaultYhteystieto: YhteystietoInput = { interface Props {} function hasHyvaksyttyNahtavillaoloVaiheJulkaisu(projekti: Projekti | null | undefined) { - return ( - ( - projekti?.nahtavillaoloVaiheJulkaisut?.filter((julkaisu) => julkaisu.tila == NahtavillaoloVaiheTila.HYVAKSYTTY) || - [] - ).length > 0 - ); + return (findJulkaisutWithTila(projekti?.nahtavillaoloVaiheJulkaisut, NahtavillaoloVaiheTila.HYVAKSYTTY) || []).length > 0; } export default function EsitettavatYhteystiedot({}: Props): ReactElement { diff --git a/src/locales/fi/projekti.json b/src/locales/fi/projekti.json index ffe6fe851..ea452ad01 100644 --- a/src/locales/fi/projekti.json +++ b/src/locales/fi/projekti.json @@ -74,7 +74,10 @@ "HYVAKSYTTY": "Hyväksymispäätös", "HYVAKSYMISMENETTELYSSA": "Hyväksynnässä", "LAINVOIMA": "Lainvoima", - "ARKISTOITU": "Arkistoitu" + "ARKISTOITU": "Arkistoitu", + "EPAAKTIIVINEN" : "Epäaktiivinen", + "JATKOPAATOS_1" : "1. jatkopäätös", + "JATKOPAATOS_2" : "2. jatkopäätös" }, "projekti-status-kansalaiselle": { "ALOITUSKUULUTUS": "Aloituskuulutus", @@ -82,7 +85,9 @@ "NAHTAVILLAOLO": "Suunnitelmat nähtävillä", "HYVAKSYTTY": "Suunnitelma hyväksytty", "HYVAKSYMISMENETTELYSSA": "Hyväksymismenettelyssä", - "LAINVOIMA": "Lainvoimainen" + "LAINVOIMA": "Lainvoimainen", + "JATKOPAATOS_1" : "1. jatkopäätös", + "JATKOPAATOS_2" : "2. jatkopäätös" }, "ui-kuvatekstit": { "eu_aluerahoitus": "EU aluerahoitus" diff --git a/src/locales/sv/projekti.json b/src/locales/sv/projekti.json index 5c2664930..b9496c099 100644 --- a/src/locales/sv/projekti.json +++ b/src/locales/sv/projekti.json @@ -74,7 +74,10 @@ "HYVAKSYTTY": "RUOTSIKSI Hyväksymispäätös", "HYVAKSYMISMENETTELYSSA": "RUOTSIKSI Hyväksynnässä", "LAINVOIMA": "RUOTSIKSI Lainvoima", - "ARKISTOITU": "RUOTSIKSI Arkistoitu" + "ARKISTOITU": "RUOTSIKSI Arkistoitu", + "EPAAKTIIVINEN": "RUOTSIKSI Epäaktiivinen", + "JATKOPAATOS_1": "RUOTSIKSI 1. jatkopäätös", + "JATKOPAATOS_2": "RUOTSIKSI 2. jatkopäätös" }, "projekti-status-kansalaiselle": { "ALOITUSKUULUTUS": "RUOTSIKSI Aloituskuulutus", @@ -82,7 +85,9 @@ "NAHTAVILLAOLO": "RUOTSIKSI Suunnitelmat nähtävillä", "HYVAKSYTTY": "RUOTSIKSI Suunnitelma hyväksytty", "HYVAKSYMISMENETTELYSSA": "RUOTSIKSI Hyväksymismenettelyssä", - "LAINVOIMA": "RUOTSIKSI Lainvoimainen" + "LAINVOIMA": "RUOTSIKSI Lainvoimainen", + "JATKOPAATOS_1": "RUOTSIKSI 1. jatkopäätös", + "JATKOPAATOS_2": "RUOTSIKSI 2. jatkopäätös" }, "ui-kuvatekstit": { "eu_aluerahoitus": "RUOTSIKSI EU aluerahoitus" diff --git a/src/pages/api/test/[oid]/hyvaksymispaatosmenneisyyteen.dev.ts b/src/pages/api/test/[oid]/hyvaksymispaatosmenneisyyteen.dev.ts new file mode 100644 index 000000000..62a8c98af --- /dev/null +++ b/src/pages/api/test/[oid]/hyvaksymispaatosmenneisyyteen.dev.ts @@ -0,0 +1,17 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { projektiDatabase } from "../../../../../backend/src/database/projektiDatabase"; +import { authenticateAndLoadProjekti } from "../../../../util/apiUtil.dev"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const dbProjekti = await authenticateAndLoadProjekti(req, res); + if (!dbProjekti) { + return; + } + if (dbProjekti?.hyvaksymisPaatosVaiheJulkaisut) { + for (const julkaisu of dbProjekti.hyvaksymisPaatosVaiheJulkaisut) { + julkaisu.kuulutusVaihePaattyyPaiva = "2020-01-01"; + await projektiDatabase.updateHyvaksymisPaatosVaiheJulkaisu(dbProjekti, julkaisu); + } + } + res.send("OK"); +} diff --git a/src/pages/api/test/[oid]/nahtavillaolomenneisyyteen.dev.ts b/src/pages/api/test/[oid]/nahtavillaolomenneisyyteen.dev.ts index 3f01ef20e..ba50aac01 100644 --- a/src/pages/api/test/[oid]/nahtavillaolomenneisyyteen.dev.ts +++ b/src/pages/api/test/[oid]/nahtavillaolomenneisyyteen.dev.ts @@ -1,29 +1,10 @@ import { NextApiRequest, NextApiResponse } from "next"; import { projektiDatabase } from "../../../../../backend/src/database/projektiDatabase"; -import { validateApiCredentials } from "../../../../util/basicAuthentication"; +import { authenticateAndLoadProjekti } from "../../../../util/apiUtil.dev"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - let environment = process.env.ENVIRONMENT; - if ((environment == "dev" || environment == "test") && !(await validateApiCredentials(req.headers.authorization))) { - res.status(401); - res.setHeader("www-authenticate", "Basic"); - - res.send(""); - return; - } - - const oid = req.query["oid"]; - if (oid instanceof Array || !oid || oid.length == 0) { - res.status(400); - res.send(""); - return; - } - - res.setHeader("Content-Type", "text/plain; charset=UTF-8"); - - let dbProjekti = await projektiDatabase.loadProjektiByOid(oid); + const dbProjekti = await authenticateAndLoadProjekti(req, res); if (!dbProjekti) { - res.send("Projektia ei löydy"); return; } if (dbProjekti?.nahtavillaoloVaiheJulkaisut) { diff --git a/src/pages/yllapito/index.tsx b/src/pages/yllapito/index.tsx index 5009ce5b6..8c67f00d2 100644 --- a/src/pages/yllapito/index.tsx +++ b/src/pages/yllapito/index.tsx @@ -193,7 +193,6 @@ const VirkamiesHomePage = () => { Status.NAHTAVILLAOLO, Status.HYVAKSYMISMENETTELYSSA, Status.HYVAKSYTTY, - Status.LAINVOIMA, ]; return ( diff --git a/src/services/api/commonApi.ts b/src/services/api/commonApi.ts index a95443dc4..ab954427b 100644 --- a/src/services/api/commonApi.ts +++ b/src/services/api/commonApi.ts @@ -85,7 +85,7 @@ export class API extends AbstractApi { fetchResults: true, }); const data = queryResponse.data?.[operation.name]; - if (queryResponse.errors && !data) { + if (queryResponse.errors) { for (const error of queryResponse.errors) { if (error.message === ERROR_MESSAGE_NOT_AUTHENTICATED) { window.location.assign("/yllapito/kirjaudu"); diff --git a/src/services/api/fragmentTypes.json b/src/services/api/fragmentTypes.json index e337dfd28..dd5c057f6 100644 --- a/src/services/api/fragmentTypes.json +++ b/src/services/api/fragmentTypes.json @@ -109,12 +109,6 @@ "possibleTypes": null, "__typename": "__Type" }, - { - "kind": "OBJECT", - "name": "ArkistointiTunnus", - "possibleTypes": null, - "__typename": "__Type" - }, { "kind": "ENUM", "name": "AsiakirjaTyyppi", diff --git a/src/util/apiUtil.dev.ts b/src/util/apiUtil.dev.ts new file mode 100644 index 000000000..f35465f32 --- /dev/null +++ b/src/util/apiUtil.dev.ts @@ -0,0 +1,30 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { DBProjekti } from "../../backend/src/database/model"; +import { validateApiCredentials } from "./basicAuthentication"; +import { projektiDatabase } from "../../backend/src/database/projektiDatabase"; + +export async function authenticateAndLoadProjekti(req: NextApiRequest, res: NextApiResponse): Promise { + let environment = process.env.ENVIRONMENT; + if ((environment == "dev" || environment == "test") && !(await validateApiCredentials(req.headers.authorization))) { + res.status(401); + res.setHeader("www-authenticate", "Basic"); + + res.send(""); + return; + } + + const oid = req.query["oid"]; + if (oid instanceof Array || !oid || oid.length == 0) { + res.status(400); + res.send(""); + return; + } + + res.setHeader("Content-Type", "text/plain; charset=UTF-8"); + + let dbProjekti = await projektiDatabase.loadProjektiByOid(oid); + if (dbProjekti) { + return dbProjekti; + } + res.send("Projektia ei löydy"); +}