feat(lodeom): ajout du simulateur lodem (zone un - barème compétitivité)
parent
cd9ea8ac12
commit
8f6f33556e
|
@ -402,7 +402,6 @@ salarié . cotisations . exonérations . lodeom . montant:
|
|||
|
||||
avec:
|
||||
coefficient:
|
||||
privé: oui
|
||||
variations:
|
||||
- si: cotisations . assiette <= seuil inflexion * temps de travail . SMIC
|
||||
alors: T
|
||||
|
@ -465,12 +464,10 @@ salarié . cotisations . exonérations . lodeom . montant:
|
|||
alors: 4.5
|
||||
|
||||
imputation retraite complémentaire:
|
||||
privé: oui
|
||||
non applicable si: lodeom . zone deux
|
||||
valeur: lodeom . montant - imputation sécurité sociale
|
||||
|
||||
imputation sécurité sociale:
|
||||
privé: oui
|
||||
non applicable si: lodeom . zone deux
|
||||
produit:
|
||||
- lodeom . montant
|
||||
|
|
|
@ -20,7 +20,7 @@ import { Li, Ul } from '@/design-system/typography/list'
|
|||
import { Body, SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery'
|
||||
|
||||
import { Options } from '../utils'
|
||||
import { Options } from '../pages/simulateurs/reduction-generale/utils'
|
||||
|
||||
type Props = {
|
||||
month: string
|
||||
|
@ -260,8 +260,8 @@ const HeuresSupplémentairesPopoverContent = () => (
|
|||
<Trans i18nKey="pages.simulateurs.réduction-générale.options.heures-sup.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.
|
||||
calcul de la réduction : la rémunération brute est comparée au montant du
|
||||
SMIC majoré de ce nombre d'heures.
|
||||
</Body>
|
||||
</Trans>
|
||||
)
|
|
@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
|
|||
|
||||
import { Radio, ToggleGroup } from '@/design-system'
|
||||
|
||||
import { RégularisationMethod } from '../utils'
|
||||
import { RégularisationMethod } from '../pages/simulateurs/reduction-generale/utils'
|
||||
|
||||
type Props = {
|
||||
régularisationMethod: RégularisationMethod
|
|
@ -4,7 +4,7 @@ import { styled } from 'styled-components'
|
|||
|
||||
import LectureGuide from '@/components/Simulation/LectureGuide'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { SmallBody } from '@/design-system/typography/paragraphs'
|
||||
|
||||
type Props = {
|
||||
value: number
|
||||
|
@ -26,19 +26,19 @@ export default function RépartitionValue({ value, label, idPrefix }: Props) {
|
|||
spacing={2}
|
||||
>
|
||||
<Grid item md="auto" sm={9} xs={8}>
|
||||
<StyledBody id={`${idPrefix}-label`}>{label}</StyledBody>
|
||||
<StyledSmallBody id={`${idPrefix}-label`}>{label}</StyledSmallBody>
|
||||
</Grid>
|
||||
|
||||
<LectureGuide />
|
||||
|
||||
<Grid item>
|
||||
<StyledBody id={`${idPrefix}-value`}>
|
||||
<StyledSmallBody id={`${idPrefix}-value`}>
|
||||
{formatValue(value, {
|
||||
displayedUnit: '€',
|
||||
precision: 2,
|
||||
language,
|
||||
})}
|
||||
</StyledBody>
|
||||
</StyledSmallBody>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StyledValue>
|
||||
|
@ -55,7 +55,7 @@ const StyledValue = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
const StyledBody = styled(Body)`
|
||||
const StyledSmallBody = styled(SmallBody)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[100]};
|
||||
margin: 0;
|
||||
padding: ${({ theme }) => `${theme.spacings.xs} ${theme.spacings.sm} 0 0`};
|
|
@ -1447,6 +1447,43 @@ pages:
|
|||
gains (sale of securities, sale of patents). These regimes are not
|
||||
included in the simulator.</5>
|
||||
title: Corporate tax simulator
|
||||
lodeom:
|
||||
legend: Employee's gross salary and applicable Lodeom exemption
|
||||
meta:
|
||||
description: Estimated amount of Lodeom exemption. This exemption applies, under
|
||||
certain conditions, to overseas employees.
|
||||
title: Lodeom exemption
|
||||
month-by-month:
|
||||
caption: "Lodeom exemption month by month :"
|
||||
options:
|
||||
description: Adds fields to modulate employee activity
|
||||
recap:
|
||||
T1: 1st quarter
|
||||
T2: 2nd quarter
|
||||
T3: 3rd quarter
|
||||
T4: 4th quarter
|
||||
caption: "Quarterly summary :"
|
||||
header:
|
||||
réduction: Calculated reduction
|
||||
régularisation: Calculated regularization
|
||||
répartition:
|
||||
retraite: IRC
|
||||
urssaf: URSSAF
|
||||
shortname: Lodeom exemption
|
||||
tab:
|
||||
month: Monthly exemption
|
||||
month-by-month: Month-by-month exemption
|
||||
year: Annual exemption
|
||||
title: Lodeom exemption simulator
|
||||
warnings:
|
||||
JEI: The Lodeom exemption cannot be combined with the Young Innovative Company
|
||||
(JEI) exemption.
|
||||
salaire:
|
||||
zone-un:
|
||||
barème-compétitivité: The competitiveness scale only applies to salaries below
|
||||
2.2 SMIC. This means, for 2024, a total remuneration not exceeding
|
||||
<1>€3,964</1> gross per month.
|
||||
stage: The Lodeom exemption does not apply to internship bonuses.
|
||||
médecin:
|
||||
meta:
|
||||
description: Calculation of net income after deduction of contributions, based
|
||||
|
@ -1493,8 +1530,8 @@ pages:
|
|||
description: Adds fields to modulate employee activity
|
||||
heures-sup:
|
||||
popover: "<0>The number of hours of overtime and complementary work is used to
|
||||
calculate the general reduction: gross remuneration is compared with
|
||||
the SMIC increased by this number of hours.</0>"
|
||||
calculate the reduction: gross remuneration is compared with the
|
||||
SMIC increased by this number of hours.</0>"
|
||||
label:
|
||||
heures-complémentaires: Number of overtime hours
|
||||
heures-supplémentaires: Number of overtime hours
|
||||
|
|
|
@ -1540,6 +1540,44 @@ pages:
|
|||
plus-values (cession de titres, cession de brevets). Ces régimes ne sont
|
||||
pas intégrés dans le simulateur.</5>
|
||||
title: Simulateur d'impôt sur les sociétés
|
||||
lodeom:
|
||||
legend: Rémunération brute du salarié et exonération Lodeom applicable
|
||||
meta:
|
||||
description: Estimation du montant de l'exonération Lodeom. Cette exonération
|
||||
est applicable, sous conditions, aux salariés d'Outre-mer.
|
||||
title: Éxonération Lodeom
|
||||
month-by-month:
|
||||
caption: "Exonération Lodeom mois par mois :"
|
||||
options:
|
||||
description: Ajoute des champs pour moduler l'activité du salarié
|
||||
recap:
|
||||
T1: 1er trimestre
|
||||
T2: 2ème trimestre
|
||||
T3: 3ème trimestre
|
||||
T4: 4ème trimestre
|
||||
caption: "Récapitulatif trimestriel :"
|
||||
header:
|
||||
réduction: Réduction calculée
|
||||
régularisation: Régularisation calculée
|
||||
répartition:
|
||||
retraite: IRC
|
||||
urssaf: URSSAF
|
||||
shortname: Éxonération Lodeom
|
||||
tab:
|
||||
month: Exonération mensuelle
|
||||
month-by-month: Exonération mois par mois
|
||||
year: Exonération annuelle
|
||||
title: Simulateur d'éxonération Lodeom
|
||||
warnings:
|
||||
JEI: L'exonération Lodeom n'est pas cumulable avec l'exonération Jeune
|
||||
Entreprise Innovante (JEI).
|
||||
salaire:
|
||||
zone-un:
|
||||
barème-compétitivité: Le barème de compétitivité concerne uniquement les
|
||||
salaires inférieurs à 2,2 SMIC. C'est-à-dire, pour 2024, une
|
||||
rémunération totale qui ne dépasse pas <1>3 964 €</1> bruts par
|
||||
mois.
|
||||
stage: L'exonération Lodeom ne s'applique pas sur les gratifications de stage.
|
||||
médecin:
|
||||
meta:
|
||||
description: Calcul du revenu net après déduction des cotisations à partir du
|
||||
|
@ -1587,8 +1625,8 @@ pages:
|
|||
description: Ajoute des champs pour moduler l'activité du salarié
|
||||
heures-sup:
|
||||
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.</0>"
|
||||
dans le calcul de la réduction : la rémunération brute est comparée
|
||||
au montant du SMIC majoré de ce nombre d'heures.</0>"
|
||||
label:
|
||||
heures-complémentaires: Nombre d'heures complémentaires
|
||||
heures-supplémentaires: Nombre d'heures supplémentaires
|
||||
|
|
|
@ -22,6 +22,7 @@ import { eurlConfig } from '../simulateurs/eurl/config'
|
|||
import { expertComptableConfig } from '../simulateurs/expert-comptable/config'
|
||||
import { impôtSociétéConfig } from '../simulateurs/impot-societe/config'
|
||||
import { indépendantConfig } from '../simulateurs/indépendant/config'
|
||||
import { lodeomConfig } from '../simulateurs/lodeom/config'
|
||||
import { médecinConfig } from '../simulateurs/médecin/config'
|
||||
import { pamcConfig } from '../simulateurs/pamc/config'
|
||||
import { pharmacienConfig } from '../simulateurs/pharmacien/config'
|
||||
|
@ -63,6 +64,7 @@ const getMetadataSrc = (params: SimulatorsDataParams) => {
|
|||
...impôtSociétéConfig(params),
|
||||
...cipavConfig(params),
|
||||
...réductionGénéraleConfig(params),
|
||||
...lodeomConfig(params),
|
||||
|
||||
// assistants:
|
||||
...choixStatutJuridiqueConfig(params),
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import { Condition } from '@/components/EngineValue/Condition'
|
||||
import { SimulationGoal } 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 { Body } from '@/design-system/typography/paragraphs'
|
||||
import { targetUnitSelector } from '@/store/selectors/simulationSelectors'
|
||||
|
||||
import Répartition from './components/Répartition'
|
||||
import Warnings from './components/Warnings'
|
||||
import WarningSalaireTrans from './components/WarningSalaireTrans'
|
||||
import { lodeomDottedName, rémunérationBruteDottedName } from './utils'
|
||||
|
||||
type Props = {
|
||||
onUpdate: () => void
|
||||
}
|
||||
|
||||
export default function LodeomBasique({ onUpdate }: Props) {
|
||||
const engine = useEngine()
|
||||
const currentUnit = useSelector(targetUnitSelector)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const répartition = {
|
||||
IRC:
|
||||
(engine.evaluate({
|
||||
valeur: `${lodeomDottedName} . imputation retraite complémentaire`,
|
||||
unité: currentUnit,
|
||||
})?.nodeValue as number) ?? 0,
|
||||
Urssaf:
|
||||
(engine.evaluate({
|
||||
valeur: `${lodeomDottedName} . imputation sécurité sociale`,
|
||||
unité: currentUnit,
|
||||
})?.nodeValue as number) ?? 0,
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SimulationGoal
|
||||
dottedName={rémunérationBruteDottedName}
|
||||
round={false}
|
||||
label={t('Rémunération brute')}
|
||||
onUpdateSituation={onUpdate}
|
||||
/>
|
||||
|
||||
<Warnings />
|
||||
<Condition expression="salarié . cotisations . exonérations . lodeom . montant = 0">
|
||||
<Message type="info">
|
||||
<Body>
|
||||
<WarningSalaireTrans />
|
||||
</Body>
|
||||
</Message>
|
||||
</Condition>
|
||||
|
||||
<Condition expression={`${lodeomDottedName} >= 0`}>
|
||||
<SimulationValue
|
||||
dottedName={lodeomDottedName}
|
||||
isInfoMode={true}
|
||||
round={false}
|
||||
/>
|
||||
<Spacing md />
|
||||
<Répartition répartition={répartition} />
|
||||
</Condition>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import { PublicodesExpression } from 'publicodes'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { SimulationGoals } from '@/components/Simulation'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import useYear from '@/components/utils/useYear'
|
||||
import { SimpleRuleEvaluation } from '@/domaine/engine/SimpleRuleEvaluation'
|
||||
import { Situation } from '@/domaine/Situation'
|
||||
import { ajusteLaSituation } from '@/store/actions/actions'
|
||||
import { situationSelector } from '@/store/selectors/simulationSelectors'
|
||||
|
||||
import LodeomBasique from './Basique'
|
||||
import LodeomMoisParMois from './MoisParMois'
|
||||
import {
|
||||
getInitialLodeomMoisParMois,
|
||||
heuresComplémentairesDottedName,
|
||||
heuresSupplémentairesDottedName,
|
||||
MonthState,
|
||||
Options,
|
||||
reevaluateLodeomMoisParMois,
|
||||
RégularisationMethod,
|
||||
rémunérationBruteDottedName,
|
||||
} from './utils'
|
||||
|
||||
type SituationType = Situation & {
|
||||
[heuresSupplémentairesDottedName]?: {
|
||||
explanation: {
|
||||
nodeValue: number
|
||||
}
|
||||
}
|
||||
[heuresComplémentairesDottedName]?: {
|
||||
valeur: number
|
||||
}
|
||||
}
|
||||
|
||||
export default function LodeomSimulationGoals({
|
||||
monthByMonth,
|
||||
toggles,
|
||||
legend,
|
||||
régularisationMethod,
|
||||
}: {
|
||||
monthByMonth: boolean
|
||||
toggles?: React.ReactNode
|
||||
legend: string
|
||||
régularisationMethod: RégularisationMethod
|
||||
}) {
|
||||
const engine = useEngine()
|
||||
const dispatch = useDispatch()
|
||||
const [lodeomMoisParMoisData, setData] = useState<MonthState[]>([])
|
||||
const year = useYear()
|
||||
const situation = useSelector(situationSelector) as SituationType
|
||||
const previousSituation = useRef(situation)
|
||||
|
||||
const initializeLodeomMoisParMoisData = useCallback(() => {
|
||||
const data = getInitialLodeomMoisParMois(year, engine)
|
||||
setData(data)
|
||||
}, [engine, year])
|
||||
|
||||
useEffect(() => {
|
||||
if (lodeomMoisParMoisData.length === 0) {
|
||||
initializeLodeomMoisParMoisData()
|
||||
}
|
||||
}, [initializeLodeomMoisParMoisData, lodeomMoisParMoisData.length])
|
||||
|
||||
const getOptionsFromSituations = (
|
||||
previousSituation: SituationType,
|
||||
newSituation: SituationType
|
||||
): Partial<Options> => {
|
||||
const options = {} as Partial<Options>
|
||||
|
||||
const previousHeuresSupplémentaires =
|
||||
previousSituation[heuresSupplémentairesDottedName]?.explanation.nodeValue
|
||||
const newHeuresSupplémentaires =
|
||||
newSituation[heuresSupplémentairesDottedName]?.explanation.nodeValue
|
||||
if (newHeuresSupplémentaires !== previousHeuresSupplémentaires) {
|
||||
options.heuresSupplémentaires = newHeuresSupplémentaires || 0
|
||||
}
|
||||
|
||||
const previousHeuresComplémentaires =
|
||||
previousSituation[heuresComplémentairesDottedName]?.valeur
|
||||
const newHeuresComplémentaires =
|
||||
newSituation[heuresComplémentairesDottedName]?.valeur
|
||||
if (newHeuresComplémentaires !== previousHeuresComplémentaires) {
|
||||
options.heuresComplémentaires = newHeuresComplémentaires || 0
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setData((previousData) => {
|
||||
if (!Object.keys(situation).length) {
|
||||
return getInitialLodeomMoisParMois(year, engine)
|
||||
}
|
||||
|
||||
const newOptions = getOptionsFromSituations(
|
||||
previousSituation.current,
|
||||
situation
|
||||
)
|
||||
|
||||
const updatedData = previousData.map((data) => {
|
||||
return {
|
||||
...data,
|
||||
options: {
|
||||
...data.options,
|
||||
...newOptions,
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
return reevaluateLodeomMoisParMois(
|
||||
updatedData,
|
||||
engine,
|
||||
year,
|
||||
régularisationMethod
|
||||
)
|
||||
})
|
||||
}, [engine, situation, régularisationMethod, year])
|
||||
|
||||
const updateRémunérationBruteAnnuelle = (data: MonthState[]): void => {
|
||||
const rémunérationBruteAnnuelle = data.reduce(
|
||||
(total: number, monthState: MonthState) =>
|
||||
total + monthState.rémunérationBrute,
|
||||
0
|
||||
)
|
||||
dispatch(
|
||||
ajusteLaSituation({
|
||||
[rémunérationBruteDottedName]: {
|
||||
valeur: rémunérationBruteAnnuelle,
|
||||
unité: '€/an',
|
||||
} as PublicodesExpression,
|
||||
} as Record<DottedName, SimpleRuleEvaluation>)
|
||||
)
|
||||
}
|
||||
|
||||
const onRémunérationChange = (
|
||||
monthIndex: number,
|
||||
rémunérationBrute: number
|
||||
) => {
|
||||
setData((previousData) => {
|
||||
const updatedData = [...previousData]
|
||||
updatedData[monthIndex] = {
|
||||
...updatedData[monthIndex],
|
||||
rémunérationBrute,
|
||||
}
|
||||
|
||||
updateRémunérationBruteAnnuelle(updatedData)
|
||||
|
||||
return reevaluateLodeomMoisParMois(
|
||||
updatedData,
|
||||
engine,
|
||||
year,
|
||||
régularisationMethod
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const onOptionsChange = (monthIndex: number, options: Options) => {
|
||||
setData((previousData) => {
|
||||
const updatedData = [...previousData]
|
||||
updatedData[monthIndex] = {
|
||||
...updatedData[monthIndex],
|
||||
options,
|
||||
}
|
||||
|
||||
return reevaluateLodeomMoisParMois(
|
||||
updatedData,
|
||||
engine,
|
||||
year,
|
||||
régularisationMethod
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<SimulationGoals toggles={toggles} legend={legend}>
|
||||
{monthByMonth ? (
|
||||
<LodeomMoisParMois
|
||||
data={lodeomMoisParMoisData}
|
||||
onRémunérationChange={onRémunérationChange}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
) : (
|
||||
<LodeomBasique onUpdate={initializeLodeomMoisParMoisData} />
|
||||
)}
|
||||
</SimulationGoals>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import PeriodSwitch from '@/components/PeriodSwitch'
|
||||
import RégularisationSwitch from '@/components/RégularisationSwitch'
|
||||
import { SelectSimulationYear } from '@/components/SelectSimulationYear'
|
||||
import SimulateurWarning from '@/components/SimulateurWarning'
|
||||
import Simulation from '@/components/Simulation'
|
||||
|
||||
import LodeomSimulationGoals from './Goals'
|
||||
import { RégularisationMethod } from './utils'
|
||||
|
||||
export default function LodeomSimulation() {
|
||||
const { t } = useTranslation()
|
||||
const [monthByMonth, setMonthByMonth] = useState(false)
|
||||
const periods = [
|
||||
{
|
||||
label: t('pages.simulateurs.lodeom.tab.month', 'Exonération mensuelle'),
|
||||
unit: '€/mois',
|
||||
},
|
||||
{
|
||||
label: t('pages.simulateurs.lodeom.tab.year', 'Exonération annuelle'),
|
||||
unit: '€/an',
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'pages.simulateurs.lodeom.tab.month-by-month',
|
||||
'Exonération mois par mois'
|
||||
),
|
||||
unit: '€',
|
||||
},
|
||||
]
|
||||
const onPeriodSwitch = useCallback((unit: string) => {
|
||||
setMonthByMonth(unit === '€')
|
||||
}, [])
|
||||
|
||||
const [régularisationMethod, setRégularisationMethod] =
|
||||
useState<RégularisationMethod>('progressive')
|
||||
|
||||
return (
|
||||
<>
|
||||
<Simulation afterQuestionsSlot={<SelectSimulationYear />}>
|
||||
<SimulateurWarning simulateur="lodeom" />
|
||||
<LodeomSimulationGoals
|
||||
monthByMonth={monthByMonth}
|
||||
legend={t(
|
||||
'pages.simulateurs.lodeom.legend',
|
||||
'Rémunération brute du salarié et exonération Lodeom applicable'
|
||||
)}
|
||||
toggles={
|
||||
<>
|
||||
<RégularisationSwitch
|
||||
régularisationMethod={régularisationMethod}
|
||||
setRégularisationMethod={setRégularisationMethod}
|
||||
/>
|
||||
<PeriodSwitch periods={periods} onSwitch={onPeriodSwitch} />
|
||||
</>
|
||||
}
|
||||
régularisationMethod={régularisationMethod}
|
||||
/>
|
||||
</Simulation>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import RuleLink from '@/components/RuleLink'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { baseTheme } from '@/design-system/theme'
|
||||
import { H3 } from '@/design-system/typography/heading'
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery'
|
||||
|
||||
import LodeomMois from './components/LodeomMois'
|
||||
import RécapitulatifTrimestre from './components/RécapitulatifTrimestre'
|
||||
import Warnings from './components/Warnings'
|
||||
import { lodeomDottedName, MonthState, Options } from './utils'
|
||||
|
||||
type Props = {
|
||||
data: MonthState[]
|
||||
onRémunérationChange: (monthIndex: number, rémunérationBrute: number) => void
|
||||
onOptionsChange: (monthIndex: number, options: Options) => void
|
||||
}
|
||||
|
||||
export default function LodeomMoisParMois({
|
||||
data,
|
||||
onRémunérationChange,
|
||||
onOptionsChange,
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
const isDesktop = useMediaQuery(
|
||||
`(min-width: ${baseTheme.breakpointsWidth.md})`
|
||||
)
|
||||
|
||||
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 quarters = {
|
||||
[t('pages.simulateurs.lodeom.recap.T1', '1er trimestre')]: data.slice(0, 3),
|
||||
[t('pages.simulateurs.lodeom.recap.T2', '2ème trimestre')]: data.slice(
|
||||
3,
|
||||
6
|
||||
),
|
||||
[t('pages.simulateurs.lodeom.recap.T3', '3ème trimestre')]: data.slice(
|
||||
6,
|
||||
9
|
||||
),
|
||||
[t('pages.simulateurs.lodeom.recap.T4', '4ème trimestre')]: data.slice(9),
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isDesktop ? (
|
||||
<>
|
||||
<H3 as="h2">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.month-by-month.caption',
|
||||
'Exonération Lodeom mois par mois :'
|
||||
)}
|
||||
</H3>
|
||||
<StyledTable>
|
||||
<caption className="sr-only">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.month-by-month.caption',
|
||||
'Exonération Lodeom mois par mois :'
|
||||
)}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{t('Mois')}</th>
|
||||
<th scope="col">
|
||||
{/* TODO: remplacer par rémunérationBruteDottedName lorsque ... */}
|
||||
<RuleLink dottedName="salarié . rémunération . brut" />
|
||||
</th>
|
||||
<th scope="col">
|
||||
<RuleLink dottedName={lodeomDottedName} />
|
||||
</th>
|
||||
<th scope="col">
|
||||
<RuleLink dottedName="salarié . cotisations . exonérations . réduction générale . régularisation" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.length > 0 &&
|
||||
months.map((monthName, monthIndex) => (
|
||||
<LodeomMois
|
||||
key={`month-${monthIndex}`}
|
||||
monthName={monthName}
|
||||
data={data[monthIndex]}
|
||||
index={monthIndex}
|
||||
onRémunérationChange={(
|
||||
monthIndex: number,
|
||||
rémunérationBrute: number
|
||||
) => {
|
||||
onRémunérationChange(monthIndex, rémunérationBrute)
|
||||
}}
|
||||
onOptionsChange={(monthIndex: number, options: Options) => {
|
||||
onOptionsChange(monthIndex, options)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
|
||||
<Spacing md />
|
||||
|
||||
<H3 as="h2">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.caption',
|
||||
'Récapitulatif trimestriel :'
|
||||
)}
|
||||
</H3>
|
||||
<StyledRecapTable>
|
||||
<caption className="sr-only">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.caption',
|
||||
'Récapitulatif trimestriel :'
|
||||
)}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{t('Trimestre')}</th>
|
||||
<th scope="col">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.header.réduction',
|
||||
'Réduction calculée'
|
||||
)}
|
||||
{/* <br />
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.code671',
|
||||
'code 671(€)'
|
||||
)} */}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.header.régularisation',
|
||||
'Régularisation calculée'
|
||||
)}
|
||||
{/* <br />
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.code801',
|
||||
'code 801(€)'
|
||||
)} */}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(quarters).map((label, index) => (
|
||||
<RécapitulatifTrimestre
|
||||
key={index}
|
||||
label={label}
|
||||
data={quarters[label]}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledRecapTable>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<H3 as="h2">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.month-by-month.caption',
|
||||
'Exonération Lodeom mois par mois :'
|
||||
)}
|
||||
</H3>
|
||||
{data.length > 0 &&
|
||||
months.map((monthName, monthIndex) => (
|
||||
<LodeomMois
|
||||
key={`month-${monthIndex}`}
|
||||
monthName={monthName}
|
||||
data={data[monthIndex]}
|
||||
index={monthIndex}
|
||||
onRémunérationChange={(
|
||||
monthIndex: number,
|
||||
rémunérationBrute: number
|
||||
) => {
|
||||
onRémunérationChange(monthIndex, rémunérationBrute)
|
||||
}}
|
||||
onOptionsChange={(monthIndex: number, options: Options) => {
|
||||
onOptionsChange(monthIndex, options)
|
||||
}}
|
||||
mobileVersion={true}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Spacing xxl />
|
||||
|
||||
<H3 as="h2">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.caption',
|
||||
'Récapitulatif trimestriel :'
|
||||
)}
|
||||
</H3>
|
||||
{Object.keys(quarters).map((label, index) => (
|
||||
<RécapitulatifTrimestre
|
||||
key={index}
|
||||
label={label}
|
||||
data={quarters[label]}
|
||||
mobileVersion={true}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<span id="options-description" className="sr-only">
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.options.description',
|
||||
"Ajoute des champs pour moduler l'activité du salarié"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<Warnings />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledTable = styled.table`
|
||||
border-collapse: collapse;
|
||||
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,
|
||||
td {
|
||||
padding: ${({ theme }) => theme.spacings.xs};
|
||||
}
|
||||
tbody tr th {
|
||||
text-transform: capitalize;
|
||||
font-weight: normal;
|
||||
}
|
||||
`
|
||||
const StyledRecapTable = styled(StyledTable)`
|
||||
thead {
|
||||
border-bottom: solid 1px;
|
||||
}
|
||||
thead th:not(:last-of-type),
|
||||
tbody th,
|
||||
td:not(:last-of-type) {
|
||||
border-right: solid 1px;
|
||||
}
|
||||
thead th:not(:first-of-type) {
|
||||
text-align: center;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,254 @@
|
|||
import { PublicodesExpression } from 'publicodes'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import NumberInput from '@/components/conversation/NumberInput'
|
||||
import MonthOptions from '@/components/MonthOptions'
|
||||
import RuleLink from '@/components/RuleLink'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { FlexCenter } from '@/design-system/global-style'
|
||||
import { RotatingChevronIcon } from '@/design-system/icons'
|
||||
import { Grid, Spacing } from '@/design-system/layout'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import {
|
||||
lodeomDottedName,
|
||||
MonthState,
|
||||
Options,
|
||||
rémunérationBruteDottedName,
|
||||
} from '../utils'
|
||||
import MontantRéduction from './MontantRéduction'
|
||||
|
||||
type Props = {
|
||||
monthName: string
|
||||
data: MonthState
|
||||
index: number
|
||||
onRémunérationChange: (monthIndex: number, rémunérationBrute: number) => void
|
||||
onOptionsChange: (monthIndex: number, options: Options) => void
|
||||
mobileVersion?: boolean
|
||||
}
|
||||
|
||||
export type RémunérationBruteInput = {
|
||||
unité: string
|
||||
valeur: number
|
||||
}
|
||||
|
||||
export default function LodeomMois({
|
||||
monthName,
|
||||
data,
|
||||
index,
|
||||
onRémunérationChange,
|
||||
onOptionsChange,
|
||||
mobileVersion = false,
|
||||
}: Props) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const language = i18n.language
|
||||
const displayedUnit = '€'
|
||||
const engine = useEngine()
|
||||
const [isOptionVisible, setOptionVisible] = useState(false)
|
||||
|
||||
const RémunérationInput = () => {
|
||||
// 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 (
|
||||
<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,
|
||||
}}
|
||||
displaySuggestions={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const OptionsButton = () => {
|
||||
return (
|
||||
<Button
|
||||
size="XXS"
|
||||
light
|
||||
onPress={() => setOptionVisible(!isOptionVisible)}
|
||||
aria-describedby="options-description"
|
||||
aria-expanded={isOptionVisible}
|
||||
aria-controls={`options-${monthName}`}
|
||||
aria-label={!isOptionVisible ? t('Déplier') : t('Replier')}
|
||||
>
|
||||
{t('Options')}
|
||||
<RotatingChevronIcon aria-hidden $isOpen={isOptionVisible} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const MontantLodeom = () => {
|
||||
return (
|
||||
<MontantRéduction
|
||||
id={`${lodeomDottedName.replace(/\s|\./g, '_')}-${monthName}`}
|
||||
rémunérationBrute={data.rémunérationBrute}
|
||||
lodeom={data.lodeom.value}
|
||||
répartition={data.lodeom.répartition}
|
||||
displayedUnit={displayedUnit}
|
||||
language={language}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const MontantRégularisation = () => {
|
||||
return (
|
||||
<MontantRéduction
|
||||
id={`${lodeomDottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}__régularisation-${monthName}`}
|
||||
rémunérationBrute={data.rémunérationBrute}
|
||||
lodeom={data.régularisation.value}
|
||||
répartition={data.régularisation.répartition}
|
||||
displayedUnit={displayedUnit}
|
||||
language={language}
|
||||
displayNull={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return mobileVersion ? (
|
||||
<div>
|
||||
<StyledMonth>{monthName}</StyledMonth>
|
||||
<GridContainer container spacing={2}>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<RuleLink dottedName="salarié . rémunération . brut" />
|
||||
</Grid>
|
||||
<Grid item xs={7} sm={5}>
|
||||
<RémunérationInput />
|
||||
</Grid>
|
||||
<Grid item xs={5} sm={3}>
|
||||
<OptionsButton />
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
{isOptionVisible && (
|
||||
<OptionsContainer>
|
||||
<MonthOptions
|
||||
month={monthName}
|
||||
index={index}
|
||||
options={data.options}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
</OptionsContainer>
|
||||
)}
|
||||
|
||||
<Spacing xs />
|
||||
|
||||
<GridContainer container spacing={2}>
|
||||
<Grid item>
|
||||
<RuleLink dottedName={lodeomDottedName} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StyledBody>
|
||||
<MontantLodeom />
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer container spacing={2}>
|
||||
<Grid item>
|
||||
<RuleLink dottedName="salarié . cotisations . exonérations . réduction générale . régularisation" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StyledBody>
|
||||
<MontantRégularisation />
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<tr>
|
||||
<th scope="row">{monthName}</th>
|
||||
<td>
|
||||
<InputContainer>
|
||||
<RémunérationInput />
|
||||
<OptionsButton />
|
||||
</InputContainer>
|
||||
</td>
|
||||
<td>
|
||||
<MontantLodeom />
|
||||
</td>
|
||||
<td>
|
||||
<MontantRégularisation />
|
||||
</td>
|
||||
</tr>
|
||||
{isOptionVisible && (
|
||||
<StyledTableRow>
|
||||
<td />
|
||||
<td colSpan={3}>
|
||||
<MonthOptions
|
||||
month={monthName}
|
||||
index={index}
|
||||
options={data.options}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
</td>
|
||||
</StyledTableRow>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledMonth = styled(Body)`
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
border-bottom: solid 1px ${({ theme }) => theme.colors.bases.primary[100]};
|
||||
`
|
||||
const GridContainer = styled(Grid)`
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
`
|
||||
const StyledBody = styled(Body)`
|
||||
margin-top: 0;
|
||||
`
|
||||
const OptionsContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacings.xs};
|
||||
background-color: ${({ theme }) => theme.colors.bases.primary[200]};
|
||||
padding: ${({ theme }) => theme.spacings.sm};
|
||||
`
|
||||
|
||||
const StyledTableRow = styled.tr`
|
||||
background-color: ${({ theme }) => theme.colors.bases.primary[200]};
|
||||
td {
|
||||
padding-top: ${({ theme }) => theme.spacings.sm};
|
||||
padding-bottom: ${({ theme }) => theme.spacings.sm};
|
||||
}
|
||||
`
|
||||
const InputContainer = styled.div`
|
||||
${FlexCenter}
|
||||
gap: ${({ theme }) => theme.spacings.md};
|
||||
`
|
|
@ -0,0 +1,91 @@
|
|||
import { formatValue } from 'publicodes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import { Condition } from '@/components/EngineValue/Condition'
|
||||
import { FlexCenter } from '@/design-system/global-style'
|
||||
import { SearchIcon, WarningIcon } from '@/design-system/icons'
|
||||
import { Tooltip } from '@/design-system/tooltip'
|
||||
|
||||
import {
|
||||
rémunérationBruteDottedName,
|
||||
Répartition as RépartitionType,
|
||||
} from '../utils'
|
||||
import Répartition from './Répartition'
|
||||
import WarningSalaireTrans from './WarningSalaireTrans'
|
||||
|
||||
type Props = {
|
||||
id?: string
|
||||
rémunérationBrute: number
|
||||
lodeom: number
|
||||
répartition: RépartitionType
|
||||
displayedUnit: string
|
||||
language: string
|
||||
displayNull?: boolean
|
||||
alignment?: 'center' | 'end'
|
||||
}
|
||||
|
||||
export default function MontantRéduction({
|
||||
id,
|
||||
rémunérationBrute,
|
||||
lodeom,
|
||||
répartition,
|
||||
displayedUnit,
|
||||
language,
|
||||
displayNull = true,
|
||||
alignment = 'end',
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tooltip = <Répartition répartition={répartition} />
|
||||
|
||||
return lodeom ? (
|
||||
<StyledTooltip tooltip={tooltip}>
|
||||
<FlexDiv id={id} $alignment={alignment}>
|
||||
{formatValue(
|
||||
{
|
||||
nodeValue: lodeom,
|
||||
},
|
||||
{
|
||||
displayedUnit,
|
||||
language,
|
||||
}
|
||||
)}
|
||||
<StyledSearchIcon />
|
||||
</FlexDiv>
|
||||
</StyledTooltip>
|
||||
) : (
|
||||
displayNull && (
|
||||
<FlexDiv id={id} $alignment={alignment}>
|
||||
{formatValue(0, { displayedUnit, language })}
|
||||
|
||||
<Condition
|
||||
expression="salarié . cotisations . exonérations . lodeom . montant = 0"
|
||||
contexte={{
|
||||
[rémunérationBruteDottedName]: rémunérationBrute,
|
||||
}}
|
||||
>
|
||||
<Tooltip tooltip={<WarningSalaireTrans />}>
|
||||
<span className="sr-only">{t('Attention')}</span>
|
||||
<StyledWarningIcon aria-label={t('Attention')} />
|
||||
</Tooltip>
|
||||
</Condition>
|
||||
</FlexDiv>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const StyledTooltip = styled(Tooltip)`
|
||||
width: 100%;
|
||||
`
|
||||
const FlexDiv = styled.div<{ $alignment: 'end' | 'center' }>`
|
||||
${FlexCenter}
|
||||
justify-content: ${({ $alignment }) => $alignment};
|
||||
`
|
||||
const StyledSearchIcon = styled(SearchIcon)`
|
||||
margin-left: ${({ theme }) => theme.spacings.sm};
|
||||
`
|
||||
const StyledWarningIcon = styled(WarningIcon)`
|
||||
margin-top: ${({ theme }) => theme.spacings.xxs};
|
||||
margin-left: ${({ theme }) => theme.spacings.sm};
|
||||
`
|
|
@ -0,0 +1,161 @@
|
|||
import { sumAll } from 'effect/Number'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import { MonthState } from '../utils'
|
||||
import MontantRéduction from './MontantRéduction'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
data: MonthState[]
|
||||
mobileVersion?: boolean
|
||||
}
|
||||
|
||||
export type RémunérationBruteInput = {
|
||||
unité: string
|
||||
valeur: number
|
||||
}
|
||||
|
||||
export default function RécapitulatifTrimestre({
|
||||
label,
|
||||
data,
|
||||
mobileVersion = false,
|
||||
}: Props) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const language = i18n.language
|
||||
const displayedUnit = '€'
|
||||
|
||||
const rémunération = sumAll(
|
||||
data.map((monthData) => monthData.rémunérationBrute)
|
||||
)
|
||||
const répartition = {
|
||||
IRC: sumAll(
|
||||
data.map(
|
||||
(monthData) =>
|
||||
monthData.lodeom.répartition.IRC +
|
||||
monthData.régularisation.répartition.IRC
|
||||
)
|
||||
),
|
||||
Urssaf: sumAll(
|
||||
data.map(
|
||||
(monthData) =>
|
||||
monthData.lodeom.répartition.Urssaf +
|
||||
monthData.régularisation.répartition.Urssaf
|
||||
)
|
||||
),
|
||||
}
|
||||
let réduction = sumAll(data.map((monthData) => monthData.lodeom.value))
|
||||
let régularisation = sumAll(
|
||||
data.map((monthData) => monthData.régularisation.value)
|
||||
)
|
||||
if (réduction + régularisation > 0) {
|
||||
réduction += régularisation
|
||||
régularisation = 0
|
||||
} else {
|
||||
régularisation += réduction
|
||||
réduction = 0
|
||||
}
|
||||
|
||||
const MontantExonération = () => {
|
||||
return (
|
||||
<MontantRéduction
|
||||
id={`recap-${label.replace(/\s|\./g, '_')}-réduction`}
|
||||
rémunérationBrute={rémunération}
|
||||
lodeom={réduction}
|
||||
répartition={répartition}
|
||||
displayedUnit={displayedUnit}
|
||||
language={language}
|
||||
displayNull={false}
|
||||
alignment="center"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const MontantRégularisation = () => {
|
||||
return (
|
||||
<MontantRéduction
|
||||
id={`recap-${label.replace(/\s|\./g, '_')}-régularisation`}
|
||||
rémunérationBrute={rémunération}
|
||||
lodeom={régularisation}
|
||||
répartition={répartition}
|
||||
displayedUnit={displayedUnit}
|
||||
language={language}
|
||||
displayNull={false}
|
||||
alignment="center"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return mobileVersion ? (
|
||||
<div>
|
||||
<StyledMonth>{label}</StyledMonth>
|
||||
<GridContainer container spacing={2}>
|
||||
<Grid item>
|
||||
<StyledBody>
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.header.réduction',
|
||||
'Réduction calculée'
|
||||
)}
|
||||
{/* <br />
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.code671',
|
||||
'code 671(€)'
|
||||
)} */}
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StyledBody>
|
||||
<MontantExonération />
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer container spacing={2}>
|
||||
<Grid item>
|
||||
<StyledBody>
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.header.régularisation',
|
||||
'Régularisation calculée'
|
||||
)}
|
||||
{/* <br />
|
||||
{t(
|
||||
'pages.simulateurs.lodeom.recap.code801',
|
||||
'code 801(€)'
|
||||
)} */}
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StyledBody>
|
||||
<MontantRégularisation />
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
</div>
|
||||
) : (
|
||||
<tr>
|
||||
<th scope="row">{label}</th>
|
||||
<td>
|
||||
<MontantExonération />
|
||||
</td>
|
||||
<td>
|
||||
<MontantRégularisation />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledMonth = styled(Body)`
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
border-bottom: solid 1px ${({ theme }) => theme.colors.bases.primary[100]};
|
||||
`
|
||||
const GridContainer = styled(Grid)`
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`
|
||||
const StyledBody = styled(Body)`
|
||||
margin-top: 0;
|
||||
`
|
|
@ -0,0 +1,58 @@
|
|||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import RépartitionValue from '@/components/RépartitionValue'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { Li, Ul } from '@/design-system/typography/list'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import { lodeomDottedName, Répartition as RépartitionType } from '../utils'
|
||||
|
||||
type Props = {
|
||||
répartition: RépartitionType
|
||||
}
|
||||
|
||||
export default function Répartition({ répartition }: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Body>
|
||||
<Strong>
|
||||
<Trans>Détail du montant :</Trans>
|
||||
</Strong>
|
||||
</Body>
|
||||
<StyledUl>
|
||||
<StyledLi>
|
||||
<RépartitionValue
|
||||
value={répartition.IRC}
|
||||
label={t('pages.simulateurs.lodeom.répartition.retraite', 'IRC')}
|
||||
idPrefix={`${lodeomDottedName} . imputation retraite complémentaire`.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}
|
||||
/>
|
||||
</StyledLi>
|
||||
<StyledLi>
|
||||
<RépartitionValue
|
||||
value={répartition.Urssaf}
|
||||
label={t('pages.simulateurs.lodeom.répartition.urssaf', 'URSSAF')}
|
||||
idPrefix={`${lodeomDottedName} . imputation sécurité sociale`.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}
|
||||
/>
|
||||
</StyledLi>
|
||||
</StyledUl>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledUl = styled(Ul)`
|
||||
margin-top: 0;
|
||||
`
|
||||
const StyledLi = styled(Li)`
|
||||
&::before {
|
||||
margin-top: ${({ theme }) => theme.spacings.sm};
|
||||
}
|
||||
`
|
|
@ -0,0 +1,11 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
|
||||
export default function WarningSalaireTrans() {
|
||||
return (
|
||||
<Trans i18nKey="pages.simulateurs.lodeom.warnings.salaire.zone-un.barème-compétitivité">
|
||||
Le barème de compétitivité concerne uniquement les salaires inférieurs à
|
||||
2,2 SMIC. C'est-à-dire, pour 2024, une rémunération totale qui ne dépasse
|
||||
pas <strong>3 964 €</strong> bruts par mois.
|
||||
</Trans>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
|
||||
import { Condition } from '@/components/EngineValue/Condition'
|
||||
import { Message } from '@/design-system'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
export default function Warnings() {
|
||||
return (
|
||||
<>
|
||||
<Condition expression="salarié . cotisations . exonérations . JEI = oui">
|
||||
<Message type="info">
|
||||
<Body>
|
||||
<Trans i18nKey="pages.simulateurs.lodeom.warnings.JEI">
|
||||
L'exonération Lodeom n'est pas cumulable avec l'exonération Jeune
|
||||
Entreprise Innovante (JEI).
|
||||
</Trans>
|
||||
</Body>
|
||||
</Message>
|
||||
</Condition>
|
||||
|
||||
<Condition expression="salarié . contrat = 'stage'">
|
||||
<Message type="info">
|
||||
<Body>
|
||||
<Trans i18nKey="pages.simulateurs.lodeom.warnings.stage">
|
||||
L'exonération Lodeom ne s'applique pas sur les gratifications de
|
||||
stage.
|
||||
</Trans>
|
||||
</Body>
|
||||
</Message>
|
||||
</Condition>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { config } from '../_configs/config'
|
||||
import { SimulatorsDataParams } from '../_configs/types'
|
||||
import RéductionGénéraleSimulation from './Lodeom'
|
||||
import { configRéductionGénérale } from './simulationConfig'
|
||||
|
||||
export function lodeomConfig({ t, sitePaths }: SimulatorsDataParams) {
|
||||
return config({
|
||||
id: 'lodeom',
|
||||
beta: true,
|
||||
tracking: 'lodeom',
|
||||
icône: '🏷️',
|
||||
iframePath: 'simulateur-lodeom',
|
||||
pathId: 'simulateurs.lodeom',
|
||||
shortName: t('pages.simulateurs.lodeom.shortname', 'Éxonération Lodeom'),
|
||||
title: t(
|
||||
'pages.simulateurs.lodeom.title',
|
||||
"Simulateur d'éxonération Lodeom"
|
||||
),
|
||||
meta: {
|
||||
title: t('pages.simulateurs.lodeom.meta.title', 'Éxonération Lodeom'),
|
||||
description: t(
|
||||
'pages.simulateurs.lodeom.meta.description',
|
||||
"Estimation du montant de l'exonération Lodeom. Cette exonération est applicable, sous conditions, aux salariés d'Outre-mer."
|
||||
),
|
||||
},
|
||||
nextSteps: ['salarié'],
|
||||
path: sitePaths.simulateurs.lodeom,
|
||||
simulation: configRéductionGénérale,
|
||||
component: RéductionGénéraleSimulation,
|
||||
} as const)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { SimulationConfig } from '@/domaine/SimulationConfig'
|
||||
|
||||
export const configRéductionGénérale: SimulationConfig = {
|
||||
// TODO: remplacer 'salarié . cotisations . assiette' par 'salarié . rémunération . brut'
|
||||
// lorsque cette dernière n'incluera plus les frais professionnels.
|
||||
'objectifs exclusifs': ['salarié . cotisations . assiette'],
|
||||
objectifs: ['salarié . cotisations . exonérations . lodeom . montant'],
|
||||
questions: {
|
||||
"à l'affiche": [
|
||||
{
|
||||
label: 'Temps partiel',
|
||||
dottedName: 'salarié . contrat . temps de travail . temps partiel',
|
||||
},
|
||||
{
|
||||
label: 'Heures supplémentaires',
|
||||
dottedName: 'salarié . temps de travail . heures supplémentaires',
|
||||
},
|
||||
{
|
||||
label: 'Heures complémentaires',
|
||||
dottedName: 'salarié . temps de travail . heures complémentaires',
|
||||
},
|
||||
{
|
||||
label: 'DFS',
|
||||
dottedName: 'salarié . régimes spécifiques . DFS',
|
||||
},
|
||||
{
|
||||
label: 'JEI',
|
||||
dottedName: 'salarié . cotisations . exonérations . JEI',
|
||||
},
|
||||
],
|
||||
'liste noire': [
|
||||
'entreprise . salariés . effectif . seuil',
|
||||
'salarié . contrat . CDD . motif',
|
||||
'salarié . rémunération . primes . activité . base',
|
||||
'salarié . rémunération . avantages en nature',
|
||||
],
|
||||
'non prioritaires': ['salarié . convention collective'],
|
||||
},
|
||||
'unité par défaut': '€/an',
|
||||
situation: {
|
||||
dirigeant: 'non',
|
||||
'entreprise . catégorie juridique': "''",
|
||||
'entreprise . imposition': 'non',
|
||||
'salarié . cotisations . exonérations . lodeom . zone un': "'oui'",
|
||||
'salarié . cotisations . exonérations . lodeom . zone un . barème compétitivité':
|
||||
"'oui'",
|
||||
},
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
import { sumAll } from 'effect/Number'
|
||||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
|
||||
import { Situation } from '@/domaine/Situation'
|
||||
|
||||
// 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 lodeomDottedName =
|
||||
'salarié . cotisations . exonérations . lodeom . montant'
|
||||
export const heuresSupplémentairesDottedName =
|
||||
'salarié . temps de travail . heures supplémentaires'
|
||||
export const heuresComplémentairesDottedName =
|
||||
'salarié . temps de travail . heures complémentaires'
|
||||
|
||||
export type Répartition = {
|
||||
IRC: number
|
||||
Urssaf: number
|
||||
}
|
||||
|
||||
export type MonthState = {
|
||||
rémunérationBrute: number
|
||||
options: Options
|
||||
lodeom: {
|
||||
value: number
|
||||
répartition: Répartition
|
||||
}
|
||||
régularisation: {
|
||||
value: number
|
||||
répartition: Répartition
|
||||
}
|
||||
}
|
||||
|
||||
export type Options = {
|
||||
heuresSupplémentaires: number
|
||||
heuresComplémentaires: number
|
||||
rémunérationETP: number
|
||||
rémunérationPrimes: number
|
||||
rémunérationHeuresSup: number
|
||||
}
|
||||
|
||||
export type RégularisationMethod = 'annuelle' | 'progressive'
|
||||
|
||||
const getDateForContexte = (monthIndex: number, year: number): string => {
|
||||
const date = new Date(year, monthIndex)
|
||||
|
||||
return date.toLocaleDateString('fr')
|
||||
}
|
||||
|
||||
const getMonthlyLodeom = (
|
||||
date: string,
|
||||
rémunérationBrute: number,
|
||||
options: Options,
|
||||
engine: Engine<DottedName>
|
||||
): number => {
|
||||
const lodeom = engine.evaluate({
|
||||
valeur: lodeomDottedName,
|
||||
unité: '€/mois',
|
||||
contexte: {
|
||||
date,
|
||||
[rémunérationBruteDottedName]: rémunérationBrute,
|
||||
[heuresSupplémentairesDottedName]: options.heuresSupplémentaires,
|
||||
[heuresComplémentairesDottedName]: options.heuresComplémentaires,
|
||||
},
|
||||
})
|
||||
|
||||
return lodeom.nodeValue as number
|
||||
}
|
||||
|
||||
const getTotalLodeom = (
|
||||
rémunérationBrute: number,
|
||||
SMIC: number,
|
||||
coefT: number,
|
||||
engine: Engine<DottedName>
|
||||
): number => {
|
||||
const lodeom = engine.evaluate({
|
||||
valeur: lodeomDottedName,
|
||||
arrondi: 'non',
|
||||
contexte: {
|
||||
[rémunérationBruteDottedName]: rémunérationBrute,
|
||||
'salarié . temps de travail . SMIC': SMIC,
|
||||
'salarié . cotisations . exonérations . T': coefT,
|
||||
},
|
||||
})
|
||||
|
||||
return lodeom.nodeValue as number
|
||||
}
|
||||
|
||||
const emptyRépartition = {
|
||||
IRC: 0,
|
||||
Urssaf: 0,
|
||||
}
|
||||
|
||||
const getRépartition = (
|
||||
rémunération: number,
|
||||
réduction: number,
|
||||
engine: Engine<DottedName>
|
||||
): Répartition => {
|
||||
const contexte = {
|
||||
[rémunérationBruteDottedName]: rémunération,
|
||||
[lodeomDottedName]: réduction,
|
||||
}
|
||||
const IRC =
|
||||
(engine.evaluate({
|
||||
valeur: `${lodeomDottedName} . imputation retraite complémentaire`,
|
||||
unité: '€/mois',
|
||||
contexte,
|
||||
})?.nodeValue as number) ?? 0
|
||||
const Urssaf =
|
||||
(engine.evaluate({
|
||||
valeur: `${lodeomDottedName} . imputation sécurité sociale`,
|
||||
unité: '€/mois',
|
||||
contexte,
|
||||
})?.nodeValue as number) ?? 0
|
||||
|
||||
return {
|
||||
IRC,
|
||||
Urssaf,
|
||||
}
|
||||
}
|
||||
|
||||
export const getInitialLodeomMoisParMois = (
|
||||
year: number,
|
||||
engine: Engine<DottedName>
|
||||
): MonthState[] => {
|
||||
const rémunérationBrute =
|
||||
(engine.evaluate({
|
||||
valeur: rémunérationBruteDottedName,
|
||||
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émunérationETP = 0
|
||||
const rémunérationPrimes = 0
|
||||
const rémunérationHeuresSup = 0
|
||||
|
||||
if (!rémunérationBrute) {
|
||||
return Array(12).fill({
|
||||
rémunérationBrute,
|
||||
options: {
|
||||
heuresSupplémentaires,
|
||||
heuresComplémentaires,
|
||||
rémunérationETP,
|
||||
rémunérationPrimes,
|
||||
rémunérationHeuresSup,
|
||||
},
|
||||
lodeom: {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
},
|
||||
régularisation: {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
},
|
||||
}) as MonthState[]
|
||||
}
|
||||
|
||||
return Array.from({ length: 12 }, (_item, monthIndex) => {
|
||||
const date = getDateForContexte(monthIndex, year)
|
||||
|
||||
const lodeom = getMonthlyLodeom(
|
||||
date,
|
||||
rémunérationBrute,
|
||||
{
|
||||
heuresSupplémentaires,
|
||||
heuresComplémentaires,
|
||||
rémunérationETP,
|
||||
rémunérationPrimes,
|
||||
rémunérationHeuresSup,
|
||||
},
|
||||
engine
|
||||
)
|
||||
const répartition = getRépartition(rémunérationBrute, lodeom, engine)
|
||||
|
||||
return {
|
||||
rémunérationBrute,
|
||||
options: {
|
||||
heuresSupplémentaires,
|
||||
heuresComplémentaires,
|
||||
rémunérationETP,
|
||||
rémunérationPrimes,
|
||||
rémunérationHeuresSup,
|
||||
},
|
||||
lodeom: {
|
||||
value: lodeom,
|
||||
répartition,
|
||||
},
|
||||
régularisation: {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const reevaluateLodeomMoisParMois = (
|
||||
data: MonthState[],
|
||||
engine: Engine<DottedName>,
|
||||
year: number,
|
||||
régularisationMethod: RégularisationMethod
|
||||
): MonthState[] => {
|
||||
const totalRémunérationBrute = sumAll(
|
||||
data.map((monthData) => monthData.rémunérationBrute)
|
||||
)
|
||||
|
||||
if (!totalRémunérationBrute) {
|
||||
return data.map((monthData) => {
|
||||
return {
|
||||
...monthData,
|
||||
lodeom: {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
},
|
||||
régularisation: {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const rémunérationBruteCumulées = getRémunérationBruteCumulées(data)
|
||||
const SMICCumulés = getSMICCumulés(data, year, engine)
|
||||
// Si on laisse l'engine calculer T dans le calcul de l'exonération Lodeom,
|
||||
// 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 reevaluatedData = data.reduce(
|
||||
(reevaluatedData: MonthState[], monthState, monthIndex) => {
|
||||
const rémunérationBrute = monthState.rémunérationBrute
|
||||
const options = monthState.options
|
||||
const lodeom = {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
}
|
||||
const régularisation = {
|
||||
value: 0,
|
||||
répartition: emptyRépartition,
|
||||
}
|
||||
|
||||
if (!rémunérationBrute) {
|
||||
return [
|
||||
...reevaluatedData,
|
||||
{
|
||||
rémunérationBrute,
|
||||
options,
|
||||
lodeom,
|
||||
régularisation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (régularisationMethod === 'progressive') {
|
||||
// La régularisation progressive du mois N est la différence entre l'exonération Lodeom
|
||||
// calculée pour la rémunération totale jusqu'à N (comparée au SMIC équivalent pour ces N mois)
|
||||
// et la somme des N-1 exonérations déjà accordées (en incluant les régularisations).
|
||||
const lodeomTotale = getTotalLodeom(
|
||||
rémunérationBruteCumulées[monthIndex],
|
||||
SMICCumulés[monthIndex],
|
||||
coefT,
|
||||
engine
|
||||
)
|
||||
const lodeomCumulée = sumAll(
|
||||
reevaluatedData.map(
|
||||
(monthData) =>
|
||||
monthData.lodeom.value + monthData.régularisation.value
|
||||
)
|
||||
)
|
||||
régularisation.value = lodeomTotale - lodeomCumulée
|
||||
|
||||
if (régularisation.value > 0) {
|
||||
lodeom.value = régularisation.value
|
||||
lodeom.répartition = getRépartition(
|
||||
rémunérationBrute,
|
||||
lodeom.value,
|
||||
engine
|
||||
)
|
||||
régularisation.value = 0
|
||||
} else if (régularisation.value < 0) {
|
||||
régularisation.répartition = getRépartition(
|
||||
rémunérationBrute,
|
||||
régularisation.value,
|
||||
engine
|
||||
)
|
||||
}
|
||||
} else if (régularisationMethod === 'annuelle') {
|
||||
const date = getDateForContexte(monthIndex, year)
|
||||
lodeom.value = getMonthlyLodeom(
|
||||
date,
|
||||
rémunérationBrute,
|
||||
options,
|
||||
engine
|
||||
)
|
||||
|
||||
if (monthIndex === data.length - 1) {
|
||||
// La régularisation annuelle est la différence entre l'exonération Lodeom calculée
|
||||
// pour la rémunération annuelle (comparée au SMIC annuel) et la somme des exonérations
|
||||
// Lodeom déjà accordées.
|
||||
const lodeomTotale = getTotalLodeom(
|
||||
rémunérationBruteCumulées[monthIndex],
|
||||
SMICCumulés[monthIndex],
|
||||
coefT,
|
||||
engine
|
||||
)
|
||||
const currentLodeomCumulée =
|
||||
lodeom.value +
|
||||
sumAll(reevaluatedData.map((monthData) => monthData.lodeom.value))
|
||||
régularisation.value = lodeomTotale - currentLodeomCumulée
|
||||
|
||||
if (lodeom.value + régularisation.value > 0) {
|
||||
lodeom.value += régularisation.value
|
||||
lodeom.répartition = getRépartition(
|
||||
rémunérationBrute,
|
||||
lodeom.value,
|
||||
engine
|
||||
)
|
||||
régularisation.value = 0
|
||||
} else if (régularisation.value < 0) {
|
||||
régularisation.répartition = getRépartition(
|
||||
rémunérationBrute,
|
||||
régularisation.value,
|
||||
engine
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
...reevaluatedData,
|
||||
{
|
||||
rémunérationBrute,
|
||||
options,
|
||||
lodeom,
|
||||
régularisation,
|
||||
},
|
||||
]
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return reevaluatedData
|
||||
}
|
||||
|
||||
const getSMICCumulés = (
|
||||
data: MonthState[],
|
||||
year: number,
|
||||
engine: Engine<DottedName>
|
||||
): number[] => {
|
||||
return data.reduce((SMICCumulés: number[], monthData, monthIndex) => {
|
||||
// S'il n'y a pas de rémunération ce mois-ci, il n'y a pas d'exonération Lodeom
|
||||
// et il ne faut pas compter le SMIC de ce mois-ci dans le SMIC cumulé.
|
||||
if (!monthData.rémunérationBrute) {
|
||||
SMICCumulés.push(0)
|
||||
|
||||
return SMICCumulés
|
||||
}
|
||||
|
||||
const contexte = {
|
||||
date: getDateForContexte(monthIndex, year),
|
||||
} as Situation
|
||||
|
||||
if (!monthData.options.rémunérationETP) {
|
||||
contexte[heuresSupplémentairesDottedName] =
|
||||
monthData.options.heuresSupplémentaires
|
||||
contexte[heuresComplémentairesDottedName] =
|
||||
monthData.options.heuresComplémentaires
|
||||
}
|
||||
|
||||
let SMIC = engine.evaluate({
|
||||
valeur: 'salarié . temps de travail . SMIC',
|
||||
unité: '€/mois',
|
||||
contexte,
|
||||
}).nodeValue as number
|
||||
|
||||
if (monthData.options.rémunérationETP) {
|
||||
const SMICHoraire = engine.evaluate({
|
||||
valeur: 'SMIC . horaire',
|
||||
contexte,
|
||||
}).nodeValue as number
|
||||
// On retranche les primes et le paiements des heures supplémentaires à la rémunération versée
|
||||
// et on la compare à la rémunération équivalente "mois complet" sans les primes
|
||||
const prorata =
|
||||
(monthData.rémunérationBrute -
|
||||
monthData.options.rémunérationPrimes -
|
||||
monthData.options.rémunérationHeuresSup) /
|
||||
monthData.options.rémunérationETP
|
||||
// On applique ce prorata au SMIC mensuel et on y ajoute les heures supplémentaires et complémentaires
|
||||
SMIC =
|
||||
SMIC * prorata +
|
||||
SMICHoraire *
|
||||
(monthData.options.heuresSupplémentaires +
|
||||
monthData.options.heuresComplémentaires)
|
||||
}
|
||||
|
||||
let SMICCumulé = SMIC
|
||||
if (monthIndex > 0) {
|
||||
// Il faut aller chercher la dernière valeur positive du SMIC cumulé.
|
||||
const previousSMICCumulé =
|
||||
SMICCumulés.findLast((SMICCumulé) => SMICCumulé > 0) ?? 0
|
||||
SMICCumulé = previousSMICCumulé + SMIC
|
||||
}
|
||||
|
||||
SMICCumulés.push(SMICCumulé)
|
||||
|
||||
return SMICCumulés
|
||||
}, [])
|
||||
}
|
||||
|
||||
const getRémunérationBruteCumulées = (data: MonthState[]) => {
|
||||
return data.reduce(
|
||||
(rémunérationBruteCumulées: number[], monthData, monthIndex) => {
|
||||
// S'il n'y a pas de rémunération ce mois-ci, il n'y a pas d'exonération Lodeom
|
||||
// et elle ne compte pas non plus pour la régularisation des mois à venir.
|
||||
if (!monthData.rémunérationBrute) {
|
||||
rémunérationBruteCumulées.push(0)
|
||||
|
||||
return rémunérationBruteCumulées
|
||||
}
|
||||
|
||||
let rémunérationBruteCumulée = monthData.rémunérationBrute
|
||||
if (monthIndex > 0) {
|
||||
// Il faut aller chercher la dernière valeur positive de la rémunération cumulée.
|
||||
const previousRémunérationBruteCumulée =
|
||||
rémunérationBruteCumulées.findLast(
|
||||
(rémunérationBruteCumulée) => rémunérationBruteCumulée > 0
|
||||
) ?? 0
|
||||
rémunérationBruteCumulée =
|
||||
previousRémunérationBruteCumulée + monthData.rémunérationBrute
|
||||
}
|
||||
|
||||
rémunérationBruteCumulées.push(rémunérationBruteCumulée)
|
||||
|
||||
return rémunérationBruteCumulées
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
|
@ -2,13 +2,13 @@ import { useCallback, useState } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import PeriodSwitch from '@/components/PeriodSwitch'
|
||||
import RégularisationSwitch from '@/components/RégularisationSwitch'
|
||||
import { SelectSimulationYear } from '@/components/SelectSimulationYear'
|
||||
import SimulateurWarning from '@/components/SimulateurWarning'
|
||||
import Simulation from '@/components/Simulation'
|
||||
|
||||
import CongésPayésSwitch from './components/CongésPayésSwitch'
|
||||
import EffectifSwitch from './components/EffectifSwitch'
|
||||
import RégularisationSwitch from './components/RégularisationSwitch'
|
||||
import RéductionGénéraleSimulationGoals from './Goals'
|
||||
import { RégularisationMethod } from './utils'
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { styled } from 'styled-components'
|
||||
|
||||
import NumberInput from '@/components/conversation/NumberInput'
|
||||
import MonthOptions from '@/components/MonthOptions'
|
||||
import RuleLink from '@/components/RuleLink'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
|
@ -19,7 +20,6 @@ import {
|
|||
rémunérationBruteDottedName,
|
||||
} from '../utils'
|
||||
import MontantRéduction from './MontantRéduction'
|
||||
import MonthOptions from './MonthOptions'
|
||||
|
||||
type Props = {
|
||||
monthName: string
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
|
||||
import RépartitionValue from '@/components/RépartitionValue'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { Li, Ul } from '@/design-system/typography/list'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
@ -9,7 +10,6 @@ import {
|
|||
réductionGénéraleDottedName,
|
||||
Répartition as RépartitionType,
|
||||
} from '../utils'
|
||||
import RépartitionValue from './RépartitionValue'
|
||||
|
||||
type Props = {
|
||||
répartition: RépartitionType
|
||||
|
|
|
@ -5,7 +5,7 @@ export default function WarningSalaireTrans() {
|
|||
<Trans i18nKey="pages.simulateurs.réduction-générale.warnings.salaire">
|
||||
La RGCP concerne uniquement les salaires inférieurs à 1,6 SMIC.
|
||||
C'est-à-dire, pour 2024, une rémunération totale qui ne dépasse pas{' '}
|
||||
<strong>2 827,07 €</strong> bruts par mois.
|
||||
<strong>2 827,07 €</strong> bruts par mois.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { SimulateurCard } from '@/components/SimulateurCard'
|
||||
import { Item } from '@/design-system'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Select } from '@/design-system/field/Select'
|
||||
import useSimulatorsData from '@/hooks/useSimulatorsData'
|
||||
|
||||
import { SimulateurCard } from '../../../components/SimulateurCard'
|
||||
import { getFilter } from '../StatsPage'
|
||||
import { Filter } from '../types'
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ const rawSitePathsFr = {
|
|||
is: 'impot-societe',
|
||||
dividendes: 'dividendes',
|
||||
'réduction-générale': 'réduction-générale',
|
||||
lodeom: 'lodeom',
|
||||
},
|
||||
nouveautés: {
|
||||
index: 'nouveautés',
|
||||
|
@ -172,6 +173,7 @@ const rawSitePathsEn = {
|
|||
is: 'corporate-tax',
|
||||
dividendes: 'dividends',
|
||||
'réduction-générale': 'réduction-générale',
|
||||
lodeom: 'lodeom',
|
||||
},
|
||||
nouveautés: {
|
||||
index: 'news',
|
||||
|
|
Loading…
Reference in New Issue