feat: ajout d'un champ heures supplémentaires pour le calcul de la RGCP mois par mois

feat/3221-rgcp-hs-par-mois
Alice Dahan 2024-11-18 17:01:56 +01:00
parent 099511a6cd
commit 8bdb44dd41
9 changed files with 456 additions and 135 deletions

View File

@ -142,7 +142,7 @@ export default function NumberField(props: NumberFieldProps) {
</StyledUnit>
)}
{props.label && (
{props.label && !props.small && (
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
)}
</StyledInputContainer>

View File

@ -269,14 +269,8 @@ Revenu disponible: Disposable income
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
Régularisation annuelle: Annual adjustment
Régularisation progressive: Progressive regularization
Régularisaton: Regularization
Réinitialiser: Reset
Réinitialiser la situation enregistrée: Reset registered situation
@ -337,7 +331,6 @@ Tout plier: Fold everything
Tout réinitialiser: Reset all
Travailleurs Non Salariés (TNS): Self-employed workers (TNS)
Type: Type
Type de régularisation: Type of adjustment
URSSAF Mon entreprise, accéder à la page d'accueil: URSSAF Mon entreprise, go to home page
Un <1>capital « orphelin »</1> est versé aux <4>enfants des travailleurs indépendants</4> décédés, sous certaines conditions.:
An <1>"orphan" capital</1> is paid to the <4>children</4> of deceased
@ -1480,12 +1473,39 @@ pages:
shortname: Liberal profession
title: Income simulator for self-employed professionals
réduction-générale:
legend: Employee's gross salary and applicable general reduction
meta:
description: Estimate of the amount of the general reduction in employer
contributions (RGCP). This reduction applies to salaries below 1.6
times the SMIC.
title: General reduction
month-by-month:
caption: "General discount month by month :"
option:
aria-label: Adds fields for adjusting remuneration (overtime)
label:
heures-complémentaires: Overtime
heures-supplémentaires: Overtime
popover: "<0>The number of hours of overtime or complementary work is used to
calculate the general reduction: the gross remuneration is compared
with the SMIC increased by the number of hours of overtime or
complementary work.</0><1>If you have answered the question on
overtime or complementary hours, the value will be overwritten by the
value you enter month by month.</1>"
title: More options (overtime)
régularisation:
annuelle: Annual adjustment
progressive: Progressive regularization
type: Type of adjustment
répartition:
chômage: of which unemployment
retraite: IRC
urssaf: URSSAF
shortname: General reduction
tab:
month: Monthly discount
month-by-month: Monthly discount
year: Annual discount
title: General contribution reduction simulator
warnings:
JEI: The general reduction cannot be combined with the Young Innovative Company

View File

@ -284,14 +284,8 @@ Revenu disponible: Revenu disponible
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
Régularisation annuelle: Régularisation annuelle
Régularisation progressive: Régularisation progressive
Régularisaton: Régularisaton
Réinitialiser: Réinitialiser
Réinitialiser la situation enregistrée: Réinitialiser la situation enregistrée
@ -352,7 +346,6 @@ Tout plier: Tout plier
Tout réinitialiser: Tout réinitialiser
Travailleurs Non Salariés (TNS): Travailleurs Non Salariés (TNS)
Type: Type
Type de régularisation: Type de régularisation
URSSAF Mon entreprise, accéder à la page d'accueil: URSSAF Mon entreprise, accéder à la page d'accueil
Un <1>capital « orphelin »</1> est versé aux <4>enfants des travailleurs indépendants</4> décédés, sous certaines conditions.:
Un <1>capital « orphelin »</1> est versé aux <4>enfants des travailleurs
@ -1574,12 +1567,39 @@ pages:
shortname: Profession libérale
title: Simulateur de revenus pour profession libérale
réduction-générale:
legend: Salaire brut du salarié et réduction générale applicable
meta:
description: Estimation du montant de la réduction générale des cotisations
patronales (RGCP). Cette réduction est applicable pour les salaires
inférieurs à 1,6 fois le SMIC.
title: Réduction générale
month-by-month:
caption: "Réduction générale mois par mois :"
option:
aria-label: Ajoute des champs pour ajuster la rémunération (heures supplémentaires)
label:
heures-complémentaires: Heures complémentaires
heures-supplémentaires: Heures supplémentaires
popover: "<0>Le nombre d'heures supplémentaires et complémentaires est utilisé
dans le calcul de la réduction générale : la rémunération brute est
comparée au montant du SMIC majoré de ce nombre d'heures
supplémentaires ou complémentaires.</0><1>Si vous avez répondu à la
question sur les heures supplémentaires ou complémentaires, la valeur
sera écrasée par celle que vous saisissez mois par mois.</1>"
title: Plus d'options (heures supplémentaires)
régularisation:
annuelle: Régularisation annuelle
progressive: Régularisation progressive
type: Type de régularisation
répartition:
chômage: dont chômage
retraite: IRC
urssaf: URSSAF
shortname: Réduction générale
tab:
month: Réduction mensuelle
month-by-month: Réduction mois par mois
year: Réduction annuelle
title: Simulateur de réduction générale des cotisations
warnings:
JEI: La réduction générale n'est pas cumulable avec l'exonération Jeune

View File

@ -31,6 +31,7 @@ import {
getInitialRéductionGénéraleMoisParMois,
getRéductionGénéraleFromRémunération,
MonthState,
Options,
réductionGénéraleDottedName,
reevaluateRéductionGénéraleMoisParMois,
RégularisationMethod,
@ -42,15 +43,24 @@ export default function RéductionGénéraleSimulation() {
const [monthByMonth, setMonthByMonth] = useState(false)
const periods = [
{
label: t('Réduction mensuelle'),
label: t(
'pages.simulateurs.réduction-générale.tab.month',
'Réduction mensuelle'
),
unit: '€/mois',
},
{
label: t('Réduction annuelle'),
label: t(
'pages.simulateurs.réduction-générale.tab.year',
'Réduction annuelle'
),
unit: '€/an',
},
{
label: t('Réduction mois par mois'),
label: t(
'pages.simulateurs.réduction-générale.tab.month-by-month',
'Réduction mois par mois'
),
unit: '€',
},
]
@ -67,7 +77,10 @@ export default function RéductionGénéraleSimulation() {
<SimulateurWarning simulateur="réduction-générale" />
<RéductionGénéraleSimulationGoals
monthByMonth={monthByMonth}
legend="Salaire brut du salarié et réduction générale applicable"
legend={t(
'pages.simulateurs.réduction-générale.legend',
'Salaire brut du salarié et réduction générale applicable'
)}
toggles={
<>
<RégularisationSwitch
@ -148,10 +161,12 @@ function RéductionGénéraleSimulationGoals({
setData((previousData) => {
const updatedData = [...previousData]
updatedData[monthIndex] = {
...updatedData[monthIndex],
rémunérationBrute,
réductionGénérale: getRéductionGénéraleFromRémunération(
engine,
rémunérationBrute
rémunérationBrute,
updatedData[monthIndex].options
),
régularisation: 0,
}
@ -162,19 +177,40 @@ function RéductionGénéraleSimulationGoals({
})
}
const onOptionChange = (monthIndex: number, options: Options) => {
setData((previousData) => {
const updatedData = [...previousData]
const réductionGénérale = getRéductionGénéraleFromRémunération(
engine,
updatedData[monthIndex].rémunérationBrute,
options
)
updatedData[monthIndex] = {
...updatedData[monthIndex],
options,
réductionGénérale,
régularisation: 0,
}
return updatedData
})
}
return (
<SimulationGoals toggles={toggles} legend={legend}>
{monthByMonth ? (
<RéductionGénéraleMoisParMois
data={réductionGénéraleMoisParMoisData}
onChange={onRémunérationChange}
onRémunérationChange={onRémunérationChange}
onOptionChange={onOptionChange}
/>
) : (
<>
<SimulationGoal
dottedName={rémunérationBruteDottedName}
round={false}
label={t('Rémunération brute', 'Rémunération brute')}
label={t('Rémunération brute')}
onUpdateSituation={initializeRéductionGénéraleMoisParMoisData}
/>

View File

@ -5,16 +5,18 @@ import { ExplicableRule } from '@/components/conversation/Explicable'
import RéductionGénéraleMoisParMoisRow from './components/RéductionGénéraleMoisParMoisRow'
import Warnings from './components/Warnings'
import { MonthState, réductionGénéraleDottedName } from './utils'
import { MonthState, Options, réductionGénéraleDottedName } from './utils'
type Props = {
data: MonthState[]
onChange: (monthIndex: number, rémunérationBrute: number) => void
onRémunérationChange: (monthIndex: number, rémunérationBrute: number) => void
onOptionChange: (monthIndex: number, options: Options) => void
}
export default function RéductionGénéraleMoisParMois({
data,
onChange,
onRémunérationChange,
onOptionChange,
}: Props) {
const { t } = useTranslation()
@ -36,14 +38,20 @@ export default function RéductionGénéraleMoisParMois({
return (
<>
<StyledTable style={{ width: '100%' }}>
<caption>{t('Réduction générale mois par mois :')}</caption>
<caption>
{t(
'pages.simulateurs.réduction-générale.month-by-month.caption',
'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')}
{t('Rémunération brute')}
<ExplicableRule dottedName="salarié . rémunération . brut" />
</th>
<th />
<th scope="col">
{t('Réduction générale')}
<ExplicableRule dottedName={réductionGénéraleDottedName} light />
@ -59,8 +67,14 @@ export default function RéductionGénéraleMoisParMois({
monthName={monthName}
data={data[monthIndex]}
index={monthIndex}
onChange={(monthIndex: number, rémunérationBrute: number) => {
onChange(monthIndex, rémunérationBrute)
onRémunérationChange={(
monthIndex: number,
rémunérationBrute: number
) => {
onRémunérationChange(monthIndex, rémunérationBrute)
}}
onOptionChange={(monthIndex: number, options: Options) => {
onOptionChange(monthIndex, options)
}}
/>
))}

View File

@ -1,15 +1,21 @@
import { formatValue, PublicodesExpression } from 'publicodes'
import { useTranslation } from 'react-i18next'
import { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { styled } from 'styled-components'
import NumberInput from '@/components/conversation/NumberInput'
import { Condition } from '@/components/EngineValue/Condition'
import { Appear } from '@/components/ui/animate'
import { useEngine } from '@/components/utils/EngineContext'
import { SearchIcon, WarningIcon } from '@/design-system/icons'
import { Message, NumberField } from '@/design-system'
import { HelpButtonWithPopover } from '@/design-system/buttons'
import { PlusCircleIcon, SearchIcon, WarningIcon } from '@/design-system/icons'
import { Tooltip } from '@/design-system/tooltip'
import { Body, ExtraSmallBody } from '@/design-system/typography/paragraphs'
import {
MonthState,
Options,
réductionGénéraleDottedName,
rémunérationBruteDottedName,
} from '../utils'
@ -20,7 +26,8 @@ type Props = {
monthName: string
data: MonthState
index: number
onChange: (monthIndex: number, rémunérationBrute: number) => void
onRémunérationChange: (monthIndex: number, rémunérationBrute: number) => void
onOptionChange: (monthIndex: number, options: Options) => void
}
type RémunérationBruteInput = {
@ -32,20 +39,14 @@ export default function RéductionGénéraleMoisParMoisRow({
monthName,
data,
index,
onChange,
onRémunérationChange,
onOptionChange,
}: Props) {
const { t, i18n } = useTranslation()
const language = i18n.language
const displayedUnit = '€'
const engine = useEngine()
const onRémunérationChange = (
monthIndex: number,
rémunérationBrute: RémunérationBruteInput
) => {
onChange(monthIndex, rémunérationBrute.valeur)
}
// TODO: enlever les 4 premières props après résolution de #3123
const ruleInputProps = {
dottedName: rémunérationBruteDottedName,
@ -64,6 +65,16 @@ export default function RéductionGénéraleMoisParMoisRow({
},
}
const [isOptionVisible, setOptionVisible] = useState(false)
const heuresSupplémentairesLabel = t(
'pages.simulateurs.réduction-générale.option.label.heures-supplémentaires',
'Heures supplémentaires'
)
const heuresComplémentairesLabel = t(
'pages.simulateurs.réduction-générale.option.label.heures-complémentaires',
'Heures complémentaires'
)
const tooltip = (
<Répartition
contexte={{
@ -75,88 +86,197 @@ export default function RéductionGénéraleMoisParMoisRow({
)
return (
<tr>
<th scope="row">{monthName}</th>
<td>
<NumberInput
{...ruleInputProps}
id={`${rémunérationBruteDottedName.replace(
<>
<tr>
<th scope="row">{monthName}</th>
<td>
<NumberInput
{...ruleInputProps}
id={`${rémunérationBruteDottedName.replace(
/\s|\./g,
'_'
)}-${monthName}`}
aria-label={`${engine.getRule(rémunérationBruteDottedName)
?.title} (${monthName})`}
onChange={(rémunérationBrute?: PublicodesExpression) =>
onRémunérationChange(
index,
(rémunérationBrute as RémunérationBruteInput).valeur
)
}
value={data.rémunérationBrute}
formatOptions={{
maximumFractionDigits: 2,
}}
/>
</td>
<td>
<StyledPlusIcon
role="button"
title={t(
'pages.simulateurs.réduction-générale.option.title',
"Plus d'options (heures supplémentaires)"
)}
aria-label={t(
'pages.simulateurs.réduction-générale.option.aria-label',
'Ajoute des champs pour ajuster la rémunération (heures supplémentaires)'
)}
onClick={() => setOptionVisible(!isOptionVisible)}
>
<PlusCircleIcon />
</StyledPlusIcon>
</td>
<td
id={`${réductionGénéraleDottedName.replace(
/\s|\./g,
'_'
)}-${monthName}`}
aria-label={`${engine.getRule(rémunérationBruteDottedName)
?.title} (${monthName})`}
onChange={(rémunérationBrute?: PublicodesExpression) =>
onRémunérationChange(
index,
rémunérationBrute as RémunérationBruteInput
)
}
value={data.rémunérationBrute}
formatOptions={{
maximumFractionDigits: 2,
}}
/>
</td>
<td
id={`${réductionGénéraleDottedName.replace(
/\s|\./g,
'_'
)}-${monthName}`}
>
{data.réductionGénérale ? (
<Tooltip tooltip={tooltip}>
>
{data.réductionGénérale ? (
<Tooltip tooltip={tooltip}>
<StyledDiv>
{formatValue(
{
nodeValue: data.réductionGénérale,
},
{
displayedUnit,
language,
}
)}
<SearchIcon />
</StyledDiv>
</Tooltip>
) : (
<StyledDiv>
{formatValue(
{
nodeValue: data.réductionGénérale,
},
{
displayedUnit,
language,
}
)}
<SearchIcon />
</StyledDiv>
</Tooltip>
) : (
<StyledDiv>
{formatValue(0, { displayedUnit, language })}
{formatValue(0, { displayedUnit, language })}
<Condition
expression={`${rémunérationBruteDottedName} > 1.6 * SMIC`}
contexte={{
[rémunérationBruteDottedName]: data.rémunérationBrute,
}}
>
<Tooltip tooltip={<WarningSalaireTrans />}>
<span className="sr-only">{t('Attention')}</span>
<StyledWarningIcon aria-label={t('Attention')} />
</Tooltip>
</Condition>
</StyledDiv>
)}
</td>
<td
id={`${réductionGénéraleDottedName.replace(
/\s|\./g,
'_'
)}__régularisation-${monthName}`}
>
{formatValue(
{
nodeValue: data.régularisation,
},
{
displayedUnit,
language,
}
)}
</td>
</tr>
<Condition
expression={`${rémunérationBruteDottedName} > 1.6 * SMIC`}
contexte={{
[rémunérationBruteDottedName]: data.rémunérationBrute,
}}
>
<Tooltip tooltip={<WarningSalaireTrans />}>
<span className="sr-only">{t('Attention')}</span>
<StyledWarningIcon aria-label={t('Attention')} />
</Tooltip>
</Condition>
</StyledDiv>
)}
</td>
<td
id={`${réductionGénéraleDottedName.replace(
/\s|\./g,
'_'
)}__régularisation-${monthName}`}
>
{formatValue(
{
nodeValue: data.régularisation,
},
{
displayedUnit,
language,
}
)}
</td>
</tr>
{isOptionVisible && (
<tr>
<td />
<td>
<Appear>
<Condition expression="salarié . contrat . temps de travail . temps partiel = non">
<StyledLabel>
<StyledExtraSmallBody id="heures-supplémentaires-label">
{heuresSupplémentairesLabel}
</StyledExtraSmallBody>
<HelpButtonWithPopover
type="info"
title={heuresSupplémentairesLabel}
>
<HeuresSupplémentairesPopoverContent />
</HelpButtonWithPopover>
</StyledLabel>
<NumberField
small={true}
value={data.options.heuresSupplémentaires}
onChange={(value?: number) =>
onOptionChange(index, {
heuresSupplémentaires: value,
heuresComplémentaires: 0,
})
}
aria-labelledby="heures-supplémentaires-label"
displayedUnit="heures"
/>
</Condition>
<Condition expression="salarié . contrat . temps de travail . temps partiel = oui">
<StyledLabel>
<StyledExtraSmallBody id="heures-complémentaires-label">
{heuresComplémentairesLabel}
</StyledExtraSmallBody>
<HelpButtonWithPopover
type="info"
title={heuresComplémentairesLabel}
>
<HeuresSupplémentairesPopoverContent />
</HelpButtonWithPopover>
</StyledLabel>
<NumberField
small={true}
value={data.options.heuresComplémentaires}
onChange={(value?: number) =>
onOptionChange(index, {
heuresSupplémentaires: 0,
heuresComplémentaires: value,
})
}
aria-labelledby="heures-complémentaires-label"
displayedUnit="heures"
/>
</Condition>
</Appear>
</td>
</tr>
)}
</>
)
}
function HeuresSupplémentairesPopoverContent() {
return (
<Trans i18nKey="pages.simulateurs.réduction-générale.option.popover">
<Body>
Le nombre d'heures supplémentaires et complémentaires est utilisé dans
le calcul de la réduction générale : la rémunération brute est comparée
au montant du SMIC majoré de ce nombre d'heures supplémentaires ou
complémentaires.
</Body>
<Message type="info">
Si vous avez répondu à la question sur les heures supplémentaires ou
complémentaires, la valeur sera écrasée par celle que vous saisissez
mois par mois.
</Message>
</Trans>
)
}
const StyledPlusIcon = styled.div`
cursor: pointer;
svg {
fill: ${({ theme }) => theme.colors.extended.grey[100]};
}
&:hover {
svg {
fill: ${({ theme }) => theme.colors.extended.grey[300]};
}
}
`
const StyledDiv = styled.div`
display: flex;
align-items: center;
@ -166,3 +286,14 @@ const StyledDiv = styled.div`
const StyledWarningIcon = styled(WarningIcon)`
margin-top: ${({ theme }) => theme.spacings.xxs};
`
const StyledLabel = styled.div`
margin-top: -${({ theme }) => theme.spacings.md};
margin-bottom: ${({ theme }) => theme.spacings.xs};
display: flex;
align-items: center;
`
const StyledExtraSmallBody = styled(ExtraSmallBody)`
margin-top: 0;
margin-bottom: 0;
`

View File

@ -21,10 +21,23 @@ export default function RégularisationSwitch({
onChange={(value) => {
setRégularisationMethod(value as RégularisationMethod)
}}
aria-label={t('Type de régularisation')}
aria-label={t(
'pages.simulateurs.réduction-générale.régularisation.type',
'Type de régularisation'
)}
>
<Radio value="annuelle">{t('Régularisation annuelle')}</Radio>
<Radio value="progressive">{t('Régularisation progressive')}</Radio>
<Radio value="annuelle">
{t(
'pages.simulateurs.réduction-générale.régularisation.annuelle',
'Régularisation annuelle'
)}
</Radio>
<Radio value="progressive">
{t(
'pages.simulateurs.réduction-générale.régularisation.progressive',
'Régularisation progressive'
)}
</Radio>
</ToggleGroup>
)
}

View File

@ -1,4 +1,4 @@
import { Trans } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import { styled } from 'styled-components'
import { SimulationValue } from '@/components/Simulation/SimulationValue'
@ -14,6 +14,8 @@ type Props = {
}
export default function Répartition({ contexte = {} }: Props) {
const { t } = useTranslation()
return (
<>
<Body>
@ -25,7 +27,10 @@ export default function Répartition({ contexte = {} }: Props) {
<StyledLi>
<SimulationValue
dottedName={`${réductionGénéraleDottedName} . imputation retraite complémentaire`}
label="IRC"
label={t(
'pages.simulateurs.réduction-générale.répartition.retraite',
'IRC'
)}
contexte={contexte}
round={false}
/>
@ -33,13 +38,19 @@ export default function Répartition({ contexte = {} }: Props) {
<StyledLi>
<SimulationValue
dottedName={`${réductionGénéraleDottedName} . imputation sécurité sociale`}
label="URSSAF"
label={t(
'pages.simulateurs.réduction-générale.répartition.urssaf',
'URSSAF'
)}
contexte={contexte}
round={false}
/>
<SimulationValue
dottedName={`${réductionGénéraleDottedName} . imputation chômage`}
label="dont chômage"
label={t(
'pages.simulateurs.réduction-générale.répartition.chômage',
'dont chômage'
)}
contexte={contexte}
round={false}
/>

View File

@ -1,30 +1,43 @@
import { sumAll } from 'effect/Number'
import { DottedName } from 'modele-social'
import Engine from 'publicodes'
import Engine, { PublicodesExpression } 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 const réductionGénéraleDottedName =
'salarié . cotisations . exonérations . réduction générale'
export const heuresSupplémentairesDottedName =
'salarié . temps de travail . heures supplémentaires'
export const heuresComplémentairesDottedName =
'salarié . temps de travail . heures complémentaires'
export type MonthState = {
rémunérationBrute: number
options: Options
réductionGénérale: number
régularisation: number
}
export type Options = {
heuresSupplémentaires?: number
heuresComplémentaires?: number
}
export type RégularisationMethod = 'annuelle' | 'progressive'
export const getRéductionGénéraleFromRémunération = (
engine: Engine<DottedName>,
rémunérationBrute: number
rémunérationBrute: number,
options: Options
): number => {
const réductionGénérale = engine.evaluate({
valeur: réductionGénéraleDottedName,
unité: '€/mois',
contexte: {
[rémunérationBruteDottedName]: rémunérationBrute,
[heuresSupplémentairesDottedName]: options.heuresSupplémentaires ?? 0,
[heuresComplémentairesDottedName]: options.heuresComplémentaires ?? 0,
},
})
@ -40,12 +53,29 @@ export const getInitialRéductionGénéraleMoisParMois = (
arrondi: 'oui',
unité: '€/mois',
})?.nodeValue as number) || 0
const heuresSupplémentaires =
(engine.evaluate({
valeur: heuresSupplémentairesDottedName,
unité: 'heures/mois',
})?.nodeValue as number) || 0
const heuresComplémentaires =
(engine.evaluate({
valeur: heuresComplémentairesDottedName,
unité: 'heures/mois',
})?.nodeValue as number) || 0
const réductionGénérale = rémunérationBrute
? getRéductionGénéraleFromRémunération(engine, rémunérationBrute)
? getRéductionGénéraleFromRémunération(engine, rémunérationBrute, {
heuresSupplémentaires,
heuresComplémentaires,
})
: 0
return Array(12).fill({
rémunérationBrute,
options: {
heuresSupplémentaires,
heuresComplémentaires,
},
réductionGénérale,
régularisation: 0,
}) as MonthState[]
@ -56,16 +86,27 @@ export const reevaluateRéductionGénéraleMoisParMois = (
engine: Engine<DottedName>,
régularisationMethod: RégularisationMethod
): MonthState[] => {
const SMICMensuel = engine.evaluate({
valeur: 'salarié . temps de travail . SMIC',
unité: 'heures/mois',
}).nodeValue as number
// Si on laisse l'engine calculer T dans le calcul de la réduction générale,
// le résultat ne sera pas bon à cause de l'assiette de cotisations du contexte
const coefT = engine.evaluate({
valeur: 'salarié . cotisations . exonérations . T',
}).nodeValue as number
const heuresSupplémentaires =
(engine.evaluate({
valeur: heuresSupplémentairesDottedName,
unité: 'heures/mois',
})?.nodeValue as number) || 0
const heuresComplémentaires =
(engine.evaluate({
valeur: heuresComplémentairesDottedName,
unité: 'heures/mois',
})?.nodeValue as number) || 0
const options = {
heuresSupplémentaires,
heuresComplémentaires,
}
const reevaluatedData = data.reduce(
(reevaluatedData: MonthState[], monthState: MonthState, index) => {
const rémunérationBrute = monthState.rémunérationBrute
@ -76,6 +117,7 @@ export const reevaluateRéductionGénéraleMoisParMois = (
...reevaluatedData,
{
rémunérationBrute,
options,
réductionGénérale,
régularisation,
},
@ -85,7 +127,6 @@ export const reevaluateRéductionGénéraleMoisParMois = (
régularisation = getRégularisationProgressive(
index,
partialData,
SMICMensuel,
coefT,
engine
)
@ -96,7 +137,8 @@ export const reevaluateRéductionGénéraleMoisParMois = (
} else if (régularisationMethod === 'annuelle') {
réductionGénérale = getRéductionGénéraleFromRémunération(
engine,
rémunérationBrute
rémunérationBrute,
options
)
if (index === data.length - 1) {
régularisation = getRégularisationAnnuelle(
@ -115,6 +157,7 @@ export const reevaluateRéductionGénéraleMoisParMois = (
...reevaluatedData,
{
rémunérationBrute,
options,
réductionGénérale,
régularisation,
},
@ -134,15 +177,26 @@ const getRégularisationAnnuelle = (
réductionGénéraleDernierMois: number,
engine: Engine<DottedName>
): number => {
const currentRéductionGénéraleAnnuelle =
réductionGénéraleDernierMois +
sumAll(data.map((monthData) => monthData.réductionGénérale))
const totalHeuresSupplémentaires = sumAll(
data.map((monthData) => monthData.options.heuresSupplémentaires ?? 0)
)
const totalHeuresComplémentaires = sumAll(
data.map((monthData) => monthData.options.heuresComplémentaires ?? 0)
)
const realRéductionGénéraleAnnuelle = engine.evaluate({
valeur: réductionGénéraleDottedName,
arrondi: 'non',
unité: '€/an',
contexte: {
[heuresSupplémentairesDottedName]: `${totalHeuresSupplémentaires} heures/an`,
[heuresComplémentairesDottedName]: `${totalHeuresComplémentaires} heures/an`,
},
}).nodeValue as number
const currentRéductionGénéraleAnnuelle =
réductionGénéraleDernierMois +
sumAll(data.map((monthData) => monthData.réductionGénérale))
return realRéductionGénéraleAnnuelle - currentRéductionGénéraleAnnuelle
}
@ -152,7 +206,6 @@ const getRégularisationAnnuelle = (
const getRégularisationProgressive = (
monthIndex: number,
data: MonthState[],
SMICMensuel: number,
coefT: number,
engine: Engine<DottedName>
): number => {
@ -170,7 +223,30 @@ const getRégularisationProgressive = (
return 0
}
const SMICCumulé = nbOfMonths * SMICMensuel
// TODO: optimiser le calcul du SMIC
// (ne pas recalculer l'équivalent SMIC du mois de janvier à chaque mois de l'année)
const SMICCumulé = partialData.reduce(
(SMICCumulé: number, monthData: MonthState) => {
const contexte: PublicodesExpression = {}
if (monthData.options.heuresSupplémentaires) {
contexte[heuresSupplémentairesDottedName] =
monthData.options.heuresSupplémentaires
}
if (monthData.options.heuresComplémentaires) {
contexte[heuresComplémentairesDottedName] =
monthData.options.heuresComplémentaires
}
const SMICCurrentMonth = engine.evaluate({
valeur: 'salarié . temps de travail . SMIC',
unité: '€/mois',
contexte,
}).nodeValue as number
return SMICCumulé + SMICCurrentMonth
},
0
)
const réductionGénéraleTotale = engine.evaluate({
valeur: réductionGénéraleDottedName,