Ajoute l'activité mixte aux simulateurs indépendants

Factorise la logique de la saisie des CA dans un nouveau composant
pull/1620/head
Johan Girod 2021-05-26 16:21:46 +02:00
parent acbfb58a3a
commit ea24a47a3f
5 changed files with 167 additions and 142 deletions

View File

@ -833,18 +833,19 @@ entreprise . activité . mixte . proportions:
- nom: service BIC
variations:
- si: activité = 'libérale'
alors: 0
alors: 0%
- sinon: 50%
- nom: service BNC
variations:
- si: activité = 'libérale'
alors: 2 / 3
- sinon: 0
- sinon: 0%
- nom: vente restauration hébergement
variations:
- si: activité = 'libérale'
alors: 1 / 3
- sinon: 50%
note: Il appartient à l'utilisateur de bien vérifier que la somme des trois pourcentages renseignés vaut 100%.
entreprise . activité . libérale réglementée:

View File

@ -0,0 +1,148 @@
import { batchUpdateSituation } from 'Actions/actions'
import { DottedName } from 'modele-social'
import { serializeEvaluation } from 'publicodes'
import { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { situationSelector } from 'Selectors/simulationSelectors'
import { Explicable } from './conversation/Explicable'
import { Condition } from './EngineValue'
import { SimulationGoal } from './SimulationGoals'
import { useEngine } from './utils/EngineContext'
import { Markdown } from './utils/markdown'
const proportions = {
'entreprise . activité . mixte . proportions . service BIC':
"entreprise . chiffre d'affaires . service BIC",
'entreprise . activité . mixte . proportions . service BNC':
"entreprise . chiffre d'affaires . service BNC",
'entreprise . activité . mixte . proportions . vente restauration hébergement':
"entreprise . chiffre d'affaires . vente restauration hébergement",
} as const
export default function ChiffreAffairesActivitéMixte({
dottedName,
}: {
dottedName: DottedName
}) {
const adjustProportions = useAdjustProportions(dottedName)
const dispatch = useDispatch()
const clearChiffreAffaireMixte = useCallback(() => {
dispatch(
batchUpdateSituation(
Object.values(proportions).reduce(
(acc, chiffreAffaires) => ({ ...acc, [chiffreAffaires]: undefined }),
{}
)
)
)
}, [dispatch])
return (
<>
<SimulationGoal
appear={false}
onUpdateSituation={clearChiffreAffaireMixte}
dottedName={dottedName}
/>
<ActivitéMixte />
<Condition expression="entreprise . activité . mixte">
<li className="small-target">
<ul>
{Object.values(proportions).map((chiffreAffaires) => (
<SimulationGoal
key={chiffreAffaires}
onUpdateSituation={adjustProportions}
dottedName={chiffreAffaires}
/>
))}
</ul>
</li>
</Condition>
</>
)
}
export function useAdjustProportions(CADottedName: DottedName): () => void {
const engine = useEngine()
const dispatch = useDispatch()
return useCallback(() => {
const nouveauCA = serializeEvaluation(
engine.evaluate({
somme: Object.values(proportions)
.map((chiffreAffaire) =>
serializeEvaluation(engine.evaluate(chiffreAffaire))
)
.filter(Boolean),
})
)
if (nouveauCA === '0€/an') {
return // Avoid division by 0
}
const situation = Object.entries(proportions).reduce(
(acc, [proportionName, valueName]) => {
const value = serializeEvaluation(
engine.evaluate({ valeur: valueName, 'par défaut': '0€/an' })
)
const newProportion = serializeEvaluation(
engine.evaluate({
valeur: `${value} / ${nouveauCA}`,
unité: '%',
})
)
return { ...acc, [proportionName]: newProportion }
},
{ [CADottedName]: nouveauCA }
)
dispatch(batchUpdateSituation(situation))
}, [engine, dispatch])
}
function ActivitéMixte() {
const dispatch = useDispatch()
const situation = useSelector(situationSelector)
const rule = useEngine().getRule('entreprise . activité . mixte')
const defaultChecked =
useEngine().evaluate('entreprise . activité . mixte').nodeValue === true
const onMixteChecked = useCallback(
(checked: boolean) => {
dispatch(
batchUpdateSituation(
Object.values(proportions).reduce(
(acc, dottedName) => ({ ...acc, [dottedName]: undefined }),
{ 'entreprise . activité . mixte': checked ? 'oui' : 'non' }
)
)
)
},
[dispatch, situation]
)
return (
<li
className="small-target"
css={`
margin-top: -1rem;
`}
>
<label
css={`
display: flex;
align-items: center;
justify-content: flex-end;
`}
>
<input
type="checkbox"
key={'' + defaultChecked}
defaultChecked={defaultChecked}
onChange={(evt) => onMixteChecked(evt.target.checked)}
/>
&nbsp; Activité mixte
<Explicable>
<Markdown source={`## ${rule.title}\n ${rule.rawNode.description}`} />
</Explicable>
</label>
</li>
)
}

View File

@ -1,104 +1,22 @@
import { batchUpdateSituation } from 'Actions/actions'
import { Explicable } from 'Components/conversation/Explicable'
import { Condition, WhenAlreadyDefined } from 'Components/EngineValue'
import ChiffreAffairesActivitéMixte from 'Components/ChiffreAffairesActivitéMixte'
import { WhenAlreadyDefined } from 'Components/EngineValue'
import PeriodSwitch from 'Components/PeriodSwitch'
import SimulateurWarning from 'Components/SimulateurWarning'
import Simulation from 'Components/Simulation'
import { SimulationGoal, SimulationGoals } from 'Components/SimulationGoals'
import StackedBarChart from 'Components/StackedBarChart'
import { ThemeColorsContext } from 'Components/utils/colors'
import { useEngine } from 'Components/utils/EngineContext'
import { Markdown } from 'Components/utils/markdown'
import { serializeEvaluation } from 'publicodes'
import { default as React, useCallback, useContext } from 'react'
import { default as React, useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { situationSelector } from 'Selectors/simulationSelectors'
const proportions = {
'entreprise . activité . mixte . proportions . service BIC':
"entreprise . chiffre d'affaires . service BIC",
'entreprise . activité . mixte . proportions . service BNC':
"entreprise . chiffre d'affaires . service BNC",
'entreprise . activité . mixte . proportions . vente restauration hébergement':
"entreprise . chiffre d'affaires . vente restauration hébergement",
} as const
function useAdjustProportions(): () => void {
const engine = useEngine()
const dispatch = useDispatch()
return useCallback(() => {
const nouveauCA = serializeEvaluation(
engine.evaluate({
somme: Object.values(proportions)
.map((chiffreAffaire) =>
serializeEvaluation(engine.evaluate(chiffreAffaire))
)
.filter(Boolean),
})
)
if (nouveauCA === '0€/an') {
return // Avoid division by 0
}
const situation = Object.entries(proportions).reduce(
(acc, [proportionName, valueName]) => {
const value = serializeEvaluation(
engine.evaluate({ valeur: valueName, 'par défaut': '0€/an' })
)
const newProportion = serializeEvaluation(
engine.evaluate({
valeur: `${value} / ${nouveauCA}`,
unité: '%',
})
)
return { ...acc, [proportionName]: newProportion }
},
{ "dirigeant . auto-entrepreneur . chiffre d'affaires": nouveauCA }
)
dispatch(batchUpdateSituation(situation))
}, [engine, dispatch])
}
export default function AutoEntrepreneur() {
const adjustProportions = useAdjustProportions()
const activitéMixte =
useEngine().evaluate('entreprise . activité . mixte').nodeValue === true
return (
<>
<SimulateurWarning simulateur="auto-entrepreneur" />
<Simulation explanations={<Explanation />}>
<PeriodSwitch />
<SimulationGoals className="plain">
<SimulationGoal
appear={false}
editable={!activitéMixte}
dottedName="dirigeant . auto-entrepreneur . chiffre d'affaires"
/>
<Condition expression="entreprise . activité . mixte">
<li className="small-target">
<ul>
<SimulationGoal
onUpdateSituation={adjustProportions}
dottedName="entreprise . chiffre d'affaires . vente restauration hébergement"
/>
<SimulationGoal
onUpdateSituation={adjustProportions}
dottedName="entreprise . chiffre d'affaires . service BIC"
/>
<SimulationGoal
onUpdateSituation={adjustProportions}
dottedName="entreprise . chiffre d'affaires . service BNC"
/>
</ul>
</li>
</Condition>
<ActivitéMixte
key={'' + activitéMixte}
defaultChecked={activitéMixte}
/>
<ChiffreAffairesActivitéMixte dottedName="dirigeant . auto-entrepreneur . chiffre d'affaires" />
<SimulationGoal
small
editable={false}
@ -119,52 +37,6 @@ export default function AutoEntrepreneur() {
)
}
function ActivitéMixte({ defaultChecked }: { defaultChecked: boolean }) {
const dispatch = useDispatch()
const situation = useSelector(situationSelector)
const rule = useEngine().getRule('entreprise . activité . mixte')
const onMixteChecked = useCallback(
(checked: boolean) => {
dispatch(
batchUpdateSituation(
Object.values(proportions).reduce(
(acc, dottedName) => ({ ...acc, [dottedName]: undefined }),
{ 'entreprise . activité . mixte': checked ? 'oui' : 'non' }
)
)
)
},
[dispatch, situation]
)
return (
<li
className="small-target"
css={`
margin-top: -1rem;
`}
>
<label
css={`
display: flex;
align-items: center;
justify-content: flex-end;
`}
>
<input
type="checkbox"
defaultChecked={defaultChecked}
onChange={(evt) => onMixteChecked(evt.target.checked)}
/>
&nbsp; Activité mixte
<Explicable>
<Markdown source={`## ${rule.title}\n ${rule.rawNode.description}`} />
</Explicable>
</label>
</li>
)
}
function Explanation() {
const { t } = useTranslation()
const { palettes } = useContext(ThemeColorsContext)

View File

@ -1,5 +1,6 @@
import { updateSituation } from 'Actions/actions'
import Banner from 'Components/Banner'
import ChiffreAffairesActivitéMixte from 'Components/ChiffreAffairesActivitéMixte'
import { Condition, WhenAlreadyDefined } from 'Components/EngineValue'
import PeriodSwitch from 'Components/PeriodSwitch'
import SimulateurWarning from 'Components/SimulateurWarning'
@ -78,11 +79,16 @@ function IndépendantSimulationGoals() {
return (
<SimulationGoals className="plain">
<Condition expression="entreprise . imposition = 'IR'">
<SimulationGoal
appear={false}
alwaysShow
dottedName="entreprise . chiffre d'affaires"
/>
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<SimulationGoal
appear={false}
alwaysShow
dottedName="entreprise . chiffre d'affaires"
/>
</Condition>
<Condition expression="entreprise . imposition . IR . micro-fiscal">
<ChiffreAffairesActivitéMixte dottedName="entreprise . chiffre d'affaires" />
</Condition>
<Condition expression="entreprise . imposition . IR . micro-fiscal != oui">
<SimulationGoal
small

View File

@ -22,9 +22,7 @@ questions:
liste noire:
- entreprise . charges
- entreprise . chiffre d'affaires
- entreprise . chiffre d'affaires . vente restauration hébergement
- entreprise . chiffre d'affaires . service BIC
- entreprise . chiffre d'affaires . service BNC
unité par défaut: €/an
situation:
entreprise . activité . mixte: non