Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Récupérer les attestations des GFs fournies à la DGEC lors de la candidature des projets #2176

Merged
merged 16 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 11 additions & 8 deletions packages/applications/scheduled-tasks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@
"start:supprimer-raccordement": "node ./dist/raccordement/supprimer-raccordement",
"start:annuler-echoir-gf-abandonnés-ou-éliminés": "node ./dist/annuler-echoir-gf-abandonnés-ou-éliminés",
"start:ajouter-rappel-gf-avec-date-echeance": "node ./dist/ajouter-rappel-gf-avec-date-echeance",
"start:migrate-event-for-revours": "node ./dist/recours/migrate-events-for-recours"
"start:migrate-event-for-recours": "node ./dist/recours/migrate-events-for-recours",
"start:récupérer-fichier-gf-candidature-référentiel-dgec": "node ./dist/garanties-financières/récupérer-fichier-gf-candidature-référentiel-dgec"
},
"dependencies": {
"@potentiel-libraries/monads": "*",
"@potentiel-libraries/file-storage": "*",
"@potentiel-libraries/monitoring": "*",
"@potentiel-applications/routes": "*",
"@potentiel-domain/common": "*",
"@potentiel-domain/laureat": "*",
"@potentiel-domain/elimine": "*",
"@potentiel-domain/laureat": "*",
"@potentiel-infrastructure/domain-adapters": "*",
"@potentiel-infrastructure/pg-projections": "*",
"@potentiel-infrastructure/pg-event-sourcing": "*",
"@potentiel-infrastructure/email": "*",
"@potentiel-applications/routes": "*"
"@potentiel-infrastructure/pg-event-sourcing": "*",
"@potentiel-infrastructure/pg-projections": "*",
"@potentiel-libraries/file-storage": "*",
"@potentiel-libraries/monads": "*",
"@potentiel-libraries/monitoring": "*",
"chardet": "^2.0.0",
"iconv-lite": "^0.6.3"
},
"devDependencies": {
"@potentiel-config/tsconfig": "*"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dgecEmail = 'aopv.dgec@developpement-durable.gouv.fr';
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import { readdir, writeFile, readFile } from 'node:fs/promises';
import path from 'path';

import { encode, decode } from 'iconv-lite';
import chardet from 'chardet';
import { mediator } from 'mediateur';

import { Option } from '@potentiel-libraries/monads';
import { DateTime, Email, IdentifiantProjet } from '@potentiel-domain/common';
import { GarantiesFinancières } from '@potentiel-domain/laureat';
import { findProjection, listProjection } from '@potentiel-infrastructure/pg-projections';
import { loadAggregate } from '@potentiel-infrastructure/pg-event-sourcing';
import {
registerDocumentProjetCommand,
registerDocumentProjetQueries,
} from '@potentiel-domain/document';
import {
CandidatureAdapter,
DocumentAdapter,
récupérerIdentifiantsProjetParEmailPorteurAdapter,
} from '@potentiel-infrastructure/domain-adapters';
import { Candidature } from '@potentiel-domain/candidature';
import { Période } from '@potentiel-domain/periode';

import { dgecEmail } from '../_utils/constant';

[
'DIRECTORY_PATH',
'EVENT_STORE_CONNECTION_STRING',
'AWS_ACCESS_KEY_ID',
'AWS_SECRET_ACCESS_KEY',
'S3_ENDPOINT',
'S3_BUCKET',
].forEach((varName) => {
if (!process.env[varName]) {
console.error(`La variable d'environnement ${varName} n'est pas définie.`);
process.exit(1);
}
});

Période.registerPériodeQueries({
find: findProjection,
list: listProjection,
});

Candidature.registerCandidatureQueries({
find: findProjection,
list: listProjection,
récupérerProjet: CandidatureAdapter.récupérerProjetAdapter,
récupérerProjets: CandidatureAdapter.récupérerProjetsAdapter,
récupérerProjetsEligiblesPreuveRecanditure:
CandidatureAdapter.récupérerProjetsEligiblesPreuveRecanditureAdapter,
});

GarantiesFinancières.registerGarantiesFinancièresUseCases({
loadAggregate,
});

GarantiesFinancières.registerGarantiesFinancièresQueries({
find: findProjection,
list: listProjection,
récupérerIdentifiantsProjetParEmailPorteur: récupérerIdentifiantsProjetParEmailPorteurAdapter,
});

registerDocumentProjetCommand({
enregistrerDocumentProjet: DocumentAdapter.téléverserDocumentProjet,
déplacerDossierProjet: DocumentAdapter.déplacerDossierProjet,
archiverDocumentProjet: DocumentAdapter.archiverDocumentProjet,
});

registerDocumentProjetQueries({
récupérerDocumentProjet: DocumentAdapter.téléchargerDocumentProjet,
});

const formatFileName = (id: string) =>
id
.replace(/PPE2 - Autoconsommation métrople/, 'PPE2 - Autoconsommation métropole')
.replace(/PPE2 - Innovant/, 'PPE2 - Innovation')
.replace(/PPE2 - Bâtiment/, 'PPE2 - Bâtiment'); // Je comprends pas pourquoi je suis obligé de faire ça
benjlevesque marked this conversation as resolved.
Show resolved Hide resolved

const detectAndConvertEncoding = (fileName: string) => {
const detectedEncoding = chardet.detect(Buffer.from(fileName));
HubM marked this conversation as resolved.
Show resolved Hide resolved
if (!detectedEncoding) {
throw new Error("Impossible de détecter l'encodage");
}
const buffer = encode(fileName, detectedEncoding);
const decodedStr = decode(buffer, 'utf-8');

return decodedStr;
};

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

(async () => {
const directoryPath = process.env.DIRECTORY_PATH!;
const dirrents = await readdir(directoryPath, {
withFileTypes: true,
});

type Statistics = {
projetInconnu: {
count: number;
ids: Array<string>;
};
attestationAjoutée: number;
gfCréeEtAttestationAjoutée: number;
attestationExistante: number;
};
const statistics: Statistics = {
projetInconnu: {
count: 0,
ids: [],
},
attestationAjoutée: 0,
gfCréeEtAttestationAjoutée: 0,
attestationExistante: 0,
};

const format = 'application/pdf';
const { items: périodes } = await mediator.send<Période.ListerPériodesQuery>({
type: 'Période.Query.ListerPériodes',
data: {},
});

for (const file of dirrents) {
try {
if (!file.isFile() || path.extname(file.name).toLowerCase() !== '.pdf') {
console.log(`❌ Fichier ${file.name} non pris en charge`);
continue;
}

const formattedFileName = formatFileName(
path.basename(detectAndConvertEncoding(file.name), '.pdf'),
);

console.log(`\n\n📂 ${formattedFileName}`);

const identifiantProjet = IdentifiantProjet.convertirEnValueType(formattedFileName);

const projet = await mediator.send<Candidature.ConsulterProjetQuery>({
type: 'Candidature.Query.ConsulterProjet',
data: {
identifiantProjet: identifiantProjet.formatter(),
},
});

if (Option.isNone(projet)) {
console.log(`❌ ${identifiantProjet.formatter()} : Projet inconnu`);
statistics.projetInconnu.count++;
statistics.projetInconnu.ids.push(identifiantProjet.formatter());
continue;
}

const gf = await mediator.send<GarantiesFinancières.ConsulterGarantiesFinancièresQuery>({
type: 'Lauréat.GarantiesFinancières.Query.ConsulterGarantiesFinancières',
data: {
identifiantProjetValue: identifiantProjet.formatter(),
},
});

const filePath = path.join(directoryPath, file.name);
const fileBuffer = await readFile(filePath);
const content = new ReadableStream({
start: async (controller) => {
controller.enqueue(fileBuffer);
controller.close();
},
});
const enregistréParValue = Email.convertirEnValueType(dgecEmail).formatter();

if (Option.isSome(gf)) {
if (gf.garantiesFinancières.attestation) {
console.log(
`ℹ️ ${identifiantProjet.formatter()} (${projet.nom}) : Le projet a déjà une attestation`,
);
statistics.attestationExistante++;
continue;
}

const dateConstitutionValue = DateTime.convertirEnValueType(
gf.garantiesFinancières.dateConstitution?.formatter() ?? DateTime.now().formatter(),
).formatter();

await mediator.send<GarantiesFinancières.EnregistrerAttestationGarantiesFinancièresUseCase>(
{
type: 'Lauréat.GarantiesFinancières.UseCase.EnregistrerAttestation',
data: {
identifiantProjetValue: identifiantProjet.formatter(),
dateConstitutionValue,
attestationValue: {
content,
format,
},
enregistréLeValue: DateTime.now().formatter(),
enregistréParValue,
},
},
);
statistics.attestationAjoutée++;
console.log(
`📝 ${identifiantProjet.formatter()} (${projet.nom}) : Attestation ajoutée aux garanties financières existante`,
);
continue;
}

const période = périodes.find(
(période) =>
période.identifiantPériode.appelOffre === identifiantProjet.appelOffre &&
période.identifiantPériode.période === identifiantProjet.période,
);

if (!période) {
console.log(`❌ ${identifiantProjet.formatter()} (${projet.nom}) : Période non trouvée`);
continue;
}

if (!période.estNotifiée || !période.notifiéeLe) {
VioMrqs marked this conversation as resolved.
Show resolved Hide resolved
console.log(`❌ ${identifiantProjet.formatter()} (${projet.nom}) : Période non notifiée`);
continue;
}

await mediator.send<GarantiesFinancières.EnregistrerGarantiesFinancièresUseCase>({
type: 'Lauréat.GarantiesFinancières.UseCase.EnregistrerGarantiesFinancières',
data: {
identifiantProjetValue: identifiantProjet.formatter(),
typeValue: Candidature.TypeGarantiesFinancières.typeInconnu.type,
dateConstitutionValue: période.notifiéeLe.formatter(),
attestationValue: {
content,
format: 'application/pdf',
},
enregistréLeValue: DateTime.now().formatter(),
enregistréParValue,
},
});

statistics.gfCréeEtAttestationAjoutée++;
console.log(
`🍀 ${identifiantProjet.formatter()} (${projet.nom}) : Garanties financières créées avec l'attestation`,
);

await delay(50);
} catch (e) {
console.error(e);
process.exit(1);
}
}

console.log('\n\nStatistiques :');
console.log(`Nombre de projets concernés : ${dirrents.length}`);
console.log(
`Nombre de projets inconnu dans potentiel : ${statistics.projetInconnu.count} / ${dirrents.length}`,
);
console.log(
`Nombre d'attestations ajoutées à des gfs existantes : ${statistics.attestationAjoutée} / ${dirrents.length}`,
);
console.log(
`Nombre de gf créées avec attestation : ${statistics.gfCréeEtAttestationAjoutée} / ${dirrents.length}`,
);
console.log(
`Nombre d'attestations déjà existantes : ${statistics.attestationExistante} / ${dirrents.length}`,
);

if (statistics.projetInconnu.ids.length) {
await writeFile('projets-non-trouvés.txt', statistics.projetInconnu.ids.join('\n'));
}

process.exit(0);
})();
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { CandidatureAdapter, DocumentAdapter } from '@potentiel-infrastructure/d
import { loadAggregate } from '@potentiel-infrastructure/pg-event-sourcing';
import { registerDocumentProjetCommand } from '@potentiel-domain/document';

import { dgecEmail } from '../_utils/constant';

Candidature.registerCandidatureQueries({
find: findProjection,
list: listProjection,
Expand Down Expand Up @@ -224,7 +226,7 @@ const getUtilisateur = async (utilisateurId: string): Promise<string> => {
identifiantProjetValue: identifiantProjet.formatter(),
identifiantUtilisateurValue: respondedBy
? await getUtilisateur(respondedBy)
: 'aopv.dgec@developpement-durable.gouv.fr',
: dgecEmail,
réponseSignéeValue: await getFile(responseFileId),
},
});
Expand All @@ -238,7 +240,7 @@ const getUtilisateur = async (utilisateurId: string): Promise<string> => {
identifiantProjetValue: identifiantProjet.formatter(),
identifiantUtilisateurValue: respondedBy
? await getUtilisateur(respondedBy)
: 'aopv.dgec@developpement-durable.gouv.fr',
: dgecEmail,
réponseSignéeValue: await getFile(responseFileId),
},
});
Expand Down