⚙️ ajoute la conversion d'unité
Gros changements en perspective : - Supprime la notion de période, au bénéfice de celle d'unité (`période : mensuelle` devient `unité: €/mois`) - Améliore les rapports d'erreur avec des messages plus clair - Ajoute un avertissement lorsque des types ne sont pas compatible - Ajoute la conversion automatique d'unité dans le moteur - Ajoute une notion d'unité par défaut de la simulation, c'est l'unité vers laquelle les règles qui ne spécifient pas d'unité seront converties - Ajoute une notion d'unité par défaut des règles, qui spécifie l'unité de la règle qui prévaut lorsque qu'il n'y a pas d'unité par défaut de la simulation (utile pour les question ou pour s'assurer du bon type d'une règle)pull/797/head
parent
6b7f50fe4a
commit
00b122fa97
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"spellright.language": ["fr"],
|
||||
"spellright.language": ["fr", "en"],
|
||||
"spellright.documentTypes": ["yaml", "git-commit"],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
)
|
||||
})
|
||||
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/)
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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<R> = 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<typeof resetSimulation>
|
||||
type UpdateAction = ReturnType<typeof updateSituation>
|
||||
type UpdatePeriodAction = ReturnType<typeof updatePeriod>
|
||||
type UpdateSituationAction = ReturnType<typeof updateSituation>
|
||||
type LoadPreviousSimulationAction = ReturnType<typeof loadPreviousSimulation>
|
||||
type SetSituationBranchAction = ReturnType<typeof setSituationBranch>
|
||||
type SetActiveTargetAction = ReturnType<typeof setActiveTarget>
|
||||
type HideControlAction = ReturnType<typeof hideControl>
|
||||
type ExplainVariableAction = ReturnType<typeof explainVariable>
|
||||
type UpdateDefaultUnit = ReturnType<typeof updateUnit>
|
||||
|
||||
export const resetSimulation = () =>
|
||||
({
|
||||
|
@ -90,9 +92,12 @@ export const setSituationBranch = (id: number) =>
|
|||
|
||||
export const setSimulationConfig = (config: Object): ThunkResult<void> => (
|
||||
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) {
|
||||
|
|
|
@ -53,10 +53,15 @@ export default compose(
|
|||
<div className="payslip__salarySection">
|
||||
<Line
|
||||
rule={getRule('contrat salarié . temps de travail')}
|
||||
unit="heures/mois"
|
||||
maximumFractionDigits={1}
|
||||
/>
|
||||
{heuresSupplémentaires?.nodeValue > 0 && (
|
||||
<Line rule={heuresSupplémentaires} maximumFractionDigits={1} />
|
||||
<Line
|
||||
rule={heuresSupplémentaires}
|
||||
unit="heures/mois"
|
||||
maximumFractionDigits={1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export let SalaireBrutSection = ({ getRule }) => {
|
|||
export let Line = ({ rule, ...props }) => (
|
||||
<>
|
||||
<RuleLink {...rule} />
|
||||
<Value {...rule} nilValueSymbol="—" {...props} />
|
||||
<Value {...rule} nilValueSymbol="—" unit="€" {...props} />
|
||||
</>
|
||||
)
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<span id="PeriodSwitch">
|
||||
<span className="base ui__ small toggle">
|
||||
{periods.map(period => (
|
||||
<label key={period}>
|
||||
{units.map(unit => (
|
||||
<label key={unit}>
|
||||
<input
|
||||
name="période"
|
||||
name="defaultUnit"
|
||||
type="radio"
|
||||
value={period}
|
||||
onChange={() => dispatch(updatePeriod(period))}
|
||||
checked={currentPeriod === period}
|
||||
value={unit}
|
||||
onChange={() => dispatch(updateUnit(unit))}
|
||||
checked={currentUnit === unit}
|
||||
/>
|
||||
<span>
|
||||
<Trans>{period}</Trans>
|
||||
</span>
|
||||
<span>{unit}</span>
|
||||
</label>
|
||||
))}
|
||||
</span>
|
||||
|
|
|
@ -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 (
|
||||
<section>
|
||||
<h2>
|
||||
<Trans>
|
||||
{period === 'mois'
|
||||
? 'Fiche de paie mensuelle'
|
||||
: 'Détail annuel des cotisations'}
|
||||
</Trans>
|
||||
{unit.endsWith('mois') ? (
|
||||
<Trans>Fiche de paie</Trans>
|
||||
) : (
|
||||
<Trans>Détail annuel des cotisations</Trans>
|
||||
)}
|
||||
</h2>
|
||||
<PaySlip />
|
||||
</section>
|
||||
|
|
|
@ -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 && (
|
||||
<>
|
||||
<T k="comparaisonRégimes.période">
|
||||
<h3 className="legend">Période</h3>
|
||||
<h3 className="legend">Unité</h3>
|
||||
</T>
|
||||
<div className="AS-indep-et-auto" style={{ alignSelf: 'start' }}>
|
||||
<PeriodSwitch />
|
||||
|
|
|
@ -94,7 +94,6 @@ export default function StackedBarChart({ data }: StackedBarChartProps) {
|
|||
}))
|
||||
|
||||
const styles = useSpring({ opacity: displayChart ? 1 : 0 })
|
||||
|
||||
return (
|
||||
<animated.div ref={intersectionRef} style={styles}>
|
||||
<BarStack>
|
||||
|
|
|
@ -187,8 +187,7 @@ const Target = ({ target, initialRender }) => {
|
|||
onFirstClick={value => {
|
||||
dispatch(updateSituation(target.dottedName, value))
|
||||
}}
|
||||
rulePeriod={target.période}
|
||||
colouredBackground={true}
|
||||
unit={target.defaultUnit}
|
||||
/>
|
||||
</Animate.fromTop>
|
||||
)}
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@ export default function Value({
|
|||
value: nodeValue
|
||||
})
|
||||
)
|
||||
|
||||
return nodeValue == undefined ? null : (
|
||||
<span css={style(customCSS)} className="value">
|
||||
{negative ? '-' : ''}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="answer">
|
||||
<NumberFormat
|
||||
autoFocus
|
||||
className={classnames({ suffixed })}
|
||||
className={'suffixed'}
|
||||
id={'step-' + dottedName}
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalSeparator={decimalSeparator}
|
||||
suffix={unit === '%' ? ' %' : ''}
|
||||
allowEmptyFormatting={true}
|
||||
style={{ border: `1px solid ${colours.textColourOnWhite}` }}
|
||||
onValueChange={({ floatValue }) => {
|
||||
debouncedSetFormValue(unit === '%' ? floatValue / 100 : floatValue)
|
||||
debouncedSetFormValue(floatValue)
|
||||
}}
|
||||
value={unit === '%' ? 100 * value : value}
|
||||
value={value}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{suffixed && (
|
||||
<label className="suffix" htmlFor={'step-' + dottedName}>
|
||||
{unit}
|
||||
{rulePeriod && rulePeriod !== 'aucune' && (
|
||||
<span>
|
||||
{' '}
|
||||
<T>par</T>{' '}
|
||||
<T>
|
||||
{
|
||||
{ mois: 'mois', année: 'an' }[
|
||||
rulePeriod === 'flexible' ? period : rulePeriod
|
||||
]
|
||||
}
|
||||
</T>
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
<label className="suffix" htmlFor={'step-' + dottedName}>
|
||||
{unit}
|
||||
</label>
|
||||
<SendButton {...{ disabled: value === undefined, submit }} />
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -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 (
|
||||
<div css="display: flex; align-items: baseline; justify-content: flex-end;">
|
||||
<small>Suggestions :</small>
|
||||
|
||||
{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 (
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
key={value}
|
||||
css="margin: 0 0.4rem !important"
|
||||
onClick={() => {
|
||||
onFirstClick(adjustedValue)
|
||||
if (suggestion !== adjustedValue) setSuggestion(adjustedValue)
|
||||
else onSecondClick && onSecondClick(adjustedValue)
|
||||
onFirstClick(value)
|
||||
if (suggestion !== value) setSuggestion(value)
|
||||
else onSecondClick && onSecondClick(value)
|
||||
}}
|
||||
title={t('cliquez pour insérer cette suggestion')}>
|
||||
title={t('cliquez pour insérer cette suggestion')}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
)
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<button
|
||||
|
@ -144,17 +142,9 @@ export default compose(
|
|||
>
|
||||
<Value
|
||||
{...displayedRule}
|
||||
nilValueSymbol={
|
||||
displayedRule.parentDependencies.some(
|
||||
parent => parent?.nodeValue == false
|
||||
)
|
||||
? '-'
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<Period
|
||||
period={flatRule['période']}
|
||||
valuesToShow={valuesToShow}
|
||||
nilValueSymbol={displayedRule.parentDependencies.some(
|
||||
parent => parent?.nodeValue == false
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{displayedRule.defaultValue != null && (
|
||||
|
@ -163,6 +153,7 @@ export default compose(
|
|||
<Value
|
||||
{...displayedRule}
|
||||
nodeValue={displayedRule.defaultValue}
|
||||
unit={displayedRule.unit || displayedRule.defaultUnit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -257,20 +248,3 @@ let NamespaceRulesList = compose(withColours)(({ namespaceRules, colours }) => {
|
|||
</section>
|
||||
)
|
||||
})
|
||||
|
||||
let Period = ({ period, valuesToShow }) =>
|
||||
period ? (
|
||||
valuesToShow && period === 'flexible' ? (
|
||||
<PeriodSwitch />
|
||||
) : (
|
||||
<span className="inlineMecanism">
|
||||
<span
|
||||
className="name"
|
||||
data-term-definition="période"
|
||||
style={{ background: '#8e44ad' }}
|
||||
>
|
||||
{period}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
) : null
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,6 +16,6 @@ questions:
|
|||
liste noire:
|
||||
- entreprise . charges
|
||||
|
||||
unités par défaut: [€/an]
|
||||
situation:
|
||||
dirigeant: 'auto-entrepreneur'
|
||||
période: année
|
||||
|
|
|
@ -22,6 +22,6 @@ questions:
|
|||
liste noire:
|
||||
- entreprise . charges
|
||||
|
||||
unités par défaut: [€/an]
|
||||
situation:
|
||||
dirigeant: 'indépendant'
|
||||
période: année
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<number>()
|
||||
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 (
|
||||
<>
|
||||
|
|
|
@ -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%
|
||||
```
|
||||
|
|
|
@ -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%
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
`
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -87,7 +87,7 @@ export function formatValue({
|
|||
style: 'percent',
|
||||
maximumFractionDigits,
|
||||
language
|
||||
})(value)
|
||||
})(value / 100)
|
||||
default:
|
||||
return (
|
||||
numberFormatter({
|
||||
|
|
|
@ -33,7 +33,17 @@ export default rules => dottedName => {
|
|||
return <SelectGéo {...{ ...commonProps }} />
|
||||
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 (
|
||||
<SelectAtmp
|
||||
{...{
|
||||
...commonProps,
|
||||
suggestions: rule.suggestions
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
if (rule.unit == null && rule.defaultUnit == null)
|
||||
return (
|
||||
<Question
|
||||
{...{
|
||||
|
@ -46,25 +56,14 @@ export default rules => dottedName => {
|
|||
/>
|
||||
)
|
||||
|
||||
if (rule.suggestions == 'atmp-2017')
|
||||
return (
|
||||
<SelectAtmp
|
||||
{...{
|
||||
...commonProps,
|
||||
suggestions: rule.suggestions
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
// Now the numeric input case
|
||||
|
||||
return (
|
||||
<Input
|
||||
{...{
|
||||
...commonProps,
|
||||
unit: serialiseUnit(rule.unit),
|
||||
suggestions: rule.suggestions,
|
||||
rulePeriod: rule.période
|
||||
unit: serialiseUnit(rule.unit || rule.defaultUnit),
|
||||
suggestions: rule.suggestions
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -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 %}
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<Node
|
||||
classes="mecanism allègement"
|
||||
name="allègement"
|
||||
value={nodeValue}
|
||||
unit={explanation.unit}
|
||||
child={
|
||||
<ul className="properties">
|
||||
<li key="assiette">
|
||||
<span className="key">assiette: </span>
|
||||
<span className="value">{makeJsx(rawExplanation.assiette)}</span>
|
||||
<span className="value">{makeJsx(explanation.assiette)}</span>
|
||||
</li>
|
||||
{explanation.franchise && (
|
||||
<li key="franchise">
|
||||
|
@ -41,6 +43,12 @@ export default function Allègement(nodeValue, rawExplanation) {
|
|||
<span className="value">{makeJsx(explanation.abattement)}</span>
|
||||
</li>
|
||||
)}
|
||||
{explanation.plafond && (
|
||||
<li key="abattement">
|
||||
<span className="key">plafond: </span>
|
||||
<span className="value">{makeJsx(explanation.plafond)}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -119,7 +119,10 @@ let Component = function Barème({
|
|||
<Trans>Taux moyen</Trans> :{' '}
|
||||
</b>
|
||||
<NodeValuePointer
|
||||
data={nodeValue / lazyEval(explanation['assiette']).nodeValue}
|
||||
data={
|
||||
(100 * nodeValue) /
|
||||
lazyEval(explanation['assiette']).nodeValue
|
||||
}
|
||||
unit="%"
|
||||
/>
|
||||
</>
|
||||
|
@ -142,6 +145,7 @@ let Tranche = ({
|
|||
de: min,
|
||||
à: max,
|
||||
taux,
|
||||
nodeValue,
|
||||
montant
|
||||
},
|
||||
multiplicateur,
|
||||
|
@ -186,7 +190,7 @@ let Tranche = ({
|
|||
<td key="taux"> {taux != null ? makeJsx(taux) : montant}</td>
|
||||
{showValues && !returnRate && taux != null && (
|
||||
<td key="value">
|
||||
<NodeValuePointer data={trancheValue} unit={resultUnit} />
|
||||
<NodeValuePointer data={nodeValue} unit={resultUnit} />
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
|
@ -212,7 +216,8 @@ function TrancheFormatter({
|
|||
{value}
|
||||
<RuleLink
|
||||
{...multiplicateur.explanation}
|
||||
title={multiplicateur.explanation.name}>
|
||||
title={multiplicateur.explanation.name}
|
||||
>
|
||||
{multiplicateurAcronym}
|
||||
</RuleLink>{' '}
|
||||
<NodeValuePointer
|
||||
|
|
|
@ -36,7 +36,8 @@ function Row({ v, i, unit }) {
|
|||
className="mecanism-somme__row"
|
||||
key={v.name || i}
|
||||
// className={isSomme ? '' : 'noNest'}
|
||||
onClick={() => setFolded(!folded)}>
|
||||
onClick={() => setFolded(!folded)}
|
||||
>
|
||||
<div className="element">
|
||||
{makeJsx(v)}
|
||||
{isSomme && (
|
||||
|
@ -46,13 +47,13 @@ function Row({ v, i, unit }) {
|
|||
)}
|
||||
</div>
|
||||
<div className="situationValue value">
|
||||
<NodeValuePointer data={v.nodeValue} unit={unit} />
|
||||
<NodeValuePointer data={v.nodeValue} unit={v.unit} />
|
||||
</div>
|
||||
</div>,
|
||||
...(isSomme && !folded
|
||||
? [
|
||||
<div className="nested" key={v.name + '-nest'}>
|
||||
<Table explanation={rowFormula.explanation} unit={unit} />
|
||||
<Table explanation={rowFormula.explanation} unit={v.unit || unit} />
|
||||
</div>
|
||||
]
|
||||
: [])
|
||||
|
|
|
@ -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 }) => (
|
||||
<div className="condition">
|
||||
{makeJsx(condition)}
|
||||
<div>{makeJsx(consequence)}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
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) => (
|
||||
<Node
|
||||
classes="mecanism numericalSwitch list"
|
||||
name="aiguillage numérique"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{explanation.map(item => (
|
||||
<li key={item.name || item.text}>{makeJsx(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Node
|
||||
classes="mecanism encadrement"
|
||||
name="encadrement"
|
||||
value={nodeValue}
|
||||
unit={explanation.unit}
|
||||
unit={unit}
|
||||
child={
|
||||
<>
|
||||
{makeJsx(explanation.valeur)}
|
||||
<ul className="properties">
|
||||
<p>
|
||||
{!explanation.plancher.isDefault && (
|
||||
<li key="plancher">
|
||||
<span
|
||||
style={
|
||||
nodeValue === val(explanation.plancher)
|
||||
? { background: 'yellow' }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<span className="key">Minimum :</span>
|
||||
<span className="value">{makeJsx(explanation.plancher)}</span>
|
||||
</span>
|
||||
</li>
|
||||
<span
|
||||
css={
|
||||
nodeValue === val(explanation.plancher) &&
|
||||
'background: yellow'
|
||||
}
|
||||
>
|
||||
<strong className="key">Minimum : </strong>
|
||||
<span className="value">{makeJsx(explanation.plancher)}</span>
|
||||
</span>
|
||||
)}
|
||||
{!explanation.plafond.isDefault && (
|
||||
<li key="plafond">
|
||||
<span
|
||||
style={
|
||||
nodeValue === val(explanation.plafond)
|
||||
? { background: 'yellow' }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<span className="key">Plafonné à :</span>
|
||||
<span className="value">{makeJsx(explanation.plafond)}</span>
|
||||
</span>
|
||||
</li>
|
||||
<span
|
||||
css={
|
||||
nodeValue === val(explanation.plafond) && 'background: yellow'
|
||||
}
|
||||
>
|
||||
<strong className="key">Plafonné à : </strong>
|
||||
<span className="value">{makeJsx(explanation.plafond)}</span>
|
||||
</span>
|
||||
)}
|
||||
</ul>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
@ -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) => (
|
||||
<MecanismEncadrement nodeValue={nodeValue} explanation={explanation} />
|
||||
jsx: (nodeValue, explanation, _, unit) => (
|
||||
<MecanismEncadrement
|
||||
nodeValue={nodeValue}
|
||||
explanation={explanation}
|
||||
unit={unit}
|
||||
/>
|
||||
),
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
|
|
|
@ -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) => (
|
||||
<Node
|
||||
classes={'inlineExpression ' + k}
|
||||
value={nodeValue}
|
||||
|
|
|
@ -80,6 +80,7 @@ export default (recurse, k, v, devariate) => {
|
|||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) => (
|
||||
<>
|
||||
<Leaf
|
||||
classes="variable filtered"
|
||||
|
@ -178,22 +199,21 @@ export let parseReference = (
|
|||
name={partialReference}
|
||||
dottedName={dottedName}
|
||||
nodeValue={nodeValue}
|
||||
unit={parsedRule.unit}
|
||||
unit={nodeUnit || explanation?.unit || unit}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<string>, 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 = <T>(element: T) => (list: Array<T>): Array<T> => {
|
||||
let index = list.indexOf(element)
|
||||
export let removeOnce = <T>(
|
||||
element: T,
|
||||
eqFn: (a: T, b: T) => boolean = equals
|
||||
) => (list: Array<T>): Array<T> => {
|
||||
let index = list.findIndex(e => eqFn(e, element))
|
||||
if (index > -1) return remove<T>(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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<DottedName>
|
||||
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<string>
|
||||
situation: Record<DottedName, any>
|
||||
defaultUnits: [string]
|
||||
}
|
||||
|
||||
function simulation(
|
||||
state: Simulation = null,
|
||||
action: Action,
|
||||
rules: Array<Rule>
|
||||
analysis: Record<DottedName, { nodeValue: any; unit: Unit | undefined }>
|
||||
): 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<Array<Rule>>,
|
||||
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<SavedSimulation>,
|
||||
currentExample,
|
||||
situationBranch,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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%
|
||||
|
||||
```
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
) => {
|
||||
|
|
|
@ -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])
|
||||
])
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<li>
|
||||
<Animate.appear unless={initialRender}>
|
||||
|
@ -100,7 +103,8 @@ function SimpleField({ dottedName, initialRender }: SimpleFieldProps) {
|
|||
</label>
|
||||
</div>
|
||||
<div className="targetInputOrValue">
|
||||
{unit === '€' && (
|
||||
{/* Super hacky */}
|
||||
{analysis.unit !== undefined ? (
|
||||
<NumberFormat
|
||||
autoFocus
|
||||
id={'step-' + dottedName}
|
||||
|
@ -120,9 +124,7 @@ function SimpleField({ dottedName, initialRender }: SimpleFieldProps) {
|
|||
padding: 10px;
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
{/* Super hacky */}
|
||||
{unit !== '€' && (
|
||||
) : (
|
||||
<ToggleSwitch
|
||||
id={`step-${dottedName}`}
|
||||
defaultChecked={rule.nodeValue}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import { setSimulationConfig } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import SalaryExplanation from 'Components/SalaryExplanation'
|
||||
import Warning from 'Components/SimulateurWarning'
|
||||
import Simulation from 'Components/Simulation'
|
||||
import assimiléConfig from 'Components/simulationConfigs/assimilé.yaml'
|
||||
import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig'
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
export default function AssimiléSalarié() {
|
||||
useSimulationConfig(assimiléConfig)
|
||||
const dispatch = useDispatch()
|
||||
dispatch(setSimulationConfig(assimiléConfig))
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import { setSimulationConfig } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import Warning from 'Components/SimulateurWarning'
|
||||
import Simulation from 'Components/Simulation'
|
||||
import indépendantConfig from 'Components/simulationConfigs/auto-entrepreneur.yaml'
|
||||
import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig'
|
||||
import autoEntrepreneurConfig from 'Components/simulationConfigs/auto-entrepreneur.yaml'
|
||||
import StackedBarChart from 'Components/StackedBarChart'
|
||||
import { ThemeColoursContext } from 'Components/utils/withColours'
|
||||
import { getRuleFromAnalysis } from 'Engine/rules'
|
||||
import React, { useContext } from 'react'
|
||||
import { default as React, useContext } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
|
||||
|
||||
export default function AutoEntrepreneur() {
|
||||
useSimulationConfig(indépendantConfig)
|
||||
const dispatch = useDispatch()
|
||||
dispatch(setSimulationConfig(autoEntrepreneurConfig))
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import { setSimulationConfig } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import Warning from 'Components/SimulateurWarning'
|
||||
import Simulation from 'Components/Simulation'
|
||||
import indépendantConfig from 'Components/simulationConfigs/indépendant.yaml'
|
||||
import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig'
|
||||
import StackedBarChart from 'Components/StackedBarChart'
|
||||
import { ThemeColoursContext } from 'Components/utils/withColours'
|
||||
import { getRuleFromAnalysis } from 'Engine/rules'
|
||||
import React, { useContext } from 'react'
|
||||
import { default as React, useContext } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
|
||||
|
||||
export default function Indépendant() {
|
||||
useSimulationConfig(indépendantConfig)
|
||||
const dispatch = useDispatch()
|
||||
dispatch(setSimulationConfig(indépendantConfig))
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -13,7 +13,7 @@ describe('bug-analyse-many', function() {
|
|||
- nom: cotisations
|
||||
formule:
|
||||
somme:
|
||||
- cotisation a [salarié]
|
||||
- cotisation a .salarié
|
||||
- cotisation b
|
||||
|
||||
- nom: cotisation a
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { expect } from 'chai'
|
||||
// $FlowFixMe
|
||||
import salariéConfig from 'Components/simulationConfigs/salarié.yaml'
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -198,7 +198,7 @@ describe('inversions', () => {
|
|||
taux: 50%
|
||||
|
||||
- nom: total
|
||||
formule: cotisation [employeur] + cotisation [salarié]
|
||||
formule: cotisation .employeur + cotisation .salarié
|
||||
|
||||
- nom: brut
|
||||
unité: €
|
||||
|
|
|
@ -32,7 +32,6 @@ describe('library', function() {
|
|||
- nom: yo
|
||||
formule: 1
|
||||
- nom: ya
|
||||
période: flexible
|
||||
formule: contrat salarié . rémunération . net + yo
|
||||
`
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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 ?
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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%
|
||||
|
|
|
@ -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: []
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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]"`;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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' },
|
||||
{
|
||||
|
|
|
@ -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', () => {})
|
||||
|
|
Loading…
Reference in New Issue