Skip to content

Commit

Permalink
feat: add data subvention source
Browse files Browse the repository at this point in the history
  • Loading branch information
rmonnier9 committed Oct 10, 2024
1 parent a489228 commit e2a0f24
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 0 deletions.
53 changes: 53 additions & 0 deletions clients/api-data-subvention/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { HttpNotFound } from '#clients/exceptions';
import routes from '#clients/routes';
import constants from '#models/constants';
import { ISubvention, ISubventions } from '#models/subventions/association';
import { Siren } from '#utils/helpers';
import { httpGet } from '#utils/network';

/**
* Data Subvention
* https://api.datasubvention.beta.gouv.fr/
*/
export const clientDataSubvention = async (
siren: Siren
): Promise<ISubventions> => {
const route = routes.apiDataSubvention.grants.replace('{identifier}', siren);
const data = await httpGet<any>(route, {
headers: { 'x-access-token': process.env.DATA_SUBVENTION_API_KEY },
timeout: constants.timeout.XXL,
});
const msgNotFound = `No subvention data found for : ${siren}`;

if (!data.subventions || data.subventions.length === 0) {
throw new HttpNotFound(msgNotFound);
}

const subventions = mapToDomainObject(data.subventions);

if (subventions.length === 0) {
throw new HttpNotFound(msgNotFound);
}
return subventions;
};

const mapToDomainObject = (grantItems: IGrantItem[]): ISubvention[] => {
return grantItems
.filter((grantItem) => Boolean(grantItem.application))
.reduce((subventions: ISubvention[], grantItem) => {
const year = grantItem.application.annee_demande?.value;
const label = grantItem.application.statut_label?.value;
const status = grantItem.application.status?.value;
const dispositif = grantItem.application.dispositif?.value;

const newSubvention: ISubvention = {
year,
label,
status,
dispositif,
};

return [...subventions, newSubvention];
}, [])
.sort((a, b) => b.year - a.year);
};
96 changes: 96 additions & 0 deletions clients/api-data-subvention/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
type ApplicationField<T> = {
value: T;
provider: string;
last_update: string;
type: string;
};

type SubventionStatus = 'Accordé' | 'Refusé' | 'Prise en charge' | 'Recevable';
type SubventionLabel = 'Accordé' | 'Refusé' | 'En instruction';

type Contact = {
email: ApplicationField<string>;
telephone: ApplicationField<string>;
};

type Montants = {
total: ApplicationField<number>;
demande: ApplicationField<number>;
propose: ApplicationField<number>;
accorde: ApplicationField<number>;
};

type Versement = {
acompte: ApplicationField<number>;
solde: ApplicationField<number>;
realise: ApplicationField<number>;
compensation: {
'n-1': ApplicationField<number>;
reversement: ApplicationField<number>;
};
};

type Payment = {
activitee: ApplicationField<string>;
amount: ApplicationField<number>;
bop: ApplicationField<string>;
branche: ApplicationField<string>;
centreFinancier: ApplicationField<string>;
codeBranche: ApplicationField<string>;
dateOperation: ApplicationField<string>;
domaineFonctionnel: ApplicationField<string>;
ej: ApplicationField<string>;
libelleProgramme: ApplicationField<string>;
numeroDemandePayment: ApplicationField<string>;
numeroTier: ApplicationField<string>;
programme: ApplicationField<string>;
siret: ApplicationField<string>;
versementKey: ApplicationField<string>;
};

type ActionProposeeType = {
ej: ApplicationField<string>;
rang: ApplicationField<number>;
intitule: ApplicationField<string>;
objectifs: ApplicationField<string>;
objectifs_operationnels: {
provider: string;
last_update: string;
type: string;
};
description: ApplicationField<string>;
};

type TerritoireType = {
status: {
provider: string;
last_update: string;
type: string;
};
commentaire: ApplicationField<string>;
};

type Application = {
actions_proposee: ActionProposeeType[];
annee_demande: ApplicationField<number>;
contact: Contact;
// This is the name of the subvention
dispositif: ApplicationField<string>;
ej: ApplicationField<string>;
financeur_principal: ApplicationField<string>;
montants: Montants;
pluriannualite: ApplicationField<string>;
service_instructeur: ApplicationField<string>;
siret: ApplicationField<string>;
sous_dispositif: ApplicationField<string>;
status: ApplicationField<SubventionStatus>;
statut_label: ApplicationField<SubventionLabel>;
territoires: TerritoireType[];
versement: Versement;
versementKey: ApplicationField<string>;
};

