diff --git a/backend/integrationtest/api/testUtil/tests.ts b/backend/integrationtest/api/testUtil/tests.ts index 4bc970022..b5abd5b86 100644 --- a/backend/integrationtest/api/testUtil/tests.ts +++ b/backend/integrationtest/api/testUtil/tests.ts @@ -139,7 +139,7 @@ export async function testProjektinTiedot(oid: string): Promise { export async function testAloitusKuulutusEsikatselu(oid: string): Promise { // Generate Aloituskuulutus PDF - const pdf = await api.esikatseleAsiakirjaPDF(oid, AsiakirjaTyyppi.ALOITUSKUULUTUS, Kieli.SUOMI); + const pdf = await api.esikatseleAsiakirjaPDF(oid, AsiakirjaTyyppi.ALOITUSKUULUTUS, Kieli.SUOMI, { oid }); expect(pdf.nimi).to.include(".pdf"); expect(pdf.sisalto).not.to.be.empty; expect(pdf.sisalto.length).to.be.greaterThan(50000); diff --git a/backend/integrationtest/email/email.test.ts b/backend/integrationtest/email/email.test.ts index 9f272e63f..67832a9c7 100644 --- a/backend/integrationtest/email/email.test.ts +++ b/backend/integrationtest/email/email.test.ts @@ -24,7 +24,7 @@ describe.skip("Email", () => { }, kayttoOikeudet: [], }; - const pdf = await new AsiakirjaService().createPdf({ + const pdf = await new AsiakirjaService().createAloituskuulutusPdf({ aloitusKuulutusJulkaisu: asiakirjaAdapter.adaptAloitusKuulutusJulkaisu(projekti), asiakirjaTyyppi: AsiakirjaTyyppi.ALOITUSKUULUTUS, kieli: Kieli.SUOMI, diff --git a/backend/src/asiakirja/asiakirjaService.ts b/backend/src/asiakirja/asiakirjaService.ts index 698b12b82..38a673812 100644 --- a/backend/src/asiakirja/asiakirjaService.ts +++ b/backend/src/asiakirja/asiakirjaService.ts @@ -1,48 +1,60 @@ import { AsiakirjaTyyppi, Kieli, PDF, ProjektiTyyppi } from "../../../common/graphql/apiModel"; -import { AloitusKuulutusJulkaisu, DBProjekti } from "../database/model/projekti"; +import { AloitusKuulutusJulkaisu, DBProjekti, NahtavillaoloVaihe } from "../database/model"; import { AloitusKuulutus10T } from "./suunnittelunAloitus/aloitusKuulutus10T"; import { AloitusKuulutus10R } from "./suunnittelunAloitus/aloitusKuulutus10R"; import { Ilmoitus12T } from "./suunnittelunAloitus/ilmoitus12T"; import { Ilmoitus12R } from "./suunnittelunAloitus/ilmoitus12R"; import { Kutsu20 } from "./suunnittelunAloitus/Kutsu20"; -import { Vuorovaikutus } from "../database/model/suunnitteluVaihe"; +import { Vuorovaikutus } from "../database/model/"; import { Kutsu21 } from "./suunnittelunAloitus/Kutsu21"; import { EmailOptions } from "../email/email"; +import { Kutsu30 } from "./suunnittelunAloitus/Kutsu30"; +import { kirjaamoOsoitteetService } from "../kirjaamoOsoitteet/kirjaamoOsoitteetService"; -interface CreatePdfOptions { - projekti?: DBProjekti; - aloitusKuulutusJulkaisu?: AloitusKuulutusJulkaisu; - vuorovaikutus?: Vuorovaikutus; +interface CreateNahtavillaoloKuulutusPdfOptions { + projekti: DBProjekti; + nahtavillaoloVaihe: NahtavillaoloVaihe; + kieli: Kieli; + luonnos: boolean; +} + +interface YleisotilaisuusKutsuPdfOptions { + projekti: DBProjekti; + vuorovaikutus: Vuorovaikutus; + kieli: Kieli; + luonnos: boolean; +} + +interface AloituskuulutusPdfOptions { + aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu; asiakirjaTyyppi: AsiakirjaTyyppi; kieli: Kieli; luonnos: boolean; } export enum AsiakirjanMuoto { - TIE, - RATA, + TIE = "TIE", + RATA = "RATA", } -export function determineAsiakirjaMuoto(tyyppi: ProjektiTyyppi, vaylamuoto: string[]): AsiakirjanMuoto | null { +export function determineAsiakirjaMuoto(tyyppi: ProjektiTyyppi, vaylamuoto: string[]): AsiakirjanMuoto | undefined { if (tyyppi === ProjektiTyyppi.TIE || (tyyppi === ProjektiTyyppi.YLEINEN && vaylamuoto?.includes("tie"))) { return AsiakirjanMuoto.TIE; } else if (tyyppi === ProjektiTyyppi.RATA || (tyyppi === ProjektiTyyppi.YLEINEN && vaylamuoto?.includes("rata"))) { return AsiakirjanMuoto.RATA; } - return null; + throw new Error("Asiakirjan muotoa ei voitu päätellä"); } export class AsiakirjaService { - createPdf({ - projekti, + createAloituskuulutusPdf({ asiakirjaTyyppi, aloitusKuulutusJulkaisu, - vuorovaikutus, kieli, - luonnos - }: CreatePdfOptions): Promise { + luonnos, + }: AloituskuulutusPdfOptions): Promise { let pdf: Promise; - const asiakirjanMuoto = determineAsiakirjaMuoto( + const asiakirjanMuoto: AsiakirjanMuoto | undefined = determineAsiakirjaMuoto( aloitusKuulutusJulkaisu.velho.tyyppi, aloitusKuulutusJulkaisu.velho.vaylamuoto ); @@ -58,7 +70,7 @@ export class AsiakirjaService { break; default: throw new Error( - `Aloituskuulutuspohjaa ei pystytä päättelemään. tyyppi: '${aloitusKuulutusJulkaisu.velho.tyyppi}', vaylamuoto: '${aloitusKuulutusJulkaisu.velho?.vaylamuoto}'` + `Aloituskuulutuspohjaa ei pystytä päättelemään. asiakirjanMuoto:'${asiakirjanMuoto}' tyyppi: '${aloitusKuulutusJulkaisu.velho.tyyppi}', vaylamuoto: '${aloitusKuulutusJulkaisu.velho?.vaylamuoto}'` ); } break; @@ -76,41 +88,42 @@ export class AsiakirjaService { ); } break; - case AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU: - switch (asiakirjanMuoto) { - case AsiakirjanMuoto.TIE: - case AsiakirjanMuoto.RATA: - pdf = new Kutsu20(projekti, vuorovaikutus, kieli, asiakirjanMuoto).pdf(luonnos); - break; - default: - throw new Error( - `Ilmoituspohjaa ei pystytä päättelemään. tyyppi: '${aloitusKuulutusJulkaisu.velho.tyyppi}', vaylamuoto: '${aloitusKuulutusJulkaisu.velho?.vaylamuoto}'` - ); - } - break; default: throw new Error(`Asiakirjatyyppi ('${asiakirjaTyyppi}') ei ole vielä tuettu`); } return pdf; } - createEmail({ projekti, asiakirjaTyyppi, vuorovaikutus, kieli }: CreatePdfOptions): EmailOptions { - const asiakirjanMuoto = determineAsiakirjaMuoto(projekti.velho.tyyppi, projekti.velho.vaylamuoto); + createYleisotilaisuusKutsuPdf({ + projekti, + vuorovaikutus, + kieli, + luonnos, + }: YleisotilaisuusKutsuPdfOptions): Promise { + const asiakirjanMuoto = determineAsiakirjaMuoto(projekti?.velho?.tyyppi, projekti?.velho?.vaylamuoto); + return new Kutsu20(projekti, vuorovaikutus, kieli, asiakirjanMuoto).pdf(luonnos); + } - switch (asiakirjaTyyppi) { - case AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU: - switch (asiakirjanMuoto) { - case AsiakirjanMuoto.TIE: - case AsiakirjanMuoto.RATA: - return new Kutsu21(projekti, vuorovaikutus, kieli, asiakirjanMuoto).createEmail(); - default: - throw new Error( - `Ilmoituspohjaa ei pystytä päättelemään. tyyppi: '${projekti.velho.tyyppi}', vaylamuoto: '${projekti.velho?.vaylamuoto}'` - ); - } - default: - throw new Error(`Asiakirjatyyppi ('${asiakirjaTyyppi}') ei ole vielä tuettu`); - } + async createNahtavillaoloKuulutusPdf({ + projekti, + nahtavillaoloVaihe, + kieli, + luonnos, + }: CreateNahtavillaoloKuulutusPdfOptions): Promise { + const asiakirjanMuoto = determineAsiakirjaMuoto(projekti?.velho?.tyyppi, projekti?.velho?.vaylamuoto); + + return new Kutsu30( + projekti, + nahtavillaoloVaihe, + kieli, + asiakirjanMuoto, + await kirjaamoOsoitteetService.listKirjaamoOsoitteet() + ).pdf(luonnos); + } + + createYleisotilaisuusKutsuEmail({ projekti, vuorovaikutus, kieli }: YleisotilaisuusKutsuPdfOptions): EmailOptions { + const asiakirjanMuoto = determineAsiakirjaMuoto(projekti.velho.tyyppi, projekti.velho.vaylamuoto); + return new Kutsu21(projekti, vuorovaikutus, kieli, asiakirjanMuoto).createEmail(); } } diff --git a/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts b/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts index a72710422..b6d1c92d7 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/Kutsu20.ts @@ -54,7 +54,14 @@ export class Kutsu20 extends CommonPdf { super( fileName, kieli, - new KutsuAdapter({ projekti, kieli, asiakirjanMuoto, projektiTyyppi: projekti.velho.tyyppi }), + new KutsuAdapter({ + oid: projekti.oid, + kielitiedot: projekti.kielitiedot, + velho: projekti.velho, + kieli, + asiakirjanMuoto, + projektiTyyppi: projekti.velho.tyyppi, + }), fileName ); this.projekti = projekti; diff --git a/backend/src/asiakirja/suunnittelunAloitus/Kutsu21.ts b/backend/src/asiakirja/suunnittelunAloitus/Kutsu21.ts index ec9301e7b..d1e2ea5ca 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/Kutsu21.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/Kutsu21.ts @@ -1,6 +1,5 @@ -import { DBProjekti } from "../../database/model/projekti"; +import { DBProjekti, Vuorovaikutus } from "../../database/model"; import { Kieli } from "../../../../common/graphql/apiModel"; -import { Vuorovaikutus } from "../../database/model/suunnitteluVaihe"; import { AsiakirjanMuoto } from "../asiakirjaService"; import { EmailOptions } from "../../email/email"; import { KutsuAdapter } from "./KutsuAdapter"; @@ -12,7 +11,9 @@ export class Kutsu21 { constructor(projekti: DBProjekti, vuorovaikutus: Vuorovaikutus, kieli: Kieli, asiakirjanMuoto: AsiakirjanMuoto) { this.kieli = kieli == Kieli.SAAME ? Kieli.SUOMI : kieli; this.adapter = new KutsuAdapter({ - projekti, + oid: projekti.oid, + kielitiedot: projekti.kielitiedot, + velho: projekti.velho, kieli: this.kieli, asiakirjanMuoto, projektiTyyppi: projekti.velho.tyyppi, diff --git a/backend/src/asiakirja/suunnittelunAloitus/Kutsu30.ts b/backend/src/asiakirja/suunnittelunAloitus/Kutsu30.ts new file mode 100644 index 000000000..2bd57aa55 --- /dev/null +++ b/backend/src/asiakirja/suunnittelunAloitus/Kutsu30.ts @@ -0,0 +1,205 @@ +import { DBProjekti, Velho } from "../../database/model/"; +import { Kieli, KirjaamoOsoite } from "../../../../common/graphql/apiModel"; +import { NahtavillaoloVaihe } from "../../database/model"; +import { CommonPdf } from "./commonPdf"; +import { AsiakirjanMuoto } from "../asiakirjaService"; +import { translate } from "../../util/localization"; +import { formatList, KutsuAdapter } from "./KutsuAdapter"; +import { formatProperNoun } from "../../../../common/util/formatProperNoun"; +import PDFStructureElement = PDFKit.PDFStructureElement; + +const headers: Record = { + SUOMI: "KUULUTUS SUUNNITELMAN NÄHTÄVILLE ASETTAMISESTA", + RUOTSI: "Kungörelse om framläggandet av planen", +}; + +const fileNamePrefix: Record = { + SUOMI: "Nähtävilläolo", + RUOTSI: "INBJUDAN TILL DISKUSSION", +}; + +function createFileName(kieli: Kieli, asiakirjanMuoto: AsiakirjanMuoto, projektiNimi: string) { + const language = kieli == Kieli.SAAME ? Kieli.SUOMI : kieli; + return fileNamePrefix[language] + "_" + projektiNimi; +} + +function formatDate(date: string) { + return date ? new Date(date).toLocaleDateString("fi") : "DD.MM.YYYY"; +} + +export class Kutsu30 extends CommonPdf { + private readonly asiakirjanMuoto: AsiakirjanMuoto; + // private readonly oid: string; + private readonly nahtavillaoloVaihe: NahtavillaoloVaihe; + // private readonly kayttoOikeudet: DBVaylaUser[]; + protected header: string; + protected kieli: Kieli; + private velho: Velho; + private kirjaamoOsoitteet: KirjaamoOsoite[]; + + constructor( + projekti: DBProjekti, + nahtavillaoloVaihe: NahtavillaoloVaihe, + kieli: Kieli, + asiakirjanMuoto: AsiakirjanMuoto, + kirjaamoOsoitteet: KirjaamoOsoite[] + ) { + const velho = projekti.velho; + const kutsuAdapter = new KutsuAdapter({ + oid: projekti.oid, + kielitiedot: projekti.kielitiedot, + velho, + kieli, + asiakirjanMuoto, + projektiTyyppi: velho.tyyppi, + }); + const fileName = createFileName(kieli, asiakirjanMuoto, kutsuAdapter.nimi); + super(fileName, kieli, kutsuAdapter, fileName); + this.velho = velho; + const language = kieli == Kieli.SAAME ? Kieli.SUOMI : kieli; + this.header = headers[language]; + this.kieli = kieli; + + this.nahtavillaoloVaihe = nahtavillaoloVaihe; + this.asiakirjanMuoto = asiakirjanMuoto; + this.kirjaamoOsoitteet = kirjaamoOsoitteet; + } + + protected addContent(): void { + const vaylaTilaaja = this.isVaylaTilaaja(this.velho); + const elements: PDFKit.PDFStructureElementChild[] = [ + this.logo(vaylaTilaaja), + this.headerElement(this.header), + this.titleElement(), + ...this.addDocumentElements(), + ].filter((element) => element); + this.doc.addStructure(this.doc.struct("Document", {}, elements)); + } + + protected titleElement(): PDFStructureElement { + return this.doc.struct("H2", {}, () => { + this.doc.text(this.kutsuAdapter.title).font("ArialMT").moveDown(); + }); + } + + protected addDocumentElements(): PDFStructureElement[] { + const hallintolaki62 = this.selectText([ + "Asianosaisten katsotaan saaneen tiedon suunnittelun käynnistymisestä ja tutkimusoikeudesta seitsemäntenä päivänä kuulutuksen julkaisusta (hallintolaki 62 a §). ", + "RUOTSIKSI Asianosaisten katsotaan saaneen tiedon suunnittelun käynnistymisestä ja tutkimusoikeudesta seitsemäntenä päivänä kuulutuksen julkaisusta (hallintolaki 62 a §). ", + ]); + return [ + this.paragraph(this.startOfPlanningPhrase), + this.localizedParagraph([ + `Kuulutus on julkaistu tietoverkossa ${this.tilaajaGenetiivi} verkkosivuilla ${this.kuulutusPaiva}. ${hallintolaki62}`, + `RUOTSIKSI Kuulutus on julkaistu tietoverkossa ${this.tilaajaGenetiivi} verkkosivuilla ${this.kuulutusPaiva}. ${hallintolaki62}`, + ]), + this.pidetaanNahtavillaParagraph(), + this.muistutuksetParagraph(), + this.tietosuojaParagraph(), + this.lisatietojaAntavatParagraph(), + this.doc.struct("P", {}, this.moreInfoElements(this.nahtavillaoloVaihe.kuulutusYhteystiedot, undefined, true)), + this.kutsuja(), + ].filter((elem) => elem); + } + + private kutsuja() { + if (this.asiakirjanMuoto == AsiakirjanMuoto.TIE) { + return this.paragraph(this.kutsuAdapter.tilaajaOrganisaatio); + } else { + return this.paragraph(translate("vaylavirasto", this.kieli)); + } + } + + private get startOfPlanningPhrase() { + let organisaatiotText: string; + if (this.asiakirjanMuoto == AsiakirjanMuoto.RATA) { + organisaatiotText = translate("info.nahtavillaolo.rata.vaylavirasto_on_laatinut", this.kieli); + } else { + organisaatiotText = translate("info.nahtavillaolo.ei-rata.vaylavirasto_on_laatinut", this.kieli); + } + return `${organisaatiotText} ${this.kutsuAdapter.nimi}, ${this.getKunnatString()}`; + } + + private getKunnatString() { + const organisaatiot = this.velho?.kunnat; + const trimmattutOrganisaatiot = organisaatiot.map((organisaatio) => formatProperNoun(organisaatio)); + const viimeinenOrganisaatio = trimmattutOrganisaatiot.slice(-1); + const muut = trimmattutOrganisaatiot.slice(0, -1); + return formatList([...muut, ...viimeinenOrganisaatio], this.kieli); + } + + protected get kuulutusPaiva(): string { + return formatDate(this.nahtavillaoloVaihe?.kuulutusPaiva); + } + + protected get tilaajaGenetiivi(): string { + const tilaajaOrganisaatio = this.velho?.tilaajaOrganisaatio; + return tilaajaOrganisaatio + ? tilaajaOrganisaatio === "Väylävirasto" + ? "Väyläviraston" + : tilaajaOrganisaatio?.slice(0, -1) + "ksen" + : "Tilaajaorganisaation"; + } + + private pidetaanNahtavillaParagraph() { + return this.doc.struct("P", {}, [ + () => { + this.doc.text( + this.selectText([ + `Suunnitelma pidetään yleisesti nähtävänä ${formatDate(this.nahtavillaoloVaihe.kuulutusPaiva)}-${formatDate( + this.nahtavillaoloVaihe.kuulutusVaihePaattyyPaiva + )} välisen ajan ${this.tilaajaGenetiivi} tietoverkossa `, + `RUOTSIKSI Suunnitelma pidetään yleisesti nähtävänä ${formatDate( + this.nahtavillaoloVaihe.kuulutusPaiva + )}-${formatDate(this.nahtavillaoloVaihe.kuulutusVaihePaattyyPaiva)} välisen ajan ${ + this.tilaajaGenetiivi + } tietoverkossa `, + ]), + { + continued: true, + } + ); + }, + this.doc.struct("Link", { alt: this.kuulutusOsoite }, () => { + this.doc.fillColor("blue").text(this.kuulutusOsoite, { + link: this.kuulutusOsoite, + continued: true, + underline: true, + }); + }), + () => { + this.doc.fillColor("black").text(" (LjMTL 27 §). ", { link: undefined, underline: false }).moveDown(); + }, + ]); + } + + private get kuulutusOsoite() { + return this.isVaylaTilaaja(this.velho) + ? "https://www.vayla.fi/kuulutukset" + : "https://www.ely-keskus.fi/kuulutukset"; + } + + private muistutuksetParagraph() { + return this.paragraph( + `Kiinteistön omistajilla ja muilla asianosaisilla sekä niillä, joiden asumiseen, työntekoon tai muihin oloihin suunnitelma saattaa vaikuttaa, on mahdollisuus muistutusten tekemiseen suunnitelmasta. Muistutukset on toimitettava ${this.kutsuAdapter.tilaajaOrganisaatiolle} ennen nähtävänäoloajan päättymistä (LjMTL 27 §) osoitteeseen ${this.kirjaamo}. Muistutukseen on liitettävä asian asianumero ${this.kutsuAdapter.asianumero}.` + ); + } + + private tietosuojaParagraph() { + if (this.asiakirjanMuoto !== AsiakirjanMuoto.RATA) { + return this.viranomainenTietosuojaParagraph(this.velho); + } else { + return this.vaylavirastoTietosuojaParagraph(); + } + } + + get kirjaamo(): string { + const kirjaamoOsoite = this.kirjaamoOsoitteet + .filter((osoite) => osoite.nimi == this.velho.suunnittelustaVastaavaViranomainen.toString()) + .pop(); + if (kirjaamoOsoite) { + return kirjaamoOsoite.sahkoposti; + } + return ""; + } +} diff --git a/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts b/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts index 2e20255e5..e565ab20b 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/KutsuAdapter.ts @@ -1,24 +1,16 @@ import { Kieli, ProjektiRooli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel"; -import { - AloitusKuulutusJulkaisu, - DBProjekti, - DBVaylaUser, - Kielitiedot, - SuunnitteluSopimus, - Velho, - Vuorovaikutus, - Yhteystieto, -} from "../../database/model"; +import { DBVaylaUser, Kielitiedot, SuunnitteluSopimus, Velho, Vuorovaikutus, Yhteystieto } from "../../database/model"; import { AsiakirjanMuoto } from "../asiakirjaService"; import { translate } from "../../util/localization"; import { linkSuunnitteluVaihe } from "../../../../common/links"; import { formatProperNoun } from "../../../../common/util/formatProperNoun"; export type KutsuAdapterProps = { - aloitusKuulutusJulkaisu?: AloitusKuulutusJulkaisu; - projekti?: DBProjekti; + oid?: string; + velho: Velho; + kielitiedot: Kielitiedot; kieli: Kieli; - asiakirjanMuoto?: AsiakirjanMuoto; + asiakirjanMuoto: AsiakirjanMuoto; projektiTyyppi: ProjektiTyyppi; vuorovaikutus?: Vuorovaikutus; kayttoOikeudet?: DBVaylaUser[]; @@ -42,24 +34,25 @@ export class KutsuAdapter { private readonly velho: Velho; private readonly kieli: Kieli; private readonly asiakirjanMuoto: AsiakirjanMuoto; - private readonly oid: string; + private readonly oid?: string; private readonly projektiTyyppi: ProjektiTyyppi; - private readonly vuorovaikutus: Vuorovaikutus; - private kayttoOikeudet: DBVaylaUser[]; + private readonly vuorovaikutus?: Vuorovaikutus; + private kayttoOikeudet?: DBVaylaUser[]; private readonly kielitiedot: Kielitiedot; constructor({ - projekti, - aloitusKuulutusJulkaisu, + oid, + velho, + kielitiedot, asiakirjanMuoto, kieli, projektiTyyppi, vuorovaikutus, kayttoOikeudet, }: KutsuAdapterProps) { - this.oid = projekti?.oid; - this.velho = aloitusKuulutusJulkaisu ? aloitusKuulutusJulkaisu.velho : projekti.velho; - this.kielitiedot = aloitusKuulutusJulkaisu ? aloitusKuulutusJulkaisu.kielitiedot : projekti.kielitiedot; + this.oid = oid; + this.velho = velho; + this.kielitiedot = kielitiedot; this.kieli = kieli; this.asiakirjanMuoto = asiakirjanMuoto; this.projektiTyyppi = projektiTyyppi; @@ -193,6 +186,10 @@ export class KutsuAdapter { ); } + get asianumero(): string { + return this.velho.asiatunnusVayla || this.velho.asiatunnusELY; + } + selectText(suomi: string, ruotsi?: string, saame?: string): string { if (this.kieli == Kieli.SUOMI && suomi) { return suomi; diff --git a/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10R.ts b/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10R.ts index 6013c0f2c..0b0d36a76 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10R.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10R.ts @@ -1,6 +1,7 @@ import { SuunnittelunAloitusPdf } from "./suunnittelunAloitusPdf"; import { AloitusKuulutusJulkaisu } from "../../database/model/projekti"; import { Kieli } from "../../../../common/graphql/apiModel"; +import { AsiakirjanMuoto } from "../asiakirjaService"; const headers: Record = { SUOMI: "KUULUTUS SUUNNITTELUN ALOITTAMISESTA", @@ -8,9 +9,8 @@ const headers: Record = { }; export class AloitusKuulutus10R extends SuunnittelunAloitusPdf { - constructor(aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, kieli: Kieli) { - super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli]); //TODO lisää tuki Saamen eri muodoille + super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli], AsiakirjanMuoto.RATA); //TODO lisää tuki Saamen eri muodoille } protected addDocumentElements(): PDFKit.PDFStructureElementChild[] { @@ -44,7 +44,14 @@ export class AloitusKuulutus10R extends SuunnittelunAloitusPdf { this.vaylavirastoTietosuojaParagraph(), this.lisatietojaAntavatParagraph(), - this.doc.struct("P", {}, this.moreInfoElements(this.aloitusKuulutusJulkaisu.yhteystiedot,this.aloitusKuulutusJulkaisu.suunnitteluSopimus)), + this.doc.struct( + "P", + {}, + this.moreInfoElements( + this.aloitusKuulutusJulkaisu.yhteystiedot, + this.aloitusKuulutusJulkaisu.suunnitteluSopimus + ) + ), ]; } } diff --git a/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10T.ts b/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10T.ts index 4e9044026..6a2ecc921 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10T.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/aloitusKuulutus10T.ts @@ -2,6 +2,7 @@ import { SuunnittelunAloitusPdf } from "./suunnittelunAloitusPdf"; import { AloitusKuulutusJulkaisu } from "../../database/model/projekti"; import { Kieli } from "../../../../common/graphql/apiModel"; import { formatProperNoun } from "../../../../common/util/formatProperNoun"; +import { AsiakirjanMuoto } from "../asiakirjaService"; const headers: Record = { SUOMI: "KUULUTUS SUUNNITTELUN ALOITTAMISESTA", @@ -10,7 +11,7 @@ const headers: Record = { export class AloitusKuulutus10T extends SuunnittelunAloitusPdf { constructor(aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, kieli: Kieli) { - super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli]); //TODO lisää tuki Saamen eri muodoille + super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli], AsiakirjanMuoto.TIE); //TODO lisää tuki Saamen eri muodoille } protected addDocumentElements(): PDFKit.PDFStructureElementChild[] { diff --git a/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12R.ts b/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12R.ts index 9e3fde7f5..eef2ed19b 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12R.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12R.ts @@ -1,6 +1,7 @@ import { SuunnittelunAloitusPdf } from "./suunnittelunAloitusPdf"; import { AloitusKuulutusJulkaisu } from "../../database/model/projekti"; import { Kieli } from "../../../../common/graphql/apiModel"; +import { AsiakirjanMuoto } from "../asiakirjaService"; const headers: Record = { SUOMI: "ILMOITUS VÄYLÄVIRASTON KUULUTUKSESTA", @@ -11,7 +12,7 @@ export class Ilmoitus12R extends SuunnittelunAloitusPdf { private kuulutusOsoite = "https://www.vayla.fi/kuulutukset"; constructor(aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, kieli: Kieli) { - super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli]); //TODO lisää tuki Saamen eri muodoille + super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli], AsiakirjanMuoto.RATA); //TODO lisää tuki Saamen eri muodoille } protected addDocumentElements(): PDFKit.PDFStructureElementChild[] { diff --git a/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12T.ts b/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12T.ts index cdab6dc4d..52974fe00 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12T.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/ilmoitus12T.ts @@ -1,6 +1,7 @@ import { SuunnittelunAloitusPdf } from "./suunnittelunAloitusPdf"; import { AloitusKuulutusJulkaisu } from "../../database/model/projekti"; import { Kieli } from "../../../../common/graphql/apiModel"; +import { AsiakirjanMuoto } from "../asiakirjaService"; const headers: Record = { SUOMI: "ILMOITUS TOIMIVALTAISEN VIRANOMAISEN KUULUTUKSESTA", @@ -9,7 +10,7 @@ const headers: Record = { export class Ilmoitus12T extends SuunnittelunAloitusPdf { constructor(aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, kieli: Kieli) { - super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli]); //TODO lisää tuki Saamen eri muodoille + super(aloitusKuulutusJulkaisu, kieli, headers[kieli == Kieli.SAAME ? Kieli.SUOMI : kieli], AsiakirjanMuoto.TIE); //TODO lisää tuki Saamen eri muodoille } protected addDocumentElements(): PDFKit.PDFStructureElementChild[] { diff --git a/backend/src/asiakirja/suunnittelunAloitus/suunnittelunAloitusPdf.ts b/backend/src/asiakirja/suunnittelunAloitus/suunnittelunAloitusPdf.ts index 5cf17bb39..5145e2fee 100644 --- a/backend/src/asiakirja/suunnittelunAloitus/suunnittelunAloitusPdf.ts +++ b/backend/src/asiakirja/suunnittelunAloitus/suunnittelunAloitusPdf.ts @@ -1,15 +1,24 @@ import { Kieli, ProjektiTyyppi } from "../../../../common/graphql/apiModel"; -import { AloitusKuulutusJulkaisu } from "../../database/model/projekti"; +import { AloitusKuulutusJulkaisu } from "../../database/model"; import { CommonPdf } from "./commonPdf"; import { KutsuAdapter } from "./KutsuAdapter"; +import { AsiakirjanMuoto } from "../asiakirjaService"; import PDFStructureElement = PDFKit.PDFStructureElement; + export abstract class SuunnittelunAloitusPdf extends CommonPdf { protected header: string; protected aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu; - constructor(aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, kieli: Kieli, header: string) { + constructor( + aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, + kieli: Kieli, + header: string, + asiakirjanMuoto: AsiakirjanMuoto + ) { const kutsuAdapter = new KutsuAdapter({ - aloitusKuulutusJulkaisu, + velho: aloitusKuulutusJulkaisu.velho, + asiakirjanMuoto, + kielitiedot: aloitusKuulutusJulkaisu.kielitiedot, kieli, projektiTyyppi: aloitusKuulutusJulkaisu.velho.tyyppi, }); diff --git a/backend/src/handler/asiakirjaHandler.ts b/backend/src/handler/asiakirjaHandler.ts index 59d7053d0..c640fb5a1 100644 --- a/backend/src/handler/asiakirjaHandler.ts +++ b/backend/src/handler/asiakirjaHandler.ts @@ -1,12 +1,81 @@ import { projektiDatabase } from "../database/projektiDatabase"; import { requirePermissionLuku } from "../user"; -import { EsikatseleAsiakirjaPDFQueryVariables, PDF } from "../../../common/graphql/apiModel"; +import { + AsiakirjaTyyppi, + EsikatseleAsiakirjaPDFQueryVariables, + Kieli, + PDF, + TallennaProjektiInput, +} from "../../../common/graphql/apiModel"; import { log } from "../logger"; import { NotFoundError } from "../error/NotFoundError"; import { asiakirjaService } from "../asiakirja/asiakirjaService"; import { projektiAdapter } from "./projektiAdapter"; import { asiakirjaAdapter } from "./asiakirjaAdapter"; -import { Vuorovaikutus } from "../database/model/suunnitteluVaihe"; +import { DBProjekti, Vuorovaikutus } from "../database/model"; + +async function handleAloitusKuulutus( + projekti: DBProjekti, + asiakirjaTyyppi: AsiakirjaTyyppi.ALOITUSKUULUTUS | AsiakirjaTyyppi.ILMOITUS_KUULUTUKSESTA, + kieli: Kieli, + muutokset: TallennaProjektiInput +) { + // AloitusKuulutusJulkaisu is waiting for approval, so that is the version to preview + const aloitusKuulutusJulkaisu = asiakirjaAdapter.findAloitusKuulutusWaitingForApproval(projekti); + if (aloitusKuulutusJulkaisu) { + return asiakirjaService.createAloituskuulutusPdf({ + aloitusKuulutusJulkaisu, + asiakirjaTyyppi, + kieli, + luonnos: true, + }); + } else { + // Previewing projekti with unsaved changes. adaptProjektiToPreview combines database content with the user provided changes + const projektiWithChanges = await projektiAdapter.adaptProjektiToPreview(projekti, muutokset); + projektiWithChanges.velho = projekti.velho; // Restore read-only velho data which was removed by adaptProjektiToSave + projektiWithChanges.suunnitteluSopimus = projekti.suunnitteluSopimus; + + return asiakirjaService.createAloituskuulutusPdf({ + aloitusKuulutusJulkaisu: asiakirjaAdapter.adaptAloitusKuulutusJulkaisu(projektiWithChanges), + asiakirjaTyyppi, + kieli, + luonnos: true, + }); + } +} + +async function handleYleisotilaisuusKutsu( + projekti: DBProjekti, + asiakirjaTyyppi: AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU, + kieli: Kieli, + muutokset: TallennaProjektiInput +) { + // Previewing projekti with unsaved changes. adaptProjektiToPreview combines database content with the user provided changes + const projektiWithChanges = await projektiAdapter.adaptProjektiToPreview(projekti, muutokset); + projektiWithChanges.velho = projekti.velho; // Restore read-only velho data which was removed by adaptProjektiToSave + projektiWithChanges.suunnitteluSopimus = projekti.suunnitteluSopimus; + + return asiakirjaService.createYleisotilaisuusKutsuPdf({ + projekti: projektiWithChanges, + vuorovaikutus: (muutokset.suunnitteluVaihe?.vuorovaikutus as Vuorovaikutus) || null, + kieli, + luonnos: true, + }); +} + +async function handleNahtavillaoloKuulutus(projekti: DBProjekti, kieli: Kieli, muutokset: TallennaProjektiInput) { + // Previewing projekti with unsaved changes. adaptProjektiToPreview combines database content with the user provided changes + const projektiWithChanges = await projektiAdapter.adaptProjektiToPreview(projekti, muutokset); + projektiWithChanges.velho = projekti.velho; // Restore read-only velho data which was removed by adaptProjektiToSave + projektiWithChanges.suunnitteluSopimus = projekti.suunnitteluSopimus; + + return asiakirjaService.createNahtavillaoloKuulutusPdf({ + projekti: projektiWithChanges, + nahtavillaoloVaihe: projektiWithChanges.nahtavillaoloVaihe, + kieli, + luonnos: true, + }); +} export async function lataaAsiakirja({ oid, @@ -19,40 +88,16 @@ export async function lataaAsiakirja({ log.info("Loading projekti", { oid }); const projekti = await projektiDatabase.loadProjektiByOid(oid); if (projekti) { - // AloitusKuulutusJulkaisu is waiting for approval, so that is the version to preview - const aloitusKuulutusJulkaisu = asiakirjaAdapter.findAloitusKuulutusWaitingForApproval(projekti); - if (aloitusKuulutusJulkaisu) { - return asiakirjaService.createPdf({ - aloitusKuulutusJulkaisu, - asiakirjaTyyppi, - kieli, - luonnos: true, - }); - } else { - if (muutokset) { - // Previewing projekti with unsaved changes. adaptProjektiToPreview combines database content with the user provided changes - const projektiWithChanges = await projektiAdapter.adaptProjektiToPreview(projekti, muutokset); - projektiWithChanges.velho = projekti.velho; // Restore read-only velho data which was removed by adaptProjektiToSave - projektiWithChanges.tyyppi = projekti.velho.tyyppi || projekti.tyyppi; // Restore tyyppi - projektiWithChanges.suunnitteluSopimus = projekti.suunnitteluSopimus; - - return asiakirjaService.createPdf({ - projekti: projektiWithChanges, - vuorovaikutus: (muutokset.suunnitteluVaihe?.vuorovaikutus as Vuorovaikutus) || null, - aloitusKuulutusJulkaisu: asiakirjaAdapter.adaptAloitusKuulutusJulkaisu(projektiWithChanges), - asiakirjaTyyppi, - kieli, - luonnos: true, - }); - } else { - // Previewing saved projekti - return asiakirjaService.createPdf({ - aloitusKuulutusJulkaisu: asiakirjaAdapter.adaptAloitusKuulutusJulkaisu(projekti), - asiakirjaTyyppi, - kieli, - luonnos: true, - }); - } + switch (asiakirjaTyyppi) { + case AsiakirjaTyyppi.ILMOITUS_KUULUTUKSESTA: + case AsiakirjaTyyppi.ALOITUSKUULUTUS: + return handleAloitusKuulutus(projekti, asiakirjaTyyppi, kieli, muutokset); + case AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU: + return handleYleisotilaisuusKutsu(projekti, asiakirjaTyyppi, kieli, muutokset); + case AsiakirjaTyyppi.NAHTAVILLAOLOKUULUTUS: + return handleNahtavillaoloKuulutus(projekti, kieli, muutokset); + default: + throw new Error("Not implemented"); } } else { throw new NotFoundError(`Projektia ${oid} ei löydy`); diff --git a/backend/src/handler/tila/aloitusKuulutusTilaManager.ts b/backend/src/handler/tila/aloitusKuulutusTilaManager.ts index 2dc7fd0d9..ced5515a3 100644 --- a/backend/src/handler/tila/aloitusKuulutusTilaManager.ts +++ b/backend/src/handler/tila/aloitusKuulutusTilaManager.ts @@ -13,7 +13,7 @@ async function createAloituskuulutusPDF( projekti: DBProjekti, kieli: Kieli ) { - const pdf = await asiakirjaService.createPdf({ + const pdf = await asiakirjaService.createAloituskuulutusPdf({ asiakirjaTyyppi, aloitusKuulutusJulkaisu: julkaisuWaitingForApproval, kieli, diff --git a/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetHandler.ts b/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetHandler.ts index 74cad0e97..f32129893 100644 --- a/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetHandler.ts +++ b/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetHandler.ts @@ -1,18 +1,8 @@ -import { GetParameterResult } from "aws-sdk/clients/ssm"; -import log from "loglevel"; import { KirjaamoOsoite } from "../../../common/graphql/apiModel"; -import { getSSM } from "../aws/client"; import { requirePermissionLuku } from "../user"; +import { kirjaamoOsoitteetService } from "./kirjaamoOsoitteetService"; export async function listKirjaamoOsoitteet(): Promise { requirePermissionLuku(); - const parameterName = "/kirjaamoOsoitteet"; - let kirjaamoOsoitteet: KirjaamoOsoite[] = []; - try { - const response: GetParameterResult = await getSSM().getParameter({ Name: parameterName }).promise(); - kirjaamoOsoitteet = response.Parameter?.Value ? JSON.parse(response.Parameter.Value) : []; - } catch (e) { - log.error(`Could not read or parse 'kirjaamoOsoitteet' from SSM`, e); - } - return kirjaamoOsoitteet; + return kirjaamoOsoitteetService.listKirjaamoOsoitteet(); } diff --git a/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetService.ts b/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetService.ts new file mode 100644 index 000000000..a96c13c26 --- /dev/null +++ b/backend/src/kirjaamoOsoitteet/kirjaamoOsoitteetService.ts @@ -0,0 +1,18 @@ +import { GetParameterResult } from "aws-sdk/clients/ssm"; +import log from "loglevel"; +import { KirjaamoOsoite } from "../../../common/graphql/apiModel"; +import { getSSM } from "../aws/client"; + +async function listKirjaamoOsoitteet(): Promise { + const parameterName = "/kirjaamoOsoitteet"; + let kirjaamoOsoitteet: KirjaamoOsoite[] = []; + try { + const response: GetParameterResult = await getSSM().getParameter({ Name: parameterName }).promise(); + kirjaamoOsoitteet = response.Parameter?.Value ? JSON.parse(response.Parameter.Value) : []; + } catch (e) { + log.error(`Could not read or parse 'kirjaamoOsoitteet' from SSM`, e); + } + return kirjaamoOsoitteet; +} + +export const kirjaamoOsoitteetService = { listKirjaamoOsoitteet }; diff --git a/backend/src/vuorovaikutus/vuorovaikutusService.ts b/backend/src/vuorovaikutus/vuorovaikutusService.ts index 517fadf90..ffc3fdee2 100644 --- a/backend/src/vuorovaikutus/vuorovaikutusService.ts +++ b/backend/src/vuorovaikutus/vuorovaikutusService.ts @@ -1,6 +1,6 @@ import { ProjektiAdaptationResult } from "../handler/projektiAdapter"; import { aineistoService } from "../aineisto/aineistoService"; -import { AloitusKuulutusTila, AsiakirjaTyyppi, IlmoituksenVastaanottajat } from "../../../common/graphql/apiModel"; +import { IlmoituksenVastaanottajat } from "../../../common/graphql/apiModel"; import { asiakirjaService } from "../asiakirja/asiakirjaService"; import { fileService } from "../files/fileService"; import { projektiDatabase } from "../database/projektiDatabase"; @@ -44,13 +44,8 @@ class VuorovaikutusService { async handleVuorovaikutusKutsu({ aineistoChanges: { vuorovaikutus }, projekti: { oid } }: ProjektiAdaptationResult) { // Generate invitation PDF const projektiInDB = await projektiDatabase.loadProjektiByOid(oid); - const aloitusKuulutusJulkaisu = projektiInDB.aloitusKuulutusJulkaisut - .filter((julkaisu) => julkaisu.tila == AloitusKuulutusTila.HYVAKSYTTY) - .pop(); - const pdf = await asiakirjaService.createPdf({ - asiakirjaTyyppi: AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU, + const pdf = await asiakirjaService.createYleisotilaisuusKutsuPdf({ projekti: projektiInDB, - aloitusKuulutusJulkaisu, vuorovaikutus, kieli: projektiInDB.kielitiedot.ensisijainenKieli, luonnos: false, @@ -67,9 +62,8 @@ class VuorovaikutusService { contentType: "application/pdf", }); - const emailOptions = asiakirjaService.createEmail({ + const emailOptions = asiakirjaService.createYleisotilaisuusKutsuEmail({ projekti: projektiInDB, - asiakirjaTyyppi: AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU, vuorovaikutus, kieli: projektiInDB.kielitiedot.ensisijainenKieli, luonnos: false, diff --git a/backend/test/asiakirja/__snapshots__/asiakirjaService.test.ts.snap b/backend/test/asiakirja/__snapshots__/asiakirjaService.test.ts.snap index adf168395..3563aef66 100644 --- a/backend/test/asiakirja/__snapshots__/asiakirjaService.test.ts.snap +++ b/backend/test/asiakirja/__snapshots__/asiakirjaService.test.ts.snap @@ -1,63 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 1`] = ` -Object { - "hankkeenKuvaus": Object { - "RUOTSI": "På svenska", - "SAAME": "Saameksi", - "SUOMI": "Lorem Ipsum", - }, - "id": 1, - "kielitiedot": Object { - "ensisijainenKieli": "SUOMI", - "projektinNimiVieraskielella": "Namnet på svenska", - "toissijainenKieli": "RUOTSI", - }, - "kuulutusPaiva": "2022-01-02", - "siirtyySuunnitteluVaiheeseen": "2022-01-01", - "suunnitteluSopimus": Object { - "email": "Joku.Jossain@vayla.fi", - "etunimi": "Joku", - "kunta": "Nokia", - "puhelinnumero": "123", - "sukunimi": "Jossain", - }, - "velho": Object { - "asiatunnusVayla": "A1", - "kunnat": Array [ - "Tampere", - "Nokia", - ], - "maakunnat": Array [ - "Uusimaa", - "Pirkanmaa", - ], - "nimi": "Testiprojekti 1", - "suunnittelustaVastaavaViranomainen": "UUDENMAAN_ELY", - "tilaajaOrganisaatio": "Uudenmaan ELY-keskus", - "tyyppi": "TIE", - "vaylamuoto": Array [ - "tie", - ], - }, - "yhteystiedot": Array [ - Object { - "etunimi": "Marko", - "organisaatio": "Kajaani", - "puhelinnumero": "0293121213", - "sahkoposti": "markku.koi@koi.com", - "sukunimi": "Koi", - }, - ], -} -`; - -exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 2`] = ` Object { "subject": "SUUNNITELMAN LAATIJAN KUTSUSTA YLEISÖTILAISUUTEEN ILMOITTAMINEN", - "text": "Testiprojekti 1, Tampere ja Nokia + "text": "Testiprojekti 1 UUDENMAAN_ELY+TIE+SUOMI, Tampere ja Nokia -Uudenmaan ELY-keskus laatii liikennejärjestelmästä ja maanteistä annetun lain (LjMTL, 503/2005) mukaista tiesuunnitelmaa Testiprojekti 1. +Uudenmaan ELY-keskus laatii liikennejärjestelmästä ja maanteistä annetun lain (LjMTL, 503/2005) mukaista tiesuunnitelmaa Testiprojekti 1 UUDENMAAN_ELY+TIE+SUOMI. Uudenmaan ELY-keskus ilmoittaa, että se julkaisee tietoverkossaan kutsun, joka koskee otsikossa mainitun tiesuunnitelman yleisötilaisuutta (laki liikennejärjestelmästä ja maanteistä 27 §). @@ -76,7 +24,7 @@ LIITTEET Kutsu tiedotus-/yleisötilaisuuteen (20T)", } `; -exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 3`] = ` +exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 2`] = ` Object { "subject": "SUUNNITELMAN LAATIJAN KUTSUSTA YLEISÖTILAISUUTEEN ILMOITTAMINEN", "text": "Namnet på svenska, Tampere och Nokia @@ -100,20 +48,20 @@ LIITTEET Kutsu tiedotus-/yleisötilaisuuteen (20T)", } `; -exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 4`] = ` +exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 3`] = ` Object { - "subject": "SUUNNITELMAN LAATIJAN KUTSUSTA YLEISÖTILAISUUTEEN ILMOITTAMINEN", - "text": "Testiprojekti 1, Tampere ja Nokia + "subject": "", + "text": "Testiprojekti 1 VAYLAVIRASTO+RATA+SUOMI, Tampere ja Nokia -Uudenmaan ELY-keskus laatii liikennejärjestelmästä ja maanteistä annetun lain (LjMTL, 503/2005) mukaista tiesuunnitelmaa Testiprojekti 1. +Väylävirasto laatii liikennejärjestelmästä ja maanteistä annetun lain (LjMTL, 503/2005) mukaista ratasuunnitelmaa Testiprojekti 1 VAYLAVIRASTO+RATA+SUOMI. -Uudenmaan ELY-keskus ilmoittaa, että se julkaisee tietoverkossaan kutsun, joka koskee otsikossa mainitun tiesuunnitelman yleisötilaisuutta (laki liikennejärjestelmästä ja maanteistä 27 §). +Väylävirasto ilmoittaa, että se julkaisee tietoverkossaan kutsun, joka koskee otsikossa mainitun ratasuunnitelman yleisötilaisuutta (laki liikennejärjestelmästä ja maanteistä 27 §). -Kutsu julkaistaan 23.3.2022 Uudenmaan ELY-keskuksen tietoverkossa osoitteessa https://localhost:3000/suunnitelma/1/suunnittelu sekä yhdessä tai useammassa alueella yleisesti ilmestyvässä sanomalehdessä. +Kutsu julkaistaan 23.3.2022 väyläviraston tietoverkossa osoitteessa https://localhost:3000/suunnitelma/1/suunnittelu sekä yhdessä tai useammassa alueella yleisesti ilmestyvässä sanomalehdessä. -Uudenmaan ELY-keskus pyytää Tampere, Nokia ja Uudenmaan ELY-keskusta julkaisemaan liitteenä olevan kutsun tietoverkossaan. Kutsu tulee julkaista tietoverkossa mahdollisuuksien mukaan edellä mainittuna kutsun julkaisupäivänä. Kutsun julkaisemista ei tarvitse todentaa Uudenmaan ELY-keskukselle. +Väylävirasto pyytää Tampere, Nokia ja Väylävirastoa julkaisemaan liitteenä olevan kutsun tietoverkossaan. Kutsu tulee julkaista tietoverkossa mahdollisuuksien mukaan edellä mainittuna kutsun julkaisupäivänä. Kutsun julkaisemista ei tarvitse todentaa Väylävirastolle. -Uudenmaan ELY-keskus käsittelee tiesuunnitelman laatimiseen liittyen tarpeellisia henkilötietoja. Lisätietoja väyläsuunnittelun tietosuojakäytänteistä on saatavilla verkkosivujen tietosuojaosiossa osoitteessa https://www.vayla.fi/tietosuoja. +Väylävirasto käsittelee ratasuunnitelman laatimiseen liittyen tarpeellisia henkilötietoja. Lisätietoja väyläsuunnittelun tietosuojakäytänteistä on saatavilla verkkosivujen tietosuojaosiossa osoitteessa https://www.vayla.fi/tietosuoja. Lisätietoja antaa: Kajaani, Marko Koi, puhelin 0293121213 ja sähköposti markku.koi@koi.com. @@ -124,20 +72,20 @@ LIITTEET Kutsu tiedotus-/yleisötilaisuuteen (20T)", } `; -exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 5`] = ` +exports[`asiakirjaService should generate kutsu 20T/R pdf succesfully 4`] = ` Object { - "subject": "SUUNNITELMAN LAATIJAN KUTSUSTA YLEISÖTILAISUUTEEN ILMOITTAMINEN", + "subject": "", "text": "Namnet på svenska, Tampere och Nokia -RUOTSIKSI Uudenmaan ELY-keskusvägplanen Namnet på svenska. +RUOTSIKSI Väylävirastojärnvägsplanen Namnet på svenska. -RUOTSIKSI Uudenmaan ELY-keskus ilmoittaa, että se julkaisee tietoverkossaan kutsun, joka koskee otsikossa mainitun vägplanen yleisötilaisuutta (laki liikennejärjestelmästä ja maanteistä 27 §). +RUOTSIKSI Väylävirasto ilmoittaa, että se julkaisee tietoverkossaan kutsun, joka koskee otsikossa mainitun järnvägsplanen yleisötilaisuutta (laki liikennejärjestelmästä ja maanteistä 27 §). - + -RUOTSIKSI Uudenmaan ELY-keskus pyytää Tampere, Nokia ja RUOTSIKSI Uudenmaan ELY-keskusta julkaisemaan liitteenä olevan kutsun tietoverkossaan. Kutsu tulee julkaista tietoverkossa mahdollisuuksien mukaan edellä mainittuna kutsun julkaisupäivänä. Kutsun julkaisemista ei tarvitse todentaa RUOTSIKSI Uudenmaan ELY-keskukselle. +RUOTSIKSI Väylävirasto pyytää Tampere, Nokia ja RUOTSIKSI Väylävirastoa julkaisemaan liitteenä olevan kutsun tietoverkossaan. Kutsu tulee julkaista tietoverkossa mahdollisuuksien mukaan edellä mainittuna kutsun julkaisupäivänä. Kutsun julkaisemista ei tarvitse todentaa RUOTSIKSI Väylävirastolle. -RUOTSIKSI Uudenmaan ELY-keskus käsittelee vägplanen laatimiseen liittyen tarpeellisia henkilötietoja. Lisätietoja väyläsuunnittelun tietosuojakäytänteistä on saatavilla verkkosivujen tietosuojaosiossa osoitteessa https://vayla.fi/sv/trafikledsverket/kontaktuppgifter/dataskyddspolicy. +RUOTSIKSI Väylävirasto käsittelee järnvägsplanen laatimiseen liittyen tarpeellisia henkilötietoja. Lisätietoja väyläsuunnittelun tietosuojakäytänteistä on saatavilla verkkosivujen tietosuojaosiossa osoitteessa https://vayla.fi/sv/trafikledsverket/kontaktuppgifter/dataskyddspolicy. Lisätietoja antaa: Kajaani, Marko Koi, puhelin 0293121213 ja sähköposti markku.koi@koi.com. diff --git a/backend/test/asiakirja/asiakirjaService.test.ts b/backend/test/asiakirja/asiakirjaService.test.ts index da355e550..00bb9fb62 100644 --- a/backend/test/asiakirja/asiakirjaService.test.ts +++ b/backend/test/asiakirja/asiakirjaService.test.ts @@ -1,27 +1,42 @@ /* tslint:disable:only-arrow-functions */ import { describe, it } from "mocha"; import { AsiakirjaService } from "../../src/asiakirja/asiakirjaService"; -import { AsiakirjaTyyppi, Kieli, ProjektiTyyppi, Viranomainen } from "../../../common/graphql/apiModel"; +import { AsiakirjaTyyppi, Kieli, KirjaamoOsoite, ProjektiTyyppi, Viranomainen } from "../../../common/graphql/apiModel"; import fs from "fs"; import { asiakirjaAdapter } from "../../src/handler/asiakirjaAdapter"; import { ProjektiFixture } from "../fixture/projektiFixture"; -import { AloitusKuulutusJulkaisu, DBProjekti } from "../../src/database/model/projekti"; -import { SuunnitteluVaihe, Vuorovaikutus } from "../../src/database/model/suunnitteluVaihe"; +import { + AloitusKuulutusJulkaisu, + DBProjekti, + NahtavillaoloVaihe, + SuunnitteluVaihe, + Vuorovaikutus, +} from "../../src/database/model"; import cloneDeep from "lodash/cloneDeep"; import { translate } from "../../src/util/localization"; import { formatList } from "../../src/asiakirja/suunnittelunAloitus/KutsuAdapter"; +import sinon from "sinon"; +import { kirjaamoOsoitteetService } from "../../src/kirjaamoOsoitteet/kirjaamoOsoitteetService"; const { assert, expect } = require("chai"); describe("asiakirjaService", async () => { const projektiFixture = new ProjektiFixture(); + let kirjaamoOsoitteetStub: sinon.SinonStub; + before(() => { + kirjaamoOsoitteetStub = sinon.stub(kirjaamoOsoitteetService, "listKirjaamoOsoitteet"); + }); + + after(() => { + kirjaamoOsoitteetStub.restore(); + }); async function testKuulutusWithLanguage( aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, kieli: Kieli, expectedFilename: string ) { - const pdf = await new AsiakirjaService().createPdf({ + const pdf = await new AsiakirjaService().createAloituskuulutusPdf({ aloitusKuulutusJulkaisu, asiakirjaTyyppi: AsiakirjaTyyppi.ALOITUSKUULUTUS, kieli, @@ -53,17 +68,14 @@ describe("asiakirjaService", async () => { async function testKutsuWithLanguage( projekti: DBProjekti, - aloitusKuulutusJulkaisu: AloitusKuulutusJulkaisu, suunnitteluVaihe: SuunnitteluVaihe, vuorovaikutus: Vuorovaikutus, kieli: Kieli, expectedFilename: string ) { - const pdf = await new AsiakirjaService().createPdf({ + const pdf = await new AsiakirjaService().createYleisotilaisuusKutsuPdf({ projekti: { ...projekti, suunnitteluVaihe }, - aloitusKuulutusJulkaisu, vuorovaikutus, - asiakirjaTyyppi: AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU, kieli, luonnos: true, }); @@ -72,11 +84,9 @@ describe("asiakirjaService", async () => { fs.mkdirSync(".report", { recursive: true }); fs.writeFileSync(".report/" + pdf.nimi, Buffer.from(pdf.sisalto, "base64")); - const email = await new AsiakirjaService().createEmail({ + const email = await new AsiakirjaService().createYleisotilaisuusKutsuEmail({ projekti: { ...projekti, suunnitteluVaihe }, - aloitusKuulutusJulkaisu, vuorovaikutus, - asiakirjaTyyppi: AsiakirjaTyyppi.YLEISOTILAISUUS_KUTSU, kieli, luonnos: true, }); @@ -85,45 +95,81 @@ describe("asiakirjaService", async () => { it("should generate kutsu 20T/R pdf succesfully", async () => { const projekti: DBProjekti = cloneDeep(projektiFixture.dbProjekti1); // Suomi+Ruotsi - const aloitusKuulutusJulkaisu = asiakirjaAdapter.adaptAloitusKuulutusJulkaisu(projekti); - aloitusKuulutusJulkaisu.velho.suunnittelustaVastaavaViranomainen = Viranomainen.UUDENMAAN_ELY; - expect(aloitusKuulutusJulkaisu).toMatchSnapshot(); - + projekti.velho.suunnittelustaVastaavaViranomainen = Viranomainen.UUDENMAAN_ELY; + projekti.velho.tyyppi = ProjektiTyyppi.TIE; + const originalNimi = projekti.velho.nimi; + projekti.velho.nimi = originalNimi + " UUDENMAAN_ELY+TIE+SUOMI"; await testKutsuWithLanguage( projekti, - aloitusKuulutusJulkaisu, { hankkeenKuvaus: projektiFixture.hankkeenKuvausSuunnitteluVaiheessa }, projektiFixture.vuorovaikutus, Kieli.SUOMI, "TS Tie Yleisotilaisuus kutsu.pdf" ); + projekti.velho.nimi = originalNimi + " UUDENMAAN_ELY+TIE+RUOTSI"; await testKutsuWithLanguage( projekti, - aloitusKuulutusJulkaisu, { hankkeenKuvaus: projektiFixture.hankkeenKuvausSuunnitteluVaiheessa }, projektiFixture.vuorovaikutus, Kieli.RUOTSI, "TS Tie INBJUDAN TILL DISKUSSION.pdf" ); - aloitusKuulutusJulkaisu.velho.suunnittelustaVastaavaViranomainen = Viranomainen.VAYLAVIRASTO; - aloitusKuulutusJulkaisu.velho.tyyppi = ProjektiTyyppi.RATA; + projekti.velho.suunnittelustaVastaavaViranomainen = Viranomainen.VAYLAVIRASTO; + projekti.velho.tyyppi = ProjektiTyyppi.RATA; + projekti.velho.nimi = originalNimi + " VAYLAVIRASTO+RATA+SUOMI"; await testKutsuWithLanguage( projekti, - aloitusKuulutusJulkaisu, { hankkeenKuvaus: projektiFixture.hankkeenKuvausSuunnitteluVaiheessa }, projektiFixture.vuorovaikutus, Kieli.SUOMI, - "TS Rata Yleisotilaisuus kutsu.pdf" + "RS Rata Yleisotilaisuus kutsu.pdf" ); + projekti.velho.nimi = originalNimi + " VAYLAVIRASTO+RATA+RUOTSI"; await testKutsuWithLanguage( projekti, - aloitusKuulutusJulkaisu, { hankkeenKuvaus: projektiFixture.hankkeenKuvausSuunnitteluVaiheessa }, projektiFixture.vuorovaikutus, Kieli.RUOTSI, - "TS Rata INBJUDAN TILL DISKUSSION.pdf" + "RS Rata INBJUDAN TILL DISKUSSION.pdf" + ); + }); + + async function testNahtavillaoloKuulutusWithLanguage( + projekti: DBProjekti, + nahtavillaoloVaihe: NahtavillaoloVaihe, + kieli: Kieli, + expectedFilename: string + ) { + const pdf = await new AsiakirjaService().createNahtavillaoloKuulutusPdf({ + projekti: { ...projekti, nahtavillaoloVaihe }, + nahtavillaoloVaihe, + kieli, + luonnos: true, + }); + // expect(pdf.sisalto.length).to.be.greaterThan(50000); + expect(pdf.nimi).to.eq(expectedFilename); + fs.mkdirSync(".report", { recursive: true }); + fs.writeFileSync(".report/" + pdf.nimi, Buffer.from(pdf.sisalto, "base64")); + } + + it("should generate kuulutus 30T/R pdf succesfully", async () => { + kirjaamoOsoitteetStub.resolves([ + { + __typename: "KirjaamoOsoite", + sahkoposti: "uudenmaan_kirjaamo@uudenmaan.ely", + nimi: "UUDENMAAN_ELY", + } as KirjaamoOsoite, + ]); + const projekti: DBProjekti = cloneDeep(projektiFixture.dbProjekti2); + const aloitusKuulutusJulkaisu = asiakirjaAdapter.adaptAloitusKuulutusJulkaisu(projekti); + aloitusKuulutusJulkaisu.velho.suunnittelustaVastaavaViranomainen = Viranomainen.UUDENMAAN_ELY; + await testNahtavillaoloKuulutusWithLanguage( + projekti, + projekti.nahtavillaoloVaihe, + Kieli.SUOMI, + "Nahtavillaolo_Testiprojekti 2.pdf" ); }); diff --git a/backend/test/email/emailHandler.test.ts b/backend/test/email/emailHandler.test.ts index 51596f008..00dcfe9ae 100644 --- a/backend/test/email/emailHandler.test.ts +++ b/backend/test/email/emailHandler.test.ts @@ -62,7 +62,7 @@ describe("emailHandler", () => { subject: "Valtion liikenneväylien suunnittelu: Aloituskuulutus odottaa hyväksyntää A2", text: "Valtion liikenneväylien suunnittelu -järjestelmän projektistasi\n" + - "Testiprojekti 2 email lahetys\n" + + "Testiprojekti 2\n" + "on luotu aloituskuulutus, joka odottaa hyväksyntääsi.\n" + "Voit tarkastella projektia osoitteessa https://localhost:3000/yllapito/projekti/2\n" + "Saat tämän viestin, koska sinut on merkitty projektin projektipäälliköksi. Tämä on automaattinen sähköposti, johon ei voi vastata.", diff --git a/backend/test/fixture/projektiFixture.ts b/backend/test/fixture/projektiFixture.ts index 1646daf85..9aaa7341c 100644 --- a/backend/test/fixture/projektiFixture.ts +++ b/backend/test/fixture/projektiFixture.ts @@ -15,14 +15,13 @@ import { VuorovaikutusTilaisuusTyyppi, Yhteystieto, } from "../../../common/graphql/apiModel"; -import { DBProjekti } from "../../src/database/model/projekti"; -import { Vuorovaikutus } from "../../src/database/model/suunnitteluVaihe"; +import { DBProjekti, Vuorovaikutus } from "../../src/database/model"; export class ProjektiFixture { public PROJEKTI1_NIMI = "Testiprojekti 1"; public PROJEKTI1_MUISTIINPANO_1 = "Testiprojekti 1:n muistiinpano"; public PROJEKTI1_OID = "1"; - public PROJEKTI2_NIMI = "Testiprojekti 2 email lahetys"; + public PROJEKTI2_NIMI = "Testiprojekti 2"; public PROJEKTI2_OID = "2"; esitettavatYhteystiedot = [ @@ -256,6 +255,7 @@ export class ProjektiFixture { vaylamuoto: ["tie"], vastuuhenkilonEmail: ProjektiFixture.pekkaProjariProjektiKayttaja.email, maakunnat: ["Uusimaa", "Pirkanmaa"], + suunnittelustaVastaavaViranomainen: Viranomainen.UUDENMAAN_ELY, asiatunnusVayla: "A" + this.PROJEKTI2_OID, }, aloitusKuulutusJulkaisut: [ @@ -349,6 +349,21 @@ export class ProjektiFixture { siirtyySuunnitteluVaiheeseen: "2022-04-28T14:28", esitettavatYhteystiedot: [], }, + nahtavillaoloVaihe: { + hankkeenKuvaus: { + SUOMI: "Lorem Ipsum nahtavillaoloVaihe", + SAAME: "Saameksi nahtavillaoloVaihe", + }, + kuulutusPaiva: "2022-06-07", + kuulutusVaihePaattyyPaiva: "2042-06-07", + muistutusoikeusPaattyyPaiva: "2042-06-08", + kuulutusYhteysHenkilot: [ + ProjektiFixture.pekkaProjariProjektiKayttaja.kayttajatunnus, + ProjektiFixture.mattiMeikalainenProjektiKayttaja.kayttajatunnus, + ], + ilmoituksenVastaanottajat: this.ilmoituksenVastaanottajat, + kuulutusYhteystiedot: this.esitettavatYhteystiedot, + }, kielitiedot: { ensisijainenKieli: Kieli.SUOMI, toissijainenKieli: Kieli.RUOTSI, diff --git a/backend/test/projektiSearch/__snapshots__/dynamoDBStreamHandler.test.ts.snap b/backend/test/projektiSearch/__snapshots__/dynamoDBStreamHandler.test.ts.snap index 7a17b53da..ef33295cc 100644 --- a/backend/test/projektiSearch/__snapshots__/dynamoDBStreamHandler.test.ts.snap +++ b/backend/test/projektiSearch/__snapshots__/dynamoDBStreamHandler.test.ts.snap @@ -11,11 +11,11 @@ Object { "A123", "A000111", ], - "nimi": "Testiprojekti 2 email lahetys", + "nimi": "Testiprojekti 2", "paivitetty": "2022-03-15T14:30:00.000Z", "projektiTyyppi": "TIE", "projektipaallikko": "Projari, Pekka", - "suunnittelustaVastaavaViranomainen": undefined, + "suunnittelustaVastaavaViranomainen": "UUDENMAAN_ELY", "vaihe": "SUUNNITTELU", "vaylamuoto": Array [ "tie", @@ -80,11 +80,11 @@ Object { "A123", "A000111", ], - "nimi": "Testiprojekti 2 email lahetys", + "nimi": "Testiprojekti 2", "paivitetty": "2022-03-15T14:30:00.000Z", "projektiTyyppi": "TIE", "projektipaallikko": "Projari, Pekka", - "suunnittelustaVastaavaViranomainen": undefined, + "suunnittelustaVastaavaViranomainen": "UUDENMAAN_ELY", "vaihe": "SUUNNITTELU", "vaylamuoto": Array [ "tie", diff --git a/common/abstractApi.ts b/common/abstractApi.ts index 1d2a1c100..42ee54818 100644 --- a/common/abstractApi.ts +++ b/common/abstractApi.ts @@ -231,7 +231,7 @@ export abstract class AbstractApi { oid: string, asiakirjaTyyppi: AsiakirjaTyyppi, kieli: Kieli, - muutokset?: TallennaProjektiInput + muutokset: TallennaProjektiInput ): Promise { return await this.callYllapitoAPI(apiConfig.esikatseleAsiakirjaPDF, { oid, diff --git a/graphql/operations.graphql b/graphql/operations.graphql index a9e756c9d..46195dc88 100644 --- a/graphql/operations.graphql +++ b/graphql/operations.graphql @@ -13,7 +13,7 @@ type Query { oid: String! asiakirjaTyyppi: AsiakirjaTyyppi! kieli: Kieli - muutokset: TallennaProjektiInput + muutokset: TallennaProjektiInput! ): PDF valmisteleTiedostonLataus(tiedostoNimi: String!, contentType: String!): LatausTiedot diff --git a/graphql/types.graphql b/graphql/types.graphql index 215c8c216..2f32244ee 100644 --- a/graphql/types.graphql +++ b/graphql/types.graphql @@ -56,6 +56,9 @@ enum AsiakirjaTyyppi { ALOITUSKUULUTUS ILMOITUS_KUULUTUKSESTA YLEISOTILAISUUS_KUTSU + + NAHTAVILLAOLOKUULUTUS + ILMOITUS_NAHTAVILLAOLOKUULUTUKSESTA } interface IProjekti { diff --git a/src/locales/fi/projekti.json b/src/locales/fi/projekti.json index 12238264b..eee1953a1 100644 --- a/src/locales/fi/projekti.json +++ b/src/locales/fi/projekti.json @@ -21,11 +21,14 @@ }, "nahtavillaolo": { "ei-rata": { - "vaylavirasto_on_laatinut": "Väylävirasto on laatinut liikennejärjestelmästä ja maanteistä annetun lain (503/2005) mukaisen tiesuunnitelman", + "vaylavirasto_on_laatinut": "Väylävirasto on laatinut liikennejärjestelmästä ja maanteistä annetun lain (503/2005) mukaisen yleis-/tiesuunnitelman", "kuulutus_julkaistu": "Kuulutus on julkaistu tietoverkossa Väyläviraston verkkosivuilla", "asianosaisten_katsotaan_saaneen": "Asianosaisten katsotaan saaneen tiedon suunnitelman nähtäville asettamisesta seitsemäntenä päivänä kuulutuksen julkaisemisajankohdasta (hallintolaki 62 a §)", "kiinteiston_omistajilla_ja": "Kiinteistön omistajilla ja muilla asianosaisilla sekä niillä, joiden asumiseen, työntekoon tai muihin oloihin suunnitelma saattaa vaikuttaa, on mahdollisuus muistutusten tekemiseen suunnitelmasta. Muistutukset on toimitettava {{viranomainen}} ennen nähtävänäoloajan päättymistä (LjMTL 27 §) Valtion liikenneväylien suunnittelu -järjestelmään osoitteeseen {{url}}", "sahkopostilla_muistutus": "Sähköpostilla muistutusta lähettäessä on liitettävä viestiin projektin asiatunnus." + }, + "rata": { + "vaylavirasto_on_laatinut": "Väylävirasto on laatinut ratalain (110/2007) mukaisen yleis-/ratasuunnitelman" } }, "hankesuunnitelmista": "Hankesuunnitelmista lorem ipsum-teksti tähän, joka kertoo lyhyesti mitä tämä sivusto pitää sisällään. Lorem ipsum dolor sit amet consectetur adipisicing elit. Magnam dicta voluptatem fugit quos praesentium architecto accusamus non earum fuga veritatis, quia facilis est repellendus et iste similique beatae perferendis totam?" diff --git a/src/locales/sv/projekti.json b/src/locales/sv/projekti.json index e339a4313..042276580 100644 --- a/src/locales/sv/projekti.json +++ b/src/locales/sv/projekti.json @@ -26,6 +26,9 @@ "asianosaisten_katsotaan_saaneen": "RUOTSIKSI Asianosaisten katsotaan saaneen tiedon suunnitelman nähtäville asettamisesta seitsemäntenä päivänä kuulutuksen julkaisemisajankohdasta (hallintolaki 62 a §)", "kiinteiston_omistajilla_ja": "RUOTSIKSI Kiinteistön omistajilla ja muilla asianosaisilla sekä niillä, joiden asumiseen, työntekoon tai muihin oloihin suunnitelma saattaa vaikuttaa, on mahdollisuus muistutusten tekemiseen suunnitelmasta. Muistutukset on toimitettava {{viranomainen}} ennen nähtävänäoloajan päättymistä (LjMTL 27 §) Valtion liikenneväylien suunnittelu -järjestelmään osoitteeseen {{url}}", "sahkopostilla_muistutus": "RUOTSIKSI Sähköpostilla muistutusta lähettäessä on liitettävä viestiin projektin asiatunnus." + }, + "rata": { + "vaylavirasto_on_laatinut": "RUOTSIKSI Väylävirasto on laatinut ratalain (110/2007) mukaisen yleis-/ratasuunnitelman" } }, "hankesuunnitelmista": "RUOTSIKSI Hankesuunnitelmista lorem ipsum-teksti tähän, joka kertoo lyhyesti mitä tämä sivusto pitää sisällään. Lorem ipsum dolor sit amet consectetur adipisicing elit. Magnam dicta voluptatem fugit quos praesentium architecto accusamus non earum fuga veritatis, quia facilis est repellendus et iste similique beatae perferendis totam?"