feat: affiche la réduction générale mois par mois

pull/3197/head
Alice Dahan 2024-10-15 16:39:10 +02:00 committed by liliced
parent efa20c9c07
commit 1cf26edea4
5 changed files with 247 additions and 7 deletions

View File

@ -275,6 +275,8 @@ Revenu du dirigeant par statut: Executive income by status
Revenu net mensuel après impôts: Net monthly income after tax
Règles de calculs: Calculation rules
Réduction annuelle: Annual discount
Réduction générale: General reduction
"Réduction générale mois par mois :": "General discount month by month :"
Réduction mensuelle: Monthly discount
Réduction mois par mois: Monthly discount
Régime d'imposition: Taxation system
@ -391,6 +393,7 @@ aide-déclaration-indépendant:
results:
title: Amounts to report on your tax return
aides différées, voir le détail du calcul pour aides différées: deferred aid, see detailed calculation for deferred aid
août: August
api:
description: Tools for developers
title: Use our REST API
@ -402,6 +405,7 @@ assistants:
cta: Visit the site
title: View your public data
au bout de 10 ans: after 10 years
avril: April
betawarning: "<0><0>This tool is in beta version</0>: we are working on
<3>validating the information and calculations</3>, but <6>errors may</6>
still occur.</0>"
@ -593,6 +597,7 @@ design-system:
prev-month: Previous month
year: Year
dont chômage: of which unemployment
décembre: December
employeur: employer
en cas d'accident pro: in the event of a professional accident
en incluant: including
@ -637,6 +642,7 @@ footer:
accessibilitéAriaLabel: "Accessibility: partially compliant, find out more"
github:
text: View source code on Github
février: February
gestion sémantique de version, en savoir plus, nouvelle fenêtre: semantic version management, learn more, new window
gérer:
choix:
@ -679,7 +685,10 @@ info: info
intégration:
description: Tools for developers
title: Integration
janvier: January
jours: days
juillet: July
juin: June
l'annuaire des entreprises, nouvelle fenêtre: business directory, new window
landing:
aboutUs: "<0>Who are we?</0><1>We're a small, independent, multi-disciplinary
@ -735,6 +744,8 @@ library:
description: Tools for developers
title: Calculation library
loading: Loading in progress...
mai: May
mars: March
moins: less
mois: month
navbar:
@ -753,6 +764,8 @@ nextSteps:
cta: See the documentation
title: Integrate the web module
nombres de votes: number of votes
novembre: November
octobre: October
pages:
"404":
description: The page you are looking for does not exist or no longer exists
@ -1674,6 +1687,7 @@ search-code-ape:
select:
value:
default: Choose an option
septembre: September
shareSimulation:
banner: Generate a share link
button:

View File

@ -291,6 +291,8 @@ Revenu du dirigeant par statut: Revenu du dirigeant par statut
Revenu net mensuel après impôts: Revenu net mensuel après impôts
Règles de calculs: Règles de calculs
Réduction annuelle: Réduction annuelle
Réduction générale: Réduction générale
"Réduction générale mois par mois :": "Réduction générale mois par mois :"
Réduction mensuelle: Réduction mensuelle
Réduction mois par mois: Réduction mois par mois
Régime d'imposition: Régime d'imposition
@ -414,6 +416,7 @@ aide-déclaration-indépendant:
results:
title: Montants à reporter dans votre déclaration de revenus
aides différées, voir le détail du calcul pour aides différées: aides différées, voir le détail du calcul pour aides différées
août: août
api:
description: Outils pour les développeurs
title: Utiliser notre API REST
@ -426,6 +429,7 @@ assistants:
cta: Visiter le site
title: Voir vos données publiques
au bout de 10 ans: au bout de 10 ans
avril: avril
betawarning: "<0><0>Cet outil est en version bêta</0> : nous travaillons à
<3>valider les informations et les calculs</3>, mais des <6>erreurs peuvent
être présentes.</6></0>"
@ -624,6 +628,7 @@ design-system:
prev-month: Mois précédent
year: Année
dont chômage: dont chômage
décembre: décembre
employeur: employeur
en cas d'accident pro: en cas d'accident pro
en incluant: en incluant
@ -669,6 +674,7 @@ footer:
accessibilitéAriaLabel: "Accessibilité : partiellement conforme, en savoir plus"
github:
text: Voir le code source sur Github
février: février
gestion sémantique de version, en savoir plus, nouvelle fenêtre: gestion sémantique de version, en savoir plus, nouvelle fenêtre
gérer:
choix:
@ -714,7 +720,10 @@ info: info
intégration:
description: Outils pour les développeurs
title: Intégration
janvier: janvier
jours: jours
juillet: juillet
juin: juin
l'annuaire des entreprises, nouvelle fenêtre: l'annuaire des entreprises, nouvelle fenêtre
landing:
aboutUs: "<0>Qui sommes-nous ?</0><1>Nous sommes une petite <2>équipe</2>
@ -774,6 +783,8 @@ library:
description: Outils pour les développeurs
title: Librairie de calcul
loading: Chargement en cours...
mai: mai
mars: mars
moins: moins
mois: mois
navbar:
@ -792,6 +803,8 @@ nextSteps:
cta: Voir la documentation
title: Intégrer le module web
nombres de votes: nombres de votes
novembre: novembre
octobre: octobre
pages:
"404":
description: La page que vous cherchez n'existe pas ou n'existe plus
@ -1782,6 +1795,7 @@ search-code-ape:
select:
value:
default: Choisissez une option
septembre: septembre
shareSimulation:
banner: Générer un lien de partage
button:

View File

@ -1,5 +1,6 @@
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { styled } from 'styled-components'
import { Condition } from '@/components/EngineValue/Condition'
@ -11,12 +12,19 @@ import Simulation, {
SimulationGoals,
} from '@/components/Simulation'
import { SimulationValue } from '@/components/Simulation/SimulationValue'
import { useEngine } from '@/components/utils/EngineContext'
import { Message } from '@/design-system'
import { Spacing } from '@/design-system/layout'
import { Li, Ul } from '@/design-system/typography/list'
import { Body } from '@/design-system/typography/paragraphs'
import EffectifSwitch from './components/EffectifSwitch'
import RéductionGénéraleMoisParMois from './RéductionGénéraleMoisParMois'
import {
getInitialRéductionGénéraleMoisParMois,
MonthState,
rémunérationBruteDottedName,
} from './utils'
export default function RéductionGénéraleSimulation() {
const { t } = useTranslation()
@ -80,22 +88,34 @@ function RéductionGénéraleSimulationGoals({
toggles?: React.ReactNode
legend: string
}) {
const engine = useEngine()
const { t } = useTranslation()
const [réductionGénéraleMoisParMoisData, setData] = useState<MonthState[]>([])
const initializeRéductionGénéraleMoisParMoisData = useCallback(() => {
setData(getInitialRéductionGénéraleMoisParMois(engine))
}, [engine, setData])
useEffect(() => {
if (réductionGénéraleMoisParMoisData.length === 0) {
initializeRéductionGénéraleMoisParMoisData()
}
}, [
initializeRéductionGénéraleMoisParMoisData,
réductionGénéraleMoisParMoisData,
])
return (
<SimulationGoals toggles={toggles} legend={legend}>
{monthByMonth ? (
<div>
Réduction générale mensuelle
</div>
<RéductionGénéraleMoisParMois data={réductionGénéraleMoisParMoisData} />
) : (
<>
{/* TODO: remplacer "salarié . cotisations . assiette" par "salarié . rémunération . brut"
lorsqu'elle n'incluera plus les frais professionnels. */}
<SimulationGoal
dottedName="salarié . cotisations . assiette"
dottedName={rémunérationBruteDottedName}
round={false}
label={t('Rémunération brute', 'Rémunération brute')}
onUpdateSituation={initializeRéductionGénéraleMoisParMoisData}
/>
<Condition expression="salarié . cotisations . exonérations . JEI = oui">

View File

@ -0,0 +1,133 @@
import { formatValue } from 'publicodes'
import { useTranslation } from 'react-i18next'
import { styled } from 'styled-components'
import { ExplicableRule } from '@/components/conversation/Explicable'
import NumberInput from '@/components/conversation/NumberInput'
import { useEngine } from '@/components/utils/EngineContext'
import { MonthState, rémunérationBruteDottedName } from './utils'
export default function RéductionGénéraleMoisParMois({
data,
}: {
data: MonthState[]
}) {
const engine = useEngine()
const { t, i18n } = useTranslation()
const language = i18n.language
const displayedUnit = '€'
const months = [
t('janvier'),
t('février'),
t('mars'),
t('avril'),
t('mai'),
t('juin'),
t('juillet'),
t('août'),
t('septembre'),
t('octobre'),
t('novembre'),
t('décembre'),
]
const onRémunérationChange = () => {}
// TODO: enlever les 4 premières props après résolution de #3123
const ruleInputProps = {
dottedName: rémunérationBruteDottedName,
suggestions: {},
description: undefined,
question: undefined,
engine,
'aria-labelledby': 'simu-update-explaining',
formatOptions: {
maximumFractionDigits: 0,
},
displayedUnit,
unit: {
numerators: ['€'],
denominators: [],
},
}
return (
<>
<StyledTable style={{ width: '100%' }}>
<caption>{t('Réduction générale mois par mois :')}</caption>
<thead>
<tr>
<th scope="col">{t('Mois')}</th>
<th scope="col">
{t('Rémunération brute', 'Rémunération brute')}
<ExplicableRule dottedName="salarié . rémunération . brut" />
</th>
<th scope="col">
{t('Réduction générale')}
<ExplicableRule
dottedName="salarié . cotisations . exonérations . réduction générale"
light
/>
</th>
</tr>
</thead>
<tbody>
{data.length > 0 &&
months.map((monthName, monthIndex) => (
<tr key={`month-${monthIndex}`}>
<th scope="row">{monthName}</th>
<td>
<NumberInput
{...ruleInputProps}
id={`${rémunérationBruteDottedName.replace(
/\s|\./g,
'_'
)}-${monthIndex}`}
aria-label={`${engine.getRule(rémunérationBruteDottedName)
?.title} (${monthName})`}
onChange={onRémunérationChange}
value={data[monthIndex].rémunérationBrute}
/>
</td>
<td>
{data[monthIndex].réductionGénérale
? formatValue(
{ nodeValue: data[monthIndex].réductionGénérale },
{
displayedUnit,
language,
}
)
: formatValue(0, { displayedUnit, language })}
</td>
</tr>
))}
</tbody>
</StyledTable>
</>
)
}
const StyledTable = styled.table`
text-align: left;
width: 100%;
color: ${({ theme }) => theme.colors.bases.primary[100]};
font-family: ${({ theme }) => theme.fonts.main};
caption {
text-align: left;
margin: ${({ theme }) => `${theme.spacings.sm} 0 `};
}
th {
padding: ${({ theme }) => `${theme.spacings.xs} 0 ${theme.spacings.lg} 0`};
}
tbody tr td:not(:first-of-type) {
padding: ${({ theme }) =>
`${theme.spacings.xs} ${theme.spacings.xxs} ${theme.spacings.lg} ${theme.spacings.xxs}`};
}
tbody tr th {
text-transform: capitalize;
font-weight: normal;
}
`

View File

@ -0,0 +1,59 @@
import { DottedName } from 'modele-social'
import Engine from 'publicodes'
// TODO: remplacer "salarié . cotisations . assiette" par "salarié . rémunération . brut"
// lorsqu'elle n'incluera plus les frais professionnels.
export const rémunérationBruteDottedName = 'salarié . cotisations . assiette'
export type MonthState = {
rémunérationBrute: number
réductionGénérale: number
}
export const getRéductionGénéraleFromRémunération = (
engine: Engine<DottedName>,
rémunérationBrute: number
): number => {
const réductionGénérale = engine.evaluate({
valeur: 'salarié . cotisations . exonérations . réduction générale',
unité: '€/mois',
contexte: {
[rémunérationBruteDottedName]: rémunérationBrute,
},
})
return réductionGénérale.nodeValue as number
}
export const getInitialRéductionGénéraleMoisParMois = (
engine: Engine<DottedName>
): MonthState[] => {
const rémunérationBrute =
(engine.evaluate({
valeur: rémunérationBruteDottedName,
arrondi: 'oui',
unité: '€/mois',
})?.nodeValue as number) || 0
const réductionGénérale = getRéductionGénéraleFromRémunération(
engine,
rémunérationBrute
)
return Array(12).fill({
rémunérationBrute,
réductionGénérale,
}) as MonthState[]
}
export const reevaluateRéductionGénéraleMoisParMois = (
data: MonthState[],
engine: Engine<DottedName>
): MonthState[] => {
return data.map((item) => ({
...item,
réductionGénérale: getRéductionGénéraleFromRémunération(
engine,
item.rémunérationBrute
),
}))
}