type IGrantItem = {
application: Application;
payments: Payment[];
};
1 change: 1 addition & 0 deletions clients/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const routes = {
ess: 'https://tabular-api.data.gouv.fr/api/resources/57bc99ca-0432-4b46-8fcc-e76a35c9efaf/data/',
},
apiDataSubvention: {
documentation: 'https://api.datasubvention.beta.gouv.fr/docs',
grants:
'https://api.datasubvention.beta.gouv.fr/association/{identifier}/grants',
},
Expand Down
77 changes: 77 additions & 0 deletions components/subventions-association-section/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client';

import { Tag } from '#components-ui/tag';
import { DataSubvention } from '#components/administrations';
import { DataSectionClient } from '#components/section/data-section';
import { FullTable } from '#components/table/full';
import { EAdministration } from '#models/administrations/EAdministration';
import { IAssociation } from '#models/core/types';
import { ISession } from '#models/user/session';
import { useAPIRouteData } from 'hooks/fetch/use-API-route-data';

export const SubventionsAssociationSection: React.FC<{
uniteLegale: IAssociation;
session: ISession | null;
}> = ({ uniteLegale, session }) => {
const subventions = useAPIRouteData(
'subventions-association',
uniteLegale.siren,
session
);
if (!subventions) return null;

return (
<DataSectionClient
notFoundInfo="Aucune demande de subvention n’a été trouvée pour cette association."
title="Détail des subventions"
sources={[EAdministration.DATA_SUBVENTION]}
data={subventions}
>
{(subventions) =>
subventions.length === 0 ? (
<>
Aucune demande de subvention n’a été trouvée pour cette association.
</>
) : (
<>
<p>
Voici le détail des subventions demandées par l’association. Ces
données sont collectées par <DataSubvention />.
</p>
<FullTable
head={['Année', 'Dispositif', 'Status', 'Label']}
body={subventions.map((subvention) => [
<strong>{subvention.year}</strong>,
<strong>{subvention.dispositif}</strong>,
// TODO Component
<Tag
color={
subvention.status === 'Accordé'
? 'success'
: subvention.status === 'Refusé'
? 'error'
: 'new'
}
>
{subvention.status}
</Tag>,
// TODO Component
<Tag
color={
subvention.status === 'Accordé'
? 'success'
: subvention.status === 'Refusé'
? 'error'
: 'new'
}
>
{subvention.label}
</Tag>,
])}
/>
</>
)
}
</DataSectionClient>
);
};
48 changes: 48 additions & 0 deletions models/subventions/association/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { clientDataSubvention } from '#clients/api-data-subvention';
import { HttpNotFound } from '#clients/exceptions';
import { EAdministration } from '#models/administrations/EAdministration';
import {
APINotRespondingFactory,
IAPINotRespondingError,
} from '#models/api-not-responding';
import { getUniteLegaleFromSlug } from '#models/core/unite-legale';
import { FetchRessourceException } from '#models/exceptions';
import logErrorInSentry from '#utils/sentry';

export type ISubventions = ISubvention[];

export interface ISubvention {
year: number;
label: string;
status: string;
dispositif: string;
}

export const getSubventionsAssociationFromSlug = async (
slug: string
): Promise<ISubventions | IAPINotRespondingError | null> => {
const uniteLegale = await getUniteLegaleFromSlug(slug, {
isBot: false,
});

const { siren } = uniteLegale;

try {
return await clientDataSubvention(siren);
} catch (e: any) {
if (e instanceof HttpNotFound) {
return APINotRespondingFactory(EAdministration.DATA_SUBVENTION, 404);
}
logErrorInSentry(
new FetchRessourceException({
ressource: 'DataSubvention',
cause: e,
context: {
siren,
},
administration: EAdministration.DATA_SUBVENTION,
})
);
return APINotRespondingFactory(EAdministration.DATA_SUBVENTION, 500);
}
};

0 comments on commit e2a0f24

Please sign in to comment.