diff --git a/.vscode/settings.json b/.vscode/settings.json index 43e46a7aa..f505e81e1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.formatOnSave": true, - "spellright.language": ["fr"], + "spellright.language": ["fr", "en"], "spellright.documentTypes": ["yaml", "git-commit"], "editor.codeActionsOnSave": { "source.organizeImports": true diff --git a/cypress/integration/mon-entreprise/simulateurs.js b/cypress/integration/mon-entreprise/simulateurs.js index d3e116547..0ce14524b 100644 --- a/cypress/integration/mon-entreprise/simulateurs.js +++ b/cypress/integration/mon-entreprise/simulateurs.js @@ -1,70 +1,86 @@ - const fr = Cypress.env('language') === 'fr' const inputSelector = 'input.currencyInput__input:not([name$="charges"])' -describe('Simulateurs', function () { - if (!fr) { return } - ['indépendant', 'assimilé-salarié', 'auto-entrepreneur', 'salarié'].forEach(simulateur => - describe(simulateur, () => { - before(() => cy.visit(`/simulateurs/${simulateur}`)) - it('should not crash', function () { - cy.get(inputSelector) - }) +describe('Simulateurs', function() { + if (!fr) { + return + } + ;['indépendant', 'assimilé-salarié', 'auto-entrepreneur', 'salarié'].forEach( + simulateur => + describe(simulateur, () => { + before(() => cy.visit(`/simulateurs/${simulateur}`)) + it('should not crash', function() { + cy.get(inputSelector) + }) - it('should display a result when entering a value in any of the currency input', () => { - cy.contains('année').click() - if (['indépendant', 'assimilé-salarié'].includes(simulateur)) { - cy.get('input.currencyInput__input[name$="charges"]').type(1000) - } - cy.get(inputSelector).each((testedInput, i) => { - cy.wrap(testedInput).type('{selectall}60000') - cy.wait(600) - cy.contains('Cotisations') - cy.get(inputSelector).each(($input, j) => { - const val = $input.val().replace(/[\s,.]/g, '') - if (i != j) { - expect(val).not.to.be.eq('60000') - } - expect(val).to.match(/[1-9][\d]*$/) - }) - }) - }) + it('should display a result when entering a value in any of the currency input', () => { + cy.contains('€/an').click() + if (['indépendant', 'assimilé-salarié'].includes(simulateur)) { + cy.get('input.currencyInput__input[name$="charges"]').type(1000) + } + cy.get(inputSelector).each((testedInput, i) => { + cy.wrap(testedInput).type('{selectall}60000') + cy.wait(600) + cy.contains('Cotisations') + cy.get(inputSelector).each(($input, j) => { + const val = $input.val().replace(/[\s,.]/g, '') + if (i != j) { + expect(val).not.to.be.eq('60000') + } + expect(val).to.match(/[1-9][\d]*$/) + }) + }) + }) - it('should allow to change period', function () { - cy.contains('année').click() - cy.wait(200) - cy.get(inputSelector).first().type('{selectall}12000') - cy.wait(600) - cy.contains('mois').click() - cy.get(inputSelector).first().invoke('val').should('match', /1[\s]000/) - }) + it('should allow to change period', function() { + cy.contains('€/an').click() + cy.wait(200) + cy.get(inputSelector) + .first() + .type('{selectall}12000') + cy.wait(600) + cy.contains('€/mois').click() + cy.get(inputSelector) + .first() + .invoke('val') + .should('match', /1[\s]000/) + }) - it('should allow to navigate to a documentation page', function () { - cy.get(inputSelector).first().type('{selectall}2000') - cy.wait(700) - cy.contains('Cotisations').click() - cy.location().should((loc) => { - expect(loc.pathname).to.match(/\/documentation\/.*\/cotisations/) - }) - }) + it('should allow to navigate to a documentation page', function() { + cy.get(inputSelector) + .first() + .type('{selectall}2000') + cy.wait(700) + cy.contains('Cotisations').click() + cy.location().should(loc => { + expect(loc.pathname).to.match(/\/documentation\/.*\/cotisations/) + }) + }) - it('should allow to go back to the simulation', function () { - cy.contains('← ').click(); - cy.get(inputSelector).first().invoke('val').should('be', '2 000') - }) + it('should allow to go back to the simulation', function() { + cy.contains('← ').click() + cy.get(inputSelector) + .first() + .invoke('val') + .should('be', '2 000') + }) - if (simulateur === 'salarié') { - it('should save the current simulation', function () { - cy.get(inputSelector).first().type('{selectall}2137') - cy.contains('Passer').click() - cy.contains('Passer').click() - cy.contains('Passer').click() - cy.wait(1600) - cy.visit('/simulateurs/salarié') - cy.contains('Retrouver ma simulation').click() - cy.get(inputSelector).first().invoke('val').should('match', /2[\s]137/) - }) - } - - }) - ) -}) \ No newline at end of file + if (simulateur === 'salarié') { + it('should save the current simulation', function() { + cy.get(inputSelector) + .first() + .type('{selectall}2137') + cy.contains('Passer').click() + cy.contains('Passer').click() + cy.contains('Passer').click() + cy.wait(1600) + cy.visit('/simulateurs/salarié') + cy.contains('Retrouver ma simulation').click() + cy.get(inputSelector) + .first() + .invoke('val') + .should('match', /2[\s]137/) + }) + } + }) + ) +}) diff --git a/source/actions/actions.ts b/source/actions/actions.ts index 9f59adb78..9a66bd12a 100644 --- a/source/actions/actions.ts +++ b/source/actions/actions.ts @@ -1,6 +1,6 @@ import { SitePaths } from 'Components/utils/withSitePaths' import { History } from 'history' -import { RootState } from 'Reducers/rootReducer' +import { RootState, SimulationConfig } from 'Reducers/rootReducer' import { ThunkAction } from 'redux-thunk' import { DottedName } from 'Types/rule' import { deletePersistedSimulation } from '../storage/persistSimulation' @@ -13,10 +13,11 @@ export type Action = | DeletePreviousSimulationAction | SetExempleAction | ExplainVariableAction - | UpdatePeriodAction + | UpdateSituationAction | HideControlAction | LoadPreviousSimulationAction | SetSituationBranchAction + | UpdateDefaultUnit | SetActiveTargetAction type ThunkResult = ThunkAction< @@ -35,7 +36,7 @@ type StepAction = { type SetSimulationConfigAction = { type: 'SET_SIMULATION' url: string - config: Object + config: SimulationConfig } type DeletePreviousSimulationAction = { @@ -51,12 +52,13 @@ type SetExempleAction = { type ResetSimulationAction = ReturnType type UpdateAction = ReturnType -type UpdatePeriodAction = ReturnType +type UpdateSituationAction = ReturnType type LoadPreviousSimulationAction = ReturnType type SetSituationBranchAction = ReturnType type SetActiveTargetAction = ReturnType type HideControlAction = ReturnType type ExplainVariableAction = ReturnType +type UpdateDefaultUnit = ReturnType export const resetSimulation = () => ({ @@ -90,9 +92,12 @@ export const setSituationBranch = (id: number) => export const setSimulationConfig = (config: Object): ThunkResult => ( dispatch, - _, + getState, { history } ): void => { + if (getState().simulation?.config === config) { + return + } const url = history.location.pathname dispatch({ type: 'SET_SIMULATION', @@ -121,10 +126,10 @@ export const updateSituation = (fieldName: DottedName, value: any) => value } as const) -export const updatePeriod = (toPeriod: string) => +export const updateUnit = (defaultUnit: string) => ({ - type: 'UPDATE_PERIOD', - toPeriod + type: 'UPDATE_DEFAULT_UNIT', + defaultUnit } as const) export function setExample(name: string, situation, dottedName: string) { diff --git a/source/components/PaySlip.js b/source/components/PaySlip.js index f2b5d43e9..cdbde0bfd 100644 --- a/source/components/PaySlip.js +++ b/source/components/PaySlip.js @@ -53,10 +53,15 @@ export default compose(
{heuresSupplémentaires?.nodeValue > 0 && ( - + )}
diff --git a/source/components/PaySlipSections.js b/source/components/PaySlipSections.js index ae097c8b6..97109e7d2 100644 --- a/source/components/PaySlipSections.js +++ b/source/components/PaySlipSections.js @@ -42,7 +42,7 @@ export let SalaireBrutSection = ({ getRule }) => { export let Line = ({ rule, ...props }) => ( <> - + ) diff --git a/source/components/PeriodSwitch.tsx b/source/components/PeriodSwitch.tsx index 1e09b45ad..e9d3a78ff 100644 --- a/source/components/PeriodSwitch.tsx +++ b/source/components/PeriodSwitch.tsx @@ -1,40 +1,28 @@ -import { updatePeriod } from 'Actions/actions' +import { updateUnit } from 'Actions/actions' import React from 'react' -import { Trans } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' -import { RootState } from 'Reducers/rootReducer' -import { situationSelector } from 'Selectors/analyseSelectors' +import { defaultUnitsSelector } from 'Selectors/analyseSelectors' import './PeriodSwitch.css' export default function PeriodSwitch() { const dispatch = useDispatch() - const situation = useSelector(situationSelector) - const defaultPeriod = useSelector( - (state: RootState) => - state.simulation?.config?.situation?.période || 'année' - ) - const currentPeriod = situation.période - let periods = ['année', 'mois'] + const currentUnit = useSelector(defaultUnitsSelector)[0] - if (!currentPeriod) { - dispatch(updatePeriod(defaultPeriod)) - } + let units = ['€/mois', '€/an'] return ( - {periods.map(period => ( - diff --git a/source/components/SalaryExplanation.tsx b/source/components/SalaryExplanation.tsx index 423c42efb..e267652a0 100644 --- a/source/components/SalaryExplanation.tsx +++ b/source/components/SalaryExplanation.tsx @@ -11,7 +11,7 @@ import { useSelector } from 'react-redux' import { RootState } from 'Reducers/rootReducer' import { analysisWithDefaultsSelector, - usePeriod + defaultUnitsSelector } from 'Selectors/analyseSelectors' import * as Animate from 'Ui/animate' @@ -126,15 +126,15 @@ function RevenueRepatitionSection() { } function PaySlipSection() { - const period = usePeriod() + const unit = useSelector(defaultUnitsSelector)[0] return (

- - {period === 'mois' - ? 'Fiche de paie mensuelle' - : 'Détail annuel des cotisations'} - + {unit.endsWith('mois') ? ( + Fiche de paie + ) : ( + Détail annuel des cotisations + )}

diff --git a/source/components/SchemeComparaison.tsx b/source/components/SchemeComparaison.tsx index 90abdd846..9162025df 100644 --- a/source/components/SchemeComparaison.tsx +++ b/source/components/SchemeComparaison.tsx @@ -1,4 +1,4 @@ -import { setSituationBranch } from 'Actions/actions' +import { setSimulationConfig, setSituationBranch } from 'Actions/actions' import { defineDirectorStatus, isAutoentrepreneur @@ -9,12 +9,11 @@ import Conversation from 'Components/conversation/Conversation' import SeeAnswersButton from 'Components/conversation/SeeAnswersButton' import PeriodSwitch from 'Components/PeriodSwitch' import ComparaisonConfig from 'Components/simulationConfigs/rémunération-dirigeant.yaml' -import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig' import { SitePathsContext } from 'Components/utils/withSitePaths' import Value from 'Components/Value' import { encodeRuleName, getRuleFromAnalysis } from 'Engine/rules.js' import revenusSVG from 'Images/revenus.svg' -import React, { useCallback, useContext, useState } from 'react' +import { default as React, useCallback, useContext, useState } from 'react' import emoji from 'react-easy-emoji' import { useDispatch, useSelector } from 'react-redux' import { Link } from 'react-router-dom' @@ -45,8 +44,9 @@ export default function SchemeComparaison({ hideAutoEntrepreneur = false, hideAssimiléSalarié = false }: SchemeComparaisonProps) { - useSimulationConfig(ComparaisonConfig) const dispatch = useDispatch() + dispatch(setSimulationConfig(ComparaisonConfig)) + const analyses = useSelector(analysisWithDefaultsSelector) const plafondAutoEntrepreneurDépassé = useSelector((state: RootState) => branchAnalyseSelector(state, { @@ -298,7 +298,7 @@ export default function SchemeComparaison({ {conversationStarted && ( <> -

Période

+

Unité

diff --git a/source/components/StackedBarChart.tsx b/source/components/StackedBarChart.tsx index 60e4a2659..66e013baa 100644 --- a/source/components/StackedBarChart.tsx +++ b/source/components/StackedBarChart.tsx @@ -94,7 +94,6 @@ export default function StackedBarChart({ data }: StackedBarChartProps) { })) const styles = useSpring({ opacity: displayChart ? 1 : 0 }) - return ( diff --git a/source/components/TargetSelection.tsx b/source/components/TargetSelection.tsx index 45d868689..332bc78ee 100644 --- a/source/components/TargetSelection.tsx +++ b/source/components/TargetSelection.tsx @@ -187,8 +187,7 @@ const Target = ({ target, initialRender }) => { onFirstClick={value => { dispatch(updateSituation(target.dottedName, value)) }} - rulePeriod={target.période} - colouredBackground={true} + unit={target.defaultUnit} /> )} @@ -237,7 +236,7 @@ let TargetInputOrValue = ({ : undefined const inversionFail = useSelector( (state: RootState) => - analysisWithDefaultsSelector(state)?.cache.inversionFail + analysisWithDefaultsSelector(state)?.cache._meta.inversionFail ) const blurValue = inversionFail && !isActiveInput && value diff --git a/source/components/Value.tsx b/source/components/Value.tsx index 0f0db9aa5..c6be2761e 100644 --- a/source/components/Value.tsx +++ b/source/components/Value.tsx @@ -72,7 +72,6 @@ export default function Value({ value: nodeValue }) ) - return nodeValue == undefined ? null : ( {negative ? '-' : ''} diff --git a/source/components/conversation/Input.js b/source/components/conversation/Input.js index afc0e6d70..0afccc189 100644 --- a/source/components/conversation/Input.js +++ b/source/components/conversation/Input.js @@ -1,12 +1,9 @@ -import classnames from 'classnames' -import { T } from 'Components' import withColours from 'Components/utils/withColours' import { currencyFormat } from 'Engine/format' import { compose } from 'ramda' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import NumberFormat from 'react-number-format' -import { usePeriod } from 'Selectors/analyseSelectors' import { debounce } from '../../utils' import { FormDecorator } from './FormDecorator' import InputSuggestions from './InputSuggestions' @@ -20,15 +17,12 @@ export default compose( suggestions, setFormValue, submit, - rulePeriod, dottedName, value, colours, unit }) { - const period = usePeriod() const debouncedSetFormValue = useCallback(debounce(750, setFormValue), []) - const suffixed = unit != null && unit !== '%' const { language } = useTranslation().i18n const { thousandSeparator, decimalSeparator } = currencyFormat(language) @@ -42,44 +36,27 @@ export default compose( setFormValue(value) }} onSecondClick={() => submit('suggestion')} - rulePeriod={rulePeriod} />
{ - debouncedSetFormValue(unit === '%' ? floatValue / 100 : floatValue) + debouncedSetFormValue(floatValue) }} - value={unit === '%' ? 100 * value : value} + value={value} autoComplete="off" /> - {suffixed && ( - - )} +
diff --git a/source/components/conversation/InputSuggestions.js b/source/components/conversation/InputSuggestions.tsx similarity index 51% rename from source/components/conversation/InputSuggestions.js rename to source/components/conversation/InputSuggestions.tsx index ad0a3de81..5c2a8f872 100644 --- a/source/components/conversation/InputSuggestions.js +++ b/source/components/conversation/InputSuggestions.tsx @@ -1,39 +1,39 @@ import { toPairs } from 'ramda' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { usePeriod } from 'Selectors/analyseSelectors' +import { useSelector } from 'react-redux' +import { defaultUnitsSelector } from 'Selectors/analyseSelectors' +import { convertUnit, parseUnit } from '../../engine/units' export default function InputSuggestions({ suggestions, - onSecondClick, + onSecondClick = x => x, onFirstClick, - rulePeriod + unit }) { const [suggestion, setSuggestion] = useState(null) - const period = usePeriod() const { t } = useTranslation() - + const defaultUnit = parseUnit(useSelector(defaultUnitsSelector)[0]) if (!suggestions) return null return (
Suggestions : - {toPairs(suggestions).map(([text, value]) => { - // TODO : ce serait mieux de déplacer cette logique dans le moteur - const adjustedValue = - rulePeriod === 'flexible' && period === 'année' ? value * 12 : value + {toPairs(suggestions).map(([text, value]: [string, number]) => { + value = unit ? convertUnit(unit, defaultUnit, value) : value return ( ) diff --git a/source/components/conversation/select/SelectTauxRisque.js b/source/components/conversation/select/SelectTauxRisque.js index faeb2e6a5..90683c4dd 100644 --- a/source/components/conversation/select/SelectTauxRisque.js +++ b/source/components/conversation/select/SelectTauxRisque.js @@ -7,7 +7,7 @@ const worker = new Worker() function SelectComponent({ setFormValue, submit, options }) { const [searchResults, setSearchResults] = useState() let submitOnChange = option => { - option.text = +option['Taux net'].replace(',', '.') / 100 + option.text = +option['Taux net'].replace(',', '.') setFormValue(option.text) submit() } diff --git a/source/components/rule/Rule.js b/source/components/rule/Rule.js index 0f32e902b..bc7a1db4e 100644 --- a/source/components/rule/Rule.js +++ b/source/components/rule/Rule.js @@ -1,5 +1,4 @@ import { T } from 'Components' -import PeriodSwitch from 'Components/PeriodSwitch' import withColours from 'Components/utils/withColours' import { SitePathsContext } from 'Components/utils/withSitePaths' import Value from 'Components/Value' @@ -59,7 +58,6 @@ export default compose( let { type, name, acronyme, title, description, question, icon } = flatRule, namespaceRules = findRuleByNamespace(flatRules, dottedName) let displayedRule = analysedExample || analysedRule - const renderToggleSourceButton = () => { return (
{displayedRule.defaultValue != null && ( @@ -163,6 +153,7 @@ export default compose( )} @@ -257,20 +248,3 @@ let NamespaceRulesList = compose(withColours)(({ namespaceRules, colours }) => { ) }) - -let Period = ({ period, valuesToShow }) => - period ? ( - valuesToShow && period === 'flexible' ? ( - - ) : ( - - - {period} - - - ) - ) : null diff --git a/source/components/simulationConfigs/assimilé.yaml b/source/components/simulationConfigs/assimilé.yaml index 3b4b1e431..758a2d41c 100644 --- a/source/components/simulationConfigs/assimilé.yaml +++ b/source/components/simulationConfigs/assimilé.yaml @@ -34,7 +34,7 @@ questions: - contrat salarié . complémentaire santé . part employeur - contrat salarié . régime des impatriés +unités par défaut: [€/an] situation: dirigeant: 'assimilé salarié' contrat salarié . ATMP . taux réduit: oui - période: année diff --git a/source/components/simulationConfigs/auto-entrepreneur.yaml b/source/components/simulationConfigs/auto-entrepreneur.yaml index b996d9e2b..fda7f5961 100644 --- a/source/components/simulationConfigs/auto-entrepreneur.yaml +++ b/source/components/simulationConfigs/auto-entrepreneur.yaml @@ -16,6 +16,6 @@ questions: liste noire: - entreprise . charges +unités par défaut: [€/an] situation: dirigeant: 'auto-entrepreneur' - période: année diff --git a/source/components/simulationConfigs/indépendant.yaml b/source/components/simulationConfigs/indépendant.yaml index c00bcdf69..5a9729808 100644 --- a/source/components/simulationConfigs/indépendant.yaml +++ b/source/components/simulationConfigs/indépendant.yaml @@ -22,6 +22,6 @@ questions: liste noire: - entreprise . charges +unités par défaut: [€/an] situation: dirigeant: 'indépendant' - période: année diff --git a/source/components/simulationConfigs/rémunération-dirigeant.yaml b/source/components/simulationConfigs/rémunération-dirigeant.yaml index ce1f0afe1..3c6bc1304 100644 --- a/source/components/simulationConfigs/rémunération-dirigeant.yaml +++ b/source/components/simulationConfigs/rémunération-dirigeant.yaml @@ -18,9 +18,7 @@ questions: - entreprise . catégorie d'activité . restauration ou hébergement - entreprise . catégorie d'activité . libérale règlementée -situation: - période: année - +unités par défaut: [€/an] branches: - nom: Assimilé salarié situation: diff --git a/source/components/simulationConfigs/salarié.yaml b/source/components/simulationConfigs/salarié.yaml index df16d0514..b3b9d93cc 100644 --- a/source/components/simulationConfigs/salarié.yaml +++ b/source/components/simulationConfigs/salarié.yaml @@ -28,6 +28,6 @@ questions: - contrat salarié . statut JEI - contrat salarié . complémentaire santé . part employeur - contrat salarié . régime des impatriés +unités par défaut: [€/mois] situation: dirigeant: non - période: mois diff --git a/source/components/simulationConfigs/useSimulationConfig.ts b/source/components/simulationConfigs/useSimulationConfig.ts deleted file mode 100644 index cbdc0a365..000000000 --- a/source/components/simulationConfigs/useSimulationConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { resetSimulation, setSimulationConfig } from 'Actions/actions' -import { useDispatch, useSelector } from 'react-redux' -import { RootState, SimulationConfig } from 'Reducers/rootReducer' - -export function useSimulationConfig(config: SimulationConfig) { - const dispatch = useDispatch() - const stateConfig = useSelector( - (state: RootState) => state.simulation?.config - ) - if (config !== stateConfig) { - dispatch(setSimulationConfig(config)) - if (stateConfig) { - dispatch(resetSimulation()) - } - } -} diff --git a/source/components/ui/AnimatedTargetValue.tsx b/source/components/ui/AnimatedTargetValue.tsx index 2004a32cf..020c574a5 100644 --- a/source/components/ui/AnimatedTargetValue.tsx +++ b/source/components/ui/AnimatedTargetValue.tsx @@ -2,7 +2,8 @@ import { formatCurrency } from 'Engine/format' import React, { useRef } from 'react' import ReactCSSTransitionGroup from 'react-addons-css-transition-group' import { useTranslation } from 'react-i18next' -import { usePeriod } from 'Selectors/analyseSelectors' +import { useSelector } from 'react-redux' +import { RootState } from 'Reducers/rootReducer' import './AnimatedTargetValue.css' type AnimatedTargetValueProps = { @@ -22,9 +23,11 @@ export default function AnimatedTargetValue({ const previousValue = useRef() const { language } = useTranslation().i18n - // We don't want to show the animated if the difference comes from a change in the period - const currentPeriod = usePeriod() - const previousPeriod = useRef(currentPeriod) + // We don't want to show the animated if the difference comes from a change in the unit + const currentUnit = useSelector( + (state: RootState) => state.simulation.defaultUnits[0] + ) + const previousUnit = useRef(currentUnit) const difference = previousValue.current === value || Number.isNaN(value) @@ -32,11 +35,11 @@ export default function AnimatedTargetValue({ : (value || 0) - (previousValue.current || 0) const shouldDisplayDifference = difference !== null && - previousPeriod.current === currentPeriod && + previousUnit.current === currentUnit && Math.abs(difference) > 1 previousValue.current = value - previousPeriod.current = currentPeriod + previousUnit.current = currentUnit return ( <> diff --git a/source/documentation/conception-des-mécanismes/aiguillage.md b/source/documentation/conception-des-mécanismes/aiguillage.md index b8dcb5ffe..f675f4d21 100644 --- a/source/documentation/conception-des-mécanismes/aiguillage.md +++ b/source/documentation/conception-des-mécanismes/aiguillage.md @@ -47,20 +47,3 @@ formule: 2017: 6% 2016: 2% ``` - -Celle-ci est similaire à `variations` mais ne contient pas de `si` et est donc plus brève. -On peut la voir comme une alternative adaptée à certains endroits (?). - -```yaml -formule: - assiette: assiette cotisations sociales - taux: - aiguillage numérique: - statut cadre = non: - 2017: 16% - 2016: 12% - - statut cadre = oui: - 2017: 6% - 2016: 2% -``` diff --git a/source/documentation/conception-des-mécanismes/périodes.yaml b/source/documentation/conception-des-mécanismes/périodes.yaml deleted file mode 100644 index 7fd2f3ff8..000000000 --- a/source/documentation/conception-des-mécanismes/périodes.yaml +++ /dev/null @@ -1,18 +0,0 @@ -- multiplication: - assiette: - transformation: - période: annuelle - variable: salaire de base - taux: 10% - -- multiplication: - assiette: - période: annuelle - variable: salaire de base - taux: 10% - -# Ce dernier est surement le plus lisible -# Mais ne permet pas un système générique de transformation -- multiplication: - assiette: salaire de base [annuel] - taux: 10% diff --git a/source/engine/controls.js b/source/engine/controls.js index 3527d5ab1..d1770de81 100644 --- a/source/engine/controls.js +++ b/source/engine/controls.js @@ -10,7 +10,7 @@ export let evaluateControls = (cache, situationGate, parsedRules) => getControls(rule).map(control => ({ ...control, evaluated: evaluateNode( - cache, + { ...cache, contextRule: [rule.dottedName] }, situationGate, parsedRules, control.testExpression diff --git a/source/engine/error.ts b/source/engine/error.ts new file mode 100644 index 000000000..b0ea437ba --- /dev/null +++ b/source/engine/error.ts @@ -0,0 +1,38 @@ +import { coerceArray } from '../utils' +export function syntaxError( + dottedName: string, + message: string, + originalError: Error +) { + throw new Error( + `[ Erreur syntaxique ] +➡️ Dans la règle \`${dottedName}\`, +✖️ ${message} + ${originalError && originalError.message} +` + ) +} + +export function typeWarning( + rules: string[] | string, + message: string, + originalError?: Error +) { + console.warn( + `[ Erreur de type ] +➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\`, +✖️ ${message} + ${originalError && originalError.message} +` + ) +} + +export function warning(dottedName: string, message: string, solution: string) { + console.warn( + `[ Avertissement ] +➡️ Dans la règle \`${dottedName}\`, +⚠️ ${message} +💡${solution} +` + ) +} diff --git a/source/engine/evaluateRule.js b/source/engine/evaluateRule.js index 143b3cbba..1d07d30d5 100644 --- a/source/engine/evaluateRule.js +++ b/source/engine/evaluateRule.js @@ -1,6 +1,9 @@ import { bonus, evaluateNode, mergeMissing } from 'Engine/evaluation' import { map, mergeAll, pick, pipe } from 'ramda' +import { typeWarning } from './error' +import { convertNodeToUnit } from './nodeUnits' import { anyNull, undefOrTruthy, val } from './traverse-common-functions' +import { areUnitConvertible } from './units' export const evaluateApplicability = ( cache, @@ -46,7 +49,8 @@ export const evaluateApplicability = ( } export default (cache, situationGate, parsedRules, node) => { - cache.parseLevel++ + cache._meta.parseLevel++ + cache._meta.contextRule.push(node.dottedName) let applicabilityEvaluation = evaluateApplicability( cache, situationGate, @@ -80,15 +84,35 @@ export default (cache, situationGate, parsedRules, node) => { bonus(condMissing, !!Object.keys(condMissing).length), formulaMissingVariables ) - cache.parseLevel-- - if (node.dottedName.startsWith('sum')) { - // console.log(node.dottedName, missingVariables, node) + const unit = + node.unit || + (node.defaultUnit && + cache._meta.defaultUnits.find(unit => + areUnitConvertible(node.defaultUnit, unit) + )) || + node.defaultUnit || + evaluatedFormula.unit + + if (unit) { + try { + nodeValue = convertNodeToUnit(unit, evaluatedFormula).nodeValue + } catch (e) { + typeWarning( + node.dottedName, + `L'unité de la règle est incompatible avec celle de sa formule`, + e + ) + } } + + cache._meta.contextRule.pop() + cache._meta.parseLevel-- return { ...node, ...applicabilityEvaluation, - ...{ formule: evaluatedFormula }, + ...(node.formule && { formule: evaluatedFormula }), nodeValue, + unit, isApplicable, missingVariables } diff --git a/source/engine/evaluation.js b/source/engine/evaluation.js index c64c69669..ededa13fe 100644 --- a/source/engine/evaluation.js +++ b/source/engine/evaluation.js @@ -9,10 +9,12 @@ import { keys, map, mergeWith, - pluck, reduce, values } from 'ramda' +import React from 'react' +import { typeWarning } from './error' +import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits' export let makeJsx = node => typeof node.jsx == 'function' @@ -28,8 +30,34 @@ export let mergeAllMissing = missings => export let mergeMissing = (left, right) => mergeWith(add, left || {}, right || {}) -export let evaluateNode = (cache, situationGate, parsedRules, node) => - node.evaluate ? node.evaluate(cache, situationGate, parsedRules, node) : node +export let evaluateNode = (cache, situationGate, parsedRules, node) => { + let evaluatedNode = node.evaluate + ? node.evaluate(cache, situationGate, parsedRules, node) + : node + if (typeof evaluatedNode.nodeValue !== 'number') { + return evaluatedNode + } + evaluatedNode = node.unité + ? convertNodeToUnit(node.unit, evaluatedNode) + : simplifyNodeUnit(evaluatedNode) + return evaluatedNode +} +const sameUnitValues = (explanation, contextRule, mecanismName) => { + const unit = explanation.map(n => n.unit).find(Boolean) + const values = explanation.map(node => { + try { + return convertNodeToUnit(unit, node).nodeValue + } catch (e) { + typeWarning( + contextRule, + `'${node.name}' a une unité incompatible avec celle du mécanisme ${mecanismName}`, + e + ) + return node.nodeValue + } + }) + return [unit, values] +} export let evaluateArray = (reducer, start) => ( cache, @@ -40,13 +68,23 @@ export let evaluateArray = (reducer, start) => ( let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), explanation = map(evaluateOne, node.explanation), - values = pluck('nodeValue', explanation), - nodeValue = any(equals(null), values) + [unit, values] = sameUnitValues( + explanation, + cache._meta.contextRule, + node.name + ), + nodeValue = values.some(value => value === null) ? null : reduce(reducer, start, values), missingVariables = node.nodeValue == null ? mergeAllMissing(explanation) : {} - return { ...node, nodeValue, explanation, missingVariables } + return { + ...node, + nodeValue, + explanation, + missingVariables, + unit + } } export let evaluateArrayWithFilter = (evaluationFilter, reducer, start) => ( @@ -61,14 +99,18 @@ export let evaluateArrayWithFilter = (evaluationFilter, reducer, start) => ( evaluateOne, filter(evaluationFilter(situationGate), node.explanation) ), - values = pluck('nodeValue', explanation), + [unit, values] = sameUnitValues( + explanation, + cache._meta.contextRule, + node.name + ), nodeValue = any(equals(null), values) ? null : reduce(reducer, start, values), missingVariables = node.nodeValue == null ? mergeAllMissing(explanation) : {} - return { ...node, nodeValue, explanation, missingVariables } + return { ...node, nodeValue, explanation, missingVariables, unit } } export let defaultNode = nodeValue => ({ @@ -102,34 +144,17 @@ export let evaluateObject = (objectShape, effect) => ( let transforms = map(k => [k, evaluateOne], keys(objectShape)), automaticExplanation = evolve(fromPairs(transforms))(node.explanation) // the result of effect can either be just a nodeValue, or an object {additionalExplanation, nodeValue}. The latter is useful for a richer JSX visualisation of the mecanism : the view should not duplicate code to recompute intermediate values (e.g. for a marginal 'barème', the marginal 'tranche') - let evaluated = effect(automaticExplanation), + let evaluated = effect(automaticExplanation, cache), explanation = is(Object, evaluated) ? { ...automaticExplanation, ...evaluated.additionalExplanation } : automaticExplanation, nodeValue = is(Object, evaluated) ? evaluated.nodeValue : evaluated, missingVariables = mergeAllMissing(values(explanation)) - return { ...node, nodeValue, explanation, missingVariables } -} - -export let E = (cache, situationGate, parsedRules) => { - let missingVariables = {} - - let valNode = element => - evaluateNode(cache, situationGate, parsedRules, element) - let val = element => { - let evaluated = valNode(element) - // automatically add missing variables when a variable is evaluated and thus needed in this mecanism's evaluation - missingVariables = mergeMissing( - missingVariables, - evaluated.missingVariables - ) - - return evaluated.nodeValue - } - - return { - val, - valNode, - missingVariables: () => missingVariables - } + return simplifyNodeUnit({ + ...node, + nodeValue, + explanation, + missingVariables, + unit: explanation.unit + }) } diff --git a/source/engine/format.test.js b/source/engine/format.test.js index a6db4380f..31a091de5 100644 --- a/source/engine/format.test.js +++ b/source/engine/format.test.js @@ -1,6 +1,6 @@ import { expect } from 'chai' -import { formatCurrency, formatPercentage, formatValue } from './format' import { parseUnit } from 'Engine/units' +import { formatCurrency, formatPercentage, formatValue } from './format' describe('format engine values', () => { it('format currencies', () => { @@ -12,9 +12,9 @@ describe('format engine values', () => { }) it('format percentages', () => { - expect(formatPercentage(0.1)).to.equal('10%') - expect(formatPercentage(1)).to.equal('100%') - expect(formatPercentage(0.102)).to.equal('10.2%') + expect(formatPercentage(10)).to.equal('10%') + expect(formatPercentage(100)).to.equal('100%') + expect(formatPercentage(10.2)).to.equal('10.2%') }) it('format values', () => { diff --git a/source/engine/format.ts b/source/engine/format.ts index 714642dd5..84e79f32d 100644 --- a/source/engine/format.ts +++ b/source/engine/format.ts @@ -87,7 +87,7 @@ export function formatValue({ style: 'percent', maximumFractionDigits, language - })(value) + })(value / 100) default: return ( numberFormatter({ diff --git a/source/engine/getInputComponent.js b/source/engine/getInputComponent.js index 3c8d388c6..2ca5cdd8b 100644 --- a/source/engine/getInputComponent.js +++ b/source/engine/getInputComponent.js @@ -33,7 +33,17 @@ export default rules => dottedName => { return if (rule.API) throw new Error("Le seul API implémenté est l'API géo") - if (rule.unit == null) + if (rule.suggestions == 'atmp-2017') + return ( + + ) + + if (rule.unit == null && rule.defaultUnit == null) return ( dottedName => { /> ) - if (rule.suggestions == 'atmp-2017') - return ( - - ) - // Now the numeric input case return ( ) diff --git a/source/engine/grammar.ne b/source/engine/grammar.ne index a548f1dd5..86cf964d1 100644 --- a/source/engine/grammar.ne +++ b/source/engine/grammar.ne @@ -6,21 +6,19 @@ # @preprocessor esmodule @{% -const {string, filteredVariable, date, variable, temporalVariable, binaryOperation, unaryOperation, boolean, number, numberWithUnit, percentage } = require('./grammarFunctions') +const {string, filteredVariable, date, variable, variableWithConversion, binaryOperation, unaryOperation, boolean, number, numberWithUnit } = require('./grammarFunctions') const moo = require("moo"); const dateRegexp = /(?:(?:0?[1-9]|[12][0-9]|3[01])\/)?(?:0?[1-9]|1[012])\/\d{4}/ -const letter = '[a-zA-Z\u00C0-\u017F]'; +const letter = '[a-zA-Z\u00C0-\u017F€$%]'; const letterOrNumber = '[a-zA-Z\u00C0-\u017F0-9\']'; const word = `${letter}(?:[\-']?${letterOrNumber}+)*`; const wordOrNumber = `(?:${word}|${letterOrNumber}+)` const words = `${word}(?:[\\s]?${wordOrNumber}+)*` const numberRegExp = '-?(?:[1-9][0-9]+|[0-9])(?:\\.[0-9]+)?'; -const percentageRegExp = numberRegExp + '\\%' const lexer = moo.compile({ date: dateRegexp, - percentage: new RegExp(percentageRegExp), '(': '(', ')': ')', '[': '[', @@ -31,8 +29,8 @@ const lexer = moo.compile({ string: /'[ \t\.'a-zA-Z\-\u00C0-\u017F0-9 ]+'/, additionSubstraction: /[\+-]/, multiplicationDivision: ['*','/'], - '€': '€', dot: ' . ', + '.': '.', letterOrNumber: new RegExp(letterOrNumber), space: { match: /[\s]+/, lineBreaks: true } }); @@ -52,7 +50,7 @@ main -> NumericTerminal -> Variable {% id %} - | TemporalVariable {% id %} + | VariableWithUnitConversion {% id %} | FilteredVariable {% id %} | number {% id %} @@ -80,18 +78,19 @@ NonNumericTerminal -> Variable -> %words (%dot %words {% ([,words]) => words %}):* {% variable %} -BaseUnit -> - %words {% id %} - | "€" {% id %} +UnitDenominator -> + (%space):? "/" %words {% join %} +UnitNumerator -> %words ("." %words):? {% flattenJoin %} -Unit -> BaseUnit ("/" BaseUnit {% join %}):? {% join %} +Unit -> UnitNumerator:? UnitDenominator:* {% flattenJoin %} +UnitConversion -> "[" Unit "]" {% ([,unit]) => unit %} +VariableWithUnitConversion -> + Variable %space UnitConversion {% variableWithConversion %} + # | FilteredVariable %space UnitConversion {% variableWithConversion %} TODO -Filter -> "[" %words "]" {% ([,filter]) => filter %} +Filter -> "." %words {% ([,filter]) => filter %} FilteredVariable -> Variable %space Filter {% filteredVariable %} -TemporalTransform -> "[" ("mensuel" | "annuel" {% id %}) "]" {% ([,temporality]) => temporality %} -TemporalVariable -> Variable %space TemporalTransform {% temporalVariable %} - AdditionSubstraction -> AdditionSubstraction %space %additionSubstraction %space MultiplicationDivision {% binaryOperation('calculation') %} | MultiplicationDivision {% id %} @@ -107,7 +106,6 @@ boolean -> number -> %number {% number %} - | %number %space Unit {% numberWithUnit %} - | %percentage {% percentage %} + | %number (%space):? Unit {% numberWithUnit %} string -> %string {% string %} \ No newline at end of file diff --git a/source/engine/grammarFunctions.js b/source/engine/grammarFunctions.js index 30c03a3cc..33df33729 100644 --- a/source/engine/grammarFunctions.js +++ b/source/engine/grammarFunctions.js @@ -16,17 +16,12 @@ export let unaryOperation = operationType => ([operator, , A]) => ({ } }) -export let filteredVariable = ( - [{ variable }, , { value: filter }], - l, - reject -) => - ['mensuel', 'annuel'].includes(filter) - ? reject - : { filter: { filter, explanation: variable } } +export let filteredVariable = ([{ variable }, , { value: filter }]) => ({ + filter: { filter, explanation: variable } +}) -export let temporalVariable = ([{ variable }, , temporalTransform]) => ({ - temporalTransform: { explanation: variable, temporalTransform } +export let variableWithConversion = ([{ variable }, , unit]) => ({ + unitConversion: { explanation: variable, unit: parseUnit(unit.value) } }) export let variable = ([firstFragment, nextFragment], _, reject) => { @@ -54,15 +49,7 @@ export let numberWithUnit = ([number, , unit]) => ({ } }) -export let percentage = ([{ value }]) => ({ - constant: { - type: 'percentage', - unit: parseUnit('%'), - nodeValue: parseFloat(value.slice(0, -1)) / 100 - } -}) - -export let date = ([{ value }], ...otherstuf) => { +export let date = ([{ value }]) => { let [jour, mois, année] = value.split('/') if (!année) { ;[jour, mois, année] = ['01', jour, mois] diff --git a/source/engine/index.js b/source/engine/index.js index 7d2e4282c..a3168d45b 100644 --- a/source/engine/index.js +++ b/source/engine/index.js @@ -21,8 +21,28 @@ let enrichRules = input => { return rulesList.map(enrichRule) } +class Engine { + situation = {} + parsedRules + constructor(rules = rulesFr) { + this.parsedRules = parseAll(rules) + this.defaultValues = collectDefaults(rules) + } + evaluate(targets, { defaultUnits, situation }) { + this.evaluation = analyseMany( + this.parsedRules, + targets, + defaultUnits + )(dottedName => situation[dottedName] || this.defaultValues[dottedName]) + return this.evaluation.targets.map(({ nodeValue }) => nodeValue) + } + getLastEvaluationExplanations() { + return this.evaluation + } +} + export default { - evaluate: (targetInput, input, config) => { + evaluate: (targetInput, input, config, defaultUnits = []) => { let rules = config ? [ ...(config.base ? enrichRules(config.base) : rulesFr), @@ -32,12 +52,14 @@ export default { let evaluation = analyseMany( parseAll(rules), - Array.isArray(targetInput) ? targetInput : [targetInput] + Array.isArray(targetInput) ? targetInput : [targetInput], + defaultUnits )(inputToStateSelector(rules)(input)) if (config?.debug) return evaluation let values = evaluation.targets.map(t => t.nodeValue) return Array.isArray(targetInput) ? values : values[0] - } + }, + Engine } diff --git a/source/engine/known-mecanisms.yaml b/source/engine/known-mecanisms.yaml index 5a2bb84d4..d6c0dffd8 100644 --- a/source/engine/known-mecanisms.yaml +++ b/source/engine/known-mecanisms.yaml @@ -30,25 +30,21 @@ toutes ces conditions: Renvoie vrai si toutes les conditions vraies. -aiguillage numérique: +variations: type: numeric description: | Contient une liste de couples condition-conséquence. Couple par couple, si la condition est vraie, alors on choisit la conséquence. - Cette conséquence peut elle-même être un mécanisme `aiguillage numérique` ou plus simplement un `taux`. + Cette conséquence peut elle-même être un mécanisme `variations` ou plus simplement un `taux`. Si aucune condition n'est vraie, alors ce mécanisme renvoie implicitement `non applicable` (ce qui peut se traduire par la valeur `0` si nous sommes dans un contexte numérique). -variations: - type: numeric - description: | - Contient une liste de couples condition-conséquence, sous une forme plus explicite que l'aiguillage numérique : ``` si: condition - alors: valeur + alors: consequence ``` Ce mécanisme peut aussi être utilisé au sein d'un mécanisme compatible, tel que la multiplication ou le barème. Par exemple, certains paramètres de la multiplication seront communs (ex. l'assiette) alors que d'autres (ex. le taux) variront selon une autre variable (ex. statut cadre). @@ -180,11 +176,3 @@ synchronisation: description: | Pour éviter trop de saisies à l'utilisateur, certaines informations sont récupérées à partir de ce que l'on appelle des API. Ce sont des services auxquels ont fait appel pour obtenir des informations sur un sujet précis. Par exemple, l'État français fournit gratuitement l'API géo, qui permet à partir du nom d'une ville, d'obtenir son code postal, son département, la population etc. Ce mécanismes `synchronisation` permet de faire le lien entre les règles de notre système et les réponses de ces API. - -période: - description: | - Une régle qui a une période `mois` ou `année`, c'est une règle qui ne peut être calculée que sur cette période. La période est `flexible` quand le calcul est valable quelle que soit la période choisie. D'autres règles ne changent pas de valeur en fonction de la période. - - Par exemple, dans une simulation mensuelle, si `indemnité kilométrique vélo` (de période flexible) appelle la règle `distance annuelle`, qui est définie sur l'année, alors la valeur de cette dernière sera divisée par 12 avant d'être passée à cette première. L'inverse est également vrai, en multipliant par 12. - - Par défaut, la période de la simulation est mensuelle. diff --git a/source/engine/mecanismViews/Allègement.js b/source/engine/mecanismViews/Allègement.js index f23467a99..0cad40835 100644 --- a/source/engine/mecanismViews/Allègement.js +++ b/source/engine/mecanismViews/Allègement.js @@ -3,23 +3,25 @@ import React from 'react' import { makeJsx } from '../evaluation' import { Node } from './common' -export default function Allègement(nodeValue, rawExplanation) { +export default function Allègement(nodeValue, rawExplanation, ...other) { // properties with a nodeValue of 0 are not interesting to display let explanation = map( k => (k && k.nodeValue !== 0 ? k : null), rawExplanation ) + console.log(other) return (
  • assiette: - {makeJsx(rawExplanation.assiette)} + {makeJsx(explanation.assiette)}
  • {explanation.franchise && (
  • @@ -41,6 +43,12 @@ export default function Allègement(nodeValue, rawExplanation) { {makeJsx(explanation.abattement)}
  • )} + {explanation.plafond && ( +
  • + plafond: + {makeJsx(explanation.plafond)} +
  • + )} } /> diff --git a/source/engine/mecanismViews/Barème.js b/source/engine/mecanismViews/Barème.js index 9771e1d1b..9ca495756 100644 --- a/source/engine/mecanismViews/Barème.js +++ b/source/engine/mecanismViews/Barème.js @@ -119,7 +119,10 @@ let Component = function Barème({ Taux moyen :{' '} @@ -142,6 +145,7 @@ let Tranche = ({ de: min, à: max, taux, + nodeValue, montant }, multiplicateur, @@ -186,7 +190,7 @@ let Tranche = ({ {taux != null ? makeJsx(taux) : montant} {showValues && !returnRate && taux != null && ( - + )} @@ -212,7 +216,8 @@ function TrancheFormatter({ {value}  + title={multiplicateur.explanation.name} + > {multiplicateurAcronym} {' '} setFolded(!folded)}> + onClick={() => setFolded(!folded)} + >
    {makeJsx(v)} {isSomme && ( @@ -46,13 +47,13 @@ function Row({ v, i, unit }) { )}
    - +
    , ...(isSomme && !folded ? [
    - +
    ] : []) diff --git a/source/engine/mecanisms.js b/source/engine/mecanisms.js index 3adf8e4cd..d6ea62303 100644 --- a/source/engine/mecanisms.js +++ b/source/engine/mecanisms.js @@ -1,26 +1,19 @@ import { decompose } from 'Engine/mecanisms/utils' import variations from 'Engine/mecanisms/variations' -import { inferUnit } from 'Engine/units' +import { convertNodeToUnit } from 'Engine/nodeUnits' +import { inferUnit, isPercentUnit } from 'Engine/units' import { add, any, - curry, equals, evolve, - filter, - find, - head, is, - isEmpty, - keys, map, max, mergeWith, min, path, - pipe, pluck, - prop, reduce, subtract, toPairs @@ -28,8 +21,8 @@ import { import React from 'react' import { Trans } from 'react-i18next' import 'react-virtualized/styles.css' +import { typeWarning } from './error' import { - bonus, collectNodeMissing, defaultNode, evaluateArray, @@ -37,7 +30,6 @@ import { evaluateObject, makeJsx, mergeAllMissing, - mergeMissing, parseObject } from './evaluation' import Allègement from './mecanismViews/Allègement' @@ -48,6 +40,7 @@ import Somme from './mecanismViews/Somme' import { disambiguateRuleReference, findRuleByDottedName } from './rules' import { anyNull, val } from './traverse-common-functions' import uniroot from './uniroot' +import { parseUnit } from './units' export let mecanismOneOf = (recurse, k, v) => { if (!is(Array, v)) throw new Error('should be array') @@ -145,117 +138,6 @@ export let mecanismAllOf = (recurse, k, v) => { } } -export let mecanismNumericalSwitch = (recurse, k, v) => { - // Si "l'aiguillage" est une constante ou une référence directe à une variable; - // l'utilité de ce cas correspond à un appel récursif au mécanisme - if (is(String, v)) return recurse(v) - - if (!is(Object, v) || keys(v).length == 0) { - throw new Error( - 'Le mécanisme "aiguillage numérique" et ses sous-logiques doivent contenir au moins une proposition' - ) - } - - // les termes sont les couples (condition, conséquence) de l'aiguillage numérique - let terms = toPairs(v) - - // la conséquence peut être un 'string' ou un autre aiguillage numérique - let parseCondition = ([condition, consequence]) => { - let conditionNode = recurse(condition), // can be a 'comparison', a 'variable' - consequenceNode = mecanismNumericalSwitch(recurse, condition, consequence) - - let evaluate = (cache, situationGate, parsedRules, node) => { - let explanation = evolve( - { - condition: curry(evaluateNode)(cache, situationGate, parsedRules), - consequence: curry(evaluateNode)(cache, situationGate, parsedRules) - }, - node.explanation - ), - leftMissing = explanation.condition.missingVariables, - investigate = explanation.condition.nodeValue !== false, - rightMissing = investigate - ? explanation.consequence.missingVariables - : {}, - missingVariables = mergeMissing(bonus(leftMissing), rightMissing) - - return { - ...node, - explanation, - missingVariables, - nodeValue: explanation.consequence.nodeValue, - condValue: explanation.condition.nodeValue - } - } - - let jsx = (nodeValue, { condition, consequence }) => ( -
    - {makeJsx(condition)} -
    {makeJsx(consequence)}
    -
    - ) - - return { - evaluate, - jsx, - explanation: { condition: conditionNode, consequence: consequenceNode }, - category: 'condition', - text: condition, - condition: conditionNode, - type: 'boolean' - } - } - - let evaluateTerms = (cache, situationGate, parsedRules, node) => { - let evaluateOne = child => - evaluateNode(cache, situationGate, parsedRules, child), - explanation = map(evaluateOne, node.explanation), - nonFalsyTerms = filter(node => node.condValue !== false, explanation), - getFirst = o => pipe(head, prop(o))(nonFalsyTerms), - nodeValue = - // voilà le "numérique" dans le nom de ce mécanisme : il renvoie zéro si aucune condition n'est vérifiée - isEmpty(nonFalsyTerms) - ? 0 - : // c'est un 'null', on renvoie null car des variables sont manquantes - getFirst('condValue') == null - ? null - : // c'est un true, on renvoie la valeur de la conséquence - getFirst('nodeValue'), - choice = find(node => node.condValue, explanation), - missingVariables = choice - ? choice.missingVariables - : mergeAllMissing(explanation) - - return { ...node, nodeValue, explanation, missingVariables } - } - - let explanation = map(parseCondition, terms) - - let jsx = (nodeValue, explanation) => ( - - {explanation.map(item => ( -
  • {makeJsx(item)}
  • - ))} - - } - /> - ) - - return { - evaluate: evaluateTerms, - jsx, - explanation, - category: 'mecanism', - name: 'aiguillage numérique', - type: 'boolean || numeric' // lol ! - } -} - export let findInversion = (situationGate, parsedRules, v, dottedName) => { let inversions = v.avec if (!inversions) @@ -304,7 +186,13 @@ let doInversion = (oldCache, situationGate, parsedRules, v, dottedName) => { let inversionCache = {} let fx = x => { - inversionCache = { parseLevel: oldCache.parseLevel + 1, op: '<' } + inversionCache = { + _meta: { + ...oldCache._meta, + parseLevel: oldCache._meta.parseLevel + 1, + op: '<' + } + } let v = evaluateNode( inversionCache, // with an empty cache n => @@ -365,7 +253,7 @@ export let mecanismInversion = dottedName => (recurse, k, v) => { missingVariables = inversion.missingVariables if (nodeValue === undefined) - cache.inversionFail = { + cache._meta.inversionFail = { given: inversion.inversedWith.rule.dottedName, estimated: dottedName } @@ -425,11 +313,31 @@ export let mecanismReduction = (recurse, k, v) => { franchise: defaultNode(0) } - let effect = ({ assiette, abattement, plafond, franchise, décote }) => { + let effect = ( + { assiette, abattement, plafond, franchise, décote }, + cache + ) => { let v_assiette = val(assiette) - if (v_assiette == null) return null - + if (assiette.unit) { + try { + franchise = convertNodeToUnit(assiette.unit, franchise) + plafond = convertNodeToUnit(assiette.unit, plafond) + if (!isPercentUnit(abattement.unit)) { + abattement = convertNodeToUnit(assiette.unit, abattement) + } + if (décote) { + décote.plafond = convertNodeToUnit(assiette.unit, décote.plafond) + décote.taux = convertNodeToUnit(parseUnit(''), décote.taux) + } + } catch (e) { + typeWarning( + cache._meta.contextRule, + "Impossible de convertir les unités de l'allègement entre elles", + e + ) + } + } let montantFranchiséDécoté = val(franchise) && v_assiette < val(franchise) ? 0 @@ -443,13 +351,12 @@ export let mecanismReduction = (recurse, k, v) => { : max(0, (1 + taux) * v_assiette - taux * plafondDécote) })() : v_assiette - - return abattement + const nodeValue = abattement ? val(abattement) == null ? montantFranchiséDécoté === 0 ? 0 : null - : abattement.type === 'percentage' + : isPercentUnit(abattement.unit) ? max( 0, montantFranchiséDécoté - @@ -457,6 +364,15 @@ export let mecanismReduction = (recurse, k, v) => { ) : max(0, montantFranchiséDécoté - min(val(plafond), val(abattement))) : montantFranchiséDécoté + return { + nodeValue, + additionalExplanation: { + unit: assiette.unit, + franchise, + plafond, + abattement + } + } } let base = parseObject(recurse, objectShape, v), @@ -494,20 +410,39 @@ export let mecanismProduct = (recurse, k, v) => { facteur: defaultNode(1), plafond: defaultNode(Infinity) } - let effect = ({ assiette, taux, facteur, plafond }) => { + let effect = ({ assiette, taux, facteur, plafond }, cache) => { + if (assiette.unit) { + try { + plafond = convertNodeToUnit(assiette.unit, plafond) + } catch (e) { + typeWarning( + cache._meta.contextRule, + "Impossible de convertir l'unité du plafond de la multiplication dans celle de l'assiette", + e + ) + } + } let mult = (base, rate, facteur, plafond) => Math.min(base, plafond) * rate * facteur + const unit = inferUnit( + '*', + [assiette, taux, facteur].map(el => el.unit) + ) + const nodeValue = + val(taux) === 0 || + val(taux) === false || + val(assiette) === 0 || + val(facteur) === 0 + ? 0 + : anyNull([taux, assiette, facteur, plafond]) + ? null + : mult(val(assiette), val(taux), val(facteur), val(plafond)) return { - nodeValue: - val(taux) === 0 || - val(taux) === false || - val(assiette) === 0 || - val(facteur) === 0 - ? 0 - : anyNull([taux, assiette, facteur, plafond]) - ? null - : mult(val(assiette), val(taux), val(facteur), val(plafond)), - additionalExplanation: { plafondActif: val(assiette) > val(plafond) } + nodeValue, + additionalExplanation: { + plafondActif: val(assiette) > val(plafond), + unit + } } } diff --git a/source/engine/mecanisms/barème-continu.js b/source/engine/mecanisms/barème-continu.js index 4f9546d2a..702a6a7dd 100644 --- a/source/engine/mecanisms/barème-continu.js +++ b/source/engine/mecanisms/barème-continu.js @@ -1,9 +1,8 @@ -import { defaultNode, evaluateObject } from 'Engine/evaluation' +import { defaultNode, evaluateObject, parseObject } from 'Engine/evaluation' import BarèmeContinu from 'Engine/mecanismViews/BarèmeContinu' -import { val, anyNull } from 'Engine/traverse-common-functions' +import { anyNull, val } from 'Engine/traverse-common-functions' import { parseUnit } from 'Engine/units' -import { parseObject } from 'Engine/evaluation' -import { reduce, toPairs, sort, aperture, pipe, reduced, last } from 'ramda' +import { aperture, last, pipe, reduce, reduced, sort, toPairs } from 'ramda' export default (recurse, k, v) => { let objectShape = { @@ -24,8 +23,8 @@ export default (recurse, k, v) => { reduce((_, [[lowerLimit, lowerRate], [upperLimit, upperRate]]) => { let x1 = val(multiplicateur) * lowerLimit, x2 = val(multiplicateur) * upperLimit, - y1 = val(assiette) * val(recurse(lowerRate)), - y2 = val(assiette) * val(recurse(upperRate)) + y1 = (val(assiette) * val(recurse(lowerRate))) / 100, + y2 = (val(assiette) * val(recurse(upperRate))) / 100 if (val(assiette) > x1 && val(assiette) <= x2) { // Outside of these 2 limits, it's a linear function a * x + b let a = (y2 - y1) / (x2 - x1), @@ -33,16 +32,16 @@ export default (recurse, k, v) => { nodeValue = a * val(assiette) + b, taux = nodeValue / val(assiette) return reduced({ - nodeValue: returnRate ? taux : nodeValue, + nodeValue: returnRate ? taux * 100 : nodeValue, additionalExplanation: { seuil: val(assiette) / val(multiplicateur), - taux + taux, + unit: returnRate ? parseUnit('%') : assiette.unit } }) } }, 0) )(points) - return result } let explanation = { diff --git a/source/engine/mecanisms/barème-linéaire.js b/source/engine/mecanisms/barème-linéaire.js index cb2366999..d872a93ec 100644 --- a/source/engine/mecanisms/barème-linéaire.js +++ b/source/engine/mecanisms/barème-linéaire.js @@ -3,8 +3,8 @@ import { decompose } from 'Engine/mecanisms/utils' import variations from 'Engine/mecanisms/variations' import Barème from 'Engine/mecanismViews/Barème' import { val } from 'Engine/traverse-common-functions' -import { desugarScale } from './barème' import { parseUnit } from 'Engine/units' +import { desugarScale } from './barème' /* on réécrit en une syntaxe plus bas niveau mais plus régulière les tranches : `en-dessous de: 1` @@ -41,13 +41,24 @@ export default (recurse, k, v) => { roundedAssiette >= val(multiplicateur) * min && roundedAssiette <= max * val(multiplicateur) ) - - if (!matchedTranche) return 0 - if (matchedTranche.taux) - return returnRate + let nodeValue + if (!matchedTranche) { + nodeValue = 0 + } else if (matchedTranche.taux) { + nodeValue = returnRate ? matchedTranche.taux.nodeValue - : matchedTranche.taux.nodeValue * val(assiette) - return matchedTranche.montant + : (matchedTranche.taux.nodeValue / 100) * val(assiette) + } else { + nodeValue = matchedTranche.montant.nodeValue + } + return { + nodeValue, + additionalExplanation: { + unit: returnRate + ? parseUnit('%') + : (v['unité'] && parseUnit(v['unité'])) || explanation.assiette.unit + } + } } let explanation = { @@ -55,8 +66,10 @@ export default (recurse, k, v) => { returnRate, tranches }, - evaluate = evaluateObject(objectShape, effect) - + evaluate = evaluateObject(objectShape, effect), + unit = returnRate + ? parseUnit('%') + : (v['unité'] && parseUnit(v['unité'])) || explanation.assiette.unit return { evaluate, jsx: Barème('linéaire'), @@ -65,6 +78,6 @@ export default (recurse, k, v) => { name: 'barème linéaire', barème: 'en taux', type: 'numeric', - unit: returnRate ? parseUnit('%') : v['unité'] || explanation.assiette.unit + unit } } diff --git a/source/engine/mecanisms/barème.js b/source/engine/mecanisms/barème.js index 226e6faad..db1a86f32 100644 --- a/source/engine/mecanisms/barème.js +++ b/source/engine/mecanisms/barème.js @@ -1,10 +1,11 @@ -import { defaultNode, E } from 'Engine/evaluation' +import { defaultNode, evaluateNode, mergeAllMissing } from 'Engine/evaluation' import { decompose } from 'Engine/mecanisms/utils' import variations from 'Engine/mecanisms/variations' import Barème from 'Engine/mecanismViews/Barème' -import { val } from 'Engine/traverse-common-functions' -import { inferUnit, parseUnit } from 'Engine/units' -import { evolve, has, pluck, sum } from 'ramda' +import { evolve, has } from 'ramda' +import { typeWarning } from '../error' +import { convertNodeToUnit } from '../nodeUnits' +import { parseUnit } from '../units' export let desugarScale = recurse => tranches => tranches @@ -15,7 +16,7 @@ export let desugarScale = recurse => tranches => ? { ...t, de: t['au-dessus de'], à: Infinity } : t ) - .map(evolve({ taux: recurse })) + .map(evolve({ taux: recurse, montant: recurse })) // This function was also used for marginal barèmes, but now only for linear ones export let trancheValue = (assiette, multiplicateur) => ({ @@ -24,10 +25,10 @@ export let trancheValue = (assiette, multiplicateur) => ({ taux, montant }) => - Math.round(val(assiette)) >= min * val(multiplicateur) && - (!max || Math.round(val(assiette)) <= max * val(multiplicateur)) + Math.round(assiette.nodeValue) >= min * multiplicateur.nodeValue && + (!max || Math.round(assiette.nodeValue) <= max * multiplicateur.nodeValue) ? taux != null - ? val(assiette) * val(taux) + ? assiette.nodeValue * taux.nodeValue : montant : 0 @@ -52,22 +53,66 @@ export default (recurse, k, v) => { } let evaluate = (cache, situationGate, parsedRules, node) => { - let e = E(cache, situationGate, parsedRules) - - let { assiette, multiplicateur } = node.explanation, - tranches = node.explanation.tranches.map(tranche => { - let { de: min, à: max, taux } = tranche - let value = - e.val(assiette) < min * e.val(multiplicateur) - ? 0 - : (Math.min(e.val(assiette), max * e.val(multiplicateur)) - - min * e.val(multiplicateur)) * - e.val(taux) - - return { ...tranche, value } - }), - nodeValue = sum(pluck('value', tranches)) + let { assiette, multiplicateur } = node.explanation + assiette = evaluateNode(cache, situationGate, parsedRules, assiette) + multiplicateur = evaluateNode( + cache, + situationGate, + parsedRules, + multiplicateur + ) + try { + multiplicateur = convertNodeToUnit(assiette.unit, multiplicateur) + } catch (e) { + typeWarning( + cache._meta.contextRule, + `L'unité du multiplicateur du barème doit être compatible avec celle de son assiette`, + e + ) + } + const tranches = node.explanation.tranches.map(tranche => { + let { de: min, à: max, taux } = tranche + if ( + [assiette, multiplicateur].every( + ({ nodeValue }) => nodeValue != null + ) && + assiette.nodeValue < min * multiplicateur.nodeValue + ) { + return { ...tranche, nodeValue: 0 } + } + taux = convertNodeToUnit( + parseUnit(''), + evaluateNode(cache, situationGate, parsedRules, taux) + ) + if ( + [assiette, multiplicateur, taux].some( + ({ nodeValue }) => nodeValue == null + ) + ) { + return { + ...tranche, + nodeValue: null, + missingVariables: taux.missingVariables + } + } + return { + ...tranche, + nodeValue: + (Math.min(assiette.nodeValue, max * multiplicateur.nodeValue) - + min * multiplicateur.nodeValue) * + taux.nodeValue + } + }) + const nodeValue = tranches.reduce( + (value, { nodeValue }) => (nodeValue == null ? null : value + nodeValue), + 0 + ) + const missingVariables = mergeAllMissing([ + assiette, + multiplicateur, + ...tranches + ]) return { ...node, nodeValue, @@ -75,8 +120,9 @@ export default (recurse, k, v) => { ...explanation, tranches }, - missingVariables: e.missingVariables(), - lazyEval: e.valNode + missingVariables, + unit: assiette.unit, + lazyEval: node => evaluateNode(cache, situationGate, parsedRules, node) } } @@ -87,6 +133,6 @@ export default (recurse, k, v) => { category: 'mecanism', name: 'barème', barème: 'marginal', - unit: inferUnit('*', [explanation.assiette.unit, parseUnit('%')]) + unit: explanation.assiette.unit } } diff --git a/source/engine/mecanisms/encadrement.tsx b/source/engine/mecanisms/encadrement.tsx index a8de29425..341749a0c 100644 --- a/source/engine/mecanisms/encadrement.tsx +++ b/source/engine/mecanisms/encadrement.tsx @@ -1,3 +1,4 @@ +import { typeWarning } from 'Engine/error' import { defaultNode, evaluateNode, @@ -5,49 +6,43 @@ import { parseObject } from 'Engine/evaluation' import { Node } from 'Engine/mecanismViews/common' +import { convertNodeToUnit } from 'Engine/nodeUnits' import React from 'react' import { val } from '../traverse-common-functions' -function MecanismEncadrement({ nodeValue, explanation }) { +function MecanismEncadrement({ nodeValue, explanation, unit }) { return ( {makeJsx(explanation.valeur)} -
      +

      {!explanation.plancher.isDefault && ( -

    • - - Minimum : - {makeJsx(explanation.plancher)} - -
    • + + Minimum : + {makeJsx(explanation.plancher)} + )} {!explanation.plafond.isDefault && ( -
    • - - Plafonné à : - {makeJsx(explanation.plafond)} - -
    • + + Plafonné à : + {makeJsx(explanation.plafond)} + )} -
    +

    } /> @@ -61,26 +56,33 @@ const objectShape = { } const evaluate = (cache, situation, parsedRules, node) => { - const valeur = evaluateNode( - cache, - situation, - parsedRules, - node.explanation.valeur - ) - const plafond = evaluateNode( - cache, - situation, - parsedRules, - node.explanation.plafond - ) - const plancher = evaluateNode( - cache, - situation, - parsedRules, - node.explanation.plancher - ) + let evaluateAttribute = evaluateNode.bind(null, cache, situation, parsedRules) + const valeur = evaluateAttribute(node.explanation.valeur) + let plafond = evaluateAttribute(node.explanation.plafond) + let plancher = evaluateAttribute(node.explanation.plancher) + if (valeur.unit) { + try { + plafond = convertNodeToUnit(valeur.unit, plafond) + plancher = convertNodeToUnit(valeur.unit, plancher) + } catch (e) { + typeWarning( + cache._meta.contextRule, + "Le plafond / plancher de l'encadrement a une unité incompatible avec celle de la valeur à encadrer", + e + ) + } + } const nodeValue = Math.max(val(plancher), Math.min(val(plafond), val(valeur))) - return { ...node, nodeValue } + return { + ...node, + nodeValue, + unit: valeur.unit, + explanation: { + valeur, + plafond, + plancher + } + } } export default (recurse, k, v) => { @@ -89,8 +91,12 @@ export default (recurse, k, v) => { return { evaluate, // eslint-disable-next-line - jsx: (nodeValue, explanation) => ( - + jsx: (nodeValue, explanation, _, unit) => ( + ), explanation, category: 'mecanism', diff --git a/source/engine/mecanisms/operation.js b/source/engine/mecanisms/operation.js index 5d662f2e0..da5586960 100644 --- a/source/engine/mecanisms/operation.js +++ b/source/engine/mecanisms/operation.js @@ -1,6 +1,8 @@ +import { typeWarning } from 'Engine/error' import { evaluateNode, makeJsx, mergeMissing } from 'Engine/evaluation' import { Node } from 'Engine/mecanismViews/common' -import { inferUnit } from 'Engine/units' +import { convertNodeToUnit } from 'Engine/nodeUnits' +import { inferUnit, serialiseUnit } from 'Engine/units' import { curry, map } from 'ramda' import React from 'react' import { convertToDateIfNeeded } from '../date.ts' @@ -11,31 +13,63 @@ export default (k, operatorFunction, symbol) => (recurse, k, v) => { curry(evaluateNode)(cache, situation, parsedRules), node.explanation ) + let [node1, node2] = explanation const missingVariables = mergeMissing( - explanation[0].missingVariables, - explanation[1].missingVariables + node1.missingVariables, + node2.missingVariables ) - const value1 = explanation[0].nodeValue - const value2 = explanation[1].nodeValue - if (value1 == null || value2 == null) { + if (node1.nodeValue == null || node2.nodeValue == null) { return { ...node, nodeValue: null, explanation, missingVariables } } - let nodeValue = operatorFunction(...convertToDateIfNeeded(value1, value2)) - + if (!['∕', '×'].includes(node.operator)) { + try { + if (node1.unit) { + node2 = convertNodeToUnit(node1.unit, node2) + } else if (node2.unit) { + node1 = convertNodeToUnit(node2.unit, node1) + } + } catch (e) { + typeWarning( + cache._meta.contextRule, + `Dans l'expression '${ + node.operator + }', la partie gauche (unité: ${serialiseUnit( + node1.unit + )}) n'est pas compatible avec la partie droite (unité: ${serialiseUnit( + node2.unit + )})`, + e + ) + } + } + let nodeValue = operatorFunction( + ...convertToDateIfNeeded(node1.nodeValue, node2.nodeValue) + ) + let unit = inferUnit(k, [node1.unit, node2.unit]) + // if (node1.name === 'revenu professionnel') { + // console.log( + // node1.name, + // node2.name, + // serialiseUnit(node1.unit), + // serialiseUnit(node2.unit), + // serialiseUnit(unit) + // ) + // } return { ...node, nodeValue, + unit, explanation, missingVariables } } let explanation = v.explanation.map(recurse) + let [node1, node2] = explanation + let unit = inferUnit(k, [node1.unit, node2.unit]) - let unit = inferUnit(k, [explanation[0].unit, explanation[1].unit]) - - let jsx = (nodeValue, explanation) => ( + let jsx = (nodeValue, explanation, _, unit) => ( { return { ...node, nodeValue, + ...(satisfiedVariation && { unit: satisfiedVariation?.consequence.unit }), explanation: resolvedExplanation, missingVariables } @@ -93,7 +94,10 @@ export default (recurse, k, v, devariate) => { category: 'mecanism', name: 'variations', type: 'numeric', - unit: inferUnit('+', explanation.map(r => r.consequence.unit)) + unit: inferUnit( + '+', + explanation.map(r => r.consequence.unit) + ) } } diff --git a/source/engine/nodeUnits.ts b/source/engine/nodeUnits.ts new file mode 100644 index 000000000..f39641f91 --- /dev/null +++ b/source/engine/nodeUnits.ts @@ -0,0 +1,46 @@ +import { + areUnitConvertible, + convertUnit, + simplifyUnitWithValue, + Unit +} from './units' + +export function simplifyNodeUnit(node) { + if (!node.unit || !node.nodeValue) { + return node + } + const [unit, nodeValue] = simplifyUnitWithValue(node.unit, node.nodeValue) + return { + ...node, + unit, + nodeValue + } +} +export const getNodeDefaultUnit = (node, cache) => { + if ( + node.question && + node.unit == null && + node.defaultUnit == null && + !node.formule?.unit == null + ) { + return false + } + + return ( + node.unit || + cache._meta.defaultUnits.find(unit => + areUnitConvertible(node.defaultUnit, unit) + ) || + node.defaultUnit + ) +} + +export function convertNodeToUnit(to: Unit, node) { + return { + ...node, + nodeValue: node.unit + ? convertUnit(node.unit, to, node.nodeValue) + : node.nodeValue, + unit: to + } +} diff --git a/source/engine/parse.js b/source/engine/parse.js index 7dd01a5f7..b3aebc166 100644 --- a/source/engine/parse.js +++ b/source/engine/parse.js @@ -29,6 +29,7 @@ import { without } from 'ramda' import React from 'react' +import { syntaxError } from './error.ts' import grammar from './grammar.ne' import { mecanismAllOf, @@ -37,7 +38,6 @@ import { mecanismInversion, mecanismMax, mecanismMin, - mecanismNumericalSwitch, mecanismOneOf, mecanismOnePossibility, mecanismProduct, @@ -73,18 +73,11 @@ export let parseString = (rules, rule, parsedRules) => rawNode => { let [parseResult] = new Parser(compiledGrammar).feed(rawNode).results return parseObject(rules, rule, parsedRules)(parseResult) } catch (e) { - throw new Error(` - -Erreur syntaxique -================= - -Dans la règle \`${rule.dottedName}\`, -\`${rawNode}\` n'est pas une formule valide - ------------------ - -${e.message} - `) + syntaxError( + rule.dottedName, + `\`${rawNode}\` n'est pas une formule valide`, + e + ) } } @@ -144,13 +137,12 @@ export let parseObject = (rules, rule, parsedRules) => rawNode => { let dispatch = { 'une de ces conditions': mecanismOneOf, 'toutes ces conditions': mecanismAllOf, - 'aiguillage numérique': mecanismNumericalSwitch, somme: mecanismSum, multiplication: mecanismProduct, barème, 'barème linéaire': barèmeLinéaire, 'barème continu': barèmeContinu, - encadrement: encadrement, + encadrement, 'le maximum de': mecanismMax, 'le minimum de': mecanismMin, complément: mecanismComplement, @@ -171,14 +163,14 @@ export let parseObject = (rules, rule, parsedRules) => rawNode => { }), variable: () => parseReferenceTransforms(rules, rule, parsedRules)({ variable: v }), - temporalTransform: () => + unitConversion: () => parseReferenceTransforms( rules, rule, parsedRules )({ variable: v.explanation, - temporalTransform: v.temporalTransform + unit: v.unit }), constant: () => ({ type: v.type, @@ -190,6 +182,8 @@ export let parseObject = (rules, rule, parsedRules) => rawNode => { {formatValue({ unit: v.unit, value: v.nodeValue, + // TODO : handle localization here + language: 'fr', // We want to display constants with full precision, // espacilly for percentages like APEC 0,036 % maximumFractionDigits: 5 diff --git a/source/engine/parseReference.js b/source/engine/parseReference.js index 3b9cb886f..f70f234bf 100644 --- a/source/engine/parseReference.js +++ b/source/engine/parseReference.js @@ -1,11 +1,14 @@ // Reference to a variable import parseRule from 'Engine/parseRule' import React from 'react' +import { typeWarning } from './error' import { evaluateApplicability } from './evaluateRule' import { evaluateNode, mergeMissing } from './evaluation' import { getSituationValue } from './getSituationValue' import { Leaf } from './mecanismViews/common' +import { convertNodeToUnit, getNodeDefaultUnit } from './nodeUnits' import { disambiguateRuleReference, findRuleByDottedName } from './rules' +import { areUnitConvertible } from './units' const getApplicableReplacements = ( filter, contextRuleName, @@ -75,6 +78,19 @@ const getApplicableReplacements = ( ? evaluateNode(cache, situation, rules, replacementNode) : evaluateReference(filter)(cache, situation, rules, referenceNode) ) + .map(replacementNode => { + const replacedRuleUnit = getNodeDefaultUnit(rule, cache) + if (!areUnitConvertible(replacementNode.unit, replacedRuleUnit)) { + typeWarning( + contextRuleName, + `L'unité de la règle de remplacement n'est pas compatible avec celle de la règle remplacée ${rule.dottedName}` + ) + } + return { + ...replacementNode, + unit: replacementNode.unit || replacedRuleUnit + } + }) return [applicableReplacements, missingVariableList] } @@ -85,7 +101,6 @@ let evaluateReference = (filter, contextRuleName) => ( node ) => { let rule = rules[node.dottedName] - // When a rule exists in different version (created using the `replace` mecanism), we add // a redirection in the evaluation of references to use a potential active replacement const [ @@ -119,7 +134,10 @@ let evaluateReference = (filter, contextRuleName) => ( cache[cacheName] = { ...node, nodeValue, - ...(explanation && { explanation }), + ...(explanation && { + explanation + }), + ...(explanation?.unit && { unit: explanation.unit }), missingVariables } return cache[cacheName] @@ -129,13 +147,15 @@ let evaluateReference = (filter, contextRuleName) => ( missingVariables: condMissingVariables } = evaluateApplicability(cache, situation, rules, rule) if (!isApplicable) { - return cacheNode(isApplicable, condMissingVariables) + return cacheNode(isApplicable, condMissingVariables, rule) } const situationValue = getSituationValue(situation, dottedName, rule) if (situationValue !== undefined) { + const unit = getNodeDefaultUnit(rule, cache) return cacheNode(situationValue, condMissingVariables, { ...rule, - nodeValue: situationValue + nodeValue: situationValue, + unit }) } @@ -166,11 +186,12 @@ export let parseReference = ( // the 'inversion numérique' formula should not exist. The instructions to the evaluation should be enough to infer that an inversion is necessary (assuming it is possible, the client decides this) (!inInversionFormula && parseRule(rules, findRuleByDottedName(rules, dottedName), parsedRules)) - + const unit = + parsedRule.unit || parsedRule.formule?.unit || parsedRule.defaultUnit return { evaluate: evaluateReference(filter, rule.dottedName), //eslint-disable-next-line react/display-name - jsx: nodeValue => ( + jsx: (nodeValue, explanation, _, nodeUnit) => ( <> ), - name: partialReference, category: 'reference', partialReference, dottedName, - unit: parsedRule.unit + unit } } // This function is a wrapper that can apply : -// - temporal transformations to the value of the variable. -// See the période.yaml test suite for details +// - unit transformations to the value of the variable. +// See the unité-temporelle.yaml test suite for details // - filters on the variable to select one part of the variable's 'composantes' const evaluateTransforms = (originalEval, rule, parseResult) => ( @@ -211,61 +231,24 @@ const evaluateTransforms = (originalEval, rule, parseResult) => ( parsedRules, node ) - if (!filteredNode.explanation) { + const { explanation, nodeValue } = filteredNode + if (!explanation || nodeValue === null) { return filteredNode } - - let nodeValue = filteredNode.nodeValue - - // Temporal transformation - let supportedPeriods = ['mois', 'année', 'flexible'] - if (nodeValue == null) return filteredNode - let ruleToTransform = parsedRules[filteredNode.explanation.dottedName] - - let inlinePeriodTransform = { mensuel: 'mois', annuel: 'année' }[ - parseResult.temporalTransform - ] - - // Exceptions - if (!rule.période && !inlinePeriodTransform && rule.formule) { - if (supportedPeriods.includes(ruleToTransform?.période)) - throw new Error( - `Attention, une variable sans période, ${rule.dottedName}, qui appelle une variable à période, ${ruleToTransform.dottedName}, c'est suspect ! - - Si la période de la variable appelée est neutralisée dans la formule de calcul, par exemple un montant mensuel divisé par 30 (comprendre 30 jours), utilisez "période: aucune" pour taire cette erreur et rassurer tout le monde. - ` + const unit = parseResult.unit + if (unit) { + try { + return convertNodeToUnit(unit, filteredNode) + } catch (e) { + typeWarning( + cache._meta.contextRule, + `Impossible de convertir la reference '${filteredNode.name}'`, + e ) - - return filteredNode - } - if (!ruleToTransform?.période) return filteredNode - let environmentPeriod = situation('période') || 'mois' - let callingPeriod = - inlinePeriodTransform || - (rule.période === 'flexible' ? environmentPeriod : rule.période) - let calledPeriod = - ruleToTransform.période === 'flexible' - ? environmentPeriod - : ruleToTransform.période - - let transformedNodeValue = - callingPeriod === 'mois' && calledPeriod === 'année' - ? nodeValue / 12 - : callingPeriod === 'année' && calledPeriod === 'mois' - ? nodeValue * 12 - : nodeValue, - periodTransform = nodeValue !== transformedNodeValue - - let result = { - ...filteredNode, - periodTransform, - ...(periodTransform ? { originPeriodValue: nodeValue } : {}), - nodeValue: transformedNodeValue, - explanation: filteredNode.explanation, - missingVariables: filteredNode.missingVariables + } } - return result + return filteredNode } export let parseReferenceTransforms = ( rules, @@ -273,9 +256,12 @@ export let parseReferenceTransforms = ( parsedRules ) => parseResult => { const referenceName = parseResult.variable.fragments.join(' . ') - let node = parseReference(rules, rule, parsedRules, parseResult.filter)( - referenceName - ) + let node = parseReference( + rules, + rule, + parsedRules, + parseResult.filter + )(referenceName) return { ...node, @@ -289,6 +275,7 @@ export let parseReferenceTransforms = ( } } : {}), - evaluate: evaluateTransforms(node.evaluate, rule, parseResult) + evaluate: evaluateTransforms(node.evaluate, rule, parseResult), + unit: parseResult.unit || node.unit } } diff --git a/source/engine/parseRule.js b/source/engine/parseRule.js index 8b76a464a..bcd7fd0e3 100644 --- a/source/engine/parseRule.js +++ b/source/engine/parseRule.js @@ -78,10 +78,8 @@ export default (rules, rule, parsedRules) => { parsedRules, node.explanation ), - nodeValue = explanation.nodeValue, - missingVariables = explanation.missingVariables - - return { ...node, nodeValue, explanation, missingVariables } + { nodeValue, unit, missingVariables } = explanation + return { ...node, nodeValue, unit, missingVariables, explanation } } let child = parse(rules, rule, parsedRules)(value) @@ -94,7 +92,7 @@ export default (rules, rule, parsedRules) => { category: 'ruleProp', rulePropType: 'formula', name: 'formule', - type: 'numeric', + unit: child.unit, explanation: child } }, @@ -125,10 +123,9 @@ export default (rules, rule, parsedRules) => { evaluate, parsed: true, isDisabledBy: [], - replacedBy: [], - unit: rule.unit || parsedRoot.formule?.explanation?.unit + defaultUnit: parsedRoot.defaultUnit || parsedRoot.formule?.unit, + replacedBy: [] } - parsedRules[rule.dottedName]['rendu non applicable'] = { evaluate: (cache, situation, parsedRules, node) => { const isDisabledBy = node.explanation.isDisabledBy.map(disablerNode => @@ -162,7 +159,6 @@ export default (rules, rule, parsedRules) => { type: 'boolean', explanation: parsedRules[rule.dottedName] } - return parsedRules[rule.dottedName] } diff --git a/source/engine/rules.js b/source/engine/rules.js index d4c65940d..e334ba82b 100644 --- a/source/engine/rules.js +++ b/source/engine/rules.js @@ -29,6 +29,7 @@ import rawRules from 'Règles/base.yaml' import translations from 'Règles/externalized.yaml' // TODO - should be in UI, not engine import { capitalise0, coerceArray } from '../utils' +import { syntaxError, warning } from './error' import possibleVariableTypes from './possibleVariableTypes.yaml' /*********************************** @@ -36,9 +37,20 @@ Functions working on one rule */ export let enrichRule = rule => { try { - let unit = rule.unité && parseUnit(rule.unité) const dottedName = rule.dottedName || rule.nom const name = nameLeaf(dottedName) + let unit = rule.unité && parseUnit(rule.unité) + let defaultUnit = + rule['unité par défaut'] && parseUnit(rule['unité par défaut']) + + if (defaultUnit && unit) { + warning( + dottedName, + "Le paramètre `unité` n'est plus contraignant que `unité par défaut`.", + 'Si vous souhaitez que la valeur de votre variable soit toujours la même unité, gardez `unité`' + ) + } + return { ...rule, dottedName, @@ -49,11 +61,15 @@ export let enrichRule = rule => { examples: rule['exemples'], icons: rule['icônes'], summary: rule['résumé'], - unit + unit, + defaultUnit } } catch (e) { - console.log(e) - throw new Error('Problem enriching ' + JSON.stringify(rule)) + syntaxError( + rule.dottedName || rule.nom, + 'Problème dans la lecture des champs de la règle', + e + ) } } @@ -71,15 +87,8 @@ export let hasKnownRuleType = rule => rule && enrichRule(rule).type export let splitName = split(' . '), joinName = join(' . ') -export let parentName = pipe( - splitName, - dropLast(1), - joinName -) -export let nameLeaf = pipe( - splitName, - last -) +export let parentName = pipe(splitName, dropLast(1), joinName) +export let nameLeaf = pipe(splitName, last) export let encodeRuleName = name => encodeURI( @@ -117,9 +126,10 @@ export let disambiguateRuleReference = ( found = reduce( (res, path) => { let dottedNameToCheck = [...path, partialName].join(' . ') - return when(is(Object), reduced)( - findRuleByDottedName(allRules, dottedNameToCheck) - ) + return when( + is(Object), + reduced + )(findRuleByDottedName(allRules, dottedNameToCheck)) }, null, pathPossibilities diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 3cf549c6f..1ebaa9d08 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -8,6 +8,7 @@ import { findRule, findRuleByDottedName } from './rules' +import { parseUnit } from './units' /* Dans ce fichier, les règles YAML sont parsées. @@ -117,10 +118,17 @@ export let getTargets = (target, rules) => { return targets } -export let analyseMany = (parsedRules, targetNames) => situationGate => { +export let analyseMany = ( + parsedRules, + targetNames, + defaultUnits = [] +) => situationGate => { // TODO: we should really make use of namespaces at this level, in particular // setRule in Rule.js needs to get smarter and pass dottedName - let cache = { parseLevel: 0 } + defaultUnits = defaultUnits.map(parseUnit) + let cache = { + _meta: { parseLevel: 0, contextRule: [], defaultUnits } + } let parsedTargets = targetNames.map(t => { let parsedTarget = findRule(parsedRules, t) @@ -137,10 +145,9 @@ export let analyseMany = (parsedRules, targetNames) => situationGate => { ) let controls = evaluateControls(cache, situationGate, parsedRules) - return { targets, cache, controls } } -export let analyse = (parsedRules, target) => { - return analyseMany(parsedRules, [target]) +export let analyse = (parsedRules, target, defaultUnits = []) => { + return analyseMany(parsedRules, [target], defaultUnits) } diff --git a/source/engine/units.ts b/source/engine/units.ts index c602690ed..8b9f01e1f 100644 --- a/source/engine/units.ts +++ b/source/engine/units.ts @@ -1,4 +1,16 @@ -import { isEmpty, remove, unnest } from 'ramda' +import { + countBy, + equals, + flatten, + isEmpty, + keys, + map, + pipe, + remove, + uniq, + unnest, + without +} from 'ramda' import i18n from '../i18n' type BaseUnit = string @@ -10,10 +22,13 @@ export type Unit = { //TODO this function does not handle complex units like passenger-kilometer/flight export let parseUnit = (string: string): Unit => { - let [a, b = ''] = string.split('/'), + let [a, ...b] = string.split('/'), result = { - numerators: a !== '' ? [getUnitKey(a)] : [], - denominators: b !== '' ? [getUnitKey(b)] : [] + numerators: a + .split('.') + .filter(Boolean) + .map(getUnitKey), + denominators: b.map(getUnitKey) } return result } @@ -32,7 +47,7 @@ let printUnits = (units: Array, count: number): string => units .filter(unit => unit !== '%') .map(unit => i18n.t(`units:${unit}`, { count })) - .join('-') + .join('.') const plural = 2 export let serialiseUnit = ( @@ -97,20 +112,166 @@ export let inferUnit = ( return null } -export let removeOnce = (element: T) => (list: Array): Array => { - let index = list.indexOf(element) +export let removeOnce = ( + element: T, + eqFn: (a: T, b: T) => boolean = equals +) => (list: Array): Array => { + let index = list.findIndex(e => eqFn(e, element)) if (index > -1) return remove(index, 1)(list) else return list } -let simplify = (unit: Unit): Unit => +let simplify = ( + unit: Unit, + eqFn: (a: string, b: string) => boolean = equals +): Unit => [...unit.numerators, ...unit.denominators].reduce( ({ numerators, denominators }, next) => - numerators.includes(next) && denominators.includes(next) + numerators.find(u => eqFn(next, u)) && + denominators.find(u => eqFn(next, u)) ? { - numerators: removeOnce(next)(numerators), - denominators: removeOnce(next)(denominators) + numerators: removeOnce(next, eqFn)(numerators), + denominators: removeOnce(next, eqFn)(denominators) } : { numerators, denominators }, unit ) + +const convertTable: { readonly [index: string]: number } = { + 'mois/an': 12, + '€/k€': 1000, + 'jour/an': 365, + 'jour/mois': 365 / 12, + 'trimestre/an': 4, + 'mois/trimestre': 3, + 'jour/trimestre': (365 / 12) * 3 +} +function singleUnitConversionFactor( + from: string, + to: string +): number | undefined { + return ( + convertTable[`${to}/${from}`] || + (convertTable[`${from}/${to}`] && 1 / convertTable[`${from}/${to}`]) + ) +} +function unitsConversionFactor(from: string[], to: string[]): number { + let factor = 1 + if (to.includes('%')) { + factor *= 100 + } + if (from.includes('%')) { + factor /= 100 + } + ;[factor] = from.reduce( + ([value, toUnits], fromUnit) => { + const index = toUnits.findIndex( + toUnit => !!singleUnitConversionFactor(fromUnit, toUnit) + ) + const factor = singleUnitConversionFactor(fromUnit, toUnits[index]) || 1 + return [ + value * factor, + [...toUnits.slice(0, index + 1), ...toUnits.slice(index + 1)] + ] + }, + [factor, to] + ) + return factor +} + +export function convertUnit(from: Unit, to: Unit, value: number) { + if (!areUnitConvertible(from, to)) { + throw new Error( + `Impossible de convertir l'unité '${serialiseUnit( + from + )}' en '${serialiseUnit(to)}'` + ) + } + if (!value) { + return value + } + const [fromSimplified, factorTo] = simplifyUnitWithValue(from) + const [toSimplified, factorFrom] = simplifyUnitWithValue(to) + return round( + ((value * factorTo) / factorFrom) * + unitsConversionFactor( + fromSimplified.numerators, + toSimplified.numerators + ) * + unitsConversionFactor( + toSimplified.denominators, + fromSimplified.denominators + ) + ) +} + +const convertibleUnitClasses = [ + ['mois', 'an', 'jour', 'trimestre'], + ['€', 'k€'] +] +function areSameClass(a: string, b: string) { + return ( + a === b || + convertibleUnitClasses.some(units => units.includes(a) && units.includes(b)) + ) +} + +function round(value: number) { + return +value.toFixed(16) +} +export function simplifyUnitWithValue( + unit: Unit, + value: number = 1 +): [Unit, number] { + const { denominators, numerators } = unit + const factor = unitsConversionFactor(numerators, denominators) + return [ + simplify( + { + numerators: without(['%'], numerators), + denominators: without(['%'], denominators) + }, + areSameClass + ), + value ? round(value * factor) : value + ] +} +export function areUnitConvertible(a: Unit, b: Unit) { + if (a == null || b == null) { + return true + } + const countByUnitClass = countBy((unit: string) => { + const classIndex = convertibleUnitClasses.findIndex(unitClass => + unitClass.includes(unit) + ) + return classIndex === -1 ? unit : '' + classIndex + }) + const [numA, denomA, numB, denomB] = [ + a.numerators, + a.denominators, + b.numerators, + b.denominators + ].map(countByUnitClass) + + const unitClasses = pipe( + map(keys), + flatten, + uniq + )([numA, denomA, numB, denomB]) + + return unitClasses.every( + unitClass => + (numA[unitClass] || 0) - (denomA[unitClass] || 0) === + (numB[unitClass] || 0) - (denomB[unitClass] || 0) || unitClass === '%' + ) +} +export function isPercentUnit(unit: Unit) { + if (!unit) { + return false + } + const simplifiedUnit = simplifyUnitWithValue(unit)[0] + return ( + simplifiedUnit.denominators.length === 0 && + simplifiedUnit.numerators.length === 0 + ) +} diff --git a/source/locales/en.yaml b/source/locales/en.yaml index 8e82ebd16..04f1ca21c 100644 --- a/source/locales/en.yaml +++ b/source/locales/en.yaml @@ -58,7 +58,6 @@ Cotisations sociales: Social contributions Part employeur: Employer share Part salariale: Employee share Total des retenues: Total withheld -Fiche de paie mensuelle: Monthly payslip Fiche de paie: Payslip Détail annuel des cotisations: Annual detail of my contributions Voir la répartition des cotisations: View contribution breakdown diff --git a/source/reducers/rootReducer.ts b/source/reducers/rootReducer.ts index 07d06d5e5..75faed9ce 100644 --- a/source/reducers/rootReducer.ts +++ b/source/reducers/rootReducer.ts @@ -1,5 +1,5 @@ import { Action } from 'Actions/actions' -import { findRuleByDottedName } from 'Engine/rules' +import { areUnitConvertible, convertUnit, parseUnit } from 'Engine/units' import { compose, defaultTo, @@ -14,10 +14,11 @@ import { } from 'ramda' import reduceReducers from 'reduce-reducers' import { combineReducers, Reducer } from 'redux' -import { targetNamesSelector } from 'Selectors/analyseSelectors' +import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors' import { SavedSimulation } from 'Selectors/storageSelectors' import { DottedName, Rule } from 'Types/rule' import i18n, { AvailableLangs } from '../i18n' +import { Unit } from './../engine/units' import inFranceAppReducer from './inFranceAppReducer' import storageRootReducer from './storageReducer' @@ -92,7 +93,7 @@ function conversationSteps( }, action: Action ): ConversationSteps { - if (action.type === 'RESET_SIMULATION') + if (['RESET_SIMULATION', 'SET_SIMULATION'].includes(action.type)) return { foldedSteps: [], unfoldedStep: null } if (action.type !== 'STEP_ACTION') return state @@ -111,48 +112,45 @@ function conversationSteps( return state } -function updateSituation(situation, { fieldName, value, config, rules }) { - const goals = targetNamesSelector({ simulation: { config } } as any).filter( - dottedName => { - const target = rules.find(r => r.dottedName === dottedName) - const isSmallTarget = !target.question || !target.formule - return !isSmallTarget - } - ) +function updateSituation(situation, { fieldName, value, analysis }) { + const goals = analysis.targets + .map(target => target.explanation || target) + .filter(target => !!target.formule == !!target.question) + .map(({ dottedName }) => dottedName) const removePreviousTarget = goals.includes(fieldName) ? omit(goals) : identity return { ...removePreviousTarget(situation), [fieldName]: value } } -function updatePeriod(situation, { toPeriod, rules }) { - const currentPeriod = situation['période'] - if (currentPeriod === toPeriod) { - return situation - } - if (!['mois', 'année'].includes(toPeriod)) { - throw new Error('Oups, changement de période invalide') - } +function updateDefaultUnit(situation, { toUnit, analysis }) { + const unit = parseUnit(toUnit) - const needConversion = Object.keys(situation).filter(dottedName => { - const rule = findRuleByDottedName(rules, dottedName) - return rule?.période === 'flexible' - }) - - const updatedSituation = Object.entries(situation) - .filter(([fieldName]) => needConversion.includes(fieldName)) - .map(([fieldName, value]) => [ - fieldName, - currentPeriod === 'mois' && toPeriod === 'année' - ? (value as number) * 12 - : (value as number) / 12 - ]) - - return { - ...situation, - ...Object.fromEntries(updatedSituation), - période: toPeriod - } + const convertedSituation = Object.keys(situation) + .map( + dottedName => + analysis.targets.find(target => target.dottedName === dottedName) || + analysis.cache[dottedName] + ) + .filter( + rule => + (rule.unit || rule.defaultUnit) && + !rule.unité && + !rule.explanation?.unité && + areUnitConvertible(rule.unit || rule.defaultUnit, unit) + ) + .reduce( + (convertedSituation, rule) => ({ + ...convertedSituation, + [rule.dottedName]: convertUnit( + rule.unit || rule.defaultUnit, + unit, + situation[rule.dottedName] + ) + }), + situation + ) + return convertedSituation } type QuestionsKind = @@ -169,6 +167,7 @@ export type SimulationConfig = Partial<{ bloquant: Array situation: Simulation['situation'] branches: Array<{ nom: string; situation: SimulationConfig['situation'] }> + defaultUnits: [string] }> export type Simulation = { @@ -176,16 +175,27 @@ export type Simulation = { url: string hiddenControls: Array situation: Record + defaultUnits: [string] } function simulation( state: Simulation = null, action: Action, - rules: Array + analysis: Record ): Simulation | null { if (action.type === 'SET_SIMULATION') { const { config, url } = action - return { config, url, hiddenControls: [], situation: {} } + if (state && state.config === config) { + return state + } + return { + config, + url, + hiddenControls: [], + situation: {}, + defaultUnits: (state && state.defaultUnits) || + config.defaultUnits || ['€/mois'] + } } if (state === null) { return state @@ -201,42 +211,34 @@ function simulation( situation: updateSituation(state.situation, { fieldName: action.fieldName, value: action.value, - config: state.config, - rules + analysis }) } - case 'UPDATE_PERIOD': + case 'UPDATE_DEFAULT_UNIT': return { ...state, - situation: updatePeriod(state.situation, { - toPeriod: action.toPeriod, - rules + defaultUnits: [action.defaultUnit], + situation: updateDefaultUnit(state.situation, { + toUnit: action.defaultUnit, + analysis }) } } return state } -const addAnswerToSituation = ( - dottedName: DottedName, - value: any, - state: RootState -) => { - console.log(state) +const addAnswerToSituation = (dottedName: DottedName, value: any, state) => { return (compose( - set(lensPath(['simulation', 'config', 'situation', dottedName]), value), + set(lensPath(['simulation', 'situation', dottedName]), value), over(lensPath(['conversationSteps', 'foldedSteps']), (steps = []) => uniq([...steps, dottedName]) ) as any ) as any)(state) } -const removeAnswerFromSituation = ( - dottedName: DottedName, - state: RootState -) => { +const removeAnswerFromSituation = (dottedName: DottedName, state) => { return (compose( - over(lensPath(['simulation', 'config', 'situation']), dissoc(dottedName)), + over(lensPath(['simulation', 'situation']), dissoc(dottedName)), over( lensPath(['conversationSteps', 'foldedSteps']), without([dottedName]) @@ -244,7 +246,7 @@ const removeAnswerFromSituation = ( ) as any)(state) } -const existingCompanyRootReducer = (state: RootState, action): RootState => { +const existingCompanyRootReducer = (state: RootState, action) => { if (!action.type.startsWith('EXISTING_COMPANY::')) { return state } @@ -268,8 +270,8 @@ const mainReducer = (state, action: Action) => rules: defaultTo(null) as Reducer>, explainedVariable, // We need to access the `rules` in the simulation reducer - simulation: (a: Simulation | null, b: Action) => - simulation(a, b, state.rules), + simulation: (a: Simulation | null, b: Action): Simulation => + simulation(a, b, a && analysisWithDefaultsSelector(state)), previousSimulation: defaultTo(null) as Reducer, currentExample, situationBranch, diff --git a/source/règles/base.yaml b/source/règles/base.yaml index 0a7924680..8fa592c78 100644 --- a/source/règles/base.yaml +++ b/source/règles/base.yaml @@ -1,16 +1,11 @@ période: - par défaut: mois - une possibilité: - - mois - - année - période . jours ouvrés moyen par mois: - période: mois - formule: 21 jours + formule: 21 jour ouvré/mois note: On retient 21 comme nombre de jours ouvrés moyen par mois période . semaines par mois: - formule: 52 semaines / 12 + unité: semaines/mois + formule: 52 semaines/an / 12 mois/an contrat salarié: icônes: 📄 @@ -53,8 +48,8 @@ contrat salarié . indemnité kilométrique vélo: Indemnité introduite en 2015 pour inciter l'usage du vélo pour aller au travail. Nous avons retenu une implémentation simplifiée de cette règle : nous fixons cette indemnité à 200€ annuels, car c'est le montant maximum donnant lieu aux exonérations de cotisations sociales et d'impôt. C'est aussi un montant tout à fait accessible, correspondant approximativement à 2km aller et 2km retour pour 218 jours travaillés dans l'année. Elle peut être supérieure, mais l'employeur n'a alors aucune incitation à verser ce supplément : la part au-dessus de 200€ devient une prime classique. - unité: € - période: année + unité: €/an + applicable si: active formule: encadrement: @@ -70,20 +65,21 @@ contrat salarié . indemnité kilométrique vélo: valeur attendue: 200 contrat salarié . indemnité kilométrique vélo . plafond d'exonération: - formule: 200 € + formule: 200 €/an contrat salarié . indemnité kilométrique vélo . indemnité kilométrique: formule: 0.25 €/km contrat salarié . indemnité kilométrique vélo . distance annuelle: - période: année + unité: km/an formule: distance journalière * jours travaillés contrat salarié . indemnité kilométrique vélo . distance journalière: description: Une estimation basse de la distance parcourue à vélo par un salarié pour se rendre à son travail. formule: 4 km/jour + contrat salarié . indemnité kilométrique vélo . jours travaillés: - formule: 218 jours + formule: 218 jours/an contrat salarié . indemnité kilométrique vélo . active: titre: indemnité vélo active @@ -100,14 +96,12 @@ contrat salarié . indemnité kilométrique vélo . active: Cette indemnité est exonérée de cotisations sociales et d'impôt sur le revenu. Pour verser une prime de salaire équivalente à son salarié sans ce dispositif, **l'employeur devrait débourser près de 500€ pour un salaire médian**. par défaut: non -contrat salarié . CDD . CPF : +contrat salarié . CDD . CPF: description: Contribution au financement du compte personnel de formation (CPF) spécifique aux CDD. cotisation: destinataire: OPCA dû par: employeur branche: formation - période: flexible - non applicable si: une de ces conditions: - événement . poursuite du CDD en CDI @@ -115,22 +109,18 @@ contrat salarié . CDD . CPF : - contrat jeune vacances - motif . classique . saisonnier - motif . contrat aidé - formule: multiplication: assiette: cotisations . assiette taux: 1% - références: Code du travail - Article L6322-37: https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000022234996&cidTexte=LEGITEXT000006072050 - exemples: - nom: Non applicable si CDI situation: CDD: non cotisations . assiette: 1480 valeur attendue: 0 - - nom: SMIC situation: CDD: oui @@ -140,17 +130,14 @@ contrat salarié . CDD . CPF : cotisations . assiette: 1480 valeur attendue: 14.8 - - nom: salaire médian situation: CDD: oui événement: aucun motif: accroissement activité contrat jeune vacances: non - cotisations . assiette: 2300 valeur attendue: 23 - - nom: motif saisonnier -> non applicable situation: contrat salarié . CDD . motif: classique . saisonnier @@ -165,14 +152,12 @@ contrat salarié . CDD . compensation pour congés non pris: Le salarié en CDD bénéficie des mêmes droits à congés payés que le salarié en CDI. Il acquiert et prend ses congés payés dans les mêmes conditions. Il est cependant courant que le salarié ne puisse pas prendre tous ses congés avant le terme de son contrat, il bénéficie alors d'une indemnité compensatrice de congés payés versée par l'employeur. - période: mois - unité: € + unité: €/mois non applicable si: une de ces conditions: - événement . poursuite du CDD en CDI # TODO Y a-t-il d'autres conditions ? Sinon supprimer la liste - formule: le maximum de: - description: Méthode "du dixième" @@ -183,7 +168,6 @@ contrat salarié . CDD . compensation pour congés non pris: assiette: assiette mensuelle taux: 10% facteur: proportion congés non pris - - description: Méthode "maintien du salaire" note: | Cette méthode sera le plus souvent favorable au salarié lorsque celui-ci a bénéficié d’une augmentation de salaire. @@ -192,11 +176,9 @@ contrat salarié . CDD . compensation pour congés non pris: - du nombre moyen de jours ouvrables (ou ouvrés), - du nombre réel de jours ouvrables (ou ouvrés). référence: https://www.service-public.fr/particuliers/vosdroits/F33359 - multiplication: - assiette: prime maintient de salaire - taux: 1 / durée contrat - + assiette: salaire journalier + facteur: congés non pris / durée contrat exemples: - nom: pas de congés non pris situation: @@ -221,43 +203,38 @@ contrat salarié . CDD . compensation pour congés non pris: congés non pris: 3 durée contrat: 6 valeur attendue: 55.21 - note: | L'indemnité est versée à la fin du contrat, sauf si le CDD se poursuit par un CDI. - À noter, la loi El Khomri modifie l'article L3141-12: - avant : Les congés peuvent être pris dès l'ouverture des droits [...] - maintenant : Les congés peuvent être pris dès l’embauche [...] - références: Fiche service-public.gouv.fr: https://www.service-public.fr/particuliers/vosdroits/F2931 Code du travail - Article L3141-24: https://www.legifrance.gouv.fr/affichCodeArticle.do?cidTexte=LEGITEXT000006072050&idArticle=LEGIARTI000006902661&dateTexte=&categorieLien=cid Congés payés et contrat CDD: https://www.easycdd.com/LEGISLATION-CDD/L-embauche-le-suivi-du-contrat-CDD-les-incidents-frequents/Conges-payes-et-contrat-CDD assiette de l'indemnité, circulaire DRT 18 du 30 octobre 1990: http://conseillerdusalarie.free.fr/Docs/TextesFrance/19901030Circulaire_DRT_90_18_du_30_octobre_1990_CDD_Travail_temporaire.htm -? contrat salarié . CDD . compensation pour congés non pris . proportion congés non pris -: unité: '%' +contrat salarié . CDD . compensation pour congés non pris . proportion congés non pris: + unité: '%' formule: congés non pris / congés dus en jours ouvrés contrat salarié . CDD . congés dus en jours ouvrés: formule: contrat salarié . congés dus par mois * durée contrat contrat salarié . congés dus par mois: - formule: 25 jours / 12 mois - -? contrat salarié . CDD . compensation pour congés non pris . prime maintient de salaire -: formule: salaire journalier * congés non pris + formule: 25 jour ouvré / 12 mois contrat salarié . CDD . compensation pour congés non pris . assiette mensuelle: - période: mois + unité: €/mois + formule: rémunération . brut de base + prime de fin de contrat contrat salarié . CDD . compensation pour congés non pris . salaire journalier: - période: aucune + unité: €/jour ouvré + formule: assiette mensuelle / période . jours ouvrés moyen par mois contrat salarié . CDD . prime de fin de contrat: - période: flexible indemnité: destinataire: salarié @@ -323,7 +300,6 @@ contrat salarié . ATMP: branche: accidents du travail et maladies professionnelles destinataire: URSSAF responsable: CARSAT - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -356,10 +332,10 @@ contrat salarié . ATMP . taux collectif ATMP: ce taux est modulé (jusqu'à 150 salariés) voire individualisé (au-delà). L'entreprise peut consulter le taux qui la concerne en ligne sur [net-entreprise](http://www.net-entreprises.fr/html/compte-accident-travail.htm). suggestions: atmp-2017 - # https://www.legifrance.gouv.fr/affichTexteArticle.do;jsessionid=4702534627E4A8CF240B990E28C81AF4.tplgfr30s_3?idArticle=JORFARTI000033735834&cidTexte=JORFTEXT000033735824&dateTexte=29990101&categorieLien=id - # article 3 Le taux net moyen national de cotisation est de 2,32 %. - par défaut: 0.0222 - unité: '%' + par défaut: 2.22 + unité par défaut: '%' + references: + taux moyen national: https://www.legifrance.gouv.fr/affichTexteArticle.do;jsessionid=4702534627E4A8CF240B990E28C81AF4.tplgfr30s_3?idArticle=JORFARTI000033735834&cidTexte=JORFTEXT000033735824&dateTexte=29990101&categorieLien=id contrat salarié . CDD . événement: titre: Événement de contrat @@ -566,7 +542,7 @@ contrat salarié . CDD . congés non pris: description: | Le contrat étant à durée déterminée, le salarié n'a pas forcément le temps de prendre tous les jours de congés qu'il a acquis comme tout salarié au cours du contrat. Par exemple, pour un contrat de 3 mois, le salarié acquiert 2,08 jours de congés par mois (25 jours / 12 mois = 2,08), donc 6,25 sur la durée du contrat. Or il se peut que l'entreprise le contraigne à n'en prendre que 4, donc 2,25 jours ne seront pas pris. Ils seront payés par l'employeur à la fin du contrat. - unité: jour + unité: jour ouvré suggestions: 3: 3 10: 10 @@ -587,35 +563,12 @@ contrat salarié . CDD . contrat jeune vacances: par défaut: non contrat salarié . CDD . indemnités salarié CDD: - période: flexible description: Cotisations employeur spécifiques au CDD formule: somme: - prime de fin de contrat - compensation pour congés non pris -contrat salarié . CDD . surcoût: - titre: Dont surcoût CDD - description: | - Le contrat à durée déterminée exige que l'employeur verse, au salarié ou aux organismes sociaux, certaines compensations - financières en contrepartie de la souplesse apportée par ce contrat; elles sont au nombre de 4. - - Certaines sont versées en fin de contrat, d'autres avec chaque salaire mensuel; elles sont ici ramenées à leur coût mensuel. - période: flexible - formule: - somme: #TODO à l'avenir, exprimer une somme par requête de type : obligation applicable au CDD - - indemnités salarié CDD - - CPF - exemples: - - nom: 'exemple 1' - situation: - CDD: oui - indemnités salarié CDD: 100 - CPF: 100 - prime de fin de contrat: 60.4 - compensation pour congés non pris: 39.6 - valeur attendue: 200 - contrat salarié . apprentissage: description: | Le contrat d'apprentissage est un contrat de travail écrit à durée limitée (CDD) ou à durée indéterminée (CDI) entre un salarié et un employeur. Il permet à l'apprenti de suivre une formation en alternance en entreprise sous la responsabilité d'un maître d'apprentissage et en centre de formation des apprentis (CFA) pendant 1 à 3 ans. @@ -698,7 +651,6 @@ contrat salarié . stage: - régime des impatriés contrat salarié . stage . gratification minimale: - période: flexible formule: 15% * plafond sécurité sociale temps plein références: Gratification minimale: https://www.service-public.fr/professionnels-entreprises/vosdroits/F32131 @@ -712,7 +664,6 @@ contrat salarié . exonération d'impôt des stagiaires et apprentis: une de ces conditions: - apprentissage - stage - période: flexible formule: SMIC contrat salarié . CDD: @@ -737,7 +688,7 @@ contrat salarié . cotisations . assiette: description: | L'assiette des cotisations sociales est la base de calcul d'un grand nombre de cotisations sur le travail salarié. Elle comprend notamment les rémunérations en espèces (salaire de base, indemnité, primes...) et les avantages en nature (logement, véhicule...). référence: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/la-base-de-calcul.html - période: flexible + unité par défaut: €/mois formule: allègement: assiette: rémunération . brut @@ -752,7 +703,6 @@ contrat salarié . cotisations . assiette . salariale: Les apprentis bénéficient d'une exonération de cotisations sociales jusqu'à 79% du SMIC. références: URSSAF: https://www.urssaf.fr/portail/home/employeur/beneficier-dune-exoneration/exonerations-ou-aides-liees-a-la/le-contrat-dapprentissage/exonerations.html - période: flexible formule: variations: - si: apprentissage @@ -771,15 +721,14 @@ contrat salarié . rémunération . brut de base: C'est le salaire *brut* régulier inscrit dans le contrat de travail. Il ne change jamais entre les mois et ne peut pas être modifié sans signature des deux parties. Il ne comprend pas les indemnités, avantages sociaux, avantages en nature et primes... - période: flexible - unité: € + unité par défaut: €/mois suggestions: salaire médian: 2300 SMIC: 1522 contrôles: - si: toutes ces conditions: - - rémunération . assiette de vérification du SMIC [mensuel] < SMIC contractuel [mensuel] + - rémunération . assiette de vérification du SMIC < SMIC contractuel - dirigeant != 'assimilé salarié' - stage != oui - apprentissage != oui @@ -790,15 +739,15 @@ contrat salarié . rémunération . brut de base: - si: toutes ces conditions: - stage - - brut de base [mensuel] < stage . gratification minimale [mensuel] + - brut de base < stage . gratification minimale niveau: avertissement message: | La rémunération du stage est inférieure à la [gratification minimale](https://www.service-public.fr/professionnels-entreprises/vosdroits/F32131). - - si: + - si: toutes ces conditions: - - brut de base [mensuel] > 10000 - - période = 'mois' + - brut de base > 10000 €/mois + - dirigeant = non niveau: information message: | Le salaire mensuel saisi est élevé. Ne vous êtes-vous pas trompé de période de calcul ? @@ -824,20 +773,17 @@ contrat salarié . rémunération . brut de base . équivalent temps plein: titre: Salaire brut équivalent temps plein résumé: Le salaire si l'embauche se faisait à temps plein question: Quel est le salaire en équivalent temps plein ? - unité: € + unité par défaut: €/mois formule: brut de base / temps de travail . quotité de travail - période: flexible suggestions: salaire médian: 2300 SMIC: 1522 contrat salarié . rémunération . heures supplémentaires . taux horaire: description: Le taux horaire utilisé pour calculer la rémunération liée au heures supplémentaires. Il intègre les avantages en nature. - période: aucune - formule: - multiplication: - assiette: assiette de vérification du SMIC [mensuel] - facteur: 1 / temps de travail . temps contractuel + unité: €/heure + + formule: assiette de vérification du SMIC / temps de travail . temps contractuel références: e-Paye (privé): https://e-paye.com/faq/les-heures-supplementaires-quelles-primes-inclure-dans-la-base-de-calcul-de-la-majoration-pour-heure-supplementaire/ rfPaye (privé): https://rfpaye.grouperf.com/article/0168/ms/rfpayems0168_2027146.html @@ -846,8 +792,8 @@ contrat salarié . rémunération . heures supplémentaires . taux horaire: contrat salarié . rémunération . assiette de vérification du SMIC: description: > C'est le salaire pris en compte pour vérifier que le SMIC est atteint. - unité: € - période: flexible + unité: €/mois + formule: somme: - brut de base @@ -855,17 +801,17 @@ contrat salarié . rémunération . assiette de vérification du SMIC: - primes . activité contrat salarié . rémunération . primes: - unité: € - période: flexible + unité par défaut: €/mois + formule: somme: - base - activité contrat salarié . rémunération . primes . base: - formule: 0 + formule: 0€/mois contrat salarié . rémunération . primes . activité: - période: flexible - unité: € + unité: €/mois + titre: primes d'activité description: > Primes et gratifications versées en contrepartie, ou à l’occasion du travail, directement liées à l’exécution par le salarié de sa prestation de travail. Tel est le cas, par exemple, d’une prime de vente exclusivement basée sur les résultats du salarié. @@ -878,21 +824,21 @@ contrat salarié . rémunération . primes . activité: contrat salarié . rémunération . primes . activité . base: titre: primes d'activité - période: flexible - unité: € + unité: €/mois + question: Quel est le montant des primes liées à l'activité du salarié ? par défaut: 0 contrat salarié . rémunération . primes . activité . conventionnelles: - période: flexible - unité: € + unité: €/mois + formule: 0 contrat salarié . rémunération . brut: description: Toutes les sommes versées au salarié sous forme monétaire en échange de son travail. titre: Rémunération brute - unité: € - période: flexible + unité par défaut: €/mois + formule: somme: - rémunération . brut de base @@ -904,8 +850,8 @@ contrat salarié . rémunération . brut: contrat salarié . rémunération . heures supplémentaires: titre: rémunération heures supplémentaires description: La rémunération relative aux heures supplémentaires - unité: € - période: flexible + unité par défaut: €/mois + formule: multiplication: assiette: taux horaire @@ -918,12 +864,12 @@ contrat salarié . avantages sociaux: description: > Ce sont les avantages sociaux payés par l'employeur. Ils sont spécifiques à l'entreprise, et fournis par des structures privées (mutuelle, assurance...). Ils sont soumis à l'impôt sur le revenu. - unité: € - période: flexible + unité: €/mois + formule: somme: - prévoyance obligatoire cadre - - complémentaire santé [employeur] + - complémentaire santé .employeur contrat salarié . rémunération . avantages en nature: icônes: 🛏️🚗🥗📱 @@ -936,9 +882,9 @@ contrat salarié . rémunération . avantages en nature: contrat salarié . rémunération . avantages en nature . montant: titre: Avantages en nature (montant) - période: flexible description: > Les avantages en nature sont soumis aux cotisations et à l'impôt sur le revenu. Ils sont pris en compte pour vérifier que le salaire minimum est atteint. + unité par défa: €/mois formule: somme: @@ -964,18 +910,16 @@ contrat salarié . rémunération . avantages en nature . autres: par défaut: non contrat salarié . rémunération . avantages en nature . autres . montant: - période: flexible question: > Quel est le montant de ces autres avantages ? par défaut: 0 suggestions: 🚗 véhicule: 260 - unité: € + unité par défaut: €/mois contrat salarié . rémunération . avantages en nature . ntic . montant: titre: outils NTIC - unité: € - période: année + description: | Pour les avantages en nature de type NTIC (ordinateurs, smartphones, tablettes...), il y a une évaluation forfaitaire annuelle correspondant à 10% du prix d'achat. Par exemple, pour un téléphone acheté à 850€ TTC avec un abonnement de 30€ / mois, l'avantage en nature à reporter sur le bulletin de paie sera de : @@ -988,8 +932,8 @@ contrat salarié . rémunération . avantages en nature . ntic . montant: assiette: somme: - coût appareils - - abonnements - taux: 10% + - abonnements * 12 mois + taux: 10% /an références: urssaf.fr: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-elements-a-prendre-en-compte/les-avantages-en-nature/les-outils-issus-des-nouvelles-t/dans-quel-cas-la-mise-a-disposit/levaluation-forfaitaire.html @@ -997,7 +941,7 @@ contrat salarié . rémunération . avantages en nature . ntic . coût appareils question: > Quel est le coût total neuf des appareils mis à disposition ? unité: € - période: année + par défaut: 800 # TODO : vérifier et documenter les chiffres suggestions: @@ -1008,8 +952,8 @@ contrat salarié . rémunération . avantages en nature . ntic . coût appareils contrat salarié . rémunération . avantages en nature . ntic . abonnements: question: Quel est le coût de l'abonnement (forfait mobile, etc.) pris en charge par l'employeur ? - unité: € - période: mois + unité: €/mois + par défaut: 20 suggestions: aucun: 0 @@ -1026,32 +970,30 @@ contrat salarié . rémunération . avantages en nature . nourriture: contrat salarié . rémunération . avantages en nature . nourriture . montant: titre: nourriture - période: flexible - unité: € + unité: €/mois + formule: multiplication: assiette: montant forfaitaire d'un repas facteur: repas par mois -? contrat salarié . rémunération . avantages en nature . nourriture . montant forfaitaire d'un repas -: période: aucune +contrat salarié . rémunération . avantages en nature . nourriture . montant forfaitaire d'un repas: unité: €/repas + formule: 4.85 références: urssaf.fr: https://www.urssaf.fr/portail/home/taux-et-baremes/avantages-en-nature/nourriture.html -? contrat salarié . rémunération . avantages en nature . nourriture . repas par mois -: période: mois +contrat salarié . rémunération . avantages en nature . nourriture . repas par mois: question: > Combien de repas par mois sont payés par l'employeur ? par défaut: 21 - unité: repas + unité: repas/mois suggestions: 1 par jour: 21 2 par jour: 42 contrat salarié . indemnités salarié: - période: flexible formule: somme: - CDD . indemnités salarié CDD @@ -1071,32 +1013,31 @@ contrat salarié . statut cadre: plafond sécurité sociale temps plein: description: Le plafond de Sécurité sociale est le montant maximum des rémunérations à prendre en compte pour le calcul de certaines cotisations. - période: mois acronyme: PSS - formule: 3377 € + formule: 3377 €/mois références: 2019: https://www.urssaf.fr/portail/home/actualites/toute-lactualite-employeur/plafond-de-la-securite-social-1.html arrêté: https://www.legifrance.gouv.fr/eli/arrete/2018/12/11/SSAS1833942A/jo/texte plafond horaire sécurité sociale: - période: aucune acronyme: PHSS - formule: plafond sécurité sociale temps plein / 135 - unité: € + formule: plafond sécurité sociale temps plein / 135 heures/mois + unité: €/heure plafond journalier sécurité sociale: - période: aucune acronyme: PJSS - formule: plafond sécurité sociale temps plein / 18.2 - unité: € + formule: plafond sécurité sociale temps plein / 18.2 jours/mois + unité: €/jour contrat salarié . plafond sécurité sociale: - période: flexible acronyme: PSS + unité: €/mois + formule: plafond sécurité sociale temps plein * temps de travail . quotité de travail contrat salarié . SMIC temps plein: - période: mois + unité: €/mois + formule: multiplication: assiette: temps de travail . base légale * période . semaines par mois @@ -1105,10 +1046,8 @@ contrat salarié . SMIC temps plein: décret: https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000037833206 contrat salarié . SMIC temps plein . net imposable: - période: mois - unité: € description: Montant du SMIC net imposable - formule: 1247.55 + formule: 1247.55 €/mois note: Ce montant est codé en dur, il faudrait le calculer à partir du montant du SMIC brut références: barème PAS: https://bofip.impots.gouv.fr/bofip/11255-PGP.html @@ -1122,7 +1061,6 @@ SMIC horaire: contrat salarié . SMIC contractuel: description: > Valeur du SMIC pro-ratisé pour prendre en compte le temps partiel et utilisé pour la détermination du salaire minimum - période: flexible formule: SMIC temps plein * temps de travail . quotité de travail contrat salarié . SMIC: @@ -1130,47 +1068,43 @@ contrat salarié . SMIC: Plusieurs réductions de cotisations ([réduction générale](/documentation/contrat-salarié/réduction-générale), taux réduit d'[allocations familiales](/documentation/contrat-salarié/allocations-familiales/taux-réduit) et de [maladie](/documentation/contrat-salarié/maladie/taux-employeur/taux-réduit), réduction outre-mer) reposent sur un paramètre SMIC faisant l'objet de plusieurs ajustements pour prendre en compte le temps de travail effectif. Les heures supplémentaires sont prises en compte sans tenir compte de la majoration. - période: flexible formule: temps de travail * SMIC horaire références: Détermination du SMIC: https://www.urssaf.fr/portail/home/employeur/beneficier-dune-exoneration/exonerations-generales/la-reduction-generale/le-calcul-de-la-reduction/etape-1--determination-du-coeffi/determination-du-smic-a-prendre.html contrat salarié . cotisations . salariales: titre: cotisations salariales - unité: € - période: flexible + formule: somme: - - vieillesse [salarié] - - maladie [salarié] - - retraite complémentaire [salarié] - - contribution d'équilibre général [salarié] - - contribution d'équilibre technique [salarié] - - chômage [salarié] + - vieillesse .salarié + - maladie .salarié + - retraite complémentaire .salarié + - contribution d'équilibre général .salarié + - contribution d'équilibre technique .salarié + - chômage .salarié - CSG - CRDS - - APEC [salarié] - - complémentaire santé [salarié] + - APEC .salarié + - complémentaire santé .salarié - conventionnelles - (- réduction heures supplémentaires) contrat salarié . cotisations . patronales: titre: cotisations patronales - période: flexible - unité: € formule: somme: - - maladie [employeur] + - maladie .employeur - ATMP - prévoyance obligatoire cadre - - vieillesse [employeur] - - retraite complémentaire [employeur] - - complémentaire santé [employeur] - - contribution d'équilibre général [employeur] - - contribution d'équilibre technique [employeur] + - vieillesse .employeur + - retraite complémentaire .employeur + - complémentaire santé .employeur + - contribution d'équilibre général .employeur + - contribution d'équilibre technique .employeur - allocations familiales - - chômage [employeur] - - APEC [employeur] + - chômage .employeur + - APEC .employeur - AGS - FNAL - participation effort de construction @@ -1190,17 +1124,16 @@ contrat salarié . rémunération: contrat salarié . rémunération . net de cotisations: titre: Salaire net de cotisations type: rémunération - unité: € - période: flexible + unité: €/mois + formule: brut - cotisations . salariales contrat salarié . rémunération . net imposable: titre: Salaire net imposable type: salaire - unité: € + unité par défaut: €/mois description: | C'est la base utilisée pour calculer l'impôt sur le revenu. - période: flexible formule: allègement: assiette: base @@ -1214,19 +1147,15 @@ contrat salarié . rémunération . net imposable: DSN: https://dsn-info.custhelp.com/app/answers/detail/a_id/2110 contrat salarié . rémunération . net imposable . base: - unité: € - période: flexible description: Le net imposable avant les exonérations et déductions formule: somme: - rémunération . net de cotisations - avantages sociaux - - CSG [non déductible] + - CSG .non déductible - CRDS -? contrat salarié . rémunération . net imposable . heures supplémentaires défiscalisées -: période: flexible - unité: € +contrat salarié . rémunération . net imposable . heures supplémentaires défiscalisées: formule: encadrement: valeur: heures supplémentaires @@ -1234,17 +1163,14 @@ contrat salarié . rémunération . net imposable . base: références: DSN: https://dsn-info.custhelp.com/app/answers/detail/a_id/2110 -? contrat salarié . rémunération . net imposable . heures supplémentaires défiscalisées . plafond brut -: période: année - formule: 5358 € +contrat salarié . rémunération . net imposable . heures supplémentaires défiscalisées . plafond brut: + formule: 5358 €/an références: DSN: https://dsn-info.custhelp.com/app/answers/detail/a_id/2110 contrat salarié . prime d'impatriation: description: La prime d'impatriation est une partie de la rémunération exonérée d'impôt sur le revenu. applicable si: régime des impatriés - période: flexible - unité: € formule: multiplication: assiette: rémunération . net imposable . base @@ -1256,7 +1182,6 @@ contrat salarié . prime d'impatriation: contrat salarié . rémunération . net: titre: Salaire net type: salaire - unité: € question: Quel est le salaire net ? résumé: Salaire net avant impôt description: | @@ -1265,7 +1190,6 @@ contrat salarié . rémunération . net: Aussi appelé salaire net à payer (c'était du moins le cas avant l'impôt à la source). Cette somme peut varier en fonction de décisions politiques (augmentation ou diminution des cotisations) alors que le salaire brut est contractuel (pour le changer, il faut signer un avenant au contrat). - période: flexible formule: rémunération . net de cotisations - avantages en nature . montant contrat salarié . rémunération . net après impôt: @@ -1273,8 +1197,7 @@ contrat salarié . rémunération . net après impôt: résumé: Versé sur le compte bancaire question: Quel est le revenu net du salarié après impôt ? type: salaire - unité: € - période: flexible + unité par défaut: €/mois description: | Le 1er janvier 2019, l'impôt sur le revenu est prélevé à la source et apparaît donc sur la fiche de paie. @@ -1288,11 +1211,11 @@ contrat salarié . rémunération . net après impôt: formule: net - impôt -? impôt . taux neutre d'impôt sur le revenu . barème Guadeloupe Réunion Martinique -: icônes: 🇬🇵🇷🇪 🇲🇶 +impôt . taux neutre d'impôt sur le revenu . barème Guadeloupe Réunion Martinique: + icônes: 🇬🇵🇷🇪 🇲🇶 formule: barème linéaire: - assiette: revenu imposable [mensuel] + assiette: revenu imposable [€/mois] retourne seulement le taux: oui tranches: - de: 0 @@ -1378,7 +1301,7 @@ impôt . taux neutre d'impôt sur le revenu . barème Guyane Mayotte: icônes: 🇬🇾 🇾🇹 formule: barème linéaire: - assiette: revenu imposable [mensuel] + assiette: revenu imposable [€/mois] retourne seulement le taux: oui tranches: - de: 0 @@ -1480,7 +1403,7 @@ impôt . taux neutre d'impôt sur le revenu: alors: barème Guyane Mayotte - sinon: barème linéaire: - assiette: revenu imposable [mensuel] + assiette: revenu imposable [€/mois] retourne seulement le taux: oui tranches: - de: 0 @@ -1579,7 +1502,6 @@ contrat salarié . prix du travail: titre: Coût total résumé: Dépensé par l'entreprise question: Quel est le coût total de cette embauche ? - période: flexible description: | Coût total d'embauche d'un salarié en incluant, en plus des éléments de rémunération, les aides différées et les coûts de medecine du travail > C'est donc aussi une mesure de la valeur apportée par le salarié à l'entreprise : l'employeur est prêt à verser cette somme en contrepartie du travail fourni. @@ -1590,15 +1512,14 @@ contrat salarié . prix du travail: - rémunération . total - (- aides employeur) - médecine du travail - unité: € + unité par défaut: €/mois contrat salarié . rémunération . total: titre: Total chargé question: Quel est la rémunération chargée ? résumé: Dépensé par l'entreprise type: salaire - unité: € - période: flexible + unité par défaut: €/mois description: | C'est le total que l'employeur doit verser pour employer un salarié. formule: @@ -1607,8 +1528,6 @@ contrat salarié . rémunération . total: - cotisations . patronales contrat salarié . cotisations . patronales . réductions de cotisations: - période: flexible - unité: € formule: somme: - réduction générale @@ -1617,8 +1536,7 @@ contrat salarié . cotisations . patronales . réductions de cotisations: - réduction ACRE - déduction heures supplémentaires -? contrat salarié . cotisations . patronales . réductions de cotisations . déduction heures supplémentaires -: période: flexible +contrat salarié . cotisations . patronales . réductions de cotisations . déduction heures supplémentaires: applicable si: entreprise . effectif < 20 titre: déduction forfaitaire pour heures supplémentaires formule: @@ -1634,7 +1552,6 @@ contrat salarié . réduction ACRE: toutes ces conditions: - dirigeant = 'assimilé salarié' - entreprise . ACRE - période: flexible formule: multiplication: assiette: @@ -1646,7 +1563,6 @@ contrat salarié . réduction ACRE: contrat salarié . réduction ACRE . taux: titre: taux ACRE - période: flexible formule: barème continu: assiette: cotisations . assiette @@ -1663,14 +1579,11 @@ contrat salarié . cotisations . salariales . réduction heures supplémentaires dû par: salarié aide: type: réduction de cotisations - unité: € - période: flexible formule: rémunération . heures supplémentaires * taux des cotisations réduites références: Code de la sécurité sociale - Article D241-21: https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000038056813&cidTexte=LEGITEXT000006073189 -? contrat salarié . cotisations . salariales . réduction heures supplémentaires . taux des cotisations réduites -: période: aucune +contrat salarié . cotisations . salariales . réduction heures supplémentaires . taux des cotisations réduites: unité: '%' description: le taux effectif des cotisations d'assurance vieillesse à la charge du salarié formule: @@ -1679,9 +1592,9 @@ contrat salarié . cotisations . salariales . réduction heures supplémentaires multiplication: assiette: somme: - - vieillesse [salarié] - - retraite complémentaire [salarié] - - contribution d'équilibre général [salarié] + - vieillesse .salarié + - retraite complémentaire .salarié + - contribution d'équilibre général .salarié facteur: 1 / assiette plafond: 11.31% références: @@ -1689,7 +1602,6 @@ contrat salarié . cotisations . salariales . réduction heures supplémentaires Circulaire DSS/5B/2019/71: http://circulaire.legifrance.gouv.fr/pdf/2019/04/cir_44492.pdf contrat salarié . cotisations: - période: flexible description: Total des cotisations patronales et salariales formule: somme: @@ -1698,21 +1610,20 @@ contrat salarié . cotisations: contrat salarié . cotisations . salariales . conventionnelles: titre: cotisations salariales conventionnelles - période: flexible description: Cotisations spécifiques à la convention collective + unité par défaut: €/mois formule: 0 contrat salarié . cotisations . patronales . conventionnelles: titre: cotisations patronales conventionnelles - période: flexible description: Cotisations spécifiques à la convention collective + unité par défaut: €/mois formule: 0 contrat salarié . aides employeur: titre: aides à l'embauche résumé: Pour l'employeur, différées dans le temps icônes: 🎁 - période: flexible description: | Ces aides sont appelées différées, car elles ne consistent pas en une simple réduction des cotisations mensuelles : elles interviendront a posteriori par exemple sous forme de crédit d'impôt. @@ -1731,20 +1642,19 @@ contrat salarié . aides employeur . aide à l'embauche d'apprentis: toutes ces conditions: - entreprise . effectif < 250 - apprentissage - - contrat salarié . apprentissage . diplôme préparé . niveau bac ou moins + - apprentissage . diplôme préparé . niveau bac ou moins # HACK: "apprentissage . ancienneté" n'est pas détecté par le moteur dans les dépendances ("missingVariables") de cette aide. # On l'ajoute ici uniquement pour faire remonter la question au bon niveau, mais ça ne devrait pas être nécessaire. - apprentissage . ancienneté - période: année - unité: € + formule: variations: - si: apprentissage . ancienneté = 'moins d'un an' - alors: 4125 + alors: 4125 €/an - si: apprentissage . ancienneté = 'moins de deux ans' - alors: 2000 - - sinon: 1200 + alors: 2000 €/an + - sinon: 1200 €/an références: Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23556 @@ -1752,29 +1662,6 @@ entreprise: description: | Le contrat lie une entreprise, identifiée par un code SIREN, et un employé. -entreprise . prélèvements obligatoires: - période: flexible - formule: - somme: - - CVAE - - CFE - -entreprise . prélèvements obligatoires . CVAE: - titre: Cotisation sur la valeur ajoutée des entreprises - période: flexible - formule: 0 - note: Cette contribution concerne les entreprises ou travailleurs indépendants qui réalisent plus de 500 000 € de chiffre d'affaires. Elle n'est pour l'instant **pas intégrée dans nos calculs**. - références: - Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23546 - -entreprise . prélèvements obligatoires . CFE: - titre: Cotisation foncière des entreprises - formule: 0 - période: flexible - note: Cette cotisation n'est pas due pour la première année d'une entreprise, ni pour un chiffre d'affaires inférieur à 5000€. La CFE n'est **pas encore intégrée** dans nos calculs. - références: - Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23547 - entreprise . effectif: question: Quel est l'effectif de l'entreprise ? description: | @@ -1847,7 +1734,6 @@ entreprise . établissement bancaire: chemin: nom établissement . taux du versement transport: - unité: '%' formule: synchronisation: API: localisation @@ -1869,17 +1755,15 @@ entreprise . établissement bancaire: - établissement . localisation . département = 'Mayotte' contrat salarié . temps de travail: - unité: heures + unité: heures/mois formule: somme: - temps contractuel - heures supplémentaires - période: flexible description: En France, la base légale du travail est de 35h/semaine. Mais un grand nombre de dispositions existantes permettent de faire varier ce nombre. Vous pouvez les retrouver sur la page [service-public.fr](https://www.service-public.fr/particuliers/vosdroits/N458) dédiée. contrat salarié . temps de travail . temps contractuel: - unité: heures - période: mois + unité: heures/mois formule: multiplication: assiette: temps hebdomadaire @@ -1897,7 +1781,6 @@ contrat salarié . temps de travail . base légale: formule: 35 heures/semaine contrat salarié . temps de travail . temps partiel: - période: aucune question: Le contrat est-il à temps partiel ? description: | Deux contrats au même salaire, l'un à temps partiel, l'autre à temps complet, peuvent donner lieu à des montants de cotisation différents. @@ -1919,10 +1802,9 @@ contrat salarié . temps de travail . temps partiel . heures par semaine: contrat salarié . temps de travail . quotité de travail: description: Temps de travail en proportion du temps complet légal. - période: aucune formule: encadrement: - valeur: temps de travail [mensuel] / (base légale * période . semaines par mois) + valeur: temps de travail / (base légale * période . semaines par mois) plafond: 100% unité: '%' @@ -1931,8 +1813,7 @@ contrat salarié . temps de travail . heures supplémentaires: titre: Nombre d'heures supplémentaires question: Combien d'heures supplémentaires (non récupérées en repos) sont effectuées par mois ? par défaut: 0 - unité: heures - période: mois + unité: heure/mois suggestions: aucune: 0 39h / semaine: 17.33 @@ -1940,17 +1821,17 @@ contrat salarié . temps de travail . heures supplémentaires: contrôles: - si: toutes ces conditions: - - heures supplémentaires > 9 * 4.33 - - heures supplémentaires <= 13 * 4.33 + - heures supplémentaires > 9 heures/semaine * période . semaines par mois + - heures supplémentaires <= 13 heures/semaine * période . semaines par mois niveau: info message: La durée hebdomadaire moyenne de travail ne peut pas dépasser 44h - - si: heures supplémentaires > 13 * 4.33 + - si: heures supplémentaires > 13 heures/semaine * période . semaines par mois niveau: avertissement message: La durée hebdomadaire maximale de travail ne peut pas dépasser 48h - si: toutes ces conditions: - temps partiel - - heures supplémentaires > 0 + - heures supplémentaires > 0 heure/mois niveau: info message: Le simulateur ne prends pas encore en compte les heures complémentaires pour les temps partiels. Si cette fonctionnalité vous manque, envoyez-nous un mail à contact@mon-entreprise.beta.gouv.fr @@ -1966,12 +1847,11 @@ contrat salarié . temps de travail . heures supplémentaires . majoration: - 50 % pour les heures suivantes. titre: majoration heures supplémentaires note: Pour l'instant, nous implémentons uniquement les taux standards et ceux de la convention HCR (Hôtel café restaurant). Si vous dépendez d'une convention avec des taux spécifiques, merci de nous le signaler à `contact@mon-entreprise.beta.gouv.fr` - période: mois - unité: heure + unité: heure/mois formule: barème: assiette: heures supplémentaires - multiplicateur: période . semaines par mois + multiplicateur: 1 heure/semaine * période . semaines par mois tranches: - en-dessous de: 8 taux: 25% @@ -2002,17 +1882,18 @@ contrat salarié . statut JEI . exonération de cotisations: calcul: https://www.urssaf.fr/portail/home/employeur/beneficier-dune-exoneration/exonerations-ou-aides-liees-au-s/jeunes-entreprises-innovantes/quelle-exoneration.html cumuls: https://www.legisocial.fr/actualites-sociales/2068-comment-declarer-les-cotisations-dallocations-familiales-si-lentreprise-beneficie-du-regime-jei.html - période: mois + unité: €/mois + formule: # TODO - le plafonnement à 4,5 SMIC, précalculé pour 09/2017; cette approximation n'est bien sûr pas satisfaisante, # il faut fournir un mécanisme "exonération" capable de recalculer une règle en introduisant un plafond encadrement: - plafond: 1634.39 € + plafond: 1634.39 €/mois valeur: somme: - allocations familiales - - maladie [employeur] - - vieillesse [employeur] + - maladie .employeur + - vieillesse .employeur contrat salarié . réduction générale: aide: @@ -2027,7 +1908,6 @@ contrat salarié . réduction générale: calcul: https://www.urssaf.fr/portail/home/employeur/beneficier-dune-exoneration/exonerations-generales/la-reduction-generale.html cumuls: https://www.legisocial.fr/actualites-sociales/2068-comment-declarer-les-cotisations-dallocations-familiales-si-lentreprise-beneficie-du-regime-jei.html applicable si: cotisations . assiette <= plafond de l'assiette - période: flexible formule: encadrement: valeur: assiette @@ -2053,13 +1933,13 @@ contrat salarié . réduction générale: valeur attendue: 0 contrat salarié . réduction générale . écart au plafond de l'assiette: - période: flexible formule: plafond de l'assiette - cotisations . assiette contrat salarié . réduction générale . multiplicateur: formule: paramètre T / 0.6 contrat salarié . réduction générale . paramètre T: + unité: '' formule: variations: - si: entreprise . effectif < 20 @@ -2068,22 +1948,20 @@ contrat salarié . réduction générale . paramètre T: contrat salarié . réduction générale . assiette: titre: Assiette de la réduction générale - période: flexible formule: somme: - allocations familiales - - FNAL [employeur] - - maladie [employeur] - - vieillesse [employeur] + - FNAL .employeur + - maladie .employeur + - vieillesse .employeur - part de la cotisation ATMP - - retraite complémentaire [employeur] - - contribution d'équilibre général [employeur] - - chômage [employeur] + - retraite complémentaire .employeur + - contribution d'équilibre général .employeur + - chômage .employeur références: changements 2019: https://www.urssaf.fr/portail/home/actualites/toute-lactualite-employeur/la-reduction-generale-des-cotisa.html contrat salarié . réduction générale . assiette . part de la cotisation ATMP: - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2094,7 +1972,6 @@ contrat salarié . réduction générale . assiette . part de la cotisation ATMP Code de la sécurité sociale - Mise à jour du taux: https://www.legifrance.gouv.fr/affichTexteArticle.do;jsessionid=B2573099C91B1ACDA218B214D650C071.tplgfr25s_3?idArticle=JORFARTI000037884643&cidTexte=JORFTEXT000037884638&dateTexte=29990101&categorieLien=id contrat salarié . réduction générale . plafond de l'assiette: - période: flexible formule: 1.6 * SMIC contrat salarié . contribution d'équilibre général: @@ -2103,7 +1980,6 @@ contrat salarié . contribution d'équilibre général: branche: retraite type de retraite: complémentaire destinataire: AGIRC-ARRCO - période: flexible formule: barème: assiette: cotisations . assiette @@ -2137,7 +2013,6 @@ contrat salarié . contribution d'équilibre technique: type de retraite: complémentaire destinataire: AGIRC-ARRCO applicable si: cotisations . assiette > plafond sécurité sociale - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2159,7 +2034,6 @@ contrat salarié . retraite complémentaire: destinataire: AGIRC-ARRCO description: | Cotisations de retraite complémentaire. - période: flexible formule: barème: assiette: cotisations . assiette @@ -2204,7 +2078,6 @@ contrat salarié . AGS: references: calcul: https://www.service-public.fr/professionnels-entreprises/vosdroits/F31409 - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2212,7 +2085,6 @@ contrat salarié . AGS: taux: 0.15% contrat salarié . allocations familiales: - période: flexible cotisation: dû par: employeur branche: famille @@ -2224,7 +2096,6 @@ contrat salarié . allocations familiales: calcul: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-cotisation-dallocations-famil.html contrat salarié . allocations familiales . taux: - période: aucune formule: variations: - si: taux réduit @@ -2234,12 +2105,10 @@ contrat salarié . allocations familiales . taux: calcul: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-cotisation-dallocations-famil.html contrat salarié . allocations familiales . taux réduit: - période: flexible formule: cotisations . assiette < plafond de réduction contrat salarié . allocations familiales . taux réduit . plafond de réduction: titre: Plafond de la réduction des allocations familiales - période: flexible formule: SMIC * 3.5 contrat salarié . APEC: @@ -2255,7 +2124,6 @@ contrat salarié . APEC: applicable si: statut cadre - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2278,7 +2146,6 @@ contrat salarié . chômage: calcul: http://www.pole-emploi.fr/employeur/taux-des-contributions-de-l-assurance-chomage-et-cotisations-ags-@/article.jspz?id=61567 urssaf: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/lassurance-chomage-et-lags/les-taux.html changements 2017: https://www.urssaf.fr/portail/home/actualites/toute-lactualite-employeur/contributions-patronales-dassura.html - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2317,7 +2184,6 @@ contrat salarié . complémentaire santé: branche: santé références: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F20739 - période: flexible formule: multiplication: assiette: forfait @@ -2337,17 +2203,17 @@ contrat salarié . complémentaire santé: - nom: forfait à 100€ payé par l'employeur situation: forfait: 100 - part employeur: 1 + part employeur: 100 valeur attendue: 100 contrat salarié . complémentaire santé . part employeur: description: Part de la complémentaire santé payée par l'employeur. Doit être de 50% minimum - question: Quel est la part de la complémentaire santé payée par l'employeur ? + question: Quelle est la part de la complémentaire santé payée par l'employeur ? unité: '%' suggestions: - 50%: 0.50 - 100%: 1 - par défaut: 0.50 + 50%: 50 + 100%: 100 + par défaut: 50 contrôles: - si: part employeur < 50% niveau: avertissement @@ -2355,12 +2221,11 @@ contrat salarié . complémentaire santé . part employeur: contrat salarié . complémentaire santé . part salarié: description: Part de la complémentaire santé payée par l'employé. Ne peut pas être supérieure à 50% - unité: '%' formule: 100% - part employeur contrat salarié . complémentaire santé . forfait: titre: Forfait de complémentaire santé entreprise - période: flexible + unité par défaut: €/mois description: | L'employeur a l'obligation de proposer une offre de complémentaire santé. Il doit prendre en charge la moitié du montant, ce que nous avons retenu pour cette simulation, ou davantage. Le montant est libre, tant qu'elle couvre un panier légal de soins. @@ -2372,7 +2237,7 @@ contrat salarié . complémentaire santé . forfait: alors: en alsace moselle - sinon: en france contrôles: - - si: complémentaire santé . forfait [mensuel] < 15 + - si: complémentaire santé . forfait < 15 €/mois message: Vérifiez bien qu'une complémentaire santé si peu chère couvre le panier de soin minimal défini dans la loi. niveau: avertissement @@ -2384,8 +2249,8 @@ contrat salarié . complémentaire santé . forfait . en alsace moselle: Une étude de Meilleureassurance.com nous permet de supposer qu'il vaut en moyenne ~ 70% du prix moyen en France. références: étude Meilleureassurance.com: http://www.lefigaro.fr/conjoncture/2018/10/16/20002-20181016ARTFIG00248-les-tarifs-des-complementaires-sante-font-le-grand-ecart-d-un-departement-a-l-autre.php - période: mois - unité: € + unité: €/mois + par défaut: 30 suggestions: basique: 30 @@ -2394,8 +2259,8 @@ contrat salarié . complémentaire santé . forfait . en alsace moselle: contrat salarié . complémentaire santé . forfait . en france: titre: forfait complémentaire santé en France question: Quel est le montant mensuel total (salarié et employeur) de la complémentaire santé entreprise ? - période: mois - unité: € + unité: €/mois + par défaut: 40 suggestions: basique: 40 @@ -2426,7 +2291,6 @@ contrat salarié . contribution au dialogue social: - https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-contribution-patronale-au-dia.html - https://www.service-public.fr/professionnels-entreprises/vosdroits/F33308 - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2434,7 +2298,6 @@ contrat salarié . contribution au dialogue social: contrat salarié . assiette CSG et CRDS: note: Cette assiette est complexe, cette version n'est qu'une simplification. - période: flexible références: calcul: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-csg-crds/les-revenus-salariaux-soumis-a-l.html abattement: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-csg-crds/abattement-et-deductions/les-revenus-exclus-de-labattemen.html @@ -2445,10 +2308,9 @@ contrat salarié . assiette CSG et CRDS: - avantages sociaux contrat salarié . assiette CSG et CRDS . assiette abattue: - période: flexible formule: barème: - assiette: cotisations . assiette + assiette: cotisations . assiette [€/mois] multiplicateur: plafond sécurité sociale # c'est en fait un abattement de 1,75% sur la partie en-dessous de 4 fois le plafond tranches: @@ -2458,7 +2320,6 @@ contrat salarié . assiette CSG et CRDS . assiette abattue: taux: 100% contrat salarié . CSG . assiette heures supplémentaires défiscalisées: - période: flexible formule: multiplication: assiette: rémunération . net imposable . heures supplémentaires défiscalisées @@ -2467,8 +2328,6 @@ contrat salarié . CSG . assiette heures supplémentaires défiscalisées: DSN: https://dsn-info.custhelp.com/app/answers/detail/a_id/2110 contrat salarié . CSG . assiette CSG déductible: - période: flexible - unité: € formule: assiette CSG et CRDS - assiette heures supplémentaires défiscalisées contrat salarié . CSG: @@ -2478,7 +2337,6 @@ contrat salarié . CSG: description: | Contribution sociale généralisée. Prélèvement obligatoire qui participe au financement de la sécurité sociale. - période: flexible formule: multiplication: composantes: @@ -2516,7 +2374,6 @@ contrat salarié . CRDS: impôt: oui dû par: salarié description: Contribution pour le remboursement de la dette sociale - période: flexible formule: multiplication: assiette: assiette CSG et CRDS @@ -2532,7 +2389,6 @@ contrat salarié . FNAL: branche: famille références: calcul: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-contribution-au-fonds-nationa.html - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2575,7 +2431,6 @@ contrat salarié . formation professionnelle: - taux de **0,70 %** pour le franchissement en année **N+3** (1,3 % pour les entreprises de travail temporaire) - taux de **0,90 %** pour le franchissement en année **N+4** (1,3 % pour les entreprises de travail temporaire) - taux de **1 %** pour le franchissement en année **N+5** (1,3 % pour les entreprises de travail temporaire) - période: flexible non applicable si: toutes ces conditions: - entreprise . effectif < 11 @@ -2603,7 +2458,6 @@ contrat salarié . maladie: fiche: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-cotisation-maladie---maternit.html Décret n° 2017-1891 relatif au taux des cotisations d'assurance maladie: https://www.legifrance.gouv.fr/eli/decret/2017/12/30/CPAS1732212D/jo/texte Réduction 2019: https://www.urssaf.fr/portail/home/actualites/toute-lactualite-employeur/une-reduction-des-cotisations-pa.html - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2626,7 +2480,6 @@ contrat salarié . maladie: taux: 0.3% contrat salarié . maladie . taux employeur: - période: aucune formule: variations: - si: taux réduit @@ -2634,11 +2487,9 @@ contrat salarié . maladie . taux employeur: - sinon: 13% contrat salarié . maladie . taux employeur . taux réduit: - période: flexible formule: cotisations . assiette < plafond de réduction employeur contrat salarié . maladie . taux salarié: - période: aucune formule: variations: - si: régime alsace moselle @@ -2646,7 +2497,6 @@ contrat salarié . maladie . taux salarié: - sinon: 0% contrat salarié . maladie . plafond de réduction employeur: - période: flexible formule: 2.5 * SMIC contrat salarié . médecine du travail: @@ -2662,8 +2512,7 @@ contrat salarié . médecine du travail: L'employeur a l'obligation d'organiser un service de santé au travail, en adhérant à un service interentreprises, ou en créant un service interne. Dans le cas de l'adhésion à un service, le montant de cette cotisation n'est pas défini par la loi, mais il doit être proportionnel au nombre d'employés. Nous avons choisi un montant indicatif (voir les références) ajusté avec l'inflation depuis 2007. - période: année - formule: 80 + formule: 80 €/an contrat salarié . participation effort de construction: titre: Participation à l'effort de construction @@ -2680,7 +2529,6 @@ contrat salarié . participation effort de construction: L'employeur a le choix entre verser cet impôt à un "organisme du 1% patronal" agréé, investir la somme dans le logement de ses salariés, ou accorder à eux et leur famille des prêts de construction à taux réduit. applicable si: entreprise . effectif >= 20 - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2699,7 +2547,6 @@ contrat salarié . prévoyance obligatoire cadre: références: minimum: http://www.axios.fr/150-tranche-a-evitez-une-erreur-a-160-000-euros applicable si: statut cadre - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2722,7 +2569,6 @@ contrat salarié . taxe d'apprentissage: csa: http://www.opcalia.com/employeurs/financer-la-formation-et-lapprentissage/taxe-dapprentissage/contribution-supplementaire-a-lapprentissage-csa/ note: Taxe complexe, comportant notamment des exonérations non prises en compte ici. - période: flexible formule: somme: - base @@ -2730,7 +2576,6 @@ contrat salarié . taxe d'apprentissage: contrat salarié . taxe d'apprentissage . assiette: titre: assiette de la taxe d'apprentissage - période: flexible description: Le salaire des apprentis est partiellement exonéré dans la base de calcul de la taxe d'apprentissage. formule: variations: @@ -2747,7 +2592,6 @@ contrat salarié . taxe d'apprentissage . assiette: contrat salarié . taxe d'apprentissage . base: titre: taxe d'apprentissage de base - période: flexible formule: multiplication: assiette: assiette @@ -2763,7 +2607,6 @@ contrat salarié . taxe d'apprentissage . contribution supplémentaire: - entreprise . effectif >= 250 - entreprise . ratio alternants < 5% - période: flexible formule: multiplication: assiette: assiette @@ -2801,7 +2644,6 @@ contrat salarié . taxe d'apprentissage . csa au taux majoré: - entreprise . ratio alternants < 1% contrat salarié . taxe sur les salaires . assiette de base: - période: flexible formule: somme: - cotisations . assiette @@ -2810,7 +2652,7 @@ contrat salarié . taxe sur les salaires . assiette de base: assiette: http://bofip.impots.gouv.fr/bofip/6690-PGP.html contrat salarié . taxe sur les salaires . assiette: - période: flexible + unité par défaut: €/mois formule: allègement: assiette: assiette de base @@ -2822,10 +2664,11 @@ contrat salarié . taxe sur les salaires . assiette: contrat salarié . taxe sur les salaires . barème: références: barème: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22576 - période: année + unité: €/an + formule: barème: - assiette: assiette + assiette: assiette [€/an] tranches: - en-dessous de: 7799 taux: 4.25% @@ -2871,22 +2714,20 @@ contrat salarié . régime des impatriés: Article 155B du Code général des impôts: https://www.legifrance.gouv.fr/affichCodeArticle.do?cidTexte=LEGITEXT000006069577&idArticle=LEGIARTI000006307476&dateTexte=&categorieLien=cid entreprise . taxe sur les salaires . barème: - période: flexible - formule: contrat salarié . taxe sur les salaires . barème * effectif + formule: contrat salarié . taxe sur les salaires . barème / 1 employé * effectif entreprise . taxe sur les salaires . abattement associations: applicable si: entreprise . association non lucrative - période: année - formule: 20507 + # TODO : 20835 + formule: 20507 €/an entreprise . taxe sur les salaires: - période: année formule: allègement: assiette: barème - franchise: 1200 + franchise: 1200 €/an décote: - plafond: 2040 + plafond: 2040 €/an taux: 75% abattement: abattement associations @@ -2898,8 +2739,7 @@ contrat salarié . taxe sur les salaires: une de ces conditions: - entreprise . association non lucrative - entreprise . établissement bancaire - période: flexible - formule: entreprise . taxe sur les salaires / entreprise . effectif + formule: entreprise . taxe sur les salaires * 1 employé / entreprise . effectif note: Cette implémentation de la taxe sur les salaires est spécifique aux associations à but non lucratif, elle est donc largement simplifiée. Plein d'autres organisations sont concernées, en fonction de la TVA qu'elles paient. Les associations y sont assujetties automatiquement. exemples: @@ -2914,12 +2754,13 @@ contrat salarié . taxe sur les salaires: entreprise . effectif: 1 valeur attendue: 0 - nom: association non lucrative + unités par défaut: [€/mois] situation: entreprise . association non lucrative: oui rémunération . brut de base: 2300 entreprise . effectif: 10 complémentaire santé . forfait: 0 - valeur attendue: 48.1 + valeur attendue: 48.10 références: fiche: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22576 @@ -2930,7 +2771,6 @@ contrat salarié . versement transport: cotisation: branche: transport dû par: employeur - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -2946,7 +2786,6 @@ contrat salarié . vieillesse: destinataire: CNAV # CTP: 100 description: Cotisation au régime de retraite de base des salariés. - période: flexible formule: multiplication: assiette: cotisations . assiette @@ -3005,7 +2844,6 @@ contrat salarié . forfait social: Fiche URSSAF: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/le-forfait-social.html Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F31532 Code du travail - Article L137-15: https://www.legifrance.gouv.fr/affichCode.do?idSectionTA=LEGISCTA000019950196&cidTexte=LEGITEXT000006073189 - période: flexible formule: multiplication: assiette: avantages sociaux @@ -3018,8 +2856,6 @@ impôt: icônes: 🏛️ description: Cet ensemble de formules est un modèle ultra-simplifié de l'impôt sur le revenu, qui ne prend en compte que l'abattement 10%, le barème et la décôte. titre: impôts sur le revenu - période: flexible - unité: € formule: somme: - variations: @@ -3054,7 +2890,6 @@ impôt . méthode de calcul: # applicable si: revenu imposable > 0 # bizarrement, cette condition ne semble pas marcher, on se résout donc à utiliser une version plus "hacky" et moins proche de la loi. Elle posera problème le jour où l'on aura a calculer l'impot avec plusieurs sources de revenu non applicable si: dirigeant . auto-entrepreneur . impôt . versement libératoire - période: aucune par défaut: barème standard formule: une possibilité: @@ -3091,7 +2926,6 @@ impôt . méthode de calcul . prélèvement à la source: impôt . revenu imposable: description: | C'est le revenu à prendre en compte pour calculer l'impôt avec un taux moyen d'imposition (neutre ou personnalisé). - période: flexible formule: allègement: assiette: @@ -3099,10 +2933,11 @@ impôt . revenu imposable: - contrat salarié . rémunération . net imposable - dirigeant . indépendant . revenu professionnel - dirigeant . auto-entrepreneur . impôt . revenu imposable - abattement: abattement contrat court + abattement: abattement contrat court / 1 an impôt . revenu imposable . abattement contrat court: - période: mois + unité: € + description: Lorsque la durée d'un contrat de travail est inférieure à 2 mois, il est possible d'appliquer un abattement pour diminuer le montant du prélèvement à la source. applicable si: toutes ces conditions: @@ -3110,7 +2945,7 @@ impôt . revenu imposable . abattement contrat court: - contrat salarié - contrat salarié . CDD - contrat salarié . CDD . durée contrat <= 2 - formule: 50% * contrat salarié . SMIC temps plein . net imposable [mois] + formule: 50% * contrat salarié . SMIC temps plein . net imposable * 1 mois note: Cet abattement s'applique aussi pour les conventions de stage ou les contrats de mission (intérim) de moins de 2 mois. références: Bofip - dispositions spécifiques aux contrats courts: https://bofip.impots.gouv.fr/bofip/11252-PGP.html?identifiant=BOI-IR-PAS-20-20-30-10-20180515 @@ -3119,14 +2954,12 @@ impôt . revenu abattu: description: | L'impôt est calculé sur un revenu abattu : il est diminué (par exemple de 10%) pour prendre en compte une estimation des *frais professionnels* de l'activité rémunérée. Par exemple, on peut considérer qu'un salarié use ses chaussures pour aller au travail. Ces chaussures, il les a acheté avec son argent, donc du revenu sur lequel il a injustement payé de l'impôt. - période: flexible formule: somme: - revenu abattu par défaut - dirigeant . auto-entrepreneur . impôt . revenu imposable impôt . revenu abattu par défaut: - période: flexible description: Dans le cas général, l'impôt est calculé après l'application d'un abattement forfaitaire fixe. Chacun peut néanmoins opter pour la déclaration de ses *frais réels*, qui viendront remplacer ce forfait par défaut. formule: allègement: @@ -3135,7 +2968,7 @@ impôt . revenu abattu par défaut: - contrat salarié . rémunération . net imposable - dirigeant . indépendant . revenu professionnel abattement: 10% - plafond: 12502 + plafond: 12502 €/an note: L'abattement a aussi un minimum fixé à 437€ par personne (voir la référence ci-dessous), mais cela n'impacte que les couples, or notre implémentation de l'impôt sur le revenu est pour l'instant limitée aux célibataires. références: Frais professionnels - forfait ou frais réels: https://www.service-public.fr/particuliers/vosdroits/F1989 @@ -3146,10 +2979,10 @@ impôt . impôt sur le revenu: Une contribution sur les hauts revenus ajoute deux tranches supplémentaires. Attention : pour un revenu de 100 000€ annuels, le contribuable ne paiera 41 000€ d'impôt (le taux de la 4ème tranche est 41%) ! Ces 41% sont appliqués uniquement à la part de ses revenus supérieure à 72 617€. - période: année + unité par défaut: €/an formule: barème: - assiette: revenu abattu + assiette: revenu abattu [€/an] tranches: - en-dessous de: 9964 taux: 0% @@ -3174,12 +3007,12 @@ impôt . impôt sur le revenu: impôt . impôt sur le revenu à payer: description: Une décote est appliquée après le barème de l'impôt sur le revenu, pour réduire l'impôt des bas revenus. - période: année + unité par défaut: €/an formule: allègement: assiette: impôt sur le revenu décote: - plafond: 1594 + plafond: 1594 €/an taux: 75% exemples: - nom: Salaire d'un cadre @@ -3188,7 +3021,8 @@ impôt . impôt sur le revenu à payer: valeur attendue: 7162 impôt . revenu fiscal de référence: - période: année + unité: €/an + description: le revenu fiscal de référence correspond au revenu abattu du foyer ajusté avec un mécanisme de quotient et majoré d'un certains nombre d'exonérations. Ces dernières sont réintégrées dans le calcul. formule: somme: @@ -3199,11 +3033,12 @@ impôt . revenu fiscal de référence: Article 1417 du Code général des impôts: https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000034596743&cidTexte=LEGITEXT000006069577&categorieLien=id&dateTexte=20170505 impôt . CEHR: - période: année + unité: €/an + note: Attention, ce barème concerne les foyers célibataires. Pour les couples, le barème est adapté pour ne pas leur appliquer la même imposition alors qu'ils sont individuellement deux fois moins riches. formule: barème: - assiette: revenu fiscal de référence + assiette: revenu fiscal de référence [€/an] tranches: - en-dessous de: 250000 taux: 0% @@ -3219,7 +3054,6 @@ impôt . CEHR: revenu net de cotisations: résumé: Avant impôt - période: flexible question: Quel revenu avant impôt voulez-vous toucher ? description: | Il s'agit du revenu net de cotisations et de charges, avant le paiement de l'impôt sur le revenu. @@ -3230,9 +3064,8 @@ revenu net de cotisations: - dirigeant . auto-entrepreneur . revenu net de cotisations revenu net après impôt: - unité: € + unité par défaut: €/mois résumé: Versé sur le compte bancaire - période: flexible question: Quel revenu voulez-vous toucher ? description: | Il s'agit du revenu net de charges, cotisations et d'impôts. @@ -3263,19 +3096,16 @@ entreprise . chiffre d'affaires: titre: chiffre d'affaires (H.T.) question: Quel est votre chiffre d'affaires envisagé ? résumé: Le montant des ventes réalisées - période: flexible - unité: € + unité par défaut: €/an formule: rémunération totale du dirigeant + charges entreprise . chiffre d'affaires minimum: description: Le montant minimum des ventes (H.T) à réaliser pour atteindre le seuil de rentabilité. - période: flexible question: Quel est votre chiffre d'affaires minimum envisagé ? - unité: € + unité par défaut: €/an formule: chiffre d'affaires entreprise . chiffre d'affaires de société: - période: flexible formule: somme: - rémunération totale du dirigeant / rémunération du dirigeant @@ -3289,19 +3119,17 @@ entreprise . rémunération du dirigeant: par défaut: 1 entreprise . bénéfice: - période: flexible formule: chiffre d'affaires - charges dont rémunération dirigeant entreprise . résultat net: résumé: Ce qu'il reste après impôt sur les sociétés - période: flexible formule: bénéfice - impôt sur les sociétés entreprise . impôt sur les sociétés: - période: année + unité: €/an formule: barème: - assiette: bénéfice + assiette: bénéfice [€/an] tranches: - en-dessous de: 38120 taux: 15% @@ -3314,18 +3142,16 @@ entreprise . impôt sur les sociétés: fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23575 entreprise . charges dont rémunération dirigeant: - période: flexible formule: charges + rémunération totale du dirigeant entreprise . rémunération totale du dirigeant: question: Quel montant pensez-vous dégager pour votre rémunération ? résumé: Dépensé par l'entreprise - unité: € + unité par défaut: €/an description: C'est ce que l'entreprise dépense en tout pour la rémunération du dirigeant. Cette rémunération "super-brute" inclut toutes les cotisations sociales à payer. On peut aussi considérer que c'est la valeur monétaire du travail du dirigeant. - période: flexible formule: variations: - si: dirigeant . indépendant @@ -3363,20 +3189,17 @@ entreprise . charges: références: Charges déductibles ou non du résultat fiscal d'une entreprise: https://www.service-public.fr/professionnels-entreprises/vosdroits/F31973 - période: flexible par défaut: 0 - unité: € + unité par défaut: €/an dirigeant . indépendant . cotisations et contributions . réduction ACRE: applicable si: entreprise . ACRE - période: flexible formule: multiplication: assiette: cotisations - cotisations . retraite complémentaire taux: taux ACRE dirigeant . indépendant . cotisations et contributions . réduction ACRE . taux ACRE: - période: flexible formule: barème continu: assiette: revenu professionnel @@ -3388,15 +3211,15 @@ dirigeant . indépendant . cotisations et contributions . réduction ACRE . taux retourne seulement le taux: oui dirigeant . indépendant . revenu net de cotisations: - formule: revenu professionnel - cotisations et contributions . CSG et CRDS [non déductible] - période: flexible + formule: revenu professionnel - cotisations et contributions . CSG et CRDS .non déductible résumé: Avant impôt question: Quel revenu avant impôt voulez-vous toucher ? description: Il s'agit du revenu net de cotisations et de charges, avant le paiement de l'impôt sur le revenu. - unité: € + unité par défaut: €/an dirigeant . indépendant . revenu professionnel: titre: revenu professionnel (net imposable) + unité par défaut: €/an description: | C'est le revenu net de cotisations déductibles du travailleur indépendant, qui sert de base au calcul des cotisations et de l'impôt pour les indépendants. @@ -3405,8 +3228,6 @@ dirigeant . indépendant . revenu professionnel: Il faut donc voir ce calcul comme *le montant qui devra de toute façon être payé* à court terme après 2 ans d'exercice. - période: flexible - unité: € formule: inversion numérique: avec: @@ -3523,8 +3344,8 @@ entreprise . catégorie d'activité . libérale règlementée: références: Liste des activités libérales: https://bpifrance-creation.fr/encyclopedie/trouver-proteger-tester-son-idee/verifiertester-son-idee/liste-professions-liberales -? entreprise . catégorie d'activité . libérale règlementée . type d'activité libérale règlementée -: formule: +entreprise . catégorie d'activité . libérale règlementée . type d'activité libérale règlementée: + formule: une possibilité: choix obligatoire: oui possibilités: @@ -3599,13 +3420,11 @@ entreprise . auto entreprise impossible: - rattachée à la CIPAV != oui note: D'autres conditions d'exclusions existent, il faudra les compléter, mais la question de la catégorie d'activité doit avant être complétée. - dirigeant . indépendant: rend non applicable: contrat salarié formule: dirigeant = 'indépendant' dirigeant . indépendant . cotisations et contributions . cotisations: - période: flexible références: assiettes et taux: https://www.secu-independants.fr/baremes/cotisations-et-contributions formule: @@ -3624,8 +3443,7 @@ dirigeant . indépendant . cotisations et contributions: - CSG et CRDS - formation professionnelle - (- réduction ACRE) - unité: € - période: flexible + unité par défaut: €/an entreprise . rattachement libéral règlementé: description: | @@ -3649,10 +3467,8 @@ entreprise . rattachement libéral règlementé: rend non applicable: - protection sociale . retraite - dirigeant . indépendant . cotisations et contributions . cotisations . indemnités journalières maladie - période: aucune dirigeant . indépendant . cotisations et contributions . cotisations . maladie: - période: flexible formule: variations: - si: entreprise . rattachement libéral règlementé @@ -3660,8 +3476,7 @@ dirigeant . indépendant . cotisations et contributions . cotisations . maladie: - sinon: artisans commerçants libéraux -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . libérale règlementée -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . libérale règlementée: titre: maladie libérale règlementée formule: barème continu: @@ -3674,7 +3489,6 @@ dirigeant . indépendant . cotisations et contributions . cotisations . maladie: dirigeant . indépendant . cotisations et contributions . cotisations . maladie . assiette: titre: assiette maladie - période: flexible formule: variations: - si: situation personnelle . RSA @@ -3686,18 +3500,16 @@ dirigeant . indépendant . cotisations et contributions . cotisations . maladie références: secu-independants.fr: https://www.secu-independants.fr/cotisations/calcul-des-cotisations/cotisations-minimales/ -? dirigeant . indépendant . cotisations et contributions . cotisations . indemnités journalières maladie -: titre: Maladie 2 +dirigeant . indépendant . cotisations et contributions . cotisations . indemnités journalières maladie: + titre: Maladie 2 description: Cotisations pour les indemnités journalières des indépendants. Si l'état de santé des artisans, commerçants, industriels et conjoints collaborateurs nécessite un arrêt de travail, une part de leur ancien revenu leur sera versé. - période: flexible formule: multiplication: assiette: maladie . assiette taux: 0.85% plafond: 5 * plafond sécurité sociale temps plein -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux: formule: barème: assiette: assiette @@ -3716,16 +3528,14 @@ dirigeant . indépendant . cotisations et contributions . cotisations . maladie Le terme "lorsque" laisse entendre qu'en cas de dépassement du seuil 5xPSS, tout le revenu est soumis à 6.5%. Il semblerait qu'une interprétation inverse soit à privilégier : seule la part supérieure à ce seuil est soumise à ce taux, et c'est cette implémentation que nous avons retenue. -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux variable -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux variable: formule: variations: - si: situation personnelle . RSA alors: taux RSA - sinon: taux -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux RSA -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux RSA: formule: multiplication: assiette: taux RSA part variable + 1.35% @@ -3733,22 +3543,16 @@ dirigeant . indépendant . cotisations et contributions . cotisations . maladie note: | Pour les indépendants au RSA, seule la réduction simple définie dans le décret de calcul de la cotisation maladie est prise en compte. La réduction renforcée en-dessous de 40% du plafond de la sécurité sociale ne l'est pas, car il n'y a pas d'assiette minimale. -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux RSA part variable -: période: flexible - formule: - multiplication: - assiette: 5% - taux: revenu professionnel / seuil supérieur de réduction +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux RSA part variable: + formule: 5% * (revenu professionnel / seuil supérieur de réduction) -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . seuil supérieur de réduction -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . seuil supérieur de réduction: formule: multiplication: assiette: plafond sécurité sociale temps plein taux: 110% -? dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . maladie . artisans commerçants libéraux . taux: formule: barème continu: retourne seulement le taux: oui @@ -3767,13 +3571,13 @@ dirigeant . indépendant . cotisations et contributions . cotisations . maladie décret formule de calcul: https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036342439&categorieLien=id dirigeant . indépendant . cotisations et contributions . cotisations . retraite de base: - période: année + unité par défaut: €/an formule: variations: - si: entreprise . rattachement libéral règlementé alors: multiplication: - assiette: assiette + assiette: assiette [€/an] multiplicateur: composantes: - nom: tranche 1 @@ -3784,7 +3588,7 @@ dirigeant . indépendant . cotisations et contributions . cotisations . retraite taux: 1.87% - sinon: barème: - assiette: assiette + assiette: assiette [€/an] multiplicateur: plafond sécurité sociale temps plein tranches: - en-dessous de: 1 @@ -3792,8 +3596,7 @@ dirigeant . indépendant . cotisations et contributions . cotisations . retraite - au-dessus de: 1 taux: 0.6% -? dirigeant . indépendant . cotisations et contributions . cotisations . retraite de base . assiette -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . retraite de base . assiette: titre: assiette retraite de base formule: variations: @@ -3806,16 +3609,15 @@ dirigeant . indépendant . cotisations et contributions . cotisations . retraite références: secu-independants.fr: https://www.secu-independants.fr/cotisations/calcul-des-cotisations/cotisations-minimales/ -? dirigeant . indépendant . cotisations et contributions . cotisations . retraite complémentaire -: période: année - unité: € +dirigeant . indépendant . cotisations et contributions . cotisations . retraite complémentaire: + unité par défaut: €/an note: Pour les professions libérales, nous avons retenu un des 8 régimes de retraite, celui de la CIPAV, la caisse interprofessionnelle. formule: variations: - si: entreprise . rattachement libéral règlementé alors: barème linéaire: - assiette: revenu professionnel + assiette: revenu professionnel [€/an] tranches: - en-dessous de: 26580 montant: 1315 @@ -3841,7 +3643,7 @@ dirigeant . indépendant . cotisations et contributions . cotisations . retraite montant: 17095 - sinon: barème: - assiette: revenu professionnel + assiette: revenu professionnel [€/an] tranches: - en-dessous de: 37960 taux: 7% @@ -3852,7 +3654,6 @@ dirigeant . indépendant . cotisations et contributions . cotisations . retraite taux: 0% dirigeant . indépendant . cotisations et contributions . cotisations . invalidité et décès: - période: flexible formule: multiplication: assiette: assiette @@ -3861,8 +3662,7 @@ dirigeant . indépendant . cotisations et contributions . cotisations . invalidi # TODO invalidité décès pour les libéraux # 3 classes, 76, 228, 380€ -? dirigeant . indépendant . cotisations et contributions . cotisations . invalidité et décès . assiette -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . invalidité et décès . assiette: titre: assiette invalidité et décès formule: variations: @@ -3876,7 +3676,6 @@ dirigeant . indépendant . cotisations et contributions . cotisations . invalidi secu-independants.fr: https://www.secu-independants.fr/cotisations/calcul-des-cotisations/cotisations-minimales/ dirigeant . indépendant . cotisations et contributions . CSG et CRDS: - période: flexible formule: multiplication: assiette: assiette @@ -3892,14 +3691,12 @@ dirigeant . indépendant . cotisations et contributions . CSG et CRDS: fiche URSSAF: https://www.urssaf.fr/portail/home/independant/mes-cotisations/quelles-cotisations/les-contributions-csg-crds/taux-de-la-csg-crds.html dirigeant . indépendant . cotisations et contributions . CSG et CRDS . assiette: - période: flexible formule: somme: - revenu professionnel - cotisations dirigeant . indépendant . cotisations et contributions . formation professionnelle: - période: flexible formule: multiplication: assiette: plafond sécurité sociale temps plein @@ -3914,8 +3711,7 @@ dirigeant . indépendant . cotisations et contributions . formation professionne fiche URSSAF: https://www.urssaf.fr/portail/home/independant/mes-cotisations/quelles-cotisations/la-contribution-a-la-formation-p/base-de-calcul-et-taux-de-la-con.html brève URSSAF pour les artisans: https://www.urssaf.fr/portail/home/actualites/toute-lactualite-independant/transfert-du-recouvrement-de-la.html -? dirigeant . indépendant . cotisations et contributions . cotisations . allocations familiales -: période: flexible +dirigeant . indépendant . cotisations et contributions . cotisations . allocations familiales: formule: barème continu: assiette: revenu professionnel @@ -3976,7 +3772,6 @@ dirigeant . auto-entrepreneur: dirigeant . auto-entrepreneur . base des cotisations: formule: entreprise . chiffre d'affaires - période: flexible contrôles: - si: base des cotisations > plafond message: Seuil de chiffre d'affaires dépassé. [En savoir plus](/documentation/dirigeant/auto‑entrepreneur/plafond) @@ -3991,8 +3786,8 @@ dirigeant . auto-entrepreneur . plafond: références: Fiche sevice-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F32353 - période: année - unité: € + unité: €/an + formule: variations: - si: @@ -4001,33 +3796,30 @@ dirigeant . auto-entrepreneur . plafond: - une de ces conditions: - entreprise . catégorie d'activité . service ou vente = 'vente' - entreprise . catégorie d'activité . restauration ou hébergement - alors: 170000 - - sinon: 70000 + alors: 170000 €/an + - sinon: 70000 €/an dirigeant . auto-entrepreneur . revenu net de cotisations: résumé: Avant impôt question: Quel revenu avant impôt voulez-vous toucher ? description: Il s'agit du revenu net de cotisations et de charges, avant le paiement de l'impôt sur le revenu. formule: entreprise . rémunération totale du dirigeant - cotisations et contributions - période: flexible - unité: € + unité par défaut: €/mois dirigeant . auto-entrepreneur . cotisations et contributions: - unité: € + unité par défaut: €/mois formule: somme: - cotisations - TFC - contribution formation professionnelle - - entreprise . prélèvements obligatoires références: Imposition du micro-entrepreneur: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23267 - période: flexible dirigeant . auto-entrepreneur . cotisations et contributions . TFC: titre: Taxes pour frais de chambre - unité: € - période: flexible + unité: €/mois + note: | Nous n'avons pas intégré les exceptions suivantes : @@ -4042,8 +3834,8 @@ dirigeant . auto-entrepreneur . cotisations et contributions . TFC: dirigeant . auto-entrepreneur . cotisations et contributions . TFC . commerce: titre: taxe pour frais de chambre de commerce - période: flexible - unité: € + unité: €/mois + applicable si: entreprise . catégorie d'activité = 'commerciale ou industrielle' formule: multiplication: @@ -4058,8 +3850,8 @@ dirigeant . auto-entrepreneur . cotisations et contributions . TFC . commerce: - sinon: 0.044% dirigeant . auto-entrepreneur . cotisations et contributions . TFC . métiers: - période: flexible - unité: € + unité: €/mois + titre: taxe pour frais de chambre des métiers applicable si: entreprise . catégorie d'activité = 'artisanale' formule: @@ -4071,10 +3863,9 @@ dirigeant . auto-entrepreneur . cotisations et contributions . TFC . métiers: alors: 0.22% - sinon: 0.48% -? dirigeant . auto-entrepreneur . cotisations et contributions . contribution formation professionnelle -: titre: Contribution à la formation professionnelle - unité: € - période: flexible +dirigeant . auto-entrepreneur . cotisations et contributions . contribution formation professionnelle: + titre: Contribution à la formation professionnelle + unité par défaut: €/mois références: Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23459 formule: @@ -4091,7 +3882,7 @@ dirigeant . auto-entrepreneur . cotisations et contributions . TFC . métiers: - entreprise . catégorie d'activité = 'commerciale ou industrielle' - entreprise . catégorie d'activité = 'libérale' alors: 0.1% - - sinon: 0 + - sinon: 0% dirigeant . auto-entrepreneur . cotisations et contributions . cotisations: description: | @@ -4100,8 +3891,6 @@ dirigeant . auto-entrepreneur . cotisations et contributions . cotisations: L'auto-entreprise est un régime simplifié : plutôt qu'une fiche de paie complexe, toutes les cotisations sont groupées dans un *forfait* dont le taux dépend de la catégorie d'activité. Depuis janvier 2019, toute nouvelle auto-entreprise peut bénéficier de l'ACRE, une réduction de cotisations, dégressive sur 3 ans. - période: flexible - unité: € formule: barème: assiette: base des cotisations @@ -4114,12 +3903,10 @@ dirigeant . auto-entrepreneur . cotisations et contributions . cotisations: références: La protection sociale du micro-entrepreneur: https://bpifrance-creation.fr/encyclopedie/micro-entreprise-regime-auto-entrepreneur/fiscal-social-comptable/protection-sociale -? dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . retraite complémentaire -: description: Le montant total qui est alloué à la retraite complémentaire, utile pour estimer le montant total de la pension de retraite des auto-entrepreneurs - unité: € +dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . retraite complémentaire: + description: Le montant total qui est alloué à la retraite complémentaire, utile pour estimer le montant total de la pension de retraite des auto-entrepreneurs # L'ACOSS ne veut pas communiquer sur le pourcentage des cotisations fléchés pour la retraite complémentaire pour les PL non applicable si: entreprise . catégorie d'activité = 'libérale' - période: flexible formule: multiplication: assiette: base des cotisations @@ -4136,8 +3923,8 @@ dirigeant . auto-entrepreneur . cotisations et contributions . cotisations: - entreprise . catégorie d'activité = 'artisanale' alors: 3.50% -? dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . taux de cotisation -: description: | +dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . taux de cotisation: + description: | Les cotisations sociales de l'auto-entreprise sont simplifiées : il n'y a qu'une ligne unique dont le taux dépend de la catégorie d'activité. formule: variations: @@ -4180,13 +3967,11 @@ entreprise . ACRE . année . moins de deux ans: entreprise . ACRE . année . moins de trois ans: dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . taux ACRE: - période: flexible formule: 100% - réduction ACRE dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . réduction ACRE: titre: réduction ACRE applicable si: entreprise . ACRE - période: flexible description: Ce taux peut dans certains cas réduire le montant des cotisations sociales de l'auto-entrepreneur pour l'aider dans ses premières année d'activité. formule: variations: @@ -4201,20 +3986,18 @@ dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . ré service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F32318 dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . plafond ACRE: formule: plafond sécurité sociale temps plein / impôt . abattement . taux inversé - période: flexible dirigeant . auto-entrepreneur . impôt: dirigeant . auto-entrepreneur . impôt . abattement: - période: année - unité: € formule: encadrement: valeur: multiplication: - assiette: base des cotisations [annuel] + assiette: base des cotisations taux: taux - plancher: 305 € + plancher: 305 €/an + références: Légifrance: https://www.legifrance.gouv.fr/affichCode.do?idSectionTA=LEGISCTA000006199553&cidTexte=LEGITEXT000006069577 @@ -4244,11 +4027,10 @@ dirigeant . auto-entrepreneur . impôt . versement libératoire: contrôles: - si: toutes ces conditions: - - impôt . revenu fiscal de référence [annuel] > 27086 + - impôt . revenu fiscal de référence > 27086 €/an - versement libératoire message: Le versement libératoire n'est pas disponible si les revenus de votre ménage sont supérieurs à 27 086 € par part en 2018 niveau: info - période: aucune dirigeant . auto-entrepreneur . impôt . versement libératoire . montant: titre: versement libératoire auto-entrepreneur @@ -4258,8 +4040,6 @@ dirigeant . auto-entrepreneur . impôt . versement libératoire . montant: - 1% si l’activité est l’achat/revente, la vente à consommer sur place et la prestation d’hébergement (BIC) - 1,7% si l’activité est une activité de services relevant des bénéfices industriels et commerciaux (BIC) - 2,2% pour les autres prestations de services relevants des bénéfices non commerciaux (BNC) - unité: € - période: flexible formule: multiplication: @@ -4278,12 +4058,10 @@ dirigeant . auto-entrepreneur . impôt . versement libératoire . montant: dirigeant . auto-entrepreneur . impôt . revenu imposable: non applicable si: versement libératoire - période: flexible formule: revenu abattu dirigeant . auto-entrepreneur . impôt . revenu abattu: titre: revenu abattu auto-entrepreneur - période: flexible formule: allègement: assiette: base des cotisations @@ -4318,8 +4096,6 @@ protection sociale . retraite: OCDE: https://read.oecd-ilibrary.org/social-issues-migration-health/pensions-at-a-glance-2017_pension_glance-2017-en#page135 INSEE: https://www.insee.fr/fr/statistiques/fichier/3549496/REVPMEN18_F1.21_niv-pauv-pers-agees.pdf - unité: € - période: flexible formule: somme: - base @@ -4340,8 +4116,6 @@ protection sociale . retraite: protection sociale . retraite . base: titre: pension de retraite de base - unité: € - période: flexible formule: multiplication: plafond: plafond sécurité sociale temps plein @@ -4353,7 +4127,6 @@ protection sociale . retraite . base: protection sociale . retraite . base . taux de la pension: description: Le taux appliqué, avec décote ou surcote en fonction du nombre de trimestres cotisés. - période: flexible formule: variations: - si: trimestres validés par an = 0 @@ -4364,8 +4137,7 @@ protection sociale . retraite . base . taux de la pension: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F19666 protection sociale . retraite . trimestres validés par an: - période: aucune - unité: trimestres + unité: trimestres validés/an formule: encadrement: valeur: @@ -4376,14 +4148,12 @@ protection sociale . retraite . trimestres validés par an: plafond: 4 protection sociale . retraite . trimestres validés par an . trimestres salarié: - période: aucune - unité: trimestres + unité: trimestres validés/an applicable si: contrat salarié formule: barème trimestres générique -? protection sociale . retraite . trimestres validés par an . trimestres indépendant -: période: aucune - unité: trimestres +protection sociale . retraite . trimestres validés par an . trimestres indépendant: + unité: trimestres validés/an applicable si: dirigeant = 'indépendant' formule: variations: @@ -4394,12 +4164,12 @@ protection sociale . retraite . trimestres validés par an . trimestres salarié plancher: 3 valeur: barème trimestres générique -? protection sociale . retraite . trimestres validés par an . barème trimestres générique -: période: aucune +protection sociale . retraite . trimestres validés par an . barème trimestres générique: + unité: trimestres validés/an formule: barème linéaire: - unité: trimestres - assiette: revenu moyen [annuel] + unité: trimestres validés/an + assiette: revenu moyen [€/an] multiplicateur: SMIC horaire tranches: - en-dessous de: 150 @@ -4418,17 +4188,17 @@ protection sociale . retraite . trimestres validés par an . trimestres salarié références: cnav.fr: https://www.legislation.cnav.fr/Pages/bareme.aspx?Nom=salaire_validant_un_trimestre_montant_bar -? protection sociale . retraite . trimestres validés par an . trimestres auto-entrepreneur -: applicable si: dirigeant = 'auto-entrepreneur' - période: aucune +protection sociale . retraite . trimestres validés par an . trimestres auto-entrepreneur: + applicable si: dirigeant = 'auto-entrepreneur' description: Les seuils de chiffre d'affaires minimum pour la validation des trimestres pour la retraite en auto-entrepreneur. En-dessous du montant minimum, vous n'aurez accès qu'à l'allocation de solidarité. + unité: trimestres validés/an formule: variations: - si: entreprise . catégorie d'activité = 'libérale' alors: barème linéaire: - unité: trimestres - assiette: entreprise . chiffre d'affaires [annuel] + unité: trimestres validés/an + assiette: entreprise . chiffre d'affaires [€/an] tranches: - en-dessous de: 2880 montant: 0 @@ -4449,8 +4219,8 @@ protection sociale . retraite . trimestres validés par an . trimestres salarié - entreprise . catégorie d'activité . restauration ou hébergement alors: barème linéaire: - unité: trimestres - assiette: entreprise . chiffre d'affaires [annuel] + unité: trimestres validés/an + assiette: entreprise . chiffre d'affaires [€/an] tranches: - en-dessous de: 4137 montant: 0 @@ -4467,8 +4237,8 @@ protection sociale . retraite . trimestres validés par an . trimestres salarié montant: 4 - sinon: barème linéaire: - unité: trimestres - assiette: entreprise . chiffre d'affaires [annuel] + unité: trimestres validés/an + assiette: entreprise . chiffre d'affaires [€/an] tranches: - en-dessous de: 2412 montant: 0 @@ -4489,8 +4259,8 @@ protection sociale . retraite . trimestres validés par an . trimestres salarié protection sociale . revenu moyen: description: Le revenu utilisé pour le calcul du montant des pensions de retraite et des indemnités journalières de sécurité sociale lors d'un arrêt de travail. notes: Normalement, on prend le revenu moyen des 25 meilleures années pour la retraite et des 3 derniers mois pour les indémnités. Vu qu'on intègre pas la notions de temporalité avec notre simulateur, on simplifie en prenant le même. - unité: € - période: année + unité: €/an + formule: le maximum de: - dirigeant . indépendant . revenu professionnel @@ -4499,58 +4269,49 @@ protection sociale . revenu moyen: protection sociale . retraite . mois cotisés: unité: mois - formule: 172 * 3 + formule: 172 trimestres * 3 mois/trimestre notes: On prend l'hypothèse d'une retraite à taux plein pour un travailleur né en 1973 ou après protection sociale . retraite . complémentaire salarié: formule: points acquis * valeur du point - période: année références: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F15396 protection sociale . retraite . complémentaire salarié . valeur du point: - formule: 1.2588 €/point - période: année + formule: 1.2588 €/point/an références: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F15396 agirc-arrco: https://www.agirc-arrco.fr/ressources-documentaires/chiffres-cles/ protection sociale . retraite . complémentaire salarié . points acquis: formule: points acquis par mois * mois cotisés - période: aucune unité: points références: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F15396 protection sociale . retraite . complémentaire salarié . points acquis par mois: - période: mois unité: points/mois formule: contrat salarié . retraite complémentaire / prix d'achat du point protection sociale . retraite . complémentaire salarié . prix d'achat du point: formule: 16.7226 €/point - période: mois références: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F15396 protection sociale . retraite . complémentaire sécurité des indépendants: formule: points acquis * valeur du point - période: année références: secu-independants.fr: https://www.secu-independants.fr/retraite/calcul-retraite/retraite-complementaire/ -? protection sociale . retraite . complémentaire sécurité des indépendants . valeur du point -: formule: 1.187 €/point - période: année +protection sociale . retraite . complémentaire sécurité des indépendants . valeur du point: + formule: 1.187 €/point/an références: secu-independants.fr: https://www.secu-independants.fr/baremes/prestations-vieillesse-et-invalidite-deces -? protection sociale . retraite . complémentaire sécurité des indépendants . points acquis -: formule: points acquis par mois * mois cotisés - période: aucune +protection sociale . retraite . complémentaire sécurité des indépendants . points acquis: + formule: points acquis par mois * mois cotisés -? protection sociale . retraite . complémentaire sécurité des indépendants . points acquis par mois -: période: mois +protection sociale . retraite . complémentaire sécurité des indépendants . points acquis par mois: unité: points/mois formule: multiplication: @@ -4560,9 +4321,8 @@ protection sociale . retraite . complémentaire sécurité des indépendants: - dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . retraite complémentaire facteur: 1 / prix d'achat du point -? protection sociale . retraite . complémentaire sécurité des indépendants . prix d'achat du point -: formule: 17.456 €/point - période: mois +protection sociale . retraite . complémentaire sécurité des indépendants . prix d'achat du point: + formule: 17.456 €/point notes: il s'agit du prix d'achat 2018 (la valeur pour 2019 sur le site secu-independants.fr est marquée comme N.C) références: secu-independants.fr: https://www.secu-independants.fr/baremes/baremes-2018/baremesprestations-maladie-maternite/?reg=ile-de-france-centre&ae=oui @@ -4594,8 +4354,8 @@ protection sociale . santé . indemnités journalières: votre revenu pendant un arrêt de travail. Elles sont calculées à partir de votre revenu brut et versées tous les 14 jours en moyenne. # TODO : période : jour - période: aucune - unité: € + unité: €/jour + formule: somme: - indemnités journalières . auto-entrepreneur @@ -4604,48 +4364,47 @@ protection sociale . santé . indemnités journalières: protection sociale . santé . indemnités journalières . auto-entrepreneur: applicable si: dirigeant = 'auto-entrepreneur' - période: aucune - unité: € + unité: €/jour + formule: variations: - - si: revenu moyen [annuel] < 3919.20 - alors: 0 + - si: revenu moyen < 3919.20 €/an + alors: 0 €/jour - sinon: encadrement: valeur: multiplication: - assiette: revenu moyen [annuel] - facteur: 1 / 730 - plafond: 55.51 € + assiette: revenu moyen [€/jour] + taux: 50% + plafond: 55.51 €/jour reférences: - secu-independants.fr: https://www.secu-independants.fr/sante/indemnites-journalieres/montant-de-lindemnite protection sociale . santé . indemnités journalières . indépendant: applicable si: dirigeant = 'indépendant' - période: aucune - unité: € + unité: €/jour + formule: encadrement: valeur: multiplication: - assiette: revenu moyen [annuel] - facteur: 1 / 730 - plancher: 21 € - plafond: 55.51 € + assiette: revenu moyen [€/jour] + taux: 50% + plancher: 21 €/jour + plafond: 55.51 €/jour reférences: - secu-independants.fr: https://www.secu-independants.fr/sante/indemnites-journalieres/montant-de-lindemnite protection sociale . santé . indemnités journalières . salarié: - période: aucune - unité: € + unité: €/jour + notes: Vu que le simulateur ne permet pas encore la conversion de période vers le jour, on multiplie le salaire moyen par 3 pour avoir le salaire trimestriel, puis on le divise par 91.25, conformément à la fiche service-public.fr applicable si: contrat salarié formule: multiplication: - assiette: revenu moyen [mensuel] + assiette: revenu moyen [€/jour] + plafond: 1.8 * contrat salarié . SMIC temps plein [€/jour] taux: 50% - facteur: 3 / 91.25 - plafond: 1.8 * contrat salarié . SMIC temps plein reférences: service-public.fr: https://www.service-public.fr/particuliers/vosdroits/F3053 @@ -4710,19 +4469,19 @@ protection sociale . accidents du travail et maladies professionnelles: En cas d’AT/MP, les soins médicaux et chirurgicaux sont remboursés intégralement dans la limite des tarifs de la Sécurité sociale. - période: mois - unité: € + unité: €/jour + applicable si: contrat salarié formule: encadrement: valeur: multiplication: - assiette: revenu moyen [mensuel] + assiette: revenu moyen [€/jour] taux: 60% - facteur: 1 / 30.42 - plafond: 202.78 € + plafond: 202.78 €/jour + # TODO - # - 0.834% * plafond sécurité sociale temps plein [annuel] + # - 0.834% * plafond sécurité sociale temps plein [€/an] références: ameli.fr: https://www.ameli.fr/paris/entreprise/cotisations/mp-tarification-calculs-baremes/compte-mp @@ -4766,8 +4525,8 @@ situation personnelle . RSA: contrat salarié . lodeom: description: | - Un ensemble assez complexe de réductions de cotisation est disponible pour les salariés d'outre-mer. - Leur fonctionnement est similaire à celui de la réduction générale sur les bas salaires : pour un certain salaire donné, 100% de réduction. + Un ensemble assez complexe de réductions de cotisation est disponible pour les salariés d'outre-mer. + Leur fonctionnement est similaire à celui de la réduction générale sur les bas salaires : pour un certain salaire donné, 100% de réduction. Pour un autre salaire plus élevé, 0% de réduction. Entre les deux, on trace une ligne droite. contrat salarié . lodeom . zone un: @@ -4796,8 +4555,6 @@ contrat salarié . lodeom . réduction outre-mer: - éligible barème compétitivité renforcée - éligible barème innovation et croissance - période: flexible - formule: encadrement: valeur: contrat salarié . réduction générale . assiette @@ -4816,7 +4573,7 @@ contrat salarié . lodeom . réduction outre-mer: alors: ((borne inférieure * paramètre T) / (borne supérieure - 2.5)) * écart au plafond de l'assiette - sinon: multiplicateur * écart au plafond de l'assiette note: Nous utilisons la méthode de calcul officielle de la sécurité sociale. Il serait préférable ici de réduire directement les cotisations concernées, ce qui éviterait au calcul de reposer sur les paramètres `T` publiés chaque année (ils dépendent directement des cotisaitons réduites). - références: + références: Estimateur URSSAF: https://www.urssaf.fr/portail/home/utile-et-pratique/estimateur-exoneration-lodeom.html?ut= exemples: # Barème 1 @@ -4907,11 +4664,9 @@ contrat salarié . lodeom . réduction outre-mer: valeur attendue: 0 contrat salarié . lodeom . plafond de l'assiette: - période: flexible formule: borne supérieure * SMIC contrat salarié . lodeom . écart au plafond de l'assiette: - période: flexible formule: plafond de l'assiette - cotisations . assiette contrat salarié . lodeom . éligible barème compétitivité: @@ -4933,10 +4688,10 @@ contrat salarié . lodeom . éligible barème compétitivité . secteurs d'activ Pour être éligible au 1er barème de l'exonération LODEOM, dit barème de compétitivité, votre entreprise doit appartenir à l'un des secteurs suivants : - ✈ transport aérien assurant les liaisons entre les départements et régions d’Outre-mer et entre la métropole et ces territoires, ainsi que les dessertes intérieures - - ⛵ dessertes maritimes, fluviales ou les liaisons entre départements et régions d’Outre-mer + - ⛵ dessertes maritimes, fluviales ou les liaisons entre départements et régions d’Outre-mer - 🏗 bâtiment et travaux publics - 📰 la presse - - 🎥 la production audiovisuelle + - 🎥 la production audiovisuelle - les secteurs éligibles aux régimes de compétitivité renforcée (barème 2) ou d’innovation et de croissance (barème 3), qui ne respectent pas les conditions d’effectifs (moins de 250 salariés) ou de chiffres d’affaires annuel (moins de 50 millions d’euros). par défaut: non @@ -4977,6 +4732,7 @@ contrat salarié . lodeom . éligible barème innovation et croissance: Fiche URSSAF: https://www.urssaf.fr/portail/home/outre-mer/employeur/exoneration-de-cotisations-di-1/employeurs-situes-en-guadeloupe/bareme-dit-innovation-et-croissa.html contrat salarié . lodeom . borne inférieure: + unité: '' formule: variations: - si: éligible barème compétitivité @@ -4984,6 +4740,7 @@ contrat salarié . lodeom . borne inférieure: - sinon: 1.7 contrat salarié . lodeom . borne supérieure: + unité: '' formule: variations: - si: éligible barème compétitivité @@ -4995,10 +4752,10 @@ contrat salarié . lodeom . borne supérieure: contrat salarié . lodeom . multiplicateur: note: pour le barème 1 le dénominateur vaut 0,9 - période: flexible formule: (borne inférieure * paramètre T) / (borne supérieure - borne inférieure) contrat salarié . lodeom . paramètre T: + unité: '' formule: variations: - si: @@ -5025,7 +4782,10 @@ contrat salarié . convention collective: - sport - SVP contrôles: - - si: convention collective != 'droit commun' + - si: + toutes ces conditions: + - convention collective != non + - convention collective != 'droit commun' niveau: avertissement message: | Attention, l'implémentation des conventions collective est encore @@ -5033,7 +4793,6 @@ contrat salarié . convention collective: Néanmoins, cela permet d'obtenir une première estimation, plus précise que le régime général. - contrat salarié . convention collective . droit commun: formule: convention collective = 'droit commun' @@ -5043,18 +4802,16 @@ contrat salarié . convention collective . HCR: description: L'entreprise est un hôtel, café, restaurant ou assimilé. contrat salarié . convention collective . HCR . montant forfaitaire d'un repas: - période: aucune remplace: règle: rémunération . avantages en nature . nourriture . montant forfaitaire d'un repas formule: 3.62 €/repas -? contrat salarié . convention collective . HCR . majoration heures supplémentaires -: remplace: temps de travail . heures supplémentaires . majoration - période: flexible +contrat salarié . convention collective . HCR . majoration heures supplémentaires: + remplace: temps de travail . heures supplémentaires . majoration formule: barème: assiette: temps de travail . heures supplémentaires - multiplicateur: période . semaines par mois + multiplicateur: 1 heure/semaine * période . semaines par mois tranches: - en-dessous de: 4 taux: 10% @@ -5072,7 +4829,6 @@ contrat salarié . convention collective . SVP: contrat salarié . convention collective . SVP . cotisations patronales: titre: cotisations conventionnelles - période: flexible remplace: cotisations . patronales . conventionnelles formule: somme: @@ -5090,7 +4846,8 @@ contrat salarié . convention collective . SVP . FCAP: - De couvrir les frais relatifs au dispositif des Conseillers Conventionnels des Salariés, au nombre de 28 - De financer le rapport de branche du spectacle vivant privé. - période: année + unité: €/an + # TODO : note: les minimum et maximum sont fixé par entreprise, et non par salarié formule: @@ -5100,14 +4857,14 @@ contrat salarié . convention collective . SVP . FCAP: plafonnée à: plafond sécurité sociale assiette: rémunération . brut taux: 0.1% - plancher: 80 € / entreprise . effectif - plafond: 300 € / entreprise . effectif + plancher: 80 €.employés/an / entreprise . effectif + plafond: 300 €.employés/an / entreprise . effectif + références: Titre V de IDCC 3090: https://www.legifrance.gouv.fr/affichIDCC.do;?idSectionTA=KALISCTA000028157274&cidTexte=KALITEXT000028157267&idConvention=KALICONT000028157262 Note explicative AUDIENS: http://www.cheque-intermittents.com/wp-content/uploads/2015/05/FCAP-SVP-EXPLIC_final.pdf contrat salarié . convention collective . SVP . prévoyance: - période: flexible formule: multiplication: plafonnée à: plafond sécurité sociale @@ -5130,35 +4887,32 @@ contrat salarié . convention collective . sport . cotisations: contrat salarié . convention collective . sport . cotisations . patronales: titre: cotisations conventionnelles - période: flexible remplace: - règle: cotisations . patronales . conventionnelles formule: somme: - - prévoyance [employeur] + - prévoyance .employeur - financement du paritarisme -? contrat salarié . convention collective . sport . cotisations . financement du paritarisme -: période: année +contrat salarié . convention collective . sport . cotisations . financement du paritarisme: # TODO note: se calcule sur la masse salariale formule: encadrement: - plancher: 3 € + plancher: 3 €.employé/an / entreprise . effectif valeur: multiplication: assiette: cotisations . assiette taux: 0.06% contrat salarié . convention collective . sport . cotisations . prévoyance: - période: flexible remplace: - règle: cotisations . salariales . conventionnelles - par: prévoyance [salarié] + par: prévoyance .salarié - règle: avantages sociaux par: somme: - - prévoyance [employeur] + - prévoyance .employeur - avantages sociaux formule: multiplication: @@ -5174,16 +4928,15 @@ contrat salarié . convention collective . sport . cotisations . prévoyance: références: Article 10.8 de la CCNS (IDCC 2511): https://www.legifrance.gouv.fr/affichIDCCArticle.do;?idArticle=KALIARTI000033304755&cidTexte=KALITEXT000017577657&dateTexte=29990101&categorieLien=id -? contrat salarié . convention collective . sport . cotisations . régime frais de santé -: période: flexible +contrat salarié . convention collective . sport . cotisations . régime frais de santé: remplace: contrat salarié . complémentaire santé . forfait formule: multiplication: assiette: plafond sécurité sociale temps plein taux: taux -? contrat salarié . convention collective . sport . cotisations . régime frais de santé . taux -: formule: +contrat salarié . convention collective . sport . cotisations . régime frais de santé . taux: + formule: variations: - si: régime alsace moselle alors: @@ -5202,13 +4955,12 @@ contrat salarié . convention collective . sport . cotisations . prévoyance: alors: 1.17% - si: option . R3 alors: 1.32% - période: flexible référence: unamens.fr: https://www.umanens.fr/reglementation-couverture-sante-obligatoire/ccn-sport unamens (notice pdf): https://www.umanens.fr/documents/doc-offres-2018/sport/juin-2019/CCN_SPORT_PLAQ_EMPLOYEUR_2019.pdf -? contrat salarié . convention collective . sport . cotisations . régime frais de santé . option -: question: Quel option a été choisi pour le régime des frais de santé ? +contrat salarié . convention collective . sport . cotisations . régime frais de santé . option: + question: Quel option a été choisi pour le régime des frais de santé ? formule: une possibilité: choix obligatoire: oui @@ -5219,16 +4971,15 @@ contrat salarié . convention collective . sport . cotisations . prévoyance: par défaut: R1 références: unamens.fr: https://www.umanens.fr/reglementation-couverture-sante-obligatoire/ccn-sport -? contrat salarié . convention collective . sport . cotisations . régime frais de santé . option . R1 -: formule: option = 'R1' -? contrat salarié . convention collective . sport . cotisations . régime frais de santé . option . R2 -: formule: option = 'R2' -? contrat salarié . convention collective . sport . cotisations . régime frais de santé . option . R3 -: formule: option = 'R3' +contrat salarié . convention collective . sport . cotisations . régime frais de santé . option . R1: + formule: option = 'R1' +contrat salarié . convention collective . sport . cotisations . régime frais de santé . option . R2: + formule: option = 'R2' +contrat salarié . convention collective . sport . cotisations . régime frais de santé . option . R3: + formule: option = 'R3' -? contrat salarié . convention collective . sport . cotisations . formation professionnelle -: remplace: contrat salarié . formation professionnelle - période: flexible +contrat salarié . convention collective . sport . cotisations . formation professionnelle: + remplace: contrat salarié . formation professionnelle formule: somme: - plan de formation @@ -5238,8 +4989,7 @@ contrat salarié . convention collective . sport . cotisations . prévoyance: références: Article 8.6 de la CCNS (IDCC2511): https://www.legifrance.gouv.fr/affichIDCCArticle.do;?idArticle=KALIARTI000034406905&cidTexte=KALITEXT000017577657&dateTexte=29990101&categorieLien=id -? contrat salarié . convention collective . sport . cotisations . formation professionnelle . plan de formation -: période: flexible +contrat salarié . convention collective . sport . cotisations . formation professionnelle . plan de formation: formule: encadrement: plancher: versement minimum @@ -5253,13 +5003,11 @@ contrat salarié . convention collective . sport . cotisations . prévoyance: - si: entreprise . effectif >= 20 alors: 0.90% -? contrat salarié . convention collective . sport . cotisations . formation professionnelle . plan de formation . versement minimum -: applicable si: entreprise . effectif < 10 - période: mois - formule: 30 € +contrat salarié . convention collective . sport . cotisations . formation professionnelle . plan de formation . versement minimum: + applicable si: entreprise . effectif < 10 + formule: 30 €/mois -? contrat salarié . convention collective . sport . cotisations . formation professionnelle . professionnalisation -: période: flexible +contrat salarié . convention collective . sport . cotisations . formation professionnelle . professionnalisation: formule: encadrement: plancher: versement minimum @@ -5273,32 +5021,28 @@ contrat salarié . convention collective . sport . cotisations . prévoyance: - si: entreprise . effectif >= 20 alors: 0.50% -? contrat salarié . convention collective . sport . cotisations . formation professionnelle . professionnalisation . versement minimum -: applicable si: entreprise . effectif < 10 - période: mois +contrat salarié . convention collective . sport . cotisations . formation professionnelle . professionnalisation . versement minimum: + applicable si: entreprise . effectif < 10 formule: 5 €/mois -? contrat salarié . convention collective . sport . cotisations . formation professionnelle . CIF CDI -: applicable si: +contrat salarié . convention collective . sport . cotisations . formation professionnelle . CIF CDI: + applicable si: toutes ces conditions: - CDI - entreprise . effectif >= 20 - période: flexible formule: multiplication: assiette: cotisations . assiette taux: 0.20% -? contrat salarié . convention collective . sport . cotisations . formation professionnelle . CIF CDD -: applicable si: CDD - période: flexible +contrat salarié . convention collective . sport . cotisations . formation professionnelle . CIF CDD: + applicable si: CDD formule: multiplication: assiette: cotisations . assiette taux: 1% -? contrat salarié . convention collective . sport . cotisations . assiette franchisée -: période: flexible +contrat salarié . convention collective . sport . cotisations . assiette franchisée: formule: allègement: assiette: cotisations . assiette @@ -5317,50 +5061,54 @@ contrat salarié . convention collective . sport . exonération cotisation AT: règle: ATMP par: non -? contrat salarié . convention collective . sport . exonération cotisation AT . refus -: titre: refus exonération AT +contrat salarié . convention collective . sport . exonération cotisation AT . refus: + titre: refus exonération AT question: L'employeur a-t'il refusé d'être exonéré de cotisations AT ? par défaut: non -? contrat salarié . convention collective . sport . cotisations . assiette forfaitaire -: applicable si: assiette franchisée < 115 * SMIC horaire - période: mois +contrat salarié . convention collective . sport . cotisations . assiette forfaitaire: + applicable si: assiette franchisée < SMIC horaire * 115 heures/mois remplace: contrat salarié . cotisations . assiette forfaitaire - unité: € formule: multiplication: assiette: SMIC horaire facteur: barème linéaire: - assiette: assiette franchisée + assiette: assiette franchisée [€/mois] multiplicateur: SMIC horaire + unité: heures/mois tranches: - en-dessous de: 45 montant: 5 + # * SMIC horaire - de: 45 à: 60 montant: 15 + # * SMIC horaire - de: 60 à: 80 montant: 25 + # * SMIC horaire - de: 80 à: 100 montant: 35 + # * SMIC horaire - de: 100 à: 115 montant: 50 + # * SMIC horaire -? contrat salarié . convention collective . sport . primes . nombre de manifestations -: question: Combien de manifestations rémunérées le joueur a-t'il effectué ce mois-ci ? - applicable si: période = 'mois' +contrat salarié . convention collective . sport . primes . nombre de manifestations: + question: Combien de manifestations rémunérées le joueur a-t'il effectué ? + #TODO : gérer la période unité: manifestations par défaut: 0 contrat salarié . convention collective . sport . primes: titre: primes de manifestation + #TODO non applicable si: période = 'année' remplace: rémunération . primes . activité . conventionnelles - unité: € - période: aucune + unité: €/mois formule: somme: - manifestation 1 @@ -5371,60 +5119,52 @@ contrat salarié . convention collective . sport . primes: - autres manifestations contrat salarié . convention collective . sport . primes . manifestation 1: - période: aucune question: Quelle prime pour la première manifestation ? applicable si: nombre de manifestations > 0 par défaut: 100 unité: € -? contrat salarié . convention collective . sport . primes . manifestation 1 . franchise -: titre: franchise manifestation 1 - période: aucune +contrat salarié . convention collective . sport . primes . manifestation 1 . franchise: + titre: franchise manifestation 1 formule: encadrement: valeur: manifestation 1 plafond: 70% * plafond journalier sécurité sociale contrat salarié . convention collective . sport . primes . manifestation 2: - période: aucune question: Quelle prime pour la deuxième manifestation ? applicable si: nombre de manifestations > 1 par défaut: 100 unité: € -? contrat salarié . convention collective . sport . primes . manifestation 2 . franchise -: titre: franchise manifestation 2 - période: aucune +contrat salarié . convention collective . sport . primes . manifestation 2 . franchise: + titre: franchise manifestation 2 formule: encadrement: valeur: manifestation 2 plafond: 70% * plafond journalier sécurité sociale contrat salarié . convention collective . sport . primes . manifestation 3: - période: aucune question: Quelle prime pour la troisième manifestation ? applicable si: nombre de manifestations > 2 par défaut: 100 unité: € -? contrat salarié . convention collective . sport . primes . manifestation 3 . franchise -: titre: franchise manifestation 3 - période: aucune +contrat salarié . convention collective . sport . primes . manifestation 3 . franchise: + titre: franchise manifestation 3 formule: encadrement: valeur: manifestation 3 plafond: 70% * plafond journalier sécurité sociale contrat salarié . convention collective . sport . primes . manifestation 4: - période: aucune question: Quelle prime pour la quatrième manifestation ? applicable si: nombre de manifestations > 3 par défaut: 100 unité: € -? contrat salarié . convention collective . sport . primes . manifestation 4 . franchise -: titre: franchise manifestation 4 - période: aucune +contrat salarié . convention collective . sport . primes . manifestation 4 . franchise: + titre: franchise manifestation 4 formule: encadrement: valeur: manifestation 4 @@ -5434,28 +5174,24 @@ contrat salarié . convention collective . sport . primes . manifestation 5: question: Quelle prime pour la cinquième manifestation ? applicable si: nombre de manifestations > 4 par défaut: 100 - période: aucune unité: € -? contrat salarié . convention collective . sport . primes . manifestation 5 . franchise -: titre: franchise manifestation 5 - période: aucune +contrat salarié . convention collective . sport . primes . manifestation 5 . franchise: + titre: franchise manifestation 5 formule: encadrement: valeur: manifestation 5 plafond: 70% * plafond journalier sécurité sociale -? contrat salarié . convention collective . sport . primes . autres manifestations -: question: Quelles primes pour les autres manifestations ? +contrat salarié . convention collective . sport . primes . autres manifestations: + question: Quelles primes pour les autres manifestations ? applicable si: nombre de manifestations > 5 - période: aucune par défaut: 100 unité: € contrat salarié . convention collective . sport . cotisations . franchise: applicable si: entreprise . effectif < 10 - période: aucune - unité: € + unité: €/mois formule: somme: - primes . manifestation 1 . franchise @@ -5481,16 +5217,14 @@ contrat salarié . intermittents du spectacle: contrat salarié . intermittents du spectacle . formation professionnelle: remplace: formation professionnelle - période: mois formule: somme: - - 50 € + - 50 €/mois - multiplication: - assiette: rémunération . brut + assiette: rémunération . brut [€/mois] taux: 2.10% contrat salarié . intermittents du spectacle . caisse des congés spectacle: - période: flexible formule: multiplication: assiette: rémunération . brut @@ -5498,8 +5232,8 @@ contrat salarié . intermittents du spectacle . caisse des congés spectacle: références: audiens.org: https://www.audiens.org/files/live/sites/siteAudiens/files/03_documents/entreprise/Fiches-techniques/Conges-Spectacles-Mode-d-emploi-employeur-2019.pdf -? contrat salarié . intermittents du spectacle . retraite complémentaire techniciens et cadre -: applicable si: +contrat salarié . intermittents du spectacle . retraite complémentaire techniciens et cadre: + applicable si: une de ces conditions: - statut cadre - technicien @@ -5580,7 +5314,6 @@ contrat salarié . intermittents du spectacle . artiste . réduction de taux: # TODO : centraliser les exonérations sous un namespace commun pour plus de facilité dans leur activiation / desactivation rend non applicable: réduction générale non applicable si: activité accessoire - période: aucune remplace: # - règle: exonérations . taux réduit # par: oui @@ -5604,24 +5337,23 @@ contrat salarié . intermittents du spectacle . artiste . réduction de taux: par: FNAL . taux * réduction de taux formule: 70% -? contrat salarié . intermittents du spectacle . artiste . réduction de taux . ATMP -: remplace: ATMP . taux - période: aucune +contrat salarié . intermittents du spectacle . artiste . réduction de taux . ATMP: + remplace: ATMP . taux formule: variations: - si: régime alsace moselle alors: 1.54% - sinon: 1.12% -? contrat salarié . intermittents du spectacle . artiste . nombre jours travaillés -: question: Pour combien de jours continus l'artiste est-il engagé ? +contrat salarié . intermittents du spectacle . artiste . nombre jours travaillés: + question: Pour combien de jours continus l'artiste est-il engagé ? unité: jours par défaut: 5 contrat salarié . intermittents du spectacle . artiste . plafond proratisé: applicable si: nombre jours travaillés < 5 - période: mois - unité: € + unité: €/mois + remplace: règle: plafond sécurité sociale dans: @@ -5637,13 +5369,12 @@ contrat salarié . intermittents du spectacle . artiste . acteur de complément: question: L'artiste est-il un acteur de complément engagé à la journée pour une production cinématographique ? par defaut: non -? contrat salarié . intermittents du spectacle . artiste . acteur de complément . nombre jours travaillés -: remplace: artiste . nombre jours travaillés +contrat salarié . intermittents du spectacle . artiste . acteur de complément . nombre jours travaillés: + remplace: artiste . nombre jours travaillés formule: 1 -? contrat salarié . intermittents du spectacle . artiste . acteur de complément . assiette forfaitaire -: applicable si: rémunération . brut < 6% * plafond sécurité sociale temps plein - période: flexible +contrat salarié . intermittents du spectacle . artiste . acteur de complément . assiette forfaitaire: + applicable si: rémunération . brut < 6% * plafond sécurité sociale temps plein remplace: - contrat salarié . cotisations . assiette forfaitaire - règle: nombre jours travaillés @@ -5658,7 +5389,6 @@ contrat salarié . cotisations . assiette forfaitaire: contrat salarié . cotisations . assiette forfaitaire . montant: titre: assiette forfaitaire de cotisations - période: flexible non applicable si: rémunération réelle remplace: - règle: cotisations . assiette @@ -5684,7 +5414,6 @@ contrat salarié . cotisations . assiette forfaitaire . montant: CSG et CRDS: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/la-base-de-calcul/assiette-csg-crds.html contrat salarié . cotisations . assiette forfaitaire . minimum: - période: mois description: > Il existe une règle générale d'encadrement des assiettes forfaitaires. Lorsque la rémunération est supérieure ou égale à 1,5 fois le plafond de la sécurité sociale, l'assiette forfaitaire retenue ne peut être inférieure à 70% de la rémunération @@ -5692,7 +5421,6 @@ contrat salarié . cotisations . assiette forfaitaire . minimum: formule: 70% * rémunération . brut contrat salarié . cotisations . assiette forfaitaire . rémunération réelle: - période: aucune question: Voulez-vous calculer les cotisations sur la rémunération réelle (au lieu de la base forfaitaire) ? par défaut: non @@ -5703,8 +5431,7 @@ artiste-auteur: artiste-auteur . revenus: artiste-auteur . revenus . traitements et salaires: - unité: € - période: année + unité par défaut: €/an par défaut: 0 question: Traitements et salaires résumé: Revenus en précompte @@ -5713,7 +5440,7 @@ artiste-auteur . revenus . BNC: période: flexible formule: encadrement: - plancher: 0 € + plancher: 0 €/an valeur: variations: - si: micro-bnc @@ -5729,34 +5456,30 @@ artiste-auteur . revenus . BNC: artiste-auteur . revenus . BNC . micro-bnc: applicable si: toutes ces conditions: - - recettes [annuel] > 0 € - - recettes [annuel] < 70000 € + - recettes > 0 + - recettes < 70000 €/an par défaut: oui question: Opter pour le régime micro-BNC résumé: Évaluation forfaitaire des frais à 34% des recettes artiste-auteur . revenus . BNC . recettes: - unité: € - période: année + unité par défaut: €/an par défaut: 0 question: Recettes BNC HT résumé: Le montant des ventes réalisées artiste-auteur . revenus . BNC . frais réels: - unité: € - période: année + unité par défaut: €/an par défaut: 0 question: Frais réels BNC résumé: Vient s'imputer aux recettes pour déterminer le bénéfice - applicable si: recettes [annuel] > 0 € + applicable si: recettes > 0 €/an non applicable si: micro-bnc artiste-auteur . revenus . BNC . charges forfaitaires: - période: flexible formule: 34% * recettes artiste-auteur . cotisations: - période: flexible formule: somme: - vieillesse @@ -5766,7 +5489,6 @@ artiste-auteur . cotisations: URSSAF.fr: https://www.urssaf.fr/portail/home/espaces-dedies/artistes-auteurs-diffuseurs-comm/vous-etes-artiste-auteur/taux-des-cotisations.html artiste-auteur . cotisations . assiette: - période: flexible description: Les revenus des artistes-auteurs peuvent être catégorisés soit comme des traitements et salaires, soit comme des bénéfices non commerciaux. Les cotisations sociales sont payées sur la somme des revenus de ces deux catégories. formule: somme: @@ -5776,7 +5498,7 @@ artiste-auteur . cotisations . assiette: artiste-auteur . cotisations . option surcotisation: applicable si: toutes ces conditions: - - assiette > 0 € + - assiette > 0 - assiette < assiette surcotisation question: Souhaitez-vous surcotiser ? résumé: Vos revenus sont en dessous des seuils vous permettant de valider 4 trimestres de retraite. Vous pouvez choisir de surcotiser pour augmenter vos droits. @@ -5785,7 +5507,7 @@ artiste-auteur . cotisations . option surcotisation: URSSAF.fr: https://www.urssaf.fr/portail/home/espaces-dedies/artistes-auteurs-diffuseurs-comm/vous-etes-artiste-auteur/la-surcotisation.html artiste-auteur . cotisations . assiette surcotisation: - formule: 900 heures * SMIC horaire + formule: 900 heures/an * SMIC horaire artiste-auteur . cotisations . surcotisation: période: flexible @@ -5796,7 +5518,6 @@ artiste-auteur . cotisations . surcotisation: par: assiette surcotisation artiste-auteur . cotisations . vieillesse: - période: flexible formule: multiplication: assiette: assiette @@ -5808,21 +5529,18 @@ artiste-auteur . cotisations . vieillesse: taux: contrat salarié . vieillesse . taux salarié non plafonné - 0.4% artiste-auteur . cotisations . CSG-CRDS: - période: flexible formule: somme: - CSG - CRDS artiste-auteur . cotisations . CSG-CRDS . assiette: - période: flexible formule: somme: - cotisations . assiette - (- CSG-CRDS . abattement) artiste-auteur . cotisations . CSG-CRDS . abattement: - période: flexible formule: multiplication: assiette: revenus . traitements et salaires @@ -5830,21 +5548,18 @@ artiste-auteur . cotisations . CSG-CRDS . abattement: taux: 1.75% artiste-auteur . cotisations . CSG-CRDS . CSG: - période: flexible formule: multiplication: assiette: CSG-CRDS . assiette taux: 9.20% artiste-auteur . cotisations . CSG-CRDS . CRDS: - période: flexible formule: multiplication: assiette: CSG-CRDS . assiette taux: 0.50% artiste-auteur . cotisations . formation professionnelle: - période: flexible formule: multiplication: assiette: assiette diff --git a/source/règles/brouillons/rémunération-travail/cdd/majoration-chomage.md b/source/règles/brouillons/rémunération-travail/cdd/majoration-chomage.md deleted file mode 100644 index 381467a89..000000000 --- a/source/règles/brouillons/rémunération-travail/cdd/majoration-chomage.md +++ /dev/null @@ -1,42 +0,0 @@ -```yaml -aiguillage numérique: # première valeur trouvée, sinon 0 - - poursuite du CDD en CDI: 0% - # - Contrat . type : # mécanisme de match à introduire une fois les entités gérées. Exclusivité exprimée dans l'entité Type - - conditions exclusives: - # ce n'est pas évident de savoir le type d'un CDD, proposer le calcul dans une autre variable !! - - CDD type accroissement temporaire d'activité: - - contrat de travail durée ≤ 1 mois: 3% - - contrat de travail durée ≤ 3 mois: 1.5% - - CDD type usage: - - contrat de travail durée ≤ 3 mois: 0.5% - # - True: 0% # Ce mécanisme ajoute automatiquement cette ligne :) - - -aiguillage numérique 2: - - poursuite du CDD en CDI: 0% - - aiguillage: # signale que les deux propositions sont exclusives - sujet: Contrat . type - propositions: - - accroissement temporaire d'activité: - - contrat de travail durée ≤ 1 mois: 3% - - contrat de travail durée ≤ 3 mois: 1.5% - - usage: - - contrat de travail durée ≤ 3 mois: 0.5% -``` - - -On aurait aussi pu écrire la formule de façon plus explicite mais plus verbose : -```yaml - variations: - - si: motif . accroissement temporaire activité: - variations: - - si: durée contrat <= 1 - taux: 3% - - si: durée contrat <= 3 - taux: 1.5% - - si: motif . usage: - variations: - - si: durée contrat <= 3 - taux: 0.5% - -``` diff --git a/source/règles/co2.yaml b/source/règles/co2.yaml index ebc6f2f10..0785f901a 100644 --- a/source/règles/co2.yaml +++ b/source/règles/co2.yaml @@ -1,37 +1,30 @@ -# espace de nom implicite : douche -# non bloquant : -# - période: semaine -# bloquant : -# - ? - douche: icônes: 🚿 douche . impact: icônes: 🍃 - période: flexible unité: kgCO2eq formule: impact par douche * douche . nombre douche . nombre: - période: flexible question: Combien prenez-vous de douches ? - unité: _ + unité: douche par défaut: 30 suggestions: Une par jour: 30 douche . impact par douche: - formule: impact par litre * litres d'eau + formule: impact par litre * litres d'eau par douche douche . impact par litre: formule: eau . impact par litre froid + chauffage . impact par litre -douche . litres d'eau: +douche . litres d'eau par douche: icônes: 🇱 - formule: durée de la douche * litres par minute + formule: durée de la douche * litres par minute / 1 douche douche . litres par minute: + unité: l/min formule: variations: - si: pomme de douche économe @@ -113,7 +106,7 @@ chauffage . impact par litre: douche . durée de la douche: question: Combien de temps dure votre douche en général ? - unité: _ + unité: min par défaut: 5 suggestions: expresse: 5 diff --git a/source/règles/externalized.yaml b/source/règles/externalized.yaml index f0ce7c51e..e51123c57 100644 --- a/source/règles/externalized.yaml +++ b/source/règles/externalized.yaml @@ -698,9 +698,7 @@ contrat salarié . rémunération . brut de base: contrôles.en: - si: toutes ces conditions: - - >- - rémunération . assiette de vérification du SMIC [mensuel] < SMIC - [mensuel] + - rémunération . assiette de vérification du SMIC < SMIC - dirigeant != 'assimilé salarié' - stage != oui - apprentissage != oui @@ -710,40 +708,18 @@ contrat salarié . rémunération . brut de base: solution: cible: contrat salarié . temps partiel texte: Is it a part-time contract? - - si: - toutes ces conditions: - - 'brut de base [mensuel] > 10000' - - période = 'mois' + - si: brut de base > 10000 €/mois niveau: information message: > The monthly wage seized is high. Are you sure the calculation period isn't set to month instead of year? contrôles.fr: - - si: - toutes ces conditions: - - >- - rémunération . assiette de vérification du SMIC [mensuel] < SMIC - contractuel [mensuel] - - dirigeant != 'assimilé salarié' - - stage != oui - - apprentissage != oui - niveau: avertissement - message: | + - message: | Le salaire saisi est inférieur au SMIC. - - si: - toutes ces conditions: - - stage - - 'brut de base [mensuel] < stage . gratification minimale [mensuel]' - niveau: avertissement - message: > + - message: > La rémunération du stage est inférieure à la [gratification minimale](https://www.service-public.fr/professionnels-entreprises/vosdroits/F32131). - - si: - toutes ces conditions: - - 'brut de base [mensuel] > 10000' - - période = 'mois' - niveau: information - message: > + - message: > Le salaire mensuel saisi est élevé. Ne vous êtes-vous pas trompé de période de calcul ? contrat salarié . rémunération . brut de base . équivalent temps plein: @@ -1541,11 +1517,11 @@ contrat salarié . temps de travail . heures supplémentaires: contrôles.en: - si: toutes ces conditions: - - heures supplémentaires > 9 * 4.33 - - heures supplémentaires <= 13 * 4.33 + - heures supplémentaires > 9 heures/semaine * période . semaines par mois + - heures supplémentaires <= 13 heures/semaine * période . semaines par mois niveau: info message: The average weekly working time may not exceed 44 hours - - si: heures supplémentaires > 13 * 4.33 + - si: heures supplémentaires > 13 heures/semaine * période . semaines par mois niveau: avertissement message: The maximum weekly working time may not exceed 48 hours contrôles.fr: @@ -1803,17 +1779,15 @@ contrat salarié . complémentaire santé . forfait: ce que nous avons retenu pour cette simulation, ou davantage. Le montant est libre, tant qu'elle couvre un panier légal de soins. contrôles.en: - - si: 'complémentaire santé . forfait [mensuel] < 15' + - si: 'complémentaire santé . forfait < 15€/mois' message: >- Make sure that such an inexpensive health supplement covers the minimum care basket defined in the law. niveau: avertissement contrôles.fr: - - si: 'complémentaire santé . forfait [mensuel] < 15' - message: >- + - message: >- Vérifiez bien qu'une complémentaire santé si peu chère couvre le panier de soin minimal défini dans la loi. - niveau: avertissement contrat salarié . complémentaire santé . forfait . en alsace moselle: titre.en: Complementary health insurance plan (Alsace-Moselle) titre.fr: forfait complémentaire santé en Alsace-Moselle @@ -3155,7 +3129,7 @@ dirigeant . auto-entrepreneur . impôt . versement libératoire: contrôles.en: - si: toutes ces conditions: - - 'impôt . revenu fiscal de référence [annuel] > 27086' + - 'impôt . revenu fiscal de référence > 27086 €/an' - versement libératoire message: >- The discharge payment is not available if your household income exceeds @@ -3164,7 +3138,7 @@ dirigeant . auto-entrepreneur . impôt . versement libératoire: contrôles.fr: - si: toutes ces conditions: - - 'impôt . revenu fiscal de référence [annuel] > 27086' + - 'impôt . revenu fiscal de référence > 27086 €/an' - versement libératoire message: >- Le versement libératoire n'est pas disponible si les revenus de votre diff --git a/source/règles/sasu.yaml b/source/règles/sasu.yaml index e4b06d652..35d58b7b3 100644 --- a/source/règles/sasu.yaml +++ b/source/règles/sasu.yaml @@ -1,22 +1,19 @@ # Ce petit ensemble de règles a été historiquement utilisé pour tester l'externalisation du moteur, et est en train d'être réintégré progressivement dans la base centrale chiffre affaires: - période: flexible - unité: € + unité par défaut: €/mois charges: - période: flexible - par défaut: 0 - unité: € + par défaut: 0 €/mois répartition salaire sur dividendes: - par défaut: 0.5 + par défaut: 50 + unité: '%' impôt sur les sociétés: - période: année formule: barème: - assiette: bénéfice + assiette: bénéfice [€/an] tranches: - en-dessous de: 38120 taux: 15% @@ -29,21 +26,17 @@ impôt sur les sociétés: fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23575 bénéfice: - période: flexible formule: chiffre affaires - salaire total dividendes: dividendes . brut: - période: flexible formule: bénéfice - impôt sur les sociétés dividendes . net: - période: flexible formule: brut - prélèvement forfaitaire unique dividendes . prélèvement forfaitaire unique: - période: flexible formule: multiplication: assiette: brut @@ -52,9 +45,7 @@ dividendes . prélèvement forfaitaire unique: - taux: 12.8% salaire total: - période: flexible formule: chiffre affaires * répartition salaire sur dividendes revenu net après impôt: - période: flexible formule: contrat salarié . rémunération . net après impôt + dividendes . net diff --git a/source/selectors/analyseSelectors.ts b/source/selectors/analyseSelectors.ts index 79fa979bc..b117a1d3a 100644 --- a/source/selectors/analyseSelectors.ts +++ b/source/selectors/analyseSelectors.ts @@ -1,23 +1,50 @@ -import { collectMissingVariablesByTarget, getNextSteps } from 'Engine/generateQuestions' -import { collectDefaults, disambiguateExampleSituation, findRuleByDottedName } from 'Engine/rules' +import { + collectMissingVariablesByTarget, + getNextSteps +} from 'Engine/generateQuestions' +import { + collectDefaults, + disambiguateExampleSituation, + findRuleByDottedName +} from 'Engine/rules' import { analyse, analyseMany, parseAll } from 'Engine/traverse' -import { add, defaultTo, difference, dissoc, equals, head, intersection, isEmpty, isNil, last, length, map, mergeDeepWith, negate, pick, pipe, sortBy, split, takeWhile, zipWith } from 'ramda' +import { + add, + defaultTo, + difference, + equals, + head, + intersection, + isNil, + last, + length, + map, + mergeDeepWith, + negate, + pick, + pipe, + sortBy, + split, + takeWhile, + zipWith +} from 'ramda' import { useSelector } from 'react-redux' import { RootState } from 'Reducers/rootReducer' import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect' -import { DottedName } from "Types/rule" +import { DottedName } from 'Types/rule' import { mapOrApply } from '../utils' // create a "selector creator" that uses deep equal instead of === const createDeepEqualSelector = createSelectorCreator(defaultMemoize, equals) -let configSelector = (state: RootState) => state.simulation && state.simulation.config || {} +let configSelector = (state: RootState) => + (state.simulation && state.simulation.config) || {} // We must here compute parsedRules, flatRules, analyse which contains both targets and cache objects export let flatRulesSelector = ( state: RootState, props?: { rules: RootState['rules'] } ) => { - return props && props.rules || state.rules + return (props && props.rules) || state.rules } export let parsedRulesSelector = createSelector([flatRulesSelector], rules => @@ -50,9 +77,7 @@ export let targetNamesSelector = (state: RootState) => { type SituationSelectorType = typeof situationSelector export const situationSelector = (state: RootState) => - state.simulation && state.simulation.situation || {} - -export const usePeriod = () => useSelector(situationSelector)['période'] + (state.simulation && state.simulation.situation) || {} export const useTarget = (dottedName: DottedName) => { const targets = useSelector( @@ -61,34 +86,25 @@ export const useTarget = (dottedName: DottedName) => { return targets && targets.find(t => t.dottedName === dottedName) } -export let noUserInputSelector = createSelector( - [situationSelector], - situation => !situation || isEmpty(dissoc('période', situation)) -) +export let noUserInputSelector = state => + !Object.keys(situationSelector(state)).length export let firstStepCompletedSelector = createSelector( - [ - situationSelector, - targetNamesSelector, - parsedRulesSelector, - configSelector - ], + [situationSelector, targetNamesSelector, parsedRulesSelector, configSelector], (situation, targetNames, parsedRules, config) => { if (!situation) { return true } const situations = Object.keys(situation) const allBlockingAreAnswered = - config.bloquant && config.bloquant.every(rule => situations.includes(rule)) + config.bloquant && + config.bloquant.every(rule => situations.includes(rule)) const targetIsAnswered = targetNames && - targetNames.some( - targetName => { - const rule = findRuleByDottedName(parsedRules, targetName) - return rule && rule.formule && - targetName in situation - } - ) + targetNames.some(targetName => { + const rule = findRuleByDottedName(parsedRules, targetName) + return rule && rule.formule && targetName in situation + }) return allBlockingAreAnswered || targetIsAnswered } ) @@ -97,7 +113,9 @@ let validatedStepsSelector = createSelector( [state => state.conversationSteps.foldedSteps, targetNamesSelector], (foldedSteps, targetNames) => [...foldedSteps, ...targetNames] ) -let branchesSelector = (state: RootState) => configSelector(state).branches +export const defaultUnitsSelector = (state: RootState) => + state.simulation?.defaultUnits || [] +let branchesSelector = (state: RootState) => configSelector(state).branches let configSituationSelector = (state: RootState) => configSelector(state).situation || {} @@ -144,23 +162,29 @@ export let situationsWithDefaultsSelector = createSelector( mapOrApply(situation => ({ ...defaults, ...situation }), situations) ) -let analyseRule = (parsedRules, ruleDottedName, situationGate) => - analyse(parsedRules, ruleDottedName)(situationGate).targets[0] +let analyseRule = (parsedRules, ruleDottedName, situationGate, defaultUnits) => + analyse(parsedRules, ruleDottedName, defaultUnits)(situationGate).targets[0] export let ruleAnalysisSelector = createSelector( [ parsedRulesSelector, (_, props) => props.dottedName, situationsWithDefaultsSelector, - state => state.situationBranch || 0 + state => state.situationBranch || 0, + defaultUnitsSelector ], - (rules, dottedName, situations, situationBranch) => { - return analyseRule(rules, dottedName, dottedName => { - const currentSituation = Array.isArray(situations) - ? situations[situationBranch] - : situations - return currentSituation[dottedName] - }) + (rules, dottedName, situations, situationBranch, defaultUnits) => { + return analyseRule( + rules, + dottedName, + dottedName => { + const currentSituation = Array.isArray(situations) + ? situations[situationBranch] + : situations + return currentSituation[dottedName] + }, + defaultUnits + ) } ) @@ -183,22 +207,34 @@ export let exampleAnalysisSelector = createSelector( [ parsedRulesSelector, (_, props) => props.dottedName, - exampleSituationSelector + exampleSituationSelector, + ({ currentExample }) => currentExample ], - (rules, dottedName, situation) => + (rules, dottedName, situation, example) => situation && - analyseRule(rules, dottedName, dottedName => situation[dottedName]) + analyseRule( + rules, + dottedName, + dottedName => situation[dottedName], + example.defaultUnits + ) ) let makeAnalysisSelector = (situationSelector: SituationSelectorType) => createDeepEqualSelector( - [parsedRulesSelector, targetNamesSelector, situationSelector], - (parsedRules, targetNames, situations) => + [ + parsedRulesSelector, + targetNamesSelector, + situationSelector, + defaultUnitsSelector + ], + (parsedRules, targetNames, situations, defaultUnits) => mapOrApply( situation => analyseMany( parsedRules, - targetNames + targetNames, + defaultUnits )(dottedName => { return situation[dottedName] }), @@ -262,11 +298,13 @@ export let nextStepsSelector = createSelector( ], ( mv, - {questions: { - 'non prioritaires': notPriority = [], - uniquement: only = null, - 'liste noire': blacklist = [] - } = {}}, + { + questions: { + 'non prioritaires': notPriority = [], + uniquement: only = null, + 'liste noire': blacklist = [] + } = {} + }, foldedSteps = [], situation ) => { diff --git a/source/sites/mon-entreprise.fr/middlewares/trackSimulatorActions.ts b/source/sites/mon-entreprise.fr/middlewares/trackSimulatorActions.ts index 81c51d128..ab9f90fbe 100644 --- a/source/sites/mon-entreprise.fr/middlewares/trackSimulatorActions.ts +++ b/source/sites/mon-entreprise.fr/middlewares/trackSimulatorActions.ts @@ -38,13 +38,16 @@ export default (tracker: Tracker) => { ]) } - if (action.type === 'UPDATE_SITUATION' || action.type === 'UPDATE_PERIOD') { + if ( + action.type === 'UPDATE_SITUATION' || + action.type === 'UPDATE_DEFAULT_UNIT' + ) { tracker.push([ 'trackEvent', 'Simulator', 'update situation', - ...(action.type === 'UPDATE_PERIOD' - ? ['période', action.toPeriod] + ...(action.type === 'UPDATE_DEFAULT_UNIT' + ? ['unité', action.defaultUnit] : [action.fieldName, action.value]) ]) } diff --git a/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx b/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx index bd888f07b..fc6290bd4 100644 --- a/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx +++ b/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx @@ -1,18 +1,18 @@ -import { updateSituation } from 'Actions/actions' +import { setSimulationConfig, updateSituation } from 'Actions/actions' import { DistributionBranch } from 'Components/Distribution' import RuleLink from 'Components/RuleLink' import SimulateurWarning from 'Components/SimulateurWarning' import config from 'Components/simulationConfigs/artiste-auteur.yaml' -import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig' import 'Components/TargetSelection.css' import { formatValue } from 'Engine/format' import { getRuleFromAnalysis } from 'Engine/rules' -import { serialiseUnit } from 'Engine/units' import React, { useEffect, useState } from 'react' import NumberFormat from 'react-number-format' import { useDispatch, useSelector } from 'react-redux' +import { RootState } from 'Reducers/rootReducer' import { analysisWithDefaultsSelector, + ruleAnalysisSelector, situationSelector } from 'Selectors/analyseSelectors' import styled from 'styled-components' @@ -35,7 +35,8 @@ function useInitialRender() { } export default function ArtisteAuteur() { - useSimulationConfig(config) + const dispatch = useDispatch() + dispatch(setSimulationConfig(config)) const initialRender = useInitialRender() return ( @@ -82,13 +83,15 @@ type SimpleFieldProps = { function SimpleField({ dottedName, initialRender }: SimpleFieldProps) { const rule = useRule(dottedName) const dispatch = useDispatch() - const situation = useSelector(situationSelector) - const [value, setValue] = useState(situation[dottedName]) - if (!rule) { + const analysis = useSelector((state: RootState) => + ruleAnalysisSelector(state, { dottedName }) + ) + const [value, setValue] = useState(analysis.nodeValue) + + if (!analysis.isApplicable) { return null } - const unit = serialiseUnit(rule.unit) return (
  • @@ -100,7 +103,8 @@ function SimpleField({ dottedName, initialRender }: SimpleFieldProps) {
    - {unit === '€' && ( + {/* Super hacky */} + {analysis.unit !== undefined ? ( - )} - {/* Super hacky */} - {unit !== '€' && ( + ) : ( diff --git a/source/sites/mon-entreprise.fr/pages/Simulateurs/Salarié.tsx b/source/sites/mon-entreprise.fr/pages/Simulateurs/Salarié.tsx index aecaa3c5d..0bc0f4766 100644 --- a/source/sites/mon-entreprise.fr/pages/Simulateurs/Salarié.tsx +++ b/source/sites/mon-entreprise.fr/pages/Simulateurs/Salarié.tsx @@ -1,22 +1,24 @@ +import { setSimulationConfig } from 'Actions/actions' import { T } from 'Components' import Banner from 'Components/Banner' import PreviousSimulationBanner from 'Components/PreviousSimulationBanner' import SalaryExplanation from 'Components/SalaryExplanation' import Simulation from 'Components/Simulation' import salariéConfig from 'Components/simulationConfigs/salarié.yaml' -import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig' import { IsEmbeddedContext } from 'Components/utils/embeddedContext' import { Markdown } from 'Components/utils/markdown' import { SitePathsContext } from 'Components/utils/withSitePaths' import urlIllustrationNetBrutEn from 'Images/illustration-net-brut-en.png' import urlIllustrationNetBrut from 'Images/illustration-net-brut.png' -import React, { useContext } from 'react' +import { default as React, useContext } from 'react' import { Helmet } from 'react-helmet' import { useTranslation } from 'react-i18next' +import { useDispatch } from 'react-redux' import { Link } from 'react-router-dom' export default function Salarié() { const { t, i18n } = useTranslation() + const isEmbedded = React.useContext(IsEmbeddedContext) return ( <> @@ -101,7 +103,8 @@ In addition to the salary, our simulator takes into account the calculation of b There are deferred hiring aids that are not taken into account by our simulator, you can find them on the official portal.` export let SalarySimulation = () => { - useSimulationConfig(salariéConfig) + const dispatch = useDispatch() + dispatch(setSimulationConfig(salariéConfig)) const sitePaths = useContext(SitePathsContext) return ( <> diff --git a/test/bug-cotisations.test.js b/test/bug-cotisations.test.js index 900e41115..b53472d2e 100644 --- a/test/bug-cotisations.test.js +++ b/test/bug-cotisations.test.js @@ -13,7 +13,7 @@ describe('bug-analyse-many', function() { - nom: cotisations formule: somme: - - cotisation a [salarié] + - cotisation a .salarié - cotisation b - nom: cotisation a diff --git a/test/ficheDePaieSelector.test.js b/test/ficheDePaieSelector.test.js index f6e789e49..f14c20318 100644 --- a/test/ficheDePaieSelector.test.js +++ b/test/ficheDePaieSelector.test.js @@ -1,4 +1,3 @@ - import { expect } from 'chai' // $FlowFixMe import salariéConfig from 'Components/simulationConfigs/salarié.yaml' diff --git a/test/generateQuestions.test.js b/test/generateQuestions.test.js index b96d4d169..1743e3662 100644 --- a/test/generateQuestions.test.js +++ b/test/generateQuestions.test.js @@ -140,28 +140,6 @@ describe('collectMissingVariables', function() { expect(result).to.be.empty }) - it('should report missing variables in switch statements', function() { - let rawRules = [ - { nom: 'top' }, - { - nom: 'top . startHere', - formule: { - 'aiguillage numérique': { - '11 > dix': '1000%', - '3 > dix': '1100%', - '1 > dix': '1200%' - } - } - }, - { nom: 'top . dix' } - ], - rules = parseAll(rawRules.map(enrichRule)), - analysis = analyse(rules, 'startHere')(stateSelector), - result = collectMissingVariables(analysis.targets) - - expect(result).to.include('top . dix') - }) - // TODO : enlever ce test, depuis que l'on évalue plus les branches qui ne sont pas encore applicable it.skip('should report missing variables in variations', function() { let rawRules = [ @@ -217,75 +195,6 @@ describe('collectMissingVariables', function() { // TODO // expect(result).to.include('top . trois') }) - - it('should not report missing variables in switch for consequences of false conditions', function() { - let rawRules = [ - { nom: 'top' }, - { - nom: 'top . startHere', - formule: { - 'aiguillage numérique': { - '8 > 10': '1000%', - '1 > 2': 'dix' - } - } - }, - { nom: 'top . dix' } - ], - rules = parseAll(rawRules.map(enrichRule)), - analysis = analyse(rules, 'startHere')(stateSelector), - result = collectMissingVariables(analysis.targets) - - expect(result).to.be.empty - }) - - it('should report missing variables in consequence when its condition is unresolved', function() { - let rawRules = [ - { nom: 'top' }, - { - nom: 'top . startHere', - formule: { - 'aiguillage numérique': { - '10 > 11': '1000%', - '3 > dix': { - douze: '560%', - '1 > 2': '75015%' - } - } - } - }, - { nom: 'top . douze' }, - { nom: 'top . dix' } - ], - rules = parseAll(rawRules.map(enrichRule)), - analysis = analyse(rules, 'startHere')(stateSelector), - result = collectMissingVariables(analysis.targets) - - expect(result).to.include('top . dix') - expect(result).to.include('top . douze') - }) - - it('should not report missing variables when a switch short-circuits', function() { - let rawRules = [ - { nom: 'top' }, - { - nom: 'top . startHere', - formule: { - 'aiguillage numérique': { - '11 > 10': '1000%', - '3 > dix': '1100%', - '1 > dix': '1200%' - } - } - }, - { nom: 'top . dix' } - ], - rules = parseAll(rawRules.map(enrichRule)), - analysis = analyse(rules, 'startHere')(stateSelector), - result = collectMissingVariables(analysis.targets) - - expect(result).to.be.empty - }) }) describe('nextSteps', function() { @@ -368,9 +277,10 @@ describe('nextSteps', function() { }[name]) let rules = parseAll(realRules.map(enrichRule)), - analysis = analyse(rules, 'contrat salarié . rémunération . net')( - stateSelector - ), + analysis = analyse( + rules, + 'contrat salarié . rémunération . net' + )(stateSelector), result = collectMissingVariables(analysis.targets) expect(result).to.include('contrat salarié . CDD . motif') diff --git a/test/inversion.test.js b/test/inversion.test.js index 20edcd3f4..577565373 100644 --- a/test/inversion.test.js +++ b/test/inversion.test.js @@ -198,7 +198,7 @@ describe('inversions', () => { taux: 50% - nom: total - formule: cotisation [employeur] + cotisation [salarié] + formule: cotisation .employeur + cotisation .salarié - nom: brut unité: € diff --git a/test/library.test.js b/test/library.test.js index 6d8b6cf09..7023ca1b7 100644 --- a/test/library.test.js +++ b/test/library.test.js @@ -32,7 +32,6 @@ describe('library', function() { - nom: yo formule: 1 - nom: ya - période: flexible formule: contrat salarié . rémunération . net + yo ` diff --git a/test/mecanisms.test.js b/test/mecanisms.test.js index 1a23be7e2..a4375beb8 100644 --- a/test/mecanisms.test.js +++ b/test/mecanisms.test.js @@ -5,12 +5,12 @@ */ import { expect } from 'chai' +import { serialiseUnit } from 'Engine/units' +import * as R from 'ramda' +import { collectMissingVariables } from '../source/engine/generateQuestions' import { enrichRule } from '../source/engine/rules' import { analyse, parseAll } from '../source/engine/traverse' -import { collectMissingVariables } from '../source/engine/generateQuestions' import testSuites from './load-mecanism-tests' -import * as R from 'ramda' -import { serialiseUnit } from 'Engine/units' describe('Mécanismes', () => testSuites.map(([suiteName, suite]) => @@ -23,6 +23,7 @@ describe('Mécanismes', () => ({ nom: testTexte, situation, + 'unités par défaut': defaultUnits, 'valeur attendue': valeur, 'variables manquantes': expectedMissing }) => @@ -38,7 +39,7 @@ describe('Mécanismes', () => ), state = situation || {}, stateSelector = name => state[name], - analysis = analyse(rules, test)(stateSelector), + analysis = analyse(rules, test, defaultUnits)(stateSelector), missing = collectMissingVariables(analysis.targets), target = analysis.targets[0] diff --git a/test/mécanismes/aiguillage-numérique.yaml b/test/mécanismes/aiguillage-numérique.yaml deleted file mode 100644 index 9a88e18e1..000000000 --- a/test/mécanismes/aiguillage-numérique.yaml +++ /dev/null @@ -1,77 +0,0 @@ -# Utiliser http://romainvaleri.online.fr/ pour se donner des idées de noms de variables originales - -- nom: dégradation mineure - -- nom: dégradation majeure - -- nom: retenue sur dépot de garantie - test: Aiguillage numérique simple - formule: - aiguillage numérique: - dégradation mineure: 10% - dégradation majeure: 30% - - exemples: - - nom: le premier aiguillage est activé -> sa valeur est renvoyée - situation: - dégradation mineure: oui - valeur attendue: 0.1 - - nom: seul le 2ème aiguillage est activé - situation: - dégradation mineure: non - dégradation majeure: oui - valeur attendue: 0.3 - - nom: aucun aiguillage n'est activé - situation: - dégradation mineure: non - dégradation majeure: non - valeur attendue: 0 - - nom: L'ordre des termes est important - situation: - dégradation mineure: null - dégradation majeure: oui - valeur attendue: null - - -- nom: montant caution - unité: € - -- test: Imbrication d'aiguillages numériques - formule: - aiguillage numérique: - dégradation mineure: 5% - dégradation majeure: - montant caution > 2000: 20% - montant caution > 1000: 10% - - - exemples: - - nom: imbrication simple - situation: - dégradation mineure: oui - dégradation majeure: non - montant caution: 3000 - valeur attendue: 0.05 - - nom: imbrication simple 2 - situation: - dégradation mineure: non - dégradation majeure: oui - montant caution: 1200 - valeur attendue: 0.10 - - nom: imbrication nulle - valeur attendue: null - variables manquantes: - - montant caution - - dégradation mineure - - dégradation majeure - - nom: variables manquantes même si innaccessibles - situation: - dégradation mineure: non - valeur attendue: null - variables manquantes: - - montant caution - - dégradation majeure - - - -# pouvoir tester les variables inconnues mais requises ? diff --git a/test/mécanismes/allègement.yaml b/test/mécanismes/allègement.yaml index 35e4a33a4..48ccd85f9 100644 --- a/test/mécanismes/allègement.yaml +++ b/test/mécanismes/allègement.yaml @@ -1,126 +1,121 @@ -- nom: montant +- nom: montant unité: € - test: montant franchisé unité: € - formule: - allègement: + formule: + allègement: assiette: montant franchise: 1200 - exemples: - - situation: + exemples: + - situation: montant: 1000 valeur attendue: 0 - - situation: + - situation: valeur attendue: null variables manquantes: - montant - - test: montant décoté unité: € - formule: - allègement: + formule: + allègement: assiette: montant - décote: + décote: plafond: 2040 taux: 100% - exemples: - - situation: + exemples: + - situation: montant: 1000 valeur attendue: 0 - test: montant franchisé et décoté unité: € - formule: - allègement: + formule: + allègement: assiette: montant franchise: 1200 - décote: + décote: plafond: 2040 taux: 75% - exemples: - - situation: + exemples: + - situation: montant: 100 valeur attendue: 0 - - situation: + - situation: montant: 1200 valeur attendue: 570 - - situation: + - situation: montant: 1620 valeur attendue: 1305 - - situation: + - situation: montant: 2040 valeur attendue: 2040 - - test: montant abattu unité: € - formule: - allègement: + formule: + allègement: assiette: montant abattement: 20507 - exemples: - - situation: + exemples: + - situation: montant: 10000 valeur attendue: 0 - - situation: + - situation: montant: 80000 valeur attendue: 59493 - - test: montant abattu en pourcentage unité: € - formule: - allègement: + formule: + allègement: assiette: montant abattement: 15% - exemples: - - situation: + exemples: + - situation: montant: 10000 valeur attendue: 8500 - - situation: + - situation: montant: 80000 valeur attendue: 68000 - test: montant abattu avec plafond numérique unité: € - formule: - allègement: + formule: + allègement: assiette: montant abattement: 15% plafond: 12000 - exemples: - - situation: + exemples: + - situation: montant: 10000 valeur attendue: 8500 - - situation: + - situation: montant: 100000 valeur attendue: 88000 # 85000 s'il n'y avait pas de plafond à la somme abattue - test: montant franchisé, décote, abattu unité: € - formule: - allègement: + formule: + allègement: assiette: montant franchise: 1200 - décote: + décote: plafond: 2040 taux: 75% - abattement: 20507 - exemples: - - situation: + abattement: 20507 + exemples: + - situation: montant: 100 valeur attendue: 0 - - situation: + - situation: montant: 1620 valeur attendue: 0 - - situation: + - situation: montant: 3000 valeur attendue: 0 - - situation: + - situation: montant: 21000 valeur attendue: 493 - - diff --git a/test/mécanismes/barème-continu.yaml b/test/mécanismes/barème-continu.yaml index b204d2047..8faf67122 100644 --- a/test/mécanismes/barème-continu.yaml +++ b/test/mécanismes/barème-continu.yaml @@ -1,5 +1,5 @@ - nom: base - unité: £ + unité: £ formule: 300 - nom: assiette @@ -7,37 +7,36 @@ - test: Simple formule: - barème continu: + barème continu: assiette: assiette multiplicateur: base - points: + points: 0: 0% 0.4: 3.16% 1.1: 6.35% unité attendue: £ - exemples: + exemples: - nom: Premier point - situation: + situation: assiette: 10 valeur attendue: 0.026 - nom: Deuxième point - situation: + situation: assiette: 120 valeur attendue: 3.792 - - nom: Premier point - situation: + - nom: Premier point + situation: assiette: 150 valeur attendue: 5.423 - - nom: Troisième point - situation: + - nom: Troisième point + situation: assiette: 330 valeur attendue: 20.955 - - nom: Au-delà - situation: + - nom: Au-delà + situation: assiette: 1000 valeur attendue: 63.5 - - + - nom: base deux unité: µ formule: 300 @@ -46,6 +45,7 @@ unité: µ - test: Retour de taux, pas d'assiette + unité: '%' formule: barème continu: assiette: assiette deux @@ -56,24 +56,24 @@ 1: 0% retourne seulement le taux: oui unité attendue: '%' - exemples: + exemples: - nom: Premier point - situation: + situation: assiette deux: 200 - valeur attendue: 1 + valeur attendue: 100 - nom: Deuxième point - situation: + situation: assiette deux: 225 - valeur attendue: 1 + valeur attendue: 100 - nom: Troisième point - situation: + situation: assiette deux: 262.5 - valeur attendue: 0.5 + valeur attendue: 50 - nom: Quatrième point - situation: + situation: assiette deux: 300 valeur attendue: 0 - nom: Cinquième point - situation: + situation: assiette deux: 300 valeur attendue: 0 diff --git a/test/mécanismes/barème.yaml b/test/mécanismes/barème.yaml index 5bc50b860..af6b7a62a 100644 --- a/test/mécanismes/barème.yaml +++ b/test/mécanismes/barème.yaml @@ -78,34 +78,34 @@ assiette: assiette multiplicateur: base tranches: - - en-dessous de: 1 - taux: taux variable - - au-dessus de: 1 - taux: 90% + - en-dessous de: 1 + taux: taux variable + - au-dessus de: 1 + taux: 90% unité attendue: € exemples: - - nom: taux faible - situation: - assiette: 200 - base: 100 - ma condition: oui - valeur attendue: 119 - - nom: taux fort - situation: - assiette: 200 - base: 100 - ma condition: non - valeur attendue: 146 - - nom: assiette manquante - situation: - base: 100 - ma condition: oui - variables manquantes: - - assiette - - nom: condition manquante - situation: - base: 100 - assiette: 400 - variables manquantes: - - ma condition + - nom: taux faible + situation: + assiette: 200 + base: 100 + ma condition: oui + valeur attendue: 119 + - nom: taux fort + situation: + assiette: 200 + base: 100 + ma condition: non + valeur attendue: 146 + - nom: assiette manquante + situation: + base: 100 + ma condition: oui + variables manquantes: + - assiette + - nom: condition manquante + situation: + assiette: 40 + base: 100 + variables manquantes: + - ma condition diff --git a/test/mécanismes/conversion-unité.yaml b/test/mécanismes/conversion-unité.yaml new file mode 100644 index 000000000..f25bd6d96 --- /dev/null +++ b/test/mécanismes/conversion-unité.yaml @@ -0,0 +1,236 @@ +# This is not a mecanism test, but we make use of the simplicity of declaring tests in YAML, only available for mecanisms for now + +- nom: douches par mois + question: Combien prenez-vous de douches par mois ? + unité: douche/mois + +- test: Conversion de reference + formule: douches par mois [douche/an] + exemples: + - situation: + douches par mois: 30 + valeur attendue: 360 + +- test: Conversion de reference 2 + unité: douche/an + formule: douches par mois + exemples: + - situation: + douches par mois: 30 + valeur attendue: 360 + - nom: Unité de variable prioritaire devant les unités par défaut + situation: + douches par mois: 30 + unités par défaut: [douche/mois] + valeur attendue: 360 + +- test: Conversion de variable + formule: 1.5 kCo2/douche * douches par mois + exemples: + - situation: + douches par mois: 30 + valeur attendue: 45 + unité attendue: kCo2/mois + - nom: Unité cible de simulation + situation: + douches par mois: 20 + unités par défaut: [kCo2/an] + unité attendue: kCo2/an + valeur attendue: 360 + +- test: Conversion de variable et expressions + unité: kCo2/an + formule: 1 kCo2/douche * 10 douche/mois + exemples: + - valeur attendue: 120 + +- test: Conversion de pourcentage + unité: €/an + formule: 1000€ * 1% /mois + exemples: + - valeur attendue: 120 + +- test: Conversion en pourcentage + unité: '%' + formule: 28h / 35h + exemples: + - valeur attendue: 80 + +- test: Conversion dans un mécanisme + unité: €/an + formule: + le minimum de: + - 100 €/mois + - 1120 €/an + exemples: + - valeur attendue: 1120 + +- nom: assiette mensuelle + unité: €/mois + +- test: Conversion de mécanisme 1 + unité: €/an + formule: + barème: + assiette: assiette mensuelle [€/an] + tranches: + - en-dessous de: 30000 + taux: 4.65% + - de: 30000 + à: 90000 + taux: 3% + - au-dessus de: 90000 + taux: 1% + + exemples: + - situation: + assiette mensuelle: 3000 + valeur attendue: 1575 + +- nom: assiette annuelle + unité: €/an + +- test: Conversion de mécanisme 2 + formule: + barème: + assiette: assiette annuelle [€/mois] + tranches: + - en-dessous de: 2500 + taux: 4.65% + - de: 2500 + à: 7500 + taux: 3% + - au-dessus de: 7500 + taux: 1% + exemples: + - situation: + assiette annuelle: 36000 + valeur attendue: 131.25 + unités par défaut: [€/mois] + +- test: Conversion dans une expression + unité: €/an + formule: 80 €/mois + 1120 €/an + 20 €/mois + exemples: + - valeur attendue: 2320 + +- test: Conversion dans une comparaison + formule: 100€/mois = 1.2k€/an + exemples: + - valeur attendue: true + +- nom: mutuelle + formule: 30 €/mois + +- nom: retraite + formule: + multiplication: + assiette: assiette annuelle + plafond: 12 k€/an + taux: 10% + +- test: Conversion dans une somme compliquée + formule: + somme: + - mutuelle + - retraite + exemples: + - situation: + assiette annuelle: 20000 + unités par défaut: [€/mois] + valeur attendue: 130 + +- nom: maladie + formule: + multiplication: + assiette: assiette annuelle + composantes: + - attributs: + dû par: employeur + taux: 15% + - attributs: + dû par: salarié + taux: 5% + plafond: 1000 €/mois + +- test: Conversion avec composantes + unité: €/mois + formule: + somme: + - maladie .salarié + - retraite + - mutuelle + exemples: + - situation: + assiette annuelle: 20000 + valeur attendue: 180 + +- test: Conversion dans un allègement + formule: + allègement: + assiette: 1000€/an + abattement: 10€/mois + exemples: + - unités par défaut: [€/an] + valeur attendue: 880 + +- test: Conversion dans avec un abattement en % + unité par défaut: €/an + formule: + allègement: + assiette: 1000€/an + abattement: 10% + exemples: + - valeur attendue: 900 + +- nom: assiette cotisations + formule: + allègement: + assiette: assiette mensuelle + abattement: 1200 €/an + +- nom: prévoyance cadre + formule: + multiplication: + assiette: assiette cotisations + taux: 1.5% + +- test: Conversion avec plusieurs échelons + formule: + somme: + - prévoyance cadre + - 35€/mois + exemples: + - unités par défaut: [€/an] + situation: + assiette mensuelle: 1100 + valeur attendue: 600 + +- test: Conversion de situation + formule: + somme: + - retraite + - mutuelle + exemples: + - unités par défaut: [€/an] + situation: + retraite: 4000 + valeur attendue: 4360 + +- nom: rémunération brute + unité par défaut: €/mois + +- test: Conversion de situation avec unité + unité: €/an + formule: + multiplication: + assiette: rémunération brute + taux: 10% + exemples: + - situation: + rémunération brute: 1000 + valeur attendue: 1200 + - unités par défaut: [k€/an] + situation: + rémunération brute: 12 + valeur attendue: 1200 diff --git a/test/mécanismes/expressions.yaml b/test/mécanismes/expressions.yaml index 76770797e..186e1ea4c 100644 --- a/test/mécanismes/expressions.yaml +++ b/test/mécanismes/expressions.yaml @@ -52,12 +52,13 @@ unité: '%' - test: soustraction - formule: 1 - taux + unité: '%' + formule: 100% - taux unité attendue: '%' exemples: - situation: - taux: 0.89 - valeur attendue: 0.11 + taux: 89 + valeur attendue: 11 - test: addition formule: salaire de base + 2000 @@ -137,6 +138,7 @@ valeur attendue: false - nom: plafond sécurité sociale + unité: $ - nom: CDD @@ -218,16 +220,14 @@ valeur attendue: false - nom: revenu - période: mois - unité: € + unité: €/mois -- test: variable modifiée temporellement - formule: revenu [annuel] - période: aucune +- test: unité de variable modifiée + formule: revenu [k€/an] exemples: - situation: revenu: 1000 - valeur attendue: 12000 + valeur attendue: 12 - test: opérations multiples formule: 4 * plafond sécurité sociale * 10% diff --git a/test/mécanismes/multiplication.yaml b/test/mécanismes/multiplication.yaml index 02ec7439a..26812533e 100644 --- a/test/mécanismes/multiplication.yaml +++ b/test/mécanismes/multiplication.yaml @@ -27,7 +27,6 @@ situation: valeur attendue: 9.9 - - nom: mon plafond unité: € @@ -66,8 +65,6 @@ mon facteur: 3 valeur attendue: 300 - - - test: Multiplication complète formule: multiplication: @@ -76,7 +73,7 @@ plafond: mon plafond taux: 0.5% - unité attendue: €-patates + unité attendue: €.patates exemples: - nom: situation: @@ -84,8 +81,6 @@ mon facteur: 2 mon plafond: 100 valeur attendue: 1 - - # This should work, but with the use of objectShape & co, the short circuits are not performed #- test: Multiplication complète # formule: @@ -103,4 +98,3 @@ # valeur attendue: 0 # variables manquantes: [] - diff --git a/test/mécanismes/période.yaml b/test/mécanismes/période.yaml deleted file mode 100644 index 1c886b387..000000000 --- a/test/mécanismes/période.yaml +++ /dev/null @@ -1,157 +0,0 @@ -# This is not a mecanism test, but we make use of the simplicity of declaring tests in YAML, only available for mecanisms for now - -- nom: nombre de douches - période: mois - question: Combien prenez-vous de douches par mois ? - unité: _ - suggestions: - - 30 - -- test: impact des douches - période: année - formule: 1 * nombre de douches - - exemples: - - situation: - nombre de douches: 30 - valeur attendue: 360 - -- nom: impact par douche - formule: 1 - unité: kgCO2e - -- test: impact des douches erroné - période: année - formule: impact par douche * nombre de douches - exemples: - - situation: - nombre de douches: 30 - valeur attendue: 360 - -- nom: assiette mensuelle - période: mois - unité: € - -- test: Périodes, barème annuel assiette mensuelle - période: année - formule: - barème: - # cette formule appellant l'assiette est annuelle : - # si l'assiette est aussi annuelle dans le contexte de la simulation actuelle, c'est bon - # sinon une conversion est nécessaire et faite automatiquement par le moteur - assiette: assiette mensuelle - tranches: - # ce sont ces chiffres là qui imposent à la règle d'être annuelle - # de plus, les règles annuelles de la loi sont rarement traduites officiellement en d'autres périodes - - en-dessous de: 30000 - taux: 4.65% - - de: 30000 - à: 90000 - taux: 3% - - au-dessus de: 90000 - taux: 1% - - exemples: - - situation: - assiette mensuelle: 3000 - valeur attendue: 1575 - - -- nom: assiette annuelle - période: année - unité: € - -- test: Périodes, barème mensuel assiette annuelle - période: mois - formule: - barème: - # cette formule appellant l'assiette est annuelle : - # si l'assiette est aussi annuelle dans le contexte de la simulation actuelle, c'est bon - # sinon une conversion est nécessaire et faite automatiquement par le moteur - assiette: assiette annuelle - tranches: - # ce sont ces chiffres là qui imposent à la règle d'être annuelle - # de plus, les règles annuelles de la loi sont rarement traduites officiellement en d'autres périodes - - en-dessous de: 2500 - taux: 4.65% - - de: 2500 - à: 7500 - taux: 3% - - au-dessus de: 7500 - taux: 1% - - exemples: - - situation: - assiette annuelle: 36000 - valeur attendue: 131.25 - -- nom: assiette - période: flexible - unité: € - -- test: Périodes, période dans la situation - période: année - formule: - barème: - assiette: assiette - tranches: - - en-dessous de: 30000 - taux: 4.65% - - de: 30000 - à: 90000 - taux: 3% - - au-dessus de: 90000 - taux: 1% - - exemples: - - situation: - période: mois - assiette: 3000 - valeur attendue: 1575 - - situation: - période: année - assiette: 36000 - valeur attendue: 1575 - - -- nom: assiette deux - période: mois - unité: € - -- test: Périodes, variable neutre appelant variable mensuelle - période: flexible - formule: - multiplication: - assiette: assiette deux - taux: 10% - - exemples: - - situation: - période: mois - assiette deux: 3000 - valeur attendue: 300 - -- nom: assiette trois - période: année - unité: € - -- test: Périodes, variable neutre appelant variable annuelle - période: flexible - formule: - multiplication: - assiette: assiette trois - taux: 10% - - exemples: - - situation: - période: mois - assiette trois: 36000 - valeur attendue: 300 - - -- test: Périodes, préfixe de modification temporelle - formule: assiette trois [mensuel] - exemples: - - situation: - assiette trois: 12000 - valeur attendue: 1000 diff --git a/test/mécanismes/remplace.yaml b/test/mécanismes/remplace.yaml index 721ab1701..31287e6cf 100644 --- a/test/mécanismes/remplace.yaml +++ b/test/mécanismes/remplace.yaml @@ -18,7 +18,7 @@ applicable si: client enfant remplace: règle: prix du repas - par: 8 € + par: 8 €/repas - test: modifie une règle formule: restaurant . prix du repas @@ -40,8 +40,8 @@ - nom: cotisations formule: somme: - - retraite [salarié] - - retraite [employeur] + - retraite .salarié + - retraite .employeur - chômage - maladie @@ -140,7 +140,7 @@ formule: cotisations remplace: - règle: cotisations . chômage - par: 10 + par: 10€ - règle: cotisations . maladie par: 0 exemples: diff --git a/test/real-rules.test.js b/test/real-rules.test.js index 8274ad377..84fb5f7e4 100644 --- a/test/real-rules.test.js +++ b/test/real-rules.test.js @@ -1,8 +1,8 @@ import { AssertionError } from 'chai' +import { merge } from 'ramda' +import { exampleAnalysisSelector } from 'Selectors/analyseSelectors' import { rules } from '../source/engine/rules' import { parseAll } from '../source/engine/traverse' -import { exampleAnalysisSelector } from 'Selectors/analyseSelectors' -import { merge } from 'ramda' // les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle, // comme dans sa formule @@ -13,7 +13,8 @@ let runExamples = (examples, rule) => rules, currentExample: { situation: ex.situation, - dottedName: rule.dottedName + dottedName: rule.dottedName, + defaultUnits: ex['unités par défaut'] } }, { dottedName: rule.dottedName } diff --git a/test/regressions/__snapshots__/simulations.jest.js.snap b/test/regressions/__snapshots__/simulations.jest.js.snap index 207d753bf..c13132cbf 100644 --- a/test/regressions/__snapshots__/simulations.jest.js.snap +++ b/test/regressions/__snapshots__/simulations.jest.js.snap @@ -1,299 +1,265 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`calculate simulations-artiste-auteur: bnc 1`] = `"[1230]"`; +exports[`calculate simulations-artiste-auteur: bnc 1`] = `"[1230]"`; -exports[`calculate simulations-artiste-auteur: bnc 2`] = `"[1863]"`; +exports[`calculate simulations-artiste-auteur: bnc 2`] = `"[1230]"`; -exports[`calculate simulations-artiste-auteur: bnc 3`] = `"[932]"`; +exports[`calculate simulations-artiste-auteur: bnc 3`] = `"[1230]"`; -exports[`calculate simulations-artiste-auteur: salarié 1`] = `"[160]"`; +exports[`calculate simulations-artiste-auteur: salarié 1`] = `"[160]"`; -exports[`calculate simulations-artiste-auteur: salarié 2`] = `"[1603]"`; +exports[`calculate simulations-artiste-auteur: salarié 2`] = `"[1603]"`; -exports[`calculate simulations-artiste-auteur: salarié 3`] = `"[12372]"`; +exports[`calculate simulations-artiste-auteur: salarié 3`] = `"[12372]"`; -exports[`calculate simulations-auto-entrepreneur: aides 1`] = `"[5299,299,5000,0,5000]"`; +exports[`calculate simulations-auto-entrepreneur: aides 1`] = `"[5299,299,5000,0,5000]"`; -exports[`calculate simulations-auto-entrepreneur: aides 2`] = `"[52991,2991,50000,2314,47686]"`; +exports[`calculate simulations-auto-entrepreneur: aides 2`] = `"[52991,2991,50000,2314,47686]"`; -exports[`calculate simulations-auto-entrepreneur: impôt sur le revenu 1`] = `"[32092,7092,25000,706,24294]"`; +exports[`calculate simulations-auto-entrepreneur: impôt sur le revenu 1`] = `"[32092,7092,25000,706,24294]"`; -exports[`calculate simulations-auto-entrepreneur: périodes 1`] = `"[128,28,100,0,100]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 1`] = `"[642,142,500,0,500]"`; -exports[`calculate simulations-auto-entrepreneur: périodes 2`] = `"[642,142,500,0,500]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 2`] = `"[1284,284,1000,0,1000]"`; -exports[`calculate simulations-auto-entrepreneur: périodes 3`] = `"[1284,284,1000,0,1000]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 3`] = `"[2569,569,2000,0,2000]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 1`] = `"[642,142,500,0,500]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 4`] = `"[6422,1422,5000,0,5000]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 2`] = `"[1284,284,1000,0,1000]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 5`] = `"[12844,2844,10000,0,10000]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 3`] = `"[2569,569,2000,0,2000]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 6`] = `"[25688,5688,20000,0,20000]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 4`] = `"[6422,1422,5000,0,5000]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 7`] = `"[64221,14221,50000,3835,46165]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 5`] = `"[12844,2844,10000,0,10000]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 8`] = `"[89910,19910,70000,7688,62312]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 6`] = `"[25688,5688,20000,0,20000]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 9`] = `"[128442,28442,100000,13468,86532]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 7`] = `"[64221,14221,50000,3835,46165]"`; +exports[`calculate simulations-auto-entrepreneur: échelle de revenus 10`] = `"[1284423,284423,1000000,282020,717980]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 8`] = `"[89910,19910,70000,7688,62312]"`; +exports[`calculate simulations-indépendant: acre 1`] = `"[73015,23015,50000,51980,8237,41763,null,73015]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 9`] = `"[128442,28442,100000,13468,86532]"`; +exports[`calculate simulations-indépendant: activité 1`] = `"[29091,9091,20000,20787,947,19053,null,29091]"`; -exports[`calculate simulations-auto-entrepreneur: échelle de revenus 10`] = `"[1284423,284423,1000000,282020,717980]"`; +exports[`calculate simulations-indépendant: activité 2`] = `"[29108,9108,20000,20787,947,19053,null,29108]"`; -exports[`calculate simulations-indépendant: acre 1`] = `"[73015,23015,50000,51980,8237,41763,null,73015]"`; +exports[`calculate simulations-indépendant: impôt sur le revenu 1`] = `"[29091,9091,20000,20787,728,19272,null,29091]"`; -exports[`calculate simulations-indépendant: activité 1`] = `"[29091,9091,20000,20787,947,19053,null,29091]"`; +exports[`calculate simulations-indépendant: impôt sur le revenu 2`] = `"[73015,23015,50000,51980,8317,41683,null,73015]"`; -exports[`calculate simulations-indépendant: activité 2`] = `"[29108,9108,20000,20787,947,19053,null,29108]"`; +exports[`calculate simulations-indépendant: impôt sur le revenu 3`] = `"[29091,9091,20000,20787,2079,17921,null,29091]"`; -exports[`calculate simulations-indépendant: impôt sur le revenu 1`] = `"[29091,9091,20000,20787,728,19272,null,29091]"`; +exports[`calculate simulations-indépendant: inversions 1`] = `"[2000,1369,631,683,0,631,null,2000]"`; -exports[`calculate simulations-indépendant: impôt sur le revenu 2`] = `"[73015,23015,50000,51980,8317,41683,null,73015]"`; +exports[`calculate simulations-indépendant: inversions 2`] = `"[50000,16017,33983,35338,3743,30240,null,50000]"`; -exports[`calculate simulations-indépendant: impôt sur le revenu 3`] = `"[29091,9091,20000,20787,2079,17921,null,29091]"`; +exports[`calculate simulations-indépendant: inversions 3`] = `"[14592,4592,10000,10393,0,10000,null,14592]"`; -exports[`calculate simulations-indépendant: inversions 1`] = `"[2000,1369,631,683,0,631,null,2000]"`; +exports[`calculate simulations-indépendant: inversions 4`] = `"[88759,27318,61441,63848,11441,50000,null,88759]"`; -exports[`calculate simulations-indépendant: inversions 2`] = `"[50000,16017,33983,35338,3743,30240,null,50000]"`; +exports[`calculate simulations-indépendant: inversions 5`] = `"[14592,4592,10000,10393,0,10000,null,15592]"`; -exports[`calculate simulations-indépendant: inversions 3`] = `"[14592,4592,10000,10393,0,10000,null,14592]"`; +exports[`calculate simulations-indépendant: inversions 6`] = `"[19000,5926,13074,13588,0,13074,1000,20000]"`; -exports[`calculate simulations-indépendant: inversions 4`] = `"[88759,27318,61441,63848,11441,50000,null,88759]"`; +exports[`calculate simulations-indépendant: inversions 7`] = `"[18000,5623,12377,12863,0,12377,2000,20000]"`; -exports[`calculate simulations-indépendant: inversions 5`] = `"[14592,4592,10000,10393,0,10000,null,15592]"`; +exports[`calculate simulations-indépendant: échelle de revenus 1`] = `"[1840,1340,500,547,0,500,null,1840]"`; -exports[`calculate simulations-indépendant: inversions 6`] = `"[19000,5926,13074,13588,0,13074,1000,20000]"`; +exports[`calculate simulations-indépendant: échelle de revenus 2`] = `"[2448,1448,1000,1064,0,1000,null,2448]"`; -exports[`calculate simulations-indépendant: inversions 7`] = `"[18000,5623,12377,12863,0,12377,2000,20000]"`; +exports[`calculate simulations-indépendant: échelle de revenus 3`] = `"[3056,1556,1500,1580,0,1500,null,3056]"`; -exports[`calculate simulations-indépendant: période 1`] = `"[1455,455,1000,1039,0,1000,null,1455]"`; +exports[`calculate simulations-indépendant: échelle de revenus 4`] = `"[3664,1664,2000,2097,0,2000,null,3664]"`; -exports[`calculate simulations-indépendant: période 2`] = `"[7239,2239,5000,5196,920,4080,null,7239]"`; +exports[`calculate simulations-indépendant: échelle de revenus 5`] = `"[7423,2423,5000,5199,0,5000,null,7423]"`; -exports[`calculate simulations-indépendant: échelle de revenus 1`] = `"[1840,1340,500,547,0,500,null,1840]"`; +exports[`calculate simulations-indépendant: échelle de revenus 6`] = `"[14592,4592,10000,10393,0,10000,null,14592]"`; -exports[`calculate simulations-indépendant: échelle de revenus 2`] = `"[2448,1448,1000,1064,0,1000,null,2448]"`; +exports[`calculate simulations-indépendant: échelle de revenus 7`] = `"[139472,39472,100000,103784,24383,75617,null,139472]"`; -exports[`calculate simulations-indépendant: échelle de revenus 3`] = `"[3056,1556,1500,1580,0,1500,null,3056]"`; +exports[`calculate simulations-indépendant: échelle de revenus 8`] = `"[1239593,239593,1000000,1033657,467702,532298,null,1239593]"`; -exports[`calculate simulations-indépendant: échelle de revenus 4`] = `"[3664,1664,2000,2097,0,2000,null,3664]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 1`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-indépendant: échelle de revenus 5`] = `"[7423,2423,5000,5199,0,5000,null,7423]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 2`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-indépendant: échelle de revenus 6`] = `"[14592,4592,10000,10393,0,10000,null,14592]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 3`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-indépendant: échelle de revenus 7`] = `"[139472,39472,100000,103784,24383,75617,null,139472]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 4`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-indépendant: échelle de revenus 8`] = `"[1239593,239593,1000000,1033657,467702,532298,null,1239593]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 5`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 1`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - avec charges 1`] = `"[5291,5291,5306,4,10,12]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 2`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - avec charges 2`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 3`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 1`] = `"[169,169,139,0,1,1]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 4`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 2`] = `"[738,738,323,0,2,2]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - activités 5`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 3`] = `"[2446,2446,2588,2,5,6]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - avec charges 1`] = `"[5291,5291,5306,4,10,12]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 4`] = `"[5291,5291,5306,4,10,12]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - avec charges 2`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 5`] = `"[10982,10982,10742,4,19,23]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - périodes 1`] = `"[80,80,98,1,2,3]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 6`] = `"[25686,28055,27050,4,45,59]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - périodes 2`] = `"[251,251,261,2,6,7]"`; +exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 7`] = `"[46640,57031,52655,4,45,119]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - périodes 3`] = `"[2485,2808,2693,4,45,71]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 1`] = `"[15580,15580,6600,4,18,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 1`] = `"[169,169,139,0,1,1]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 2`] = `"[15560,15560,0,4,18,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 2`] = `"[738,738,323,0,2,2]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 3`] = `"[15444,15444,7047,4,14,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 3`] = `"[2446,2446,2588,2,5,6]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 4`] = `"[17417,17417,4093,3,8,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 4`] = `"[5291,5291,5306,4,10,12]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 5`] = `"[17417,17417,4093,3,8,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 5`] = `"[10982,10982,10742,4,19,23]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - avec charges 1`] = `"[7343,7343,4228,3,8,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 6`] = `"[25686,28055,27050,4,45,59]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - avec charges 2`] = `"[11599,12250,12332,4,24,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Assimilé salarié - échelle de rémunération 7`] = `"[46640,57031,52655,4,45,119]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 1`] = `"[779,779,102,0,0,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 1`] = `"[15580,15580,6600,4,18,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 2`] = `"[1557,1557,205,0,0,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 2`] = `"[15560,15560,0,4,18,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 3`] = `"[3893,3893,1762,2,0,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 3`] = `"[15444,15444,7047,4,14,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 4`] = `"[7786,7786,3523,3,7,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 4`] = `"[17417,17417,4093,3,8,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 5`] = `"[15571,15571,7047,4,14,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - activités 5`] = `"[17417,17417,4093,3,8,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 6`] = `"[36823,38928,17617,4,34,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - avec charges 1`] = `"[7343,7343,4228,3,8,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 7`] = `"[68654,77856,30496,4,56,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - avec charges 2`] = `"[11599,12250,12332,4,24,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 1`] = `"[13772,13772,10086,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - périodes 1`] = `"[156,156,20,0,0,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 2`] = `"[14571,14571,0,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - périodes 2`] = `"[389,389,176,2,0,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 3`] = `"[13761,13761,10077,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - périodes 3`] = `"[3626,3893,1762,4,41,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 4`] = `"[13772,13772,10086,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 1`] = `"[779,779,102,0,0,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 5`] = `"[13772,13772,10086,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 2`] = `"[1557,1557,205,0,0,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - avec charges 1`] = `"[6797,6797,4979,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 3`] = `"[3893,3893,1762,2,0,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - avec charges 2`] = `"[13772,13772,10086,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 4`] = `"[7786,7786,3523,3,7,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 1`] = `"[0,0,36807,3,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 5`] = `"[15571,15571,7047,4,14,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 2`] = `"[631,631,481,3,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 6`] = `"[36823,38928,17617,4,34,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 3`] = `"[3100,3100,2278,3,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Auto-entrepreneur - échelle de rémunération 7`] = `"[68654,77856,30496,4,56,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 4`] = `"[6797,6797,4979,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 1`] = `"[13772,13772,10085,4,21,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 5`] = `"[13772,13772,10086,4,21,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 2`] = `"[14571,14571,0,4,21,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 6`] = `"[30240,33983,24902,4,48,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 3`] = `"[13761,13761,10077,4,21,0]"`; +exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 7`] = `"[56157,69988,36158,4,56,0]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 4`] = `"[13772,13772,10085,4,21,0]"`; +exports[`calculate simulations-salarié: aides 1`] = `"[2302,0,0,2000,1561,1503]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - activités 5`] = `"[13772,13772,10085,4,21,0]"`; +exports[`calculate simulations-salarié: aides 2`] = `"[12823,0,0,10000,8910,7652]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - avec charges 1`] = `"[6797,6797,4979,4,21,0]"`; +exports[`calculate simulations-salarié: apprentissage 1`] = `"[1551,0,0,1500,1446,1446]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - avec charges 2`] = `"[13772,13772,10085,4,21,0]"`; +exports[`calculate simulations-salarié: apprentissage 2`] = `"[1384,167,0,1500,1446,1446]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - périodes 1`] = `"[80,80,60,3,21,0]"`; +exports[`calculate simulations-salarié: assimilé salarié 1`] = `"[7014,0,0,5000,3943,3304]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - périodes 2`] = `"[327,327,240,3,21,0]"`; +exports[`calculate simulations-salarié: assimilé salarié 2`] = `"[1583,0,0,1500,1163,1163]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - périodes 3`] = `"[2927,3397,2422,4,56,0]"`; +exports[`calculate simulations-salarié: assimilé salarié 3`] = `"[3742,0,0,3000,2348,2150]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 1`] = `"[0,0,36807,3,21,0]"`; +exports[`calculate simulations-salarié: atmp 1`] = `"[2549,0,0,2000,1561,1503]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 2`] = `"[631,631,481,3,21,0]"`; +exports[`calculate simulations-salarié: avantages 1`] = `"[2682,0,0,2000,1540,1464]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 3`] = `"[3100,3100,2278,3,21,0]"`; +exports[`calculate simulations-salarié: avantages 2`] = `"[2692,0,0,2000,1539,1462]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 4`] = `"[6797,6797,4979,4,21,0]"`; +exports[`calculate simulations-salarié: avantages 3`] = `"[2602,0,0,2000,1549,1481]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 5`] = `"[13772,13772,10085,4,21,0]"`; +exports[`calculate simulations-salarié: cadre 1`] = `"[4122,0,0,3000,2348,2149]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 6`] = `"[30240,33983,24902,4,48,0]"`; +exports[`calculate simulations-salarié: cdd 1`] = `"[2514,0,0,2000,1561,1503]"`; -exports[`calculate simulations-rémunération-dirigeant: Indépendant - échelle de rémunération 7`] = `"[56157,69988,36158,4,56,0]"`; +exports[`calculate simulations-salarié: cdd 2`] = `"[2605,0,0,2000,1599,1532]"`; -exports[`calculate simulations-salarié: aides 1`] = `"[2302,0,0,2000,1561,1503]"`; +exports[`calculate simulations-salarié: heures supplémentaires 1`] = `"[2599,0,0,2000,1636,1578]"`; -exports[`calculate simulations-salarié: aides 2`] = `"[12823,0,0,10000,8910,7652]"`; +exports[`calculate simulations-salarié: heures supplémentaires 2`] = `"[3123,0,0,2000,2009,1940]"`; -exports[`calculate simulations-salarié: apprentissage 1`] = `"[1551,0,0,1500,1446,1446]"`; +exports[`calculate simulations-salarié: heures supplémentaires 3`] = `"[2669,0,0,2000,1636,1578]"`; -exports[`calculate simulations-salarié: apprentissage 2`] = `"[1384,167,0,1500,1446,1446]"`; +exports[`calculate simulations-salarié: heures supplémentaires 4`] = `"[2580,0,0,2000,1627,1569]"`; -exports[`calculate simulations-salarié: assimilé salarié 1`] = `"[7014,0,0,5000,3943,3304]"`; +exports[`calculate simulations-salarié: heures supplémentaires 5`] = `"[3043,0,0,2000,1970,1911]"`; -exports[`calculate simulations-salarié: assimilé salarié 2`] = `"[1583,0,0,1500,1163,1163]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 1`] = `"[4076,0,0,3000,2353,2168]"`; -exports[`calculate simulations-salarié: assimilé salarié 3`] = `"[3742,0,0,3000,2348,2150]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 2`] = `"[41765,0,0,30000,24267,14611]"`; -exports[`calculate simulations-salarié: atmp 1`] = `"[2549,0,0,2000,1561,1503]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 3`] = `"[4106,0,0,3000,2353,2172]"`; -exports[`calculate simulations-salarié: avantages 1`] = `"[2682,0,0,2000,1540,1464]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 4`] = `"[3915,0,0,3000,2353,2205]"`; -exports[`calculate simulations-salarié: avantages 2`] = `"[2692,0,0,2000,1539,1462]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 5`] = `"[41765,0,0,30000,24267,14611]"`; -exports[`calculate simulations-salarié: avantages 3`] = `"[2602,0,0,2000,1549,1481]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 6`] = `"[4076,0,0,3000,2353,2242]"`; -exports[`calculate simulations-salarié: cadre 1`] = `"[4122,0,0,3000,2348,2149]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 7`] = `"[41765,0,0,30000,24267,15869]"`; -exports[`calculate simulations-salarié: cdd 1`] = `"[2514,0,0,2000,1561,1503]"`; +exports[`calculate simulations-salarié: impôt sur le revenu 8`] = `"[4076,0,0,3000,2353,2107]"`; -exports[`calculate simulations-salarié: cdd 2`] = `"[2605,0,0,2000,1599,1532]"`; +exports[`calculate simulations-salarié: inversions 1`] = `"[2000,0,0,1738,1354,1343]"`; -exports[`calculate simulations-salarié: heures supplémentaires 1`] = `"[2599,0,0,2000,1636,1578]"`; +exports[`calculate simulations-salarié: inversions 2`] = `"[3474,0,0,2554,2000,1852]"`; -exports[`calculate simulations-salarié: heures supplémentaires 2`] = `"[3123,0,0,2000,2009,1940]"`; +exports[`calculate simulations-salarié: inversions 3`] = `"[3764,0,0,2769,2170,2000]"`; -exports[`calculate simulations-salarié: heures supplémentaires 3`] = `"[2669,0,0,2000,1636,1578]"`; +exports[`calculate simulations-salarié: stage 1`] = `"[507,0,0,500,500,500]"`; -exports[`calculate simulations-salarié: heures supplémentaires 4`] = `"[2580,0,0,2000,1627,1569]"`; +exports[`calculate simulations-salarié: stage 2`] = `"[2493,0,0,2000,1749,1749]"`; -exports[`calculate simulations-salarié: heures supplémentaires 5`] = `"[3043,0,0,2000,1970,1911]"`; +exports[`calculate simulations-salarié: temps partiel 1`] = `"[2605,0,2188,2000,1561,1503]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 1`] = `"[4076,0,0,3000,2353,2168]"`; +exports[`calculate simulations-salarié: temps partiel 2`] = `"[2533,0,2500,1857,1448,1416]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 2`] = `"[41765,0,0,30000,24267,14656]"`; +exports[`calculate simulations-salarié: échelle de salaires 1`] = `"[130,0,0,100,57,57]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 3`] = `"[4106,0,0,3000,2353,2270]"`; +exports[`calculate simulations-salarié: échelle de salaires 2`] = `"[284,0,0,250,176,176]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 4`] = `"[3915,0,0,3000,2353,2205]"`; +exports[`calculate simulations-salarié: échelle de salaires 3`] = `"[541,0,0,500,374,374]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 5`] = `"[41765,0,0,30000,24267,14656]"`; +exports[`calculate simulations-salarié: échelle de salaires 4`] = `"[798,0,0,750,572,572]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 6`] = `"[4076,0,0,3000,2353,2242]"`; +exports[`calculate simulations-salarié: échelle de salaires 5`] = `"[1055,0,0,1000,770,770]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 7`] = `"[41765,0,0,30000,24267,15913]"`; +exports[`calculate simulations-salarié: échelle de salaires 6`] = `"[1312,0,0,1250,968,968]"`; -exports[`calculate simulations-salarié: impôt sur le revenu 8`] = `"[4076,0,0,3000,2353,2107]"`; +exports[`calculate simulations-salarié: échelle de salaires 7`] = `"[1569,0,0,1500,1165,1165]"`; -exports[`calculate simulations-salarié: inversions 1`] = `"[2000,0,0,1738,1354,1343]"`; +exports[`calculate simulations-salarié: échelle de salaires 8`] = `"[2494,0,0,2000,1561,1503]"`; -exports[`calculate simulations-salarié: inversions 2`] = `"[3474,0,0,2554,2000,1852]"`; +exports[`calculate simulations-salarié: échelle de salaires 9`] = `"[3401,0,0,2500,1957,1815]"`; -exports[`calculate simulations-salarié: inversions 3`] = `"[3764,0,0,2769,2170,2000]"`; +exports[`calculate simulations-salarié: échelle de salaires 10`] = `"[4076,0,0,3000,2353,2159]"`; -exports[`calculate simulations-salarié: périodes 1`] = `"[3405,0,0,3000,2112,2112]"`; +exports[`calculate simulations-salarié: échelle de salaires 11`] = `"[5674,0,0,4000,3146,2744]"`; -exports[`calculate simulations-salarié: périodes 2`] = `"[61150,0,0,45000,35349,31190]"`; +exports[`calculate simulations-salarié: échelle de salaires 12`] = `"[7085,0,0,5000,3948,3321]"`; -exports[`calculate simulations-salarié: périodes 3`] = `"[674660,0,0,500000,417064,243499]"`; +exports[`calculate simulations-salarié: échelle de salaires 13`] = `"[14319,0,0,10000,7959,6069]"`; -exports[`calculate simulations-salarié: stage 1`] = `"[507,0,0,500,500,500]"`; +exports[`calculate simulations-salarié: échelle de salaires 14`] = `"[28336,0,0,20000,15969,10665]"`; -exports[`calculate simulations-salarié: stage 2`] = `"[2493,0,0,2000,1749,1749]"`; +exports[`calculate simulations-salarié: échelle de salaires 15`] = `"[128506,0,0,100000,87197,46275]"`; -exports[`calculate simulations-salarié: temps partiel 1`] = `"[2605,0,2188,2000,1561,1503]"`; - -exports[`calculate simulations-salarié: temps partiel 2`] = `"[2533,0,2500,1857,1448,1416]"`; - -exports[`calculate simulations-salarié: échelle de salaires 1`] = `"[130,0,0,100,57,57]"`; - -exports[`calculate simulations-salarié: échelle de salaires 2`] = `"[284,0,0,250,176,176]"`; - -exports[`calculate simulations-salarié: échelle de salaires 3`] = `"[541,0,0,500,374,374]"`; - -exports[`calculate simulations-salarié: échelle de salaires 4`] = `"[798,0,0,750,572,572]"`; - -exports[`calculate simulations-salarié: échelle de salaires 5`] = `"[1055,0,0,1000,770,770]"`; - -exports[`calculate simulations-salarié: échelle de salaires 6`] = `"[1312,0,0,1250,968,968]"`; - -exports[`calculate simulations-salarié: échelle de salaires 7`] = `"[1569,0,0,1500,1165,1165]"`; - -exports[`calculate simulations-salarié: échelle de salaires 8`] = `"[2494,0,0,2000,1561,1503]"`; - -exports[`calculate simulations-salarié: échelle de salaires 9`] = `"[3401,0,0,2500,1957,1815]"`; - -exports[`calculate simulations-salarié: échelle de salaires 10`] = `"[4076,0,0,3000,2353,2159]"`; - -exports[`calculate simulations-salarié: échelle de salaires 11`] = `"[5674,0,0,4000,3146,2744]"`; - -exports[`calculate simulations-salarié: échelle de salaires 12`] = `"[7085,0,0,5000,3948,3321]"`; - -exports[`calculate simulations-salarié: échelle de salaires 13`] = `"[14319,0,0,10000,7959,6069]"`; - -exports[`calculate simulations-salarié: échelle de salaires 14`] = `"[28336,0,0,20000,15969,10941]"`; - -exports[`calculate simulations-salarié: échelle de salaires 15`] = `"[128506,0,0,100000,87197,50180]"`; - -exports[`calculate simulations-salarié: échelle de salaires 16`] = `"[1243750,0,0,1000000,896297,451743]"`; +exports[`calculate simulations-salarié: échelle de salaires 16`] = `"[1243750,0,0,1000000,896297,446127]"`; diff --git a/test/regressions/simulations-auto-entrepreneur.yaml b/test/regressions/simulations-auto-entrepreneur.yaml index 4976096bf..8c3b1fb20 100644 --- a/test/regressions/simulations-auto-entrepreneur.yaml +++ b/test/regressions/simulations-auto-entrepreneur.yaml @@ -10,14 +10,6 @@ - dirigeant . auto-entrepreneur . revenu net de cotisations: 100000 - dirigeant . auto-entrepreneur . revenu net de cotisations: 1000000 -périodes: - - dirigeant . auto-entrepreneur . revenu net de cotisations: 100 - période: mois - - dirigeant . auto-entrepreneur . revenu net de cotisations: 500 - période: mois - - dirigeant . auto-entrepreneur . revenu net de cotisations: 1000 - période: mois - aides: - dirigeant . auto-entrepreneur . revenu net de cotisations: 5000 entreprise . ACRE: true diff --git a/test/regressions/simulations-indépendant.yaml b/test/regressions/simulations-indépendant.yaml index 3ee392e23..61cc9f338 100644 --- a/test/regressions/simulations-indépendant.yaml +++ b/test/regressions/simulations-indépendant.yaml @@ -8,12 +8,6 @@ - dirigeant . indépendant . revenu net de cotisations: 100000 - dirigeant . indépendant . revenu net de cotisations: 1000000 -période: - - dirigeant . indépendant . revenu net de cotisations: 1000 - période: mois - - dirigeant . indépendant . revenu net de cotisations: 5000 - période: mois - inversions: - entreprise . rémunération totale du dirigeant: 2000 - entreprise . rémunération totale du dirigeant: 50000 @@ -43,4 +37,4 @@ impôt sur le revenu: impôt . méthode de calcul: taux neutre - dirigeant . indépendant . revenu net de cotisations: 20000 impôt . méthode de calcul: taux personnalisé - impôt . taux personnalisé: 0.1 + impôt . taux personnalisé: 10 diff --git a/test/regressions/simulations-rémunération-dirigeant.yaml b/test/regressions/simulations-rémunération-dirigeant.yaml index b1d6df864..383df9c25 100644 --- a/test/regressions/simulations-rémunération-dirigeant.yaml +++ b/test/regressions/simulations-rémunération-dirigeant.yaml @@ -7,14 +7,6 @@ - entreprise . rémunération totale du dirigeant: 50000 - entreprise . rémunération totale du dirigeant: 100000 -périodes: - - entreprise . rémunération totale du dirigeant: 200 - période: mois - - entreprise . rémunération totale du dirigeant: 500 - période: mois - - entreprise . rémunération totale du dirigeant: 5000 - période: mois - avec charges: - entreprise . rémunération totale du dirigeant: 10000 entreprise . charges: 2000 diff --git a/test/regressions/simulations-salarié.yaml b/test/regressions/simulations-salarié.yaml index f260531a2..2ad6203b3 100644 --- a/test/regressions/simulations-salarié.yaml +++ b/test/regressions/simulations-salarié.yaml @@ -16,14 +16,6 @@ - contrat salarié . rémunération . brut de base: 100000 - contrat salarié . rémunération . brut de base: 1000000 -périodes: - - contrat salarié . rémunération . brut de base: 3000 - période: année - - contrat salarié . rémunération . brut de base: 45000 - période: année - - contrat salarié . rémunération . brut de base: 500000 - période: année - inversions: - contrat salarié . prix du travail: 2000 - contrat salarié . rémunération . net: 2000 @@ -57,7 +49,7 @@ cdd: atmp: - contrat salarié . rémunération . brut de base: 2000 - contrat salarié . ATMP . taux collectif ATMP: 0.05 + contrat salarié . ATMP . taux collectif ATMP: 5 assimilé salarié: - dirigeant: assimilé salarié @@ -105,7 +97,7 @@ impôt sur le revenu: établissement . localisation . département: Mayotte - contrat salarié . rémunération . brut de base: 3000 impôt . méthode de calcul: taux personnalisé - impôt . taux personnalisé: 0.1 + impôt . taux personnalisé: 10 heures supplémentaires: - contrat salarié . rémunération . brut de base: 2000 diff --git a/test/regressions/simulations.jest.js b/test/regressions/simulations.jest.js index 6c0b135c3..bad6fbebd 100644 --- a/test/regressions/simulations.jest.js +++ b/test/regressions/simulations.jest.js @@ -2,7 +2,7 @@ // of simulations and persist their results in a snapshot (ie, a file commited in git). Our test runner, // Jest, then compare the existing snapshot with the current Engine calculation and reports any difference. // -// We only persist goals values in the file system, in order to be resilient to rule renaming (if a rule is +// We only persist targets values in the file system, in order to be resilient to rule renaming (if a rule is // renamed the test configuration may be adapted but the persisted snapshot will remain unchanged). /* eslint-disable no-undef */ @@ -19,21 +19,25 @@ import remunerationDirigeantSituations from './simulations-rémunération-dirige import employeeSituations from './simulations-salarié.yaml' const roundResult = arr => arr.map(x => Math.round(x)) - +const engine = new Lib.Engine() const runSimulations = ( situations, - goals, + targets, baseSituation = {}, + defaultUnits, namePrefix = '' ) => Object.entries(situations).map(([name, situations]) => situations.forEach(situation => { - const res = Lib.evaluate(goals, { ...baseSituation, ...situation }) + const res = engine.evaluate(targets, { + situation: { ...baseSituation, ...situation }, + defaultUnits + }) // Stringify is not required, but allows the result to be displayed in a single // line in the snapshot, which considerably reduce the number of lines of this snapshot // and improve its readability. expect(JSON.stringify(roundResult(res))).toMatchSnapshot( - namePrefix + name + namePrefix + ' ' + name ) }) ) @@ -42,23 +46,27 @@ it('calculate simulations-salarié', () => { runSimulations( employeeSituations, employeeConfig.objectifs, - employeeConfig.situation + employeeConfig.situation, + ['€/mois'] ) }) it('calculate simulations-indépendant', () => { - const goals = independantConfig.objectifs.reduce( + const targets = independantConfig.objectifs.reduce( (acc, cur) => [...acc, ...cur.objectifs], [] ) - runSimulations(independentSituations, goals, independantConfig.situation) + runSimulations(independentSituations, targets, independantConfig.situation, [ + '€/an' + ]) }) it('calculate simulations-auto-entrepreneur', () => { runSimulations( autoEntrepreneurSituations, autoentrepreneurConfig.objectifs, - autoentrepreneurConfig.situation + autoentrepreneurConfig.situation, + ['€/an'] ) }) @@ -69,6 +77,7 @@ it('calculate simulations-rémunération-dirigeant', () => { remunerationDirigeantSituations, remunerationDirigeantConfig.objectifs, { ...baseSituation, ...situation }, + ['€/an'], `${nom} - ` ) }) @@ -78,6 +87,7 @@ it('calculate simulations-artiste-auteur', () => { runSimulations( artisteAuteurSituations, artisteAuteurConfig.objectifs, - artisteAuteurConfig.situation + artisteAuteurConfig.situation, + ['€/an'] ) }) diff --git a/test/rules.test.js b/test/rules.test.js index 738639c1f..c4a312ff7 100644 --- a/test/rules.test.js +++ b/test/rules.test.js @@ -46,20 +46,6 @@ describe('rule checks', function() { ) expect(rulesNeedingDefault).to.be.empty }) - it('rules with a period should not have a flexible period', function() { - let problems = rules.filter( - ({ defaultValue, période }) => période === 'flexible' && defaultValue - ) - - problems.map(({ dottedName }) => - console.log( - 'La valeur règle ', - dottedName, - " a une période flexible et une valeur par défaut. C'est un problème, car on ne sait pas pour quelle période ce défaut est défini. " - ) - ) - expect(problems).to.be.empty - }) }) it('rules with a formula should not have defaults', function() { diff --git a/test/traverse.test.js b/test/traverse.test.js index f562530bb..cd6206ad9 100644 --- a/test/traverse.test.js +++ b/test/traverse.test.js @@ -111,27 +111,6 @@ describe('analyse with mecanisms', function() { ).to.have.property('nodeValue', false) }) - it('should handle switch statements', function() { - let rawRules = [ - { nom: 'top' }, - { - nom: 'top . startHere', - formule: { - 'aiguillage numérique': { - '1 > dix': '1000%', - '3 < dix': '1100%', - '3 > dix': '1200%' - } - } - }, - { nom: 'top . dix', formule: 10 } - ], - rules = parseAll(rawRules.map(enrichRule)) - expect( - analyse(rules, 'startHere')(stateSelector).targets[0] - ).to.have.property('nodeValue', 11) - }) - it('should handle percentages', function() { let rawRules = [{ nom: 'top' }, { nom: 'top . startHere', formule: '35%' }], rules = parseAll(rawRules.map(enrichRule)) @@ -353,7 +332,7 @@ describe('analyse with mecanisms', function() { it('should handle filtering on components', function() { let rawRules = [ { nom: 'top' }, - { nom: 'top . startHere', formule: 'composed [salarié]' }, + { nom: 'top . startHere', formule: 'composed .salarié' }, { nom: 'top . composed', formule: { @@ -393,7 +372,7 @@ describe('analyse with mecanisms', function() { { nom: 'top' }, { nom: 'top . startHere', - formule: 'composed [salarié] + composed [employeur]' + formule: 'composed .salarié + composed .employeur' }, { nom: 'top . orHere', formule: 'composed' }, { diff --git a/test/units.test.js b/test/units.test.js index 2f73b954e..75ab82ebb 100644 --- a/test/units.test.js +++ b/test/units.test.js @@ -1,5 +1,11 @@ import { expect } from 'chai' -import { removeOnce, parseUnit, inferUnit } from 'Engine/units' +import { + areUnitConvertible, + convertUnit, + inferUnit, + parseUnit, + removeOnce +} from 'Engine/units' describe('Units', () => { it('should remove the first element encounter in the list', () => { @@ -11,10 +17,26 @@ describe('Units', () => { numerators: ['m'], denominators: [] }) + expect(parseUnit('/an')).to.deep.equal({ + numerators: [], + denominators: ['an'] + }) expect(parseUnit('m/s')).to.deep.equal({ numerators: ['m'], denominators: ['s'] }) + expect(parseUnit('kg.m/s')).to.deep.equal({ + numerators: ['kg', 'm'], + denominators: ['s'] + }) + expect(parseUnit('kg.m/s')).to.deep.equal({ + numerators: ['kg', 'm'], + denominators: ['s'] + }) + expect(parseUnit('€/personne/mois')).to.deep.equal({ + numerators: ['€'], + denominators: ['personne', 'mois'] + }) }) it('should work with simple use case *', () => { let unit1 = { numerators: ['m'], denominators: ['s'] } @@ -47,3 +69,80 @@ describe('Units', () => { }) }) }) + +describe('convertUnit', () => { + it('should convert month to year in denominator', () => { + expect(convertUnit(parseUnit('/mois'), parseUnit('/an'), 10)).to.eq(120) + }) + it('should convert year to month in denominator', () => { + expect(convertUnit(parseUnit('/an'), parseUnit('/mois'), 120)).to.eq(10) + }) + it('should convert year to month in numerator', () => { + expect(convertUnit(parseUnit('mois'), parseUnit('an'), 12)).to.eq(1) + }) + it('should month to year in numerator', () => { + expect(convertUnit(parseUnit('mois'), parseUnit('an'), 12)).to.eq(1) + }) + it('should convert percentage to simple value', () => { + expect(convertUnit(parseUnit('%'), parseUnit(''), 83)).to.closeTo( + 0.83, + 0.0000001 + ) + }) + it('should convert more difficult value', () => { + expect(convertUnit(parseUnit('%/an'), parseUnit('/mois'), 12)).to.closeTo( + 0.01, + 0.0000001 + ) + }) + it('should convert year, month, day, k€', () => { + expect( + convertUnit( + parseUnit('€/personne/jour'), + parseUnit('k€/an/personne'), + '100' + ) + ).to.closeTo(36.5, 0.0000001) + }) + it('should handle simplification', () => { + expect( + convertUnit(parseUnit('€.an.%/mois'), parseUnit('€'), 100) + ).to.closeTo(12, 0.0000001) + }) + it('should handle complexification', () => { + expect( + convertUnit(parseUnit('€'), parseUnit('€.an.%/mois'), 12) + ).to.closeTo(100, 0.0000001) + }) +}) + +describe('areUnitConvertible', () => { + it('should be true for temporel unit', () => { + expect(areUnitConvertible(parseUnit('mois'), parseUnit('an'))).to.eq(true) + expect(areUnitConvertible(parseUnit('kg/an'), parseUnit('kg/mois'))).to.eq( + true + ) + }) + it('should be true for percentage', () => { + expect(areUnitConvertible(parseUnit('%/mois'), parseUnit('/an'))).to.eq( + true + ) + }) + it('should be true for more complicated cases', () => { + expect( + areUnitConvertible( + parseUnit('€/personne/mois'), + parseUnit('€/an/personne') + ) + ).to.eq(true) + }) + it('should be false for unit not alike', () => { + expect( + areUnitConvertible(parseUnit('mois'), parseUnit('€/an/personne')) + ).to.eq(false) + expect(areUnitConvertible(parseUnit('m.m'), parseUnit('m'))).to.eq(false) + expect(areUnitConvertible(parseUnit('m'), parseUnit('s'))).to.eq(false) + }) +}) + +describe('simplifyUnit', () => {})