diff --git a/README.md b/README.md index 8372941..1ac059a 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,64 @@ -

- Nest Logo -

- -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest - -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Coverage -Discord -Backers on Open Collective -Sponsors on Open Collective - - Support us - -

- +# VigiEau Admin - Backend ## Description -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - ## Installation -```bash -$ yarn install -``` +### Pré-requis -## Running the app +Vous aurez besoin de [NodeJS](https://nodejs.org/) v18+ et [Yarn](https://yarnpkg.com/) pour lancer ce projet. -```bash -# development -$ yarn run start +Nous vous recommandons de regarder la [documentation de NestJS](https://nestjs.com/). -# watch mode -$ yarn run start:dev +### Variables d'environnement -# production mode -$ yarn run start:prod +```bash +cp env.example .env ``` -## Test +- NODE_ENV : local / dev / prod +- PORT : Port sur lequel tournera le serveur +- OAUTH2_CLIENT : Se référer à la documentation de [ProConnect](https://www.proconnect.gouv.fr/) +- SESSION_SECRET : Token JWT +- DATABASE : Informations pour se connecter à la DB (Postgres) +- WEBSITE_URL : Site web du frontend (http://localhost:3000 en local) +- DOMAIN : Domaine sur lequel tourne le serveur (localhost en local) +- API_DATAGOUV : Informations pour se connecter à Datagouv et pouvoir upload automatiquement les données de VigiEau +- ADMINJS : User / password pour accéder au backend AdminJS +- S3 : Informations pour se connecter aux buckets S3 +- MAIL : Informations pour se connecter à la boite mail +- MAIL_MTE : Mail générique à renseigner pour l'envoi de mail systématique à une adresse +- DOMAIN_NAME : Domaine du frontend (localhost:3000 en local) +- PATH_TO_WRITE_FILE : Dossier pour stocker les fichiers temporaires ou le serveur peut lire / écrire + +### Installation des dépendances ```bash -# unit tests -$ yarn run test +yarn install +``` -# e2e tests -$ yarn run test:e2e +### Lancer nuxt en mode développement -# test coverage -$ yarn run test:cov +Démarre le serveur sur http://localhost:3001 + +```bash +yarn start:dev ``` -## Support +### Générer le code de production + +Génère le code de production de l’application: + +```bash +yarn build +``` -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). +## Contribution -## Stay in touch +Les Pull Requests sont les bienvenues. Pour des changements majeurs merci d'ouvrir auparavant une issue pour en discuter. -- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) +Assurez-vous de mettre à jour les tests en conséquence. ## License -Nest is [MIT licensed](LICENSE). +[MIT](https://choosealicense.com/licenses/mit/) diff --git a/env.example b/env.example index 04d3cc7..78a1865 100644 --- a/env.example +++ b/env.example @@ -17,9 +17,9 @@ DATABASE_NAME=regleau WEBSITE_URL=http://localhost:3000 DOMAIN=localhost -API_SANDRE= -API_GEO= -API_DATAGOUV= +API_SANDRE=https://services.sandre.eaufrance.fr/ +API_GEO=https://geo.api.gouv.fr +API_DATAGOUV=https://www.data.gouv.fr/api/1/ API_DATAGOUV_KEY= API_DATAGOUV_DATASET= @@ -39,4 +39,10 @@ MAIL_PASSWORD= MAIL_HOST= MAIL_PORT= +MAIL_MTE= + DOMAIN_NAME= + +PATH_TO_WRITE_FILE= + +NODE_TLS_REJECT_UNAUTHORIZED=0 diff --git a/src/admin.module.ts b/src/admin.module.ts index 2a1d0e6..bc214b6 100644 --- a/src/admin.module.ts +++ b/src/admin.module.ts @@ -7,6 +7,11 @@ import { Departement } from './departement/entities/departement.entity'; import { BassinVersant } from './bassin_versant/entities/bassin_versant.entity'; import { Region } from './core/entities/region.entity'; +/** + * Désactivé pour l'instant car au final pas utilisé + * Pour le réactiver il suffit de réimporter AdminModule dans AppModule + */ + const DEFAULT_ADMIN = { email: process.env.ADMINJS_USER, password: process.env.ADMINJS_PASSWORD, diff --git a/src/app.module.ts b/src/app.module.ts index 572c622..173fe8f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,7 +15,6 @@ import { ScheduleModule } from '@nestjs/schedule'; import { DepartementModule } from './departement/departement.module'; import { UsageModule } from './usage/usage.module'; import { ThematiqueModule } from './thematique/thematique.module'; -import { AdminModule } from './admin.module'; import { ArreteRestrictionModule } from './arrete_restriction/arrete_restriction.module'; import { AppController } from './app.controller'; import { LoggerModule } from './logger/logger.module'; @@ -36,6 +35,7 @@ import { UsageFeedbackModule } from './usage_feedback/usage_feedback.module'; import { StatisticModule } from './statistic/statistic.module'; import { ArreteMunicipalModule } from './arrete_municipal/arrete_municipal.module'; import { AbonnementMailModule } from './abonnement_mail/abonnement_mail.module'; +import { isArray, isObject } from './mail_templates/helpers/handlebars_helpers'; // @ts-ignore @Module({ @@ -101,13 +101,20 @@ import { AbonnementMailModule } from './abonnement_mail/abonnement_mail.module'; preview: process.env.NODE_ENV === 'local', template: { dir: __dirname + '/mail_templates', - adapter: new HandlebarsAdapter(), + adapter: new HandlebarsAdapter({'isObject': isObject, 'isArray': isArray}), options: { strict: true, }, }, + options: { + partials: { + dir: __dirname + '/mail_templates/partials', + options: { + strict: true, + }, + }, + }, }), - AdminModule, HealthModule, ArreteCadreModule, AuthModule, diff --git a/src/arrete_cadre/arrete_cadre.module.ts b/src/arrete_cadre/arrete_cadre.module.ts index 8f8e236..0d1f656 100644 --- a/src/arrete_cadre/arrete_cadre.module.ts +++ b/src/arrete_cadre/arrete_cadre.module.ts @@ -11,6 +11,9 @@ import { UserModule } from '../user/user.module'; import { FichierModule } from '../fichier/fichier.module'; import { RestrictionModule } from '../restriction/restriction.module'; import { UsageModule } from '../usage/usage.module'; +import { + ArreteCadreZoneAlerteCommunesModule +} from '../arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.module'; @Module({ imports: [ @@ -23,6 +26,7 @@ import { UsageModule } from '../usage/usage.module'; FichierModule, RestrictionModule, UsageModule, + ArreteCadreZoneAlerteCommunesModule, ], controllers: [ArreteCadreController], providers: [ArreteCadreService], diff --git a/src/arrete_cadre/arrete_cadre.service.ts b/src/arrete_cadre/arrete_cadre.service.ts index 2b462b4..a6c060a 100644 --- a/src/arrete_cadre/arrete_cadre.service.ts +++ b/src/arrete_cadre/arrete_cadre.service.ts @@ -30,6 +30,9 @@ import { FichierService } from '../fichier/fichier.service'; import { RestrictionService } from '../restriction/restriction.service'; import { UsageService } from '../usage/usage.service'; import moment from 'moment/moment'; +import { + ArreteCadreZoneAlerteCommunesService, +} from '../arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.service'; @Injectable() export class ArreteCadreService { @@ -46,6 +49,7 @@ export class ArreteCadreService { private readonly fichierService: FichierService, private readonly restrictionService: RestrictionService, private readonly usageService: UsageService, + private readonly arreteCadreZoneAlerteCommunesService: ArreteCadreZoneAlerteCommunesService, ) { } @@ -80,7 +84,7 @@ export class ArreteCadreService { : In(currentUser.role_departements), }, }; - const acToReturn = await this.arreteCadreRepository.find( { + const acToReturn = await this.arreteCadreRepository.find({ select: { id: true, numero: true, @@ -157,63 +161,30 @@ export class ArreteCadreService { code: In(currentUser.role_departements), }, }; - const [arreteCadre, usagesArreteCadre, departements]: any = await Promise.all( [ - this.arreteCadreRepository.findOne( { - select: { - id: true, - numero: true, - dateDebut: true, - dateFin: true, - statut: true, - fichier: { - id: true, - nom: true, - url: true, - size: true, - }, - departementPilote: { - id: true, - code: true, - nom: true, - }, - zonesAlerte: { - id: true, - code: true, - nom: true, - type: true, - disabled: true, - departement: { - id: true, - code: true, - }, - }, - arretesRestriction: { - id: true, - numero: true, - statut: true, - }, - arreteCadreAbroge: { - id: true, - numero: true, - dateDebut: true, - dateFin: true, - }, - }, - relations: [ - 'departementPilote', - 'zonesAlerte', - 'zonesAlerte.departement', - 'arretesRestriction', - 'fichier', - 'arreteCadreAbroge', - ], - where: whereClause, - order: { - zonesAlerte: { - code: 'ASC', - }, - }, - }), + + const qb = this.arreteCadreRepository.createQueryBuilder('arreteCadre') + .select([ + 'arreteCadre.id', 'arreteCadre.numero', 'arreteCadre.dateDebut', 'arreteCadre.dateFin', 'arreteCadre.statut', + 'fichier.id', 'fichier.nom', 'fichier.url', 'fichier.size', + 'departementPilote.id', 'departementPilote.code', 'departementPilote.nom', + 'zonesAlerte.id', 'zonesAlerte.code', 'zonesAlerte.nom', 'zonesAlerte.type', 'zonesAlerte.disabled', 'zonesAlerte.ressourceInfluencee', + 'departement.id', 'departement.code', + 'arretesRestriction.id', 'arretesRestriction.numero', 'arretesRestriction.statut', + 'arreteCadreAbroge.id', 'arreteCadreAbroge.numero', 'arreteCadreAbroge.dateDebut', 'arreteCadreAbroge.dateFin', + 'aczac.id', 'communes.id', 'communes.code', 'communes.nom', + ]) + .leftJoin('arreteCadre.departementPilote', 'departementPilote') + .leftJoin('arreteCadre.zonesAlerte', 'zonesAlerte') + .leftJoin('zonesAlerte.departement', 'departement') + .leftJoin('arreteCadre.arretesRestriction', 'arretesRestriction') + .leftJoin('arreteCadre.fichier', 'fichier') + .leftJoin('arreteCadre.arreteCadreAbroge', 'arreteCadreAbroge') + .leftJoin('zonesAlerte.arreteCadreZoneAlerteCommunes', 'aczac', 'aczac.arreteCadreId = arreteCadre.id') + .leftJoin('aczac.communes', 'communes') + .where(whereClause) + .orderBy('zonesAlerte.code', 'ASC'); + const [arreteCadre, usagesArreteCadre, departements]: any = await Promise.all([ + qb.getOne(), this.usageService.findByArreteCadre(id), this.departementService.findByArreteCadreId(id), ]); @@ -224,6 +195,13 @@ export class ArreteCadreService { ); } arreteCadre.usages = usagesArreteCadre; + arreteCadre.zonesAlerte.map(za => { + if (za.arreteCadreZoneAlerteCommunes[0] && za.arreteCadreZoneAlerteCommunes[0].communes?.length > 0) { + za.communes = structuredClone(za.arreteCadreZoneAlerteCommunes[0].communes); + } + delete za.arreteCadreZoneAlerteCommunes; + return za; + }); if (departements) { arreteCadre.departements = departements; } @@ -231,7 +209,7 @@ export class ArreteCadreService { } async findDatagouv(): Promise { - return this.arreteCadreRepository.find( { + return this.arreteCadreRepository.find({ select: { id: true, numero: true, @@ -271,7 +249,7 @@ export class ArreteCadreService { } findByArreteRestrictionId(id: number): Promise { - return this.arreteCadreRepository.find( { + return this.arreteCadreRepository.find({ select: { id: true, numero: true, @@ -326,7 +304,7 @@ export class ArreteCadreService { } findByDepartement(depCode: string): Promise { - return this.arreteCadreRepository.find( { + return this.arreteCadreRepository.find({ select: { id: true, numero: true, @@ -355,8 +333,9 @@ export class ArreteCadreService { await this.checkAci(createArreteCadreDto, false, currentUser); const arreteCadre = await this.arreteCadreRepository.save(createArreteCadreDto); - arreteCadre.usages = - await this.usageService.updateAllByArreteCadre(arreteCadre); + arreteCadre.usages = await this.usageService.updateAllByArreteCadre(arreteCadre); + await this.arreteCadreZoneAlerteCommunesService.updateAllByArreteCadre(arreteCadre.id, createArreteCadreDto); + this.sendAciMails(null, arreteCadre, currentUser); return arreteCadre; } @@ -378,8 +357,8 @@ export class ArreteCadreService { id, ...updateArreteCadreDto, }); - arreteCadre.usages = - await this.usageService.updateAllByArreteCadre(arreteCadre); + arreteCadre.usages = await this.usageService.updateAllByArreteCadre(arreteCadre); + await this.arreteCadreZoneAlerteCommunesService.updateAllByArreteCadre(arreteCadre.id, updateArreteCadreDto); await this.repercussionOnAr(oldAc, arreteCadre); this.sendAciMails(oldAc, arreteCadre, currentUser); @@ -558,7 +537,7 @@ export class ArreteCadreService { }, ); const oldUsagesUpdates = oldAc.usages.filter(u => usagesUpdated.some(uu => uu.id === u.id)); - await Promise.all( [ + await Promise.all([ this.restrictionService.deleteZonesByArreteCadreId( zonesDeleted.map((z) => z.id), oldAc.id, diff --git a/src/arrete_cadre/dto/create_update_arrete_cadre.dto.ts b/src/arrete_cadre/dto/create_update_arrete_cadre.dto.ts index 270b966..d17e35d 100644 --- a/src/arrete_cadre/dto/create_update_arrete_cadre.dto.ts +++ b/src/arrete_cadre/dto/create_update_arrete_cadre.dto.ts @@ -19,6 +19,19 @@ class UpdateLinkNestedObjectDto { id: number; } +class UpdateZoneAlerteDto { + @IsNumber() + @ApiProperty({ example: 1, description: 'Identifiant BDD' }) + id: number; + + @IsArray() + @ValidateNested({ each: true }) + @IsOptional() + @Type(() => UpdateLinkNestedObjectDto) + @ApiProperty({ type: [UpdateLinkNestedObjectDto] }) + communes: UpdateLinkNestedObjectDto[]; +} + export class CreateUpdateArreteCadreDto { @IsString() @IsNotEmpty() @@ -35,9 +48,9 @@ export class CreateUpdateArreteCadreDto { @IsArray() @ValidateNested({ each: true }) @IsOptional() - @Type(() => UpdateLinkNestedObjectDto) - @ApiProperty({ type: [UpdateLinkNestedObjectDto] }) - zonesAlerte: UpdateLinkNestedObjectDto[]; + @Type(() => UpdateZoneAlerteDto) + @ApiProperty({ type: [UpdateZoneAlerteDto] }) + zonesAlerte: UpdateZoneAlerteDto[]; @IsArray() @ValidateNested({ each: true }) diff --git a/src/arrete_cadre/entities/arrete_cadre.entity.ts b/src/arrete_cadre/entities/arrete_cadre.entity.ts index e9bd58f..f9375a9 100644 --- a/src/arrete_cadre/entities/arrete_cadre.entity.ts +++ b/src/arrete_cadre/entities/arrete_cadre.entity.ts @@ -21,6 +21,7 @@ import { ArreteRestriction } from '../../arrete_restriction/entities/arrete_rest import { Fichier } from '../../fichier/entities/fichier.entity'; import { Restriction } from '../../restriction/entities/restriction.entity'; import { Usage } from '../../usage/entities/usage.entity'; +import { ArreteCadreZoneAlerteCommunes } from '../../arrete_cadre_zone_alerte_communes/entities/arrete_cadre_zone_alerte_communes.entity'; @Entity() export class ArreteCadre extends BaseEntity { @@ -88,6 +89,9 @@ export class ArreteCadre extends BaseEntity { @OneToMany(() => Restriction, (restriction) => restriction.arreteCadre) restrictions: Restriction[]; + @OneToMany(() => ArreteCadreZoneAlerteCommunes, (arreteCadreZoneAlerteCommunes) => arreteCadreZoneAlerteCommunes.arreteCadre) + arreteCadreZoneAlerteCommunes: ArreteCadreZoneAlerteCommunes[]; + @CreateDateColumn({ select: false, type: 'timestamp' }) created_at: number; diff --git a/src/arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.module.ts b/src/arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.module.ts new file mode 100644 index 0000000..cd56435 --- /dev/null +++ b/src/arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ArreteCadreZoneAlerteCommunes } from './entities/arrete_cadre_zone_alerte_communes.entity'; +import { ArreteCadreZoneAlerteCommunesService } from './arrete_cadre_zone_alerte_communes.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ArreteCadreZoneAlerteCommunes]), + ], + providers: [ArreteCadreZoneAlerteCommunesService], + exports: [ArreteCadreZoneAlerteCommunesService], +}) +export class ArreteCadreZoneAlerteCommunesModule { +} \ No newline at end of file diff --git a/src/arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.service.ts b/src/arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.service.ts new file mode 100644 index 0000000..b924bd5 --- /dev/null +++ b/src/arrete_cadre_zone_alerte_communes/arrete_cadre_zone_alerte_communes.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Not, Repository } from 'typeorm'; +import { ArreteCadreZoneAlerteCommunes } from './entities/arrete_cadre_zone_alerte_communes.entity'; +import { CreateUpdateArreteCadreDto } from '../arrete_cadre/dto/create_update_arrete_cadre.dto'; +import { FindOptionsWhere } from 'typeorm/find-options/FindOptionsWhere'; + +@Injectable() +export class ArreteCadreZoneAlerteCommunesService { + constructor(@InjectRepository(ArreteCadreZoneAlerteCommunes) + private readonly arreteCadreZoneAlerteCommunesRepository: Repository) { + } + + async updateAllByArreteCadre(acId: number, arreteCadre: CreateUpdateArreteCadreDto) { + const zonesWithCommunes = arreteCadre.zonesAlerte.filter(z => z.communes && z.communes.length > 0); + + // SUPPRESSION DES ANCIENNES ZONES / COMMUNES + await this.arreteCadreZoneAlerteCommunesRepository.delete(>{ + arreteCadre: { + id: acId, + }, + id: Not(In(zonesWithCommunes.map(z => z.id))), + }); + + const arreteCadreZoneAlerteCommunes: ArreteCadreZoneAlerteCommunes[] = []; + zonesWithCommunes.forEach(z => { + arreteCadreZoneAlerteCommunes.push( { + arreteCadre: { id: acId }, + zoneAlerte: { id: z.id }, + communes: z.communes + }); + }); + + return this.arreteCadreZoneAlerteCommunesRepository.save(arreteCadreZoneAlerteCommunes); + } +} \ No newline at end of file diff --git a/src/arrete_cadre_zone_alerte_communes/entities/arrete_cadre_zone_alerte_communes.entity.ts b/src/arrete_cadre_zone_alerte_communes/entities/arrete_cadre_zone_alerte_communes.entity.ts new file mode 100644 index 0000000..7194c83 --- /dev/null +++ b/src/arrete_cadre_zone_alerte_communes/entities/arrete_cadre_zone_alerte_communes.entity.ts @@ -0,0 +1,22 @@ +import { BaseEntity, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { ZoneAlerte } from '../../zone_alerte/entities/zone_alerte.entity'; +import { ArreteCadre } from '../../arrete_cadre/entities/arrete_cadre.entity'; +import { Commune } from '../../commune/entities/commune.entity'; + +@Entity() +export class ArreteCadreZoneAlerteCommunes extends BaseEntity { + @PrimaryGeneratedColumn() + id: number; + + @ManyToOne(() => ZoneAlerte, (zoneAlerte) => zoneAlerte.arreteCadreZoneAlerteCommunes, { nullable: false }) + zoneAlerte: ZoneAlerte; + + @ManyToOne(() => ArreteCadre, (arreteCadre) => arreteCadre.arreteCadreZoneAlerteCommunes, { nullable: false }) + arreteCadre: ArreteCadre; + + @ManyToMany(() => Commune, (commune) => commune.arreteCadreZoneAlerteCommunes) + @JoinTable({ + name: 'ac_za_communes' + }) + communes: Commune[]; +} \ No newline at end of file diff --git a/src/arrete_restriction/arrete_restriction.service.ts b/src/arrete_restriction/arrete_restriction.service.ts index 5ccec69..bbd512d 100644 --- a/src/arrete_restriction/arrete_restriction.service.ts +++ b/src/arrete_restriction/arrete_restriction.service.ts @@ -1219,9 +1219,9 @@ export class ArreteRestrictionService { userEmail: currentUser.email, userDepartement: currentUser.role_departements?.join(', '), arreteNumero: oldAr.numero, - oldAr: JSON.stringify(oldArLight), - newAr: JSON.stringify(newArLight), - diffAr: JSON.stringify(diff), + oldAr: oldArLight, + newAr: newArLight, + diffAr: diff, arreteLien: `https://${process.env.DOMAIN_NAME}/arrete-restriction/${oldAr.id}/edition`, }, ); diff --git a/src/commune/commune.service.ts b/src/commune/commune.service.ts index 3e8810d..46133ba 100644 --- a/src/commune/commune.service.ts +++ b/src/commune/commune.service.ts @@ -135,6 +135,7 @@ export class CommuneService { .addSelect('zac.id', 'zac_id') .addSelect('zac.nom', 'zac_nom') .addSelect('zac.type', 'zac_type') + .addSelect('zac.ressourceInfluencee', 'zac_ressource_influencee') .addSelect('zac.niveauGravite', 'zac_niveau_gravite') .addSelect('ST_Area(commune.geom)', 'area') .addSelect('ST_Area(zac.geom)', 'zac_area') @@ -159,6 +160,7 @@ export class CommuneService { id: c.zac_id, nom: c.zac_nom, type: c.zac_type, + ressourceInfluencee: c.zac_ressource_influencee, niveauGravite: c.zac_niveau_gravite, area: c.zac_area, areaCommune: c.zac_commune_area, @@ -177,6 +179,7 @@ export class CommuneService { .addSelect('zac.id', 'zac_id') .addSelect('zac.nom', 'zac_nom') .addSelect('zac.type', 'zac_type') + .addSelect('zac.ressourceInfluencee', 'zac_ressource_influencee') .addSelect('zac.niveauGravite', 'zac_niveau_gravite') .addSelect('ST_Area(commune.geom)', 'area') .addSelect('ST_Area(zac.geom)', 'zac_area') diff --git a/src/commune/entities/commune.entity.ts b/src/commune/entities/commune.entity.ts index 0104cae..2f162b4 100644 --- a/src/commune/entities/commune.entity.ts +++ b/src/commune/entities/commune.entity.ts @@ -15,6 +15,7 @@ import { ZoneAlerteComputed } from '../../zone_alerte_computed/entities/zone_ale import { ZoneAlerteComputedHistoric } from '../../zone_alerte_computed/entities/zone_alerte_computed_historic.entity'; import { StatisticCommune } from '../../statistic_commune/entities/statistic_commune.entity'; import { ArreteMunicipal } from '../../arrete_municipal/entities/arrete_municipal.entity'; +import { ArreteCadreZoneAlerteCommunes } from '../../arrete_cadre_zone_alerte_communes/entities/arrete_cadre_zone_alerte_communes.entity'; @Entity() export class Commune extends BaseEntity { @@ -66,6 +67,11 @@ export class Commune extends BaseEntity { }) arretesMunicipaux: ArreteMunicipal[]; + @ManyToMany(() => ArreteCadreZoneAlerteCommunes, (arreteCadreZoneAlerteCommunes) => arreteCadreZoneAlerteCommunes.communes, { + persistence: false, + }) + arreteCadreZoneAlerteCommunes: ArreteCadreZoneAlerteCommunes[]; + @Column({ nullable: false, default: false }) disabled: boolean; diff --git a/src/config/config.service.ts b/src/config/config.service.ts index 11ae527..d56a17b 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -27,7 +27,6 @@ export class ConfigService { } async setConfig(computeMapDate: string, computeStatsDate: string) { - console.log('SET CONFIG', computeMapDate, computeStatsDate); await this.configRepository.createQueryBuilder() .update() .set({ computeMapDate }) diff --git a/src/datagouv/datagouv.service.ts b/src/datagouv/datagouv.service.ts index 6bec9d9..41ffa1f 100644 --- a/src/datagouv/datagouv.service.ts +++ b/src/datagouv/datagouv.service.ts @@ -42,7 +42,7 @@ export class DatagouvService { 'restrictions': 'e403a885-5eaf-411d-a03e-751a9c22930d', 'pmtiles_archive': 'b0b246c3-f724-4eb2-a83a-c516e0044aa2', 'geojson_archive': '3972a125-2372-41f1-b3f5-25794f860414', - 'arretes_cadre': '', + 'arretes_cadre': '0732e970-c12c-4e6a-adca-5ac9dbc3fdfa', }; constructor(private readonly httpService: HttpService, @@ -209,7 +209,7 @@ export class DatagouvService { date_debut: arrete.dateDebut, date_fin: arrete.dateFin, statut: arrete.statut, - departement_pilote: arrete.departementPilote?.code, + departement_pilote: arrete.departementPilote ? arrete.departementPilote.code : '', departements: arrete.departements.map(d => d.code), chemin_fichier: arrete.fichier ? arrete.fichier?.url : '', zones_alerte: arrete.zonesAlerte.map(zone => { @@ -316,7 +316,7 @@ export class DatagouvService { const dateDebut = date ? date : moment(); for (let y = dateDebut.year(); y <= moment().year(); y++) { - // await this.generateMapsArchive(dateDebut, y, true); + await this.generateMapsArchive(dateDebut, y, true); await this.generateMapsArchive(dateDebut, y, false); } } @@ -331,13 +331,13 @@ export class DatagouvService { try { const { data } = await firstValueFrom( this.httpService.get( - `${this.configService.get('S3_VHOST')}${this.configService.get('S3_PREFIX')}${geojsonOrPmtiles}/zones_${geojsonOrPmtiles}_${year}.zip`, + `${this.configService.get('S3_VHOST')}${this.configService.get('S3_PREFIX') ? this.configService.get('S3_PREFIX') : ''}${geojsonOrPmtiles}/zones_${geojsonOrPmtiles}_${year}.zip`, { responseType: 'arraybuffer' }, ), ); dataZip = data; } catch (e) { - this.logger.error(`ARCHIVE ${this.configService.get('S3_VHOST')}${this.configService.get('S3_PREFIX')}${geojsonOrPmtiles}/zones_${geojsonOrPmtiles}_${year}.zip non accessible`, e); + this.logger.error(`ARCHIVE ${this.configService.get('S3_VHOST')}${this.configService.get('S3_PREFIX') ? this.configService.get('S3_PREFIX') : ''}${geojsonOrPmtiles}/zones_${geojsonOrPmtiles}_${year}.zip non accessible`, e); } const zip = dataZip ? await JSZip.loadAsync(dataZip) : new JSZip(); diff --git a/src/departement/departement.service.ts b/src/departement/departement.service.ts index 1e62ed7..7b299ea 100644 --- a/src/departement/departement.service.ts +++ b/src/departement/departement.service.ts @@ -40,6 +40,7 @@ export class DepartementService { 'zonesAlerte.nom', 'zonesAlerte.code', 'zonesAlerte.type', + 'zonesAlerte.ressourceInfluencee', 'arretesCadre.id', ]) .leftJoin('departement.zonesAlerte', 'zonesAlerte') diff --git a/src/mail_templates/helpers/handlebars_helpers.ts b/src/mail_templates/helpers/handlebars_helpers.ts new file mode 100644 index 0000000..b198cbe --- /dev/null +++ b/src/mail_templates/helpers/handlebars_helpers.ts @@ -0,0 +1,7 @@ +export function isObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function isArray(value) { + return Array.isArray(value); +} \ No newline at end of file diff --git a/src/mail_templates/maj_ar.hbs b/src/mail_templates/maj_ar.hbs index a6ca10b..fb2bfd8 100644 --- a/src/mail_templates/maj_ar.hbs +++ b/src/mail_templates/maj_ar.hbs @@ -298,11 +298,20 @@ l’arrêté {{ arreteNumero }}.

