feat: ajout d'un champ heures supplémentaires pour le calcul de la RGCP mois par mois
parent
099511a6cd
commit
8bdb44dd41
|
@ -142,7 +142,7 @@ export default function NumberField(props: NumberFieldProps) {
|
|||
</StyledUnit>
|
||||
)}
|
||||
|
||||
{props.label && (
|
||||
{props.label && !props.small && (
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
||||
|
|
|
@ -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)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -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;
|
||||
`
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue