diff --git a/src/adaptateurs/adaptateurPdf.js b/src/adaptateurs/adaptateurPdf.js index f109afd76..7e2ab2337 100644 --- a/src/adaptateurs/adaptateurPdf.js +++ b/src/adaptateurs/adaptateurPdf.js @@ -78,8 +78,7 @@ const genereAnnexes = async ({ referentiel, }) => { try { - const risquesPresents = - Object.keys(donneesRisques.risquesParNiveauGravite).length > 0; + const risquesPresents = Object.keys(donneesRisques.risques).length > 0; const [description, mesures, risques] = await Promise.all([ genereHtml( diff --git a/src/modeles/objetsPDF/objetPDFAnnexeRisques.js b/src/modeles/objetsPDF/objetPDFAnnexeRisques.js index 17d3d33a7..26ac61c17 100644 --- a/src/modeles/objetsPDF/objetPDFAnnexeRisques.js +++ b/src/modeles/objetsPDF/objetPDFAnnexeRisques.js @@ -4,13 +4,45 @@ class ObjetPDFAnnexeRisques { constructor(homologation, referentiel = Referentiel.creeReferentielVide()) { this.referentiel = referentiel; this.service = homologation; + this.risques = [ + ...this.service.risquesGeneraux().tous(), + ...this.service.risquesSpecifiques().tous(), + ]; + } + + calculeGrille() { + const resultat = Array.from({ length: 4 }, () => Array(4).fill(null)); + + const positionVraisemblance = (risque) => + this.referentiel.niveauxVraisemblance()[risque.niveauVraisemblance] + .position; + + const positionGravite = (risque) => + this.referentiel.niveauxGravite()[risque.niveauGravite].position; + + const risquesCartographies = this.risques.filter( + (risque) => + risque.niveauGravite && + risque.niveauVraisemblance && + positionVraisemblance(risque) > 0 && + positionGravite(risque) > 0 + ); + + risquesCartographies.forEach((risque) => { + const x = positionVraisemblance(risque) - 1; + const y = Math.abs(4 - positionGravite(risque)); + resultat[y][x] = resultat[y][x] || []; + resultat[y][x].push(risque.identifiantNumeriqueRisque()); + }); + return resultat; } donnees() { return { - niveauxGravite: this.referentiel.infosNiveauxGraviteConcernes(true), nomService: this.service.nomService(), - risquesParNiveauGravite: this.service.risques.parNiveauGravite(), + risques: this.risques.map((r) => r.toJSON()), + grilleRisques: this.calculeGrille(), + matriceNiveauxRisque: this.referentiel.matriceNiveauxRisques(), }; } } diff --git a/src/pdf/modeles/annexeRisques.pug b/src/pdf/modeles/annexeRisques.pug index c4283c657..775706fd3 100644 --- a/src/pdf/modeles/annexeRisques.pug +++ b/src/pdf/modeles/annexeRisques.pug @@ -9,19 +9,49 @@ mixin niveau-criticite(idNiveau) .rond(class = couleur) block page - +boite-grise + +boite-grise('Cartographie des risques', 'cartographie') + p.type Évalué au départ + .matrice + span.legende.legende-y Gravité + span.legende.legende-x Vraisemblance + .axe.axe-y + span 4 + span 3 + span 2 + span 1 + .axe.axe-x + span 1 + span 2 + span 3 + span 4 + .conteneur-matrice + each _, index in new Array(16).fill(0) + - const x = index % 4 + - const y = Math.floor(index / 4) + - const classe = donneesRisques.matriceNiveauxRisque[x + 1][4 - y] + - const risquesPresent = donneesRisques.grilleRisques[y][x] + - const jointsAvecEllispe = (liste) => liste.length > 7 ? liste.splice(0,6).join(', ')+"…" : liste.join(', ') + - const libelleCellule = !risquesPresent ? "" : jointsAvecEllispe(risquesPresent) + .cellule-matrice(class=`${classe} ${index}`)!= libelleCellule + .legende-matrice + ul + - const niveaux = Object.entries(referentiel.niveauxRisque()).filter(([_, n]) => n.position >= 0).sort(([_, a], [__, b]) => a.position - b.position); + each niveau in niveaux + - const [id, descriptionNiveau] = niveau + li(class=id) + span.niveau!= descriptionNiveau.libelle + ' : ' + = descriptionNiveau.description + + +boite-grise('Risques', 'liste-risques') ul.risques - each niveau in donneesRisques.niveauxGravite - if donneesRisques.risquesParNiveauGravite[niveau.identifiant] - each risque in donneesRisques.risquesParNiveauGravite[niveau.identifiant] - li.bloc-risque.bloc-indivisible - .contenu-niveau - +niveau-criticite(niveau.identifiant) - p.niveau= niveau.description - .contenu-risque - p.description!= risque.intitule - if referentiel.definitionRisque(risque.id) - p.definition= referentiel.definitionRisque(risque.id) - if risque.commentaire - p.commentaire #[strong Commentaire :] !{risque.commentaire} - hr + each risque in donneesRisques.risques + li.bloc-risque.bloc-indivisible + div(class=`identifiant-numerique ${risque.niveauRisque}`)= risque.identifiantNumerique + .contenu-risque + p.description!= risque.intitule + if referentiel.definitionRisque(risque.id) + p.definition= referentiel.definitionRisque(risque.id) + else + p.definition= risque.description + if risque.commentaire + p.commentaire #[strong Commentaire :] !{risque.commentaire} diff --git a/src/pdf/modeles/ressources/styles.css b/src/pdf/modeles/ressources/styles.css index d0cee2896..5454e3e08 100644 --- a/src/pdf/modeles/ressources/styles.css +++ b/src/pdf/modeles/ressources/styles.css @@ -1207,3 +1207,189 @@ p { .tampon-homologation.desktop .conteneur-extras { margin-left: 164px; } + +.matrice { + position: relative; + width: fit-content; + margin-left: 48px; + margin-top: 12px; +} + +.matrice .legende { + position: absolute; + color: #667892; + font-size: 10px; + font-weight: 400; +} + +.matrice .legende-y { + top: 0; + left: -46px; +} + +.matrice .legende-x { + bottom: -20px; + right: -28px; +} + +.matrice .conteneur-matrice { + display: grid; + grid-template-columns: repeat(4, 81px); + column-gap: 3px; + grid-template-rows: repeat(4, 35px); + row-gap: 2px; + padding: 15px 13px 6px 6px; + border-bottom: 1px solid #cbd5e1; + border-left: 1px solid #cbd5e1; + width: fit-content; + position: relative; +} + +.matrice .conteneur-matrice::before { + content: ''; + width: 4px; + height: 4px; + border-top: 1px solid #cbd5e1; + border-left: 1px solid #cbd5e1; + transform: rotate(45deg); + position: absolute; + top: 0; + left: -3px; +} + +.matrice .conteneur-matrice::after { + content: ''; + width: 4px; + height: 4px; + border-top: 1px solid #cbd5e1; + border-left: 1px solid #cbd5e1; + transform: rotate(135deg); + position: absolute; + bottom: -3px; + right: 0; +} + +.matrice .axe { + position: absolute; + display: flex; +} + +.matrice .axe span { + font-size: 8px; + font-weight: 400; +} + +.matrice .axe-y { + left: -16px; + flex-direction: column; + gap: 26px; + top: 38px; +} + +.matrice .axe-x { + bottom: -18px; + left: 40px; + flex-direction: row; + gap: 80px; +} + +.matrice .cellule-matrice { + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + line-height: 16px; + color: white; + text-align: center; +} + +.matrice .cellule-matrice.faible { + background: #4cb963; +} + +.matrice .cellule-matrice.moyen { + background: #faa72c; +} + +.matrice .cellule-matrice.eleve { + background: #e32630; +} + +.legende-matrice { + margin-top: 32px; +} + +.legende-matrice ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + gap: 12px; +} + +.legende-matrice li { + font-size: 0.813rem; + line-height: 1.125rem; + display: flex; + align-items: center; +} + +.legende-matrice li:before { + content: ''; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 4px; +} + +.legende-matrice li.faible:before { + background-color: #4cb963; +} + +.legende-matrice li.moyen:before { + background-color: #faa72c; +} + +.legende-matrice li.eleve:before { + background-color: #e32630; +} + +.legende-matrice .niveau { + margin-left: 4px; + font-weight: bold; +} + +.liste-risques tbody td { + break-inside: unset; +} + +.identifiant-numerique { + font-size: 14px; + font-weight: 700; + line-height: 20px; + padding: 1px 8px 3px; + align-items: center; + border-radius: 40px; + border: 1.5px solid #667892; + width: fit-content; + color: #667892; + background: white; + white-space: nowrap; + height: 21px; +} + +.identifiant-numerique.faible { + border-color: #0c8626; + color: #0c8626; +} + +.identifiant-numerique.moyen { + border-color: #faa72c; + color: #faa72c; +} + +.identifiant-numerique.eleve { + border-color: #ff6584; + color: #ff6584; +} diff --git a/test/modeles/objetsPDF/objetPDFAnnexeRisques.spec.js b/test/modeles/objetsPDF/objetPDFAnnexeRisques.spec.js index 87afd97c9..80c886bec 100644 --- a/test/modeles/objetsPDF/objetPDFAnnexeRisques.spec.js +++ b/test/modeles/objetsPDF/objetPDFAnnexeRisques.spec.js @@ -5,104 +5,65 @@ const Referentiel = require('../../../src/referentiel'); const VueAnnexePDFRisques = require('../../../src/modeles/objetsPDF/objetPDFAnnexeRisques'); describe("L'objet PDF des descriptions des risques", () => { - const donneesReferentiel = { + const referentiel = Referentiel.creeReferentiel({ niveauxGravite: { - nonConcerne: { - position: 0, - couleur: 'blanc', - description: 'Non concerné', - descriptionLongue: '', - nonConcerne: true, - }, - grave: { - position: 3, - couleur: 'orange', - description: 'Grave', - descriptionLongue: 'Niveaux de gravité grave', - }, - critique: { - position: 4, - couleur: 'rouge', - description: 'Critique', - descriptionLongue: 'Niveaux de gravité critique', + grave: { description: 'Une description', position: 1 }, + }, + vraisemblancesRisques: { + probable: { description: 'Une description', position: 1 }, + }, + niveauxRisques: { + orange: { correspondances: [{ gravite: 0, vraisemblance: 0 }] }, + rouge: { correspondances: [{ gravite: 1, vraisemblance: 1 }] }, + }, + risques: { + unRisque: { description: 'Une description', identifiantNumerique: 'R1' }, + unSecondRisque: { + description: 'Une description', + identifiantNumerique: 'R2', }, }, - risques: { unRisque: { description: 'Une description' } }, - }; + }); const service = new Service( { id: '123', idUtilisateur: '456', descriptionService: { nomService: 'Nom Service' }, - risquesGeneraux: [{ id: 'unRisque', niveauGravite: 'grave' }], + risquesGeneraux: [ + { + id: 'unRisque', + niveauGravite: 'grave', + niveauVraisemblance: 'probable', + }, + ], }, - Referentiel.creeReferentiel(donneesReferentiel) + referentiel ); - describe('avec des informations de niveaux de gravité dans le référentiel', () => { - let referentiel; - - beforeEach(() => { - referentiel = Referentiel.creeReferentiel(donneesReferentiel); - }); - - it('utilise les informations du référentiel', () => { - const vueAnnexePDFRisques = new VueAnnexePDFRisques(service, referentiel); - - const donnees = vueAnnexePDFRisques.donnees(); - - expect(donnees).to.have.key('niveauxGravite'); - const niveauCritique = donnees.niveauxGravite.find( - (niveau) => niveau.identifiant === 'critique' - ); - expect(niveauCritique).to.eql({ - identifiant: 'critique', - ...donneesReferentiel.niveauxGravite.critique, - }); - }); - - it('ignore le niveau de gravité non concerné', () => { - const vueAnnexePDFRisques = new VueAnnexePDFRisques(service, referentiel); - - const { niveauxGravite } = vueAnnexePDFRisques.donnees(); - - expect(niveauxGravite.length).to.equal(2); - expect( - niveauxGravite.map((niveaux) => niveaux.description) - ).to.not.contain('Non concerné'); - }); - - it('trie les niveaux de gravité par position décroissante', () => { - const vueAnnexePDFRisques = new VueAnnexePDFRisques(service, referentiel); + it('ajoute le nom du service', () => { + const vueAnnexePDFRisques = new VueAnnexePDFRisques(service, referentiel); - const { niveauxGravite } = vueAnnexePDFRisques.donnees(); + const donnees = vueAnnexePDFRisques.donnees(); - const positions = niveauxGravite.map((niveaux) => niveaux.position); - expect(positions[0]).to.equal(4); - expect(positions[1]).to.equal(3); - }); + expect(donnees).to.have.key('nomService'); + expect(donnees.nomService).to.equal('Nom Service'); }); - it('ajoute le nom du service', () => { - const vueAnnexePDFRisques = new VueAnnexePDFRisques(service); + it("ajoute les risques par ordre d'identifiant numérique", () => { + const vueAnnexePDFRisques = new VueAnnexePDFRisques(service, referentiel); const donnees = vueAnnexePDFRisques.donnees(); - expect(donnees).to.have.key('nomService'); - expect(donnees.nomService).to.equal('Nom Service'); + expect(donnees).to.have.key('risques'); + expect(donnees.risques[0].identifiantNumerique).to.be('R1'); }); - it('ajoute les risques par niveau de gravité', () => { - const vueAnnexePDFRisques = new VueAnnexePDFRisques(service); + it('ajoute le risque dans la case de la grille correspondant à sa gravité et sa vraisemblance', () => { + const vueAnnexePDFRisques = new VueAnnexePDFRisques(service, referentiel); const donnees = vueAnnexePDFRisques.donnees(); - expect(donnees).to.have.key('risquesParNiveauGravite'); - expect(donnees.risquesParNiveauGravite).to.have.key('grave'); - expect(donnees.risquesParNiveauGravite.grave.length).to.equal(1); - expect(donnees.risquesParNiveauGravite.grave[0].intitule).to.equal( - 'Une description' - ); + expect(donnees.grilleRisques[3][0]).to.eql(['R1']); }); });