Voici les modifications effectuées :
- {{ diffAr }}

- Ancien AR:
- {{ oldAr }}

- Nouvel AR:
- {{ newAr }}
+

    + {{> recursiveTree diffAr }} +
+

+ Ancien AR:
+
    + {{> recursiveTree oldAr }} +
+

+ Nouvel AR:
+
    + {{> recursiveTree newAr }} +
+

diff --git a/src/mail_templates/partials/recursiveTree.hbs b/src/mail_templates/partials/recursiveTree.hbs new file mode 100644 index 0000000..e7052f5 --- /dev/null +++ b/src/mail_templates/partials/recursiveTree.hbs @@ -0,0 +1,28 @@ +{{! Partiel : recursiveTree }} +{{#each this}} +
  • + {{@key}} : + {{#if (isObject this)}} +
      + {{> recursiveTree this }} +
    + {{else if (isArray this)}} + <{{@key}} : +
      + {{#each this}} +
    • + {{#if (isObject this)}} +
        + {{> recursiveTree this }} +
      + {{else}} + {{this}} + {{/if}} +
    • + {{/each}} +
    + {{else}} + {{this}} + {{/if}} +
  • +{{/each}} \ No newline at end of file diff --git a/src/usage/usage.service.ts b/src/usage/usage.service.ts index 1ace007..08ed8f2 100644 --- a/src/usage/usage.service.ts +++ b/src/usage/usage.service.ts @@ -1,11 +1,12 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { In, Not, Repository } from 'typeorm'; +import { FindManyOptions, In, Not, Repository } from 'typeorm'; import { Usage } from './entities/usage.entity'; import { User } from '../user/entities/user.entity'; import { CreateUpdateUsageDto } from './dto/create_usage.dto'; import { Restriction } from '../restriction/entities/restriction.entity'; import { ArreteCadre } from '../arrete_cadre/entities/arrete_cadre.entity'; +import { FindOptionsWhere } from 'typeorm/find-options/FindOptionsWhere'; @Injectable() export class UsageService { @@ -69,14 +70,14 @@ export class UsageService { .flat(); // SUPPRESSION DES ANCIENS USAGES if(usagesId.length > 0) { - await this.usageRepository.delete({ + await this.usageRepository.delete(> { restriction: { id: restriction.id, }, id: Not(In(usagesId)), }); } else { - await this.usageRepository.delete({ + await this.usageRepository.delete(> { restriction: { id: restriction.id, }, @@ -96,7 +97,7 @@ export class UsageService { .map((u) => u.id) .flat(); // SUPPRESSION DES ANCIENS USAGES - await this.usageRepository.delete({ + await this.usageRepository.delete(> { arreteCadre: { id: arreteCadre.id, }, @@ -112,7 +113,7 @@ export class UsageService { } findByArreteCadre(arreteCadreId: number) { - return this.usageRepository.find({ + return this.usageRepository.find( { select: { id: true, nom: true, @@ -148,7 +149,7 @@ export class UsageService { const updates = []; for (const u of usagesAc) { const oldUsage = oldUsagesAc.find(ou => ou.id === u.id); - const tmp = await this.usageRepository.find({ + const tmp = await this.usageRepository.find( { where: { restriction: { arreteRestriction: { diff --git a/src/zone_alerte/dto/zone_alerte.dto.ts b/src/zone_alerte/dto/zone_alerte.dto.ts index e7738b9..c0e7ee3 100644 --- a/src/zone_alerte/dto/zone_alerte.dto.ts +++ b/src/zone_alerte/dto/zone_alerte.dto.ts @@ -1,12 +1,15 @@ import { + IsArray, IsBoolean, IsJSON, IsNumber, - IsObject, - IsString, + IsObject, IsOptional, + IsString, ValidateNested, } from 'class-validator'; import { DepartementDto } from '../../departement/dto/departement.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { CommuneDto } from '../../commune/dto/commune.dto'; export class ZoneAlerteDto { @IsNumber() @@ -25,6 +28,13 @@ export class ZoneAlerteDto { }) type: string; + @IsBoolean() + @ApiProperty({ + example: false, + description: "Est-ce que la zone d'alerte est une ressource stockéee / regulée ?", + }) + ressourceInfluencee: boolean; + @IsString() @ApiProperty({ example: 'Zone superficielle Ain 01', @@ -48,6 +58,13 @@ export class ZoneAlerteDto { @IsObject() departement: DepartementDto; + + @IsArray() + @ValidateNested({ each: true }) + @IsOptional() + @Type(() => CommuneDto) + @ApiProperty({ type: [CommuneDto] }) + communes: CommuneDto[]; } export class ZoneAlertGeomDto extends ZoneAlerteDto { diff --git a/src/zone_alerte/entities/zone_alerte.entity.ts b/src/zone_alerte/entities/zone_alerte.entity.ts index d2d3199..e2be7e0 100644 --- a/src/zone_alerte/entities/zone_alerte.entity.ts +++ b/src/zone_alerte/entities/zone_alerte.entity.ts @@ -13,6 +13,7 @@ import { ArreteCadre } from '../../arrete_cadre/entities/arrete_cadre.entity'; import { BassinVersant } from '../../bassin_versant/entities/bassin_versant.entity'; import { Departement } from '../../departement/entities/departement.entity'; import { Restriction } from '../../restriction/entities/restriction.entity'; +import { ArreteCadreZoneAlerteCommunes } from '../../arrete_cadre_zone_alerte_communes/entities/arrete_cadre_zone_alerte_communes.entity'; @Entity() export class ZoneAlerte extends BaseEntity { @@ -31,6 +32,9 @@ export class ZoneAlerte extends BaseEntity { @Column({ nullable: false, length: 50 }) type: 'SOU' | 'SUP'; + @Column({ default: false, nullable: false }) + ressourceInfluencee: boolean; + @Column({ nullable: true }) numeroVersion: number; @@ -59,6 +63,9 @@ export class ZoneAlerte extends BaseEntity { @OneToMany(() => Restriction, (restriction) => restriction.zoneAlerte) restrictions: Restriction[]; + @OneToMany(() => ArreteCadreZoneAlerteCommunes, (arreteCadreZoneAlerteCommunes) => arreteCadreZoneAlerteCommunes.zoneAlerte) + arreteCadreZoneAlerteCommunes: ArreteCadreZoneAlerteCommunes[]; + @CreateDateColumn() createdAt: Date; diff --git a/src/zone_alerte/zone_alerte.service.ts b/src/zone_alerte/zone_alerte.service.ts index 2c1bcfa..cdc5447 100644 --- a/src/zone_alerte/zone_alerte.service.ts +++ b/src/zone_alerte/zone_alerte.service.ts @@ -1,6 +1,6 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { In, IsNull, Not, Repository } from 'typeorm'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { DataSource, FindManyOptions, In, IsNull, Not, Repository } from 'typeorm'; import { ZoneAlerte } from './entities/zone_alerte.entity'; import { Cron, CronExpression } from '@nestjs/schedule'; import { ConfigService } from '@nestjs/config'; @@ -11,7 +11,6 @@ import { DepartementService } from '../departement/departement.service'; import { BassinVersantService } from '../bassin_versant/bassin_versant.service'; import { MailService } from '../shared/services/mail.service'; import { ArreteCadreService } from '../arrete_cadre/arrete_cadre.service'; -import { ZoneAlerteComputed } from '../zone_alerte_computed/entities/zone_alerte_computed.entity'; @Injectable() export class ZoneAlerteService { @@ -27,27 +26,41 @@ export class ZoneAlerteService { private readonly mailService: MailService, @Inject(forwardRef(() => ArreteCadreService)) private readonly arreteCadreService: ArreteCadreService, + @InjectDataSource() + private readonly dataSource: DataSource, ) { } - findOne(id: number): Promise { - return this.zoneAlerteRepository + async findOne(id: number, acIds?: number[]): Promise { + const za = await this.zoneAlerteRepository .createQueryBuilder('zone_alerte') .select('zone_alerte.id', 'id') .select('zone_alerte.idSandre', 'idSandre') .addSelect('zone_alerte.code', 'code') .addSelect('zone_alerte.nom', 'nom') .addSelect('zone_alerte.type', 'type') + .addSelect('zone_alerte.ressourceInfluencee', 'ressourceInfluencee') .addSelect( 'ST_AsGeoJSON(ST_TRANSFORM(zone_alerte.geom, 4326))', 'geom', ) .where('zone_alerte.id = :id', { id }) .getRawOne(); + + za.arreteCadreZoneAlerteCommunes = (await this.zoneAlerteRepository + .createQueryBuilder('zone_alerte') + .select(['zone_alerte.id']) + .addSelect(['aczac.id', 'communes.id']) + .leftJoin('zone_alerte.arreteCadreZoneAlerteCommunes', 'aczac', 'aczac.arreteCadreId IN(:...acIds)', {acIds: acIds}) + .leftJoin('aczac.communes', 'communes') + .where('zone_alerte.id = :id', { id }) + .getOne()).arreteCadreZoneAlerteCommunes; + + return za; } findByDepartement(departementCode: string): Promise { - return this.zoneAlerteRepository.find({ + return this.zoneAlerteRepository.find( { relations: ['departement'], where: { departement: { @@ -78,13 +91,14 @@ export class ZoneAlerteService { if (!arIds || arIds.length < 1) { return []; } - return this.zoneAlerteRepository.find({ + return this.zoneAlerteRepository.find( { select: { id: true, idSandre: true, code: true, nom: true, type: true, + ressourceInfluencee: true, departement: { code: true, nom: true, @@ -220,7 +234,7 @@ export class ZoneAlerteService { existingZone.geom = f.geometry; promises.push(this.zoneAlerteRepository.save(existingZone)); } - const idsToDisable = await this.zoneAlerteRepository.find({ + const idsToDisable = await this.zoneAlerteRepository.find( { select: { id: true, idSandre: true, @@ -293,4 +307,25 @@ export class ZoneAlerteService { .andWhere('ST_Area(ST_Intersection(ST_TRANSFORM(zone_alerte.geom, 4326), (SELECT ST_TRANSFORM(c.geom, 4326) FROM commune as c WHERE c.id = :communeId))) / ST_Area((SELECT ST_TRANSFORM(c.geom, 4326) FROM commune as c WHERE c.id = :communeId)) > 0.01', { communeId }) .getRawMany(); } + + async getUnionGeomOfZoneAndCommunes(zoneId: number, communesId: number[]): Promise { + const result = await this.dataSource.query( + ` + SELECT ST_AsGeoJSON(ST_Union(zone.geom, communes.geom)) AS combined_geom + FROM ( + SELECT ST_TRANSFORM(geom, 4326) AS geom + FROM zone_alerte + WHERE id = $1 + ) AS zone, + ( + SELECT ST_Union(ST_TRANSFORM(geom, 4326)) AS geom + FROM commune + WHERE id = ANY($2) + ) AS communes; + `, + [zoneId, communesId] + ); + + return result[0]?.combined_geom; + } } diff --git a/src/zone_alerte_computed/entities/zone_alerte_computed.entity.ts b/src/zone_alerte_computed/entities/zone_alerte_computed.entity.ts index 2090ffa..dbb17cc 100644 --- a/src/zone_alerte_computed/entities/zone_alerte_computed.entity.ts +++ b/src/zone_alerte_computed/entities/zone_alerte_computed.entity.ts @@ -30,6 +30,9 @@ export class ZoneAlerteComputed extends BaseEntity { @Column({ nullable: false, length: 50 }) type: 'SOU' | 'SUP' | 'AEP'; + @Column({ default: false, nullable: false }) + ressourceInfluencee: boolean; + @Column({ default: false }) enabled: boolean; diff --git a/src/zone_alerte_computed/entities/zone_alerte_computed_historic.entity.ts b/src/zone_alerte_computed/entities/zone_alerte_computed_historic.entity.ts index ac25fab..10a9562 100644 --- a/src/zone_alerte_computed/entities/zone_alerte_computed_historic.entity.ts +++ b/src/zone_alerte_computed/entities/zone_alerte_computed_historic.entity.ts @@ -30,6 +30,9 @@ export class ZoneAlerteComputedHistoric extends BaseEntity { @Column({ nullable: false, length: 50 }) type: 'SOU' | 'SUP' | 'AEP'; + @Column({ default: false, nullable: false }) + ressourceInfluencee: boolean; + @Column({ default: false }) enabled: boolean; diff --git a/src/zone_alerte_computed/zone_alerte_computed.service.ts b/src/zone_alerte_computed/zone_alerte_computed.service.ts index f26c6a8..2491238 100644 --- a/src/zone_alerte_computed/zone_alerte_computed.service.ts +++ b/src/zone_alerte_computed/zone_alerte_computed.service.ts @@ -92,6 +92,7 @@ export class ZoneAlerteComputedService { this.isComputing = true; await this.computeAll([...new Set(this.departementsToUpdate)], computeHistoric); } catch (e) { + this.logger.error('COMPUTE ALL', e); } this.isComputing = false; } @@ -160,9 +161,13 @@ export class ZoneAlerteComputedService { for (const ar of arretesRestrictions) { await Promise.all(ar.restrictions.map(async (restriction) => { if (restriction.zoneAlerte) { - const za = await this.zoneAlerteService.findOne(restriction.zoneAlerte.id); + const za = await this.zoneAlerteService.findOne(restriction.zoneAlerte.id, [restriction.arreteCadre.id]); za.restriction = { id: restriction.id, niveauGravite: restriction.niveauGravite }; za.departement = { id: departement.id }; + + if (za.arreteCadreZoneAlerteCommunes && za.arreteCadreZoneAlerteCommunes[0] && za.arreteCadreZoneAlerteCommunes[0].communes?.length > 0) { + za.geom = await this.zoneAlerteService.getUnionGeomOfZoneAndCommunes(restriction.zoneAlerte.id, za.arreteCadreZoneAlerteCommunes[0].communes.map(c => c.id)); + } // SAUVEGARDE ZONE ESU ou ESO zonesToSave.push(za); } else if (restriction.communes?.length > 0) { @@ -294,7 +299,15 @@ export class ZoneAlerteComputedService { const queries = []; for (const commune of communes) { for (const zoneType of zoneTypes) { - const zonesSameType = commune.zones.filter(z => z.type === zoneType); + let zonesSameType = commune.zones.filter(z => z.type === zoneType); + // Gestion des zones influencées + // Si il y a des ressources influencées ET des ressources naturelles, on exclut les ressources influencées des calculs + if (zonesSameType.length > 1 + && zonesSameType.some(z => z.ressourceInfluencee && z.areaCommunePercentage >= 5) + && zonesSameType.some(z => !z.ressourceInfluencee && z.areaCommunePercentage >= 5)) { + zonesSameType = zonesSameType.filter(z => !z.ressourceInfluencee); + } + // Quand une seule zone, on l'agrandie à la geometrie de la commune if (zonesSameType.length === 1 && zonesSameType[0].areaCommunePercentage >= 5) { queries.push(this.getQueryToExtendZone(zonesSameType[0].id, commune.id)); @@ -346,7 +359,12 @@ export class ZoneAlerteComputedService { }).niveauGravite; for (const zoneType of zoneTypes) { // Normalement il y a au maximum une zone par type mais si ils ont fait plusieurs AR avec des règles de gestions différentes il se peut que plusieurs zones AEP se superposent - const zonesSameType = zones.filter(z => z.type === zoneType); + let zonesSameType = zones.filter(z => z.type === zoneType); + + // Gestion des ressources influencées + if(zonesSameType.some(z => z.ressourceInfluencee) && zonesSameType.some(z => !z.ressourceInfluencee)) { + zonesSameType = zonesSameType.filter(z => !z.ressourceInfluencee); + } if (zonesSameType.length === 1 && zonesSameType[0].niveauGravite !== maxNiveauGravite) { @@ -378,7 +396,7 @@ export class ZoneAlerteComputedService { } else { queries.push(this.getQueryToExtendZone(zoneToKeep.id, commune.id)); } - zonesSameType.filter(z => z.id !== zoneToKeep.id).forEach(z => { + zonesSameType.filter(z => z.id !== zoneToKeep.id && !z.ressourceInfluencee).forEach(z => { queries.push(this.getQueryToReduceZone(z.id, commune.id)); }); @@ -639,6 +657,8 @@ DELETE FROM zone_alerte_computed // Au moins 1% de la surface en commun .leftJoinAndSelect('commune', 'commune', 'commune.departement = departement.id AND ST_INTERSECTS(zone_alerte_computed.geom, commune.geom) AND ST_Area(ST_Intersection(zone_alerte_computed.geom, commune.geom)) / ST_AREA(commune.geom) > 0.001') .where('departement.id = :id', { id: departement.id }) + .andWhere('ST_IsValid(ST_TRANSFORM(zone_alerte_computed.geom, 4326))') + .andWhere('ST_IsValid(ST_TRANSFORM(commune.geom, 4326))') .getRawMany(); const toSave = []; zones.forEach(z => { diff --git a/src/zone_alerte_computed/zone_alerte_computed_historic.service.ts b/src/zone_alerte_computed/zone_alerte_computed_historic.service.ts index 5b41750..f6b2503 100644 --- a/src/zone_alerte_computed/zone_alerte_computed_historic.service.ts +++ b/src/zone_alerte_computed/zone_alerte_computed_historic.service.ts @@ -240,9 +240,13 @@ export class ZoneAlerteComputedHistoricService { for (const ar of arretesRestrictions) { await Promise.all(ar.restrictions.map(async (restriction) => { if (restriction.zoneAlerte) { - const za = await this.zoneAlerteService.findOne(restriction.zoneAlerte.id); + const za = await this.zoneAlerteService.findOne(restriction.zoneAlerte.id, [restriction.arreteCadre.id]); za.restriction = { id: restriction.id, niveauGravite: restriction.niveauGravite }; za.departement = { id: departement.id }; + + if(za.arreteCadreZoneAlerteCommunes && za.arreteCadreZoneAlerteCommunes[0] && za.arreteCadreZoneAlerteCommunes[0].communes?.length > 0) { + za.geom = await this.zoneAlerteService.getUnionGeomOfZoneAndCommunes(za.id, za.arreteCadreZoneAlerteCommunes[0].communes.map(c => c.id)); + } // SAUVEGARDE ZONE ESU ou ESO zonesToSave.push(za); } else if (restriction.communes?.length > 0) { @@ -377,7 +381,15 @@ export class ZoneAlerteComputedHistoricService { const queries = []; for (const commune of communes) { for (const zoneType of zoneTypes) { - const zonesSameType = commune.zones.filter(z => z.type === zoneType); + let zonesSameType = commune.zones.filter(z => z.type === zoneType); + // Gestion des zones influencées + // Si il y a des ressources influencées ET des ressources naturelles, on exclut les ressources influencées des calculs + if (zonesSameType.length > 1 + && zonesSameType.some(z => z.ressourceInfluencee && z.areaCommunePercentage >= 5) + && zonesSameType.some(z => !z.ressourceInfluencee && z.areaCommunePercentage >= 5)) { + zonesSameType = zonesSameType.filter(z => !z.ressourceInfluencee); + } + // Quand une seule zone, on l'agrandie à la geometrie de la commune if (zonesSameType.length === 1 && zonesSameType[0].areaCommunePercentage >= 5) { queries.push(this.getQueryToExtendZone(zonesSameType[0].id, commune.id)); @@ -429,7 +441,12 @@ export class ZoneAlerteComputedHistoricService { }).niveauGravite; for (const zoneType of zoneTypes) { // Normalement il y a au maximum une zone par type mais si ils ont fait plusieurs AR avec des règles de gestions différentes il se peut que plusieurs zones AEP se superposent - const zonesSameType = zones.filter(z => z.type === zoneType); + let zonesSameType = zones.filter(z => z.type === zoneType); + + // Gestion des ressources influencées + if(zonesSameType.some(z => z.ressourceInfluencee) && zonesSameType.some(z => !z.ressourceInfluencee)) { + zonesSameType = zonesSameType.filter(z => !z.ressourceInfluencee); + } if (zonesSameType.length === 1 && zonesSameType[0].niveauGravite !== maxNiveauGravite) { @@ -461,7 +478,7 @@ export class ZoneAlerteComputedHistoricService { } else { queries.push(this.getQueryToExtendZone(zoneToKeep.id, commune.id)); } - zonesSameType.filter(z => z.id !== zoneToKeep.id).forEach(z => { + zonesSameType.filter(z => z.id !== zoneToKeep.id && !z.ressourceInfluencee).forEach(z => { queries.push(this.getQueryToReduceZone(z.id, commune.id)); }); @@ -531,6 +548,8 @@ export class ZoneAlerteComputedHistoricService { // Au moins 1% de la surface en commun .leftJoinAndSelect('commune', 'commune', 'commune.departement = departement.id AND ST_INTERSECTS(zone_alerte_computed_historic.geom, commune.geom) AND ST_Area(ST_Intersection(zone_alerte_computed_historic.geom, commune.geom)) / ST_AREA(commune.geom) > 0.01') .where('departement.id = :id', { id: departement.id }) + .andWhere('ST_IsValid(ST_TRANSFORM(zone_alerte_computed_historic.geom, 4326))') + .andWhere('ST_IsValid(ST_TRANSFORM(commune.geom, 4326))') .getRawMany(); const toSave = []; zones.forEach(z => { @@ -613,6 +632,7 @@ export class ZoneAlerteComputedHistoricService { .addSelect('zone_alerte_computed_historic.nom', 'nom') .addSelect('zone_alerte_computed_historic.type', 'type') .where('zone_alerte_computed_historic.id IN(:...zonesId)', { zonesId: zones.map(z => z.id) }) + .andWhere('ST_IsValid(zone_alerte_computed_historic.geom)') .andWhere('ST_INTERSECTS(zone_alerte_computed_historic.geom, (SELECT c.geom FROM commune as c WHERE c.id = :communeId))', { communeId }) // Au moins 1% de la surface en commun .andWhere('ST_Area(ST_Intersection(zone_alerte_computed_historic.geom, (SELECT c.geom FROM commune as c WHERE c.id = :communeId))) / ST_Area((SELECT c.geom FROM commune as c WHERE c.id = :communeId)) > 0.01', { communeId })