Skip to content
Snippets Groups Projects
Commit 60bf26c9 authored by SAFINE LAGET Anis's avatar SAFINE LAGET Anis Committed by BITARD Michaël
Browse files

chore(data): les prolongations doivent reprendre les mêmes infos que les...

chore(data): les prolongations doivent reprendre les mêmes infos que les octrois (!1674)
parent 5644954a
No related branches found
No related tags found
1 merge request!1674chore(data): les prolongations doivent reprendre les mêmes infos que les octrois
Pipeline #540026 passed
......@@ -7,7 +7,7 @@
"type": "module",
"scripts": {
"build": "tsc --incremental",
"check-perimetres": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/check-perimetres.ts",
"check-prolongations": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/check-prolongations.ts",
"daily": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/daily.ts",
"monthly": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/monthly.ts",
"db:dump": "rm -rf ./backups/* && pg_dump --host=localhost --username=postgres --clean --if-exists --format=d --no-owner --no-privileges --dbname=camino --file=./backups/",
......
import { Pool } from 'pg'
import { getDemarcheByIdOrSlug, GetDemarcheByIdOrSlugErrors, getDemarches, getEtapesByDemarcheId } from '../api/rest/demarches.queries'
import { Effect, pipe } from 'effect'
import { CaminoError } from 'camino-common/src/zod-tools'
import { DemarcheId } from 'camino-common/src/demarche'
import { isEtapeComplete } from 'camino-common/src/permissions/titres-etapes'
import { userSuper } from '../database/user-super'
import { firstEtapeDateValidator } from 'camino-common/src/date'
import { titreEtapeGet, titreEtapeUpsert } from '../database/queries/titres-etapes'
import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, isNullOrUndefinedOrEmpty, memoize, toSorted } from 'camino-common/src/typescript-tools'
import { iTitreEtapeToFlattenEtape, TitreEtapeToFlattenEtapeErrors } from '../api/_format/titres-etapes'
import { ITitreEtape } from '../types'
import {
getDocumentsByEtapeId,
getEntrepriseDocumentIdsByEtapeId,
getEtapeAvisLargeObjectIdsByEtapeId,
GetEtapeAvisLargeObjectIdsByEtapeIdErrors,
GetEtapeDocumentLargeObjectIdsByEtapeIdErrors,
} from '../database/queries/titres-etapes.queries'
import { getAdministrationsLocales } from 'camino-common/src/administrations'
import { ZodUnparseable } from '../tools/fp-tools'
import { TitreId, TitreSlug } from 'camino-common/src/validators/titres'
import { EtapeId } from 'camino-common/src/etape'
import { isDemarcheTypeProlongations } from 'camino-common/src/static/demarchesTypes'
import { getMostRecentValuePropFromEtapeFondamentaleValide, TitreGetDemarche } from 'camino-common/src/titres'
import { isDemarcheStatutNonStatue, isDemarcheStatutNonValide } from 'camino-common/src/static/demarchesStatuts'
import { getTitre } from '../api/rest/titres.queries'
type CheckedProlongation = {
titre_slug: TitreSlug | null
demarche_id: DemarcheId
etape_id: EtapeId | null
titre_id: TitreId | null
errors: string[]
}
const demarchePasProlongationError = 'pas une prolongation' as const
const demarcheSansEtapesError = "La démarche ne contient pas d'étapes" as const
const titreEtapeGetError = 'Le fetch de la firstEtape a échoué' as const
type CheckOnePrologationErrors =
| GetDemarcheByIdOrSlugErrors
| TitreEtapeToFlattenEtapeErrors
| GetEtapeDocumentLargeObjectIdsByEtapeIdErrors
| ZodUnparseable
| GetEtapeAvisLargeObjectIdsByEtapeIdErrors
| typeof titreEtapeGetError
| typeof demarcheSansEtapesError
| typeof demarchePasProlongationError
const checkOneProlongation = (pool: Pool, demarcheId: DemarcheId): Effect.Effect<CheckedProlongation, CaminoError<CheckOnePrologationErrors>> => {
return Effect.Do.pipe(
Effect.bind('demarche', () => getDemarcheByIdOrSlug(pool, demarcheId)),
Effect.filterOrFail(
({ demarche }) => isDemarcheTypeProlongations(demarche.demarche_type_id),
() => ({ message: demarchePasProlongationError })
),
Effect.bind('etapes', () => getEtapesByDemarcheId(pool, demarcheId)),
Effect.filterOrFail(
({ etapes }) => isNotNullNorUndefinedNorEmpty(etapes),
() => ({ message: demarcheSansEtapesError, extra: {} })
),
Effect.bind('firstEtape', ({ etapes }) =>
pipe(
Effect.tryPromise({
try: () => titreEtapeGet(toSorted(etapes, (a, b) => a.ordre - b.ordre)[0].id, { fields: { id: {} }, fetchHeritage: true }, userSuper),
catch: error => ({ message: titreEtapeGetError, extra: error }),
}),
Effect.filterOrFail(
(firstEtape): firstEtape is ITitreEtape => isNotNullNorUndefined(firstEtape),
() => ({ message: titreEtapeGetError, extra: 'La first étape est null ou undefined' })
)
)
),
Effect.let('titreProps', ({ demarche, firstEtape }) => ({
titreTypeId: memoize(() => Promise.resolve(demarche.titre_type_id)),
administrationsLocales: memoize(() =>
Promise.resolve(getAdministrationsLocales(isNotNullNorUndefined(firstEtape.communes) ? firstEtape.communes.map(({ id }) => id) : [], firstEtape.secteursMaritime))
),
entreprisesTitulairesOuAmodiataires: memoize(() => Promise.resolve([...(firstEtape.titulaireIds ?? []), ...(firstEtape.amodiataireIds ?? [])])),
})),
Effect.bind('etapeDocuments', ({ firstEtape, demarche, titreProps }) => {
return getDocumentsByEtapeId(firstEtape.id, pool, userSuper, titreProps.titreTypeId, titreProps.administrationsLocales, titreProps.entreprisesTitulairesOuAmodiataires, firstEtape.typeId, {
demarche_type_id: demarche.demarche_type_id,
entreprises_lecture: demarche.entreprises_lecture,
public_lecture: demarche.public_lecture,
titre_public_lecture: false,
})
}),
Effect.bind('entrepriseDocuments', ({ firstEtape }) => getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: firstEtape.id }, pool, userSuper)),
Effect.bind('etapeAvis', ({ firstEtape, demarche, titreProps }) =>
getEtapeAvisLargeObjectIdsByEtapeId(
firstEtape.id,
pool,
userSuper,
titreProps.titreTypeId,
titreProps.administrationsLocales,
titreProps.entreprisesTitulairesOuAmodiataires,
firstEtape.typeId,
{
demarche_type_id: demarche.demarche_type_id,
entreprises_lecture: demarche.entreprises_lecture,
public_lecture: demarche.public_lecture,
titre_public_lecture: false,
}
)
),
Effect.bind('flattenFirstEtape', ({ firstEtape }) => iTitreEtapeToFlattenEtape(firstEtape)),
Effect.map(({ demarche, firstEtape, flattenFirstEtape, etapeDocuments, entrepriseDocuments, etapeAvis }) => {
const isFirstEtapeComplete = isEtapeComplete(
flattenFirstEtape,
demarche.titre_type_id,
demarcheId,
demarche.demarche_type_id,
etapeDocuments,
entrepriseDocuments,
firstEtape.sdomZones,
isNotNullNorUndefined(firstEtape.communes) ? firstEtape.communes.map(({ id }) => id) : [],
etapeAvis,
userSuper,
firstEtapeDateValidator.parse(flattenFirstEtape.date)
)
return {
titre_slug: demarche.titre_slug,
demarche_id: demarche.demarche_id,
etape_id: firstEtape.id,
titre_id: demarche.titre_id,
errors: isFirstEtapeComplete.valid ? [] : isFirstEtapeComplete.errors,
}
}),
Effect.catchAll(caminoError => {
return Effect.succeed({
titre_slug: null,
demarche_id: demarcheId,
etape_id: null,
titre_id: null,
errors: [caminoError.message],
})
})
)
}
const titreIdVideError = "Pas d'id de titre" as const
const upsertFailError = "Échec de mise à jour de l'étape" as const
const titreGetError = 'Impossible de fetch le titre' as const
const titreGetIsNullError = 'Titre introuvable' as const
const etapeIntrouvableError = 'Étape introuvable dans le titre' as const
const demarcheListIsEmptyError = 'Liste des démarches héritables vide' as const
type FixProlongationErrors = typeof titreIdVideError | typeof upsertFailError | typeof titreGetError | typeof titreGetIsNullError | typeof demarcheListIsEmptyError | typeof etapeIntrouvableError
const fixProlongation = (pool: Pool, checkedProlongation: CheckedProlongation): Effect.Effect<null, CaminoError<FixProlongationErrors>> => {
return Effect.Do.pipe(
Effect.bind('titre', () =>
pipe(
Effect.succeed(checkedProlongation.titre_id),
Effect.filterOrFail(
titreId => isNotNullNorUndefined(titreId),
() => ({ message: titreIdVideError })
),
Effect.flatMap(titreId =>
Effect.tryPromise({
try: () => getTitre(pool, userSuper, titreId),
catch: error => ({ message: titreGetError, extra: error }),
})
),
Effect.filterOrFail(
(titre): titre is NonNullable<typeof titre> => isNotNullNorUndefined(titre),
error => ({ message: titreGetIsNullError, extra: error })
)
)
),
Effect.bind('etape', ({ titre }) =>
pipe(
Effect.succeed(titre.demarches.find(({ id }) => id === checkedProlongation.demarche_id)),
Effect.filterOrFail(
(demarche): demarche is NonNullable<typeof demarche> => isNotNullNorUndefined(checkedProlongation.demarche_id) && isNotNullNorUndefined(demarche),
() => ({ message: etapeIntrouvableError, extra: 'Démarche introuvable' })
),
Effect.map(demarche => demarche.etapes.find(({ id }) => id === checkedProlongation.etape_id)),
Effect.filterOrFail(
(etape): etape is NonNullable<typeof etape> => isNotNullNorUndefined(checkedProlongation.etape_id) && isNotNullNorUndefined(etape),
() => ({ message: etapeIntrouvableError, extra: 'Étape introuvable' })
)
)
),
Effect.bind('propsHeritees', ({ titre }) =>
Effect.Do.pipe(
Effect.bind('demarches', () => {
const demarches: TitreGetDemarche[] = []
for (const demarche of toSorted([...titre.demarches], (a, b) => a.ordre - b.ordre)) {
if (demarche.id === checkedProlongation.demarche_id) {
return Effect.succeed([...demarches, demarche])
} else if (!isDemarcheStatutNonStatue(demarche.demarche_statut_id) && !isDemarcheStatutNonValide(demarche.demarche_statut_id)) {
demarches.push(demarche)
}
}
return Effect.fail({ message: demarcheListIsEmptyError })
}),
Effect.map(({ demarches }) => {
return {
substances: getMostRecentValuePropFromEtapeFondamentaleValide('substances', demarches),
titulaireIds: getMostRecentValuePropFromEtapeFondamentaleValide('titulaireIds', demarches),
perimetre: getMostRecentValuePropFromEtapeFondamentaleValide('perimetre', demarches),
}
})
)
),
Effect.flatMap(({ titre, etape, propsHeritees }) =>
Effect.tryPromise({
try: () => {
console.info(`Trying to fix http://localhost:4180/etapes/${etape.id}`)
return titreEtapeUpsert(
{
id: etape.id,
titreDemarcheId: checkedProlongation.demarche_id,
date: etape.date,
substances: 'fondamentale' in etape && isNotNullNorUndefinedNorEmpty(etape.fondamentale.substances) ? etape.fondamentale.substances : propsHeritees.substances,
titulaireIds: 'fondamentale' in etape && isNotNullNorUndefinedNorEmpty(etape.fondamentale.titulaireIds) ? etape.fondamentale.titulaireIds : propsHeritees.titulaireIds,
surface: 'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.surface) ? etape.fondamentale.perimetre.surface : propsHeritees.perimetre?.surface,
geojson4326Perimetre:
'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.geojson4326_perimetre)
? etape.fondamentale.perimetre.geojson4326_perimetre
: propsHeritees.perimetre?.geojson4326_perimetre,
geojsonOriginePerimetre:
'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.geojson_origine_perimetre)
? etape.fondamentale.perimetre.geojson_origine_perimetre
: propsHeritees.perimetre?.geojson_origine_perimetre,
geojsonOrigineGeoSystemeId:
'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.geojson_origine_geo_systeme_id)
? etape.fondamentale.perimetre.geojson_origine_geo_systeme_id
: propsHeritees.perimetre?.geojson_origine_geo_systeme_id,
},
userSuper,
titre.id
)
},
catch: error => ({ message: upsertFailError, extra: error }),
})
),
Effect.map(() => null),
Effect.catchAll(error => {
console.info(error)
return Effect.succeed(null)
})
)
}
const toPercentage = (percentage: number): string => {
return `${Math.round(percentage * 10000) / 100} %`
}
export const checkProlongations = (pool: Pool): Effect.Effect<CheckedProlongation[], CaminoError<CheckOnePrologationErrors | FixProlongationErrors>> => {
return pipe(
getDemarches(pool),
Effect.flatMap(demarches => {
const initialAcc: CheckedProlongation[] = []
return Effect.reduce(
demarches.map(({ id }) => id),
initialAcc,
(acc, demarcheId) => {
return pipe(
checkOneProlongation(pool, demarcheId),
Effect.tap(checkedProlongation => {
if (isNullOrUndefinedOrEmpty(checkedProlongation.errors) || checkedProlongation.errors.includes(demarchePasProlongationError)) {
return Effect.succeed(checkedProlongation)
}
return fixProlongation(pool, checkedProlongation)
}),
Effect.map(checkedProlongation => {
// if (isNotNullNorUndefinedNorEmpty(checkedProlongation.errors) && !checkedProlongation.errors.includes(demarchePasProlongationError)) {
// const link = isNotNullNorUndefined(checkedProlongation.titre_slug)
// ? `http://localhost:4180/titres/${checkedProlongation.titre_slug}`
// : `http://localhost:4180/demarches/${checkedProlongation.demarche_id}`
// console.info(`${link} : ${checkedProlongation.errors.join(' | ')}`)
// }
return [...acc, checkedProlongation]
})
)
}
)
}),
Effect.tap(checkedProlongation => {
const prolongations = checkedProlongation.filter(({ errors }) => !errors.includes(demarchePasProlongationError))
const prolongationsEnVrac = prolongations.filter(({ errors }) => errors.length > 0)
const prolongationsAvecDureeManquante = prolongations.filter(({ errors }) => errors.some(error => error.includes('la durée est obligatoire')))
const prolongationsParType = prolongationsEnVrac.reduce<{ [key: string]: number }>((acc, prolongation) => {
const domaine = isNotNullNorUndefined(prolongation.titre_slug) ? `${prolongation.titre_slug.slice(2, 4)}${prolongation.titre_slug.slice(0, 1)}` : 'inconnu'
if (isNullOrUndefined(acc[domaine])) {
acc[domaine] = 1
} else {
acc[domaine] += 1
}
return acc
}, {})
console.info(`Prolongations en vrac : ${prolongationsEnVrac.length} (${toPercentage(prolongationsEnVrac.length / prolongations.length)})`)
console.info(JSON.stringify(prolongationsParType, null, 2))
console.info(`Prolongations sans durée : ${prolongationsAvecDureeManquante.length} (${toPercentage(prolongationsAvecDureeManquante.length / prolongationsEnVrac.length)})`)
})
)
}
import '../init'
import pg from 'pg'
import { config } from '../config'
import { checkProlongations } from '../business/check-prolongations'
import { Effect } from 'effect'
const pool = new pg.Pool({
host: config().PGHOST,
user: config().PGUSER,
password: config().PGPASSWORD,
database: config().PGDATABASE,
})
Effect.runPromiseExit(checkProlongations(pool)).catch(console.error)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment