Améliore les variables temporelles et la régularisation
- Correction de quelques bugs - Améliration des perfs du studio - Ajout de la visualisation des variables temporelles dans le studio - Ajout d'un exemple de régularisation plus complet - Complète la doc du mécanisme de régularisationpull/973/head
parent
07564964c5
commit
78e97f0f37
10
package.json
10
package.json
|
@ -19,9 +19,7 @@
|
|||
"not ie < 11"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.4.0",
|
||||
"@babel/runtime": "^7.3.4",
|
||||
"@monaco-editor/react": "^3.1.1",
|
||||
"@rehooks/local-storage": "^2.1.1",
|
||||
"classnames": "^2.2.5",
|
||||
"color-convert": "^1.9.2",
|
||||
|
@ -31,7 +29,6 @@
|
|||
"i18next": "^18.0.1",
|
||||
"iframe-resizer": "^4.1.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"moo": "^0.5.0",
|
||||
"nearley": "^2.19.0",
|
||||
"puppeteer": "^2.1.1",
|
||||
|
@ -48,6 +45,7 @@
|
|||
"react-i18next": "^11.0.0",
|
||||
"react-loading-skeleton": "^1.1.2",
|
||||
"react-markdown": "^4.1.0",
|
||||
"react-monaco-editor": "^0.36.0",
|
||||
"react-number-format": "^4.3.1",
|
||||
"react-redux": "^7.0.3",
|
||||
"react-router": "^5.1.1",
|
||||
|
@ -57,8 +55,6 @@
|
|||
"react-syntax-highlighter": "^10.1.1",
|
||||
"react-to-print": "^2.5.1",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"react-virtualized": "^9.20.0",
|
||||
"react-virtualized-select": "^3.1.3",
|
||||
"reduce-reducers": "^1.0.4",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
|
@ -67,9 +63,6 @@
|
|||
"swr": "^0.1.16",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"monaco-editor": "^0.20.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node source/scripts/prepare.js",
|
||||
"compile": "yarn run webpack --config source/webpack.prod.js && yarn run webpack --config source/webpack.prod.legacyBrowser.js",
|
||||
|
@ -112,6 +105,7 @@
|
|||
"@babel/core": "^7.6.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.1.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
toPairs
|
||||
} from 'ramda'
|
||||
import React from 'react'
|
||||
import 'react-virtualized/styles.css'
|
||||
import { typeWarning } from './error'
|
||||
import {
|
||||
collectNodeMissing,
|
||||
|
@ -480,24 +479,24 @@ export let mecanismProduct = (recurse, k, v) => {
|
|||
}
|
||||
let mult = (base, rate, facteur, plafond) =>
|
||||
Math.min(base, plafond === false ? Infinity : plafond) * rate * facteur
|
||||
|
||||
const nodeValue = [taux, assiette, facteur].some(n => n.nodeValue === false)
|
||||
? false
|
||||
: [taux, assiette, facteur].some(n => n.nodeValue === 0)
|
||||
? 0
|
||||
: [taux, assiette, facteur].some(n => n.nodeValue === null)
|
||||
? null
|
||||
: mult(
|
||||
assiette.nodeValue,
|
||||
taux.nodeValue,
|
||||
facteur.nodeValue,
|
||||
plafond.nodeValue
|
||||
)
|
||||
|
||||
const unit = inferUnit(
|
||||
'*',
|
||||
[assiette, taux, facteur].map(el => el.unit)
|
||||
)
|
||||
const nodeValue =
|
||||
taux.nodeValue === 0 ||
|
||||
taux.nodeValue === false ||
|
||||
assiette.nodeValue === 0 ||
|
||||
facteur.nodeValue === 0
|
||||
? 0
|
||||
: [taux, assiette, facteur, plafond].some(n => n.nodeValue === null)
|
||||
? null
|
||||
: mult(
|
||||
assiette.nodeValue,
|
||||
taux.nodeValue,
|
||||
facteur.nodeValue,
|
||||
plafond.nodeValue
|
||||
)
|
||||
return {
|
||||
nodeValue,
|
||||
|
||||
|
|
|
@ -206,12 +206,50 @@ régularisation:
|
|||
variables numérique mensuelle cumulée.
|
||||
|
||||
Ce mécanisme spécifique est utilisé pour le calcul des cotisations
|
||||
mensuelles.
|
||||
mensuelles, afin de "lisser" un plafond ou un calcul sur plusieurs mois.
|
||||
|
||||
La régularisation progressive s'opère le long d'une année civile complète.
|
||||
|
||||
## Explication du calcul
|
||||
|
||||
Pour chaque mois, on évalue la valeur à régulariser en faisant le cumul des
|
||||
éléments nécessaires au calcul sur la période écoulée depuis le premier
|
||||
jour de l’année.
|
||||
|
||||
Par exemple, pour la cotisation suivante :
|
||||
```yaml
|
||||
cotisation:
|
||||
formule:
|
||||
produit:
|
||||
assiette: brut
|
||||
plafond: 2000 €/mois
|
||||
taux: 10%
|
||||
```
|
||||
Avec un brut de 1000 € en janvier et 3500 € en février et 1000€ en mars, le cumul sera le
|
||||
suivant :
|
||||
|
||||
```
|
||||
brut plafond cotisation
|
||||
JAN 1000 2000 1000 * 10% = 100 €
|
||||
FEV 4500 4000 4000 * 10% = 400 €
|
||||
MAR 5500 6000 5500 * 10% = 550 €
|
||||
```
|
||||
|
||||
On regarde ensuite le cumul des valeurs déjà versée les mois précédent, pour
|
||||
ne garder que la différence entre les deux montant. Dans notre exemple, on
|
||||
abouti aux valeurs suivantes :
|
||||
|
||||
```
|
||||
cotisation cumul cotis régularisée
|
||||
JAN 100 € 100 €
|
||||
FEV 400 € 400 € - 100 € = 300 €
|
||||
MAR 550 € 550 € - 300 € = 150 €
|
||||
```
|
||||
|
||||
arguments:
|
||||
règle: règle à régulariser
|
||||
valeurs cumulées:
|
||||
- liste de variables cumulée mensuellement pour calculer la régularisation. Doit être
|
||||
- liste de variables cumulée mensuellement pour calculer la régularisation. Doivent être
|
||||
numérique, et avoir une unité `/mois`
|
||||
|
||||
exemples:
|
||||
|
@ -219,10 +257,9 @@ régularisation:
|
|||
brut:
|
||||
formule:
|
||||
somme:
|
||||
- 2000 €/mois | du 01/01/2020 | au 31/05/2020
|
||||
- 4000 €/mois | du 01/06/2020 | au 31/12/2020
|
||||
plafond:
|
||||
formule: 3000 €/mois
|
||||
- 1000 €/mois | du 01/01/2020 | au 31/01/2020
|
||||
- 3500 €/mois | du 01/02/2020 | au 29/02/2020
|
||||
- 1000 €/mois | du 01/03/2020 | au 31/03/2020
|
||||
|
||||
cotisation:
|
||||
formule:
|
||||
|
@ -230,20 +267,55 @@ régularisation:
|
|||
règle:
|
||||
produit:
|
||||
assiette: brut
|
||||
plafond: plafond
|
||||
plafond [ref]: 2000 €/mois
|
||||
taux: 10%
|
||||
valeurs cumulées:
|
||||
- brut
|
||||
- plafond
|
||||
|
||||
cotisation en mai:
|
||||
formule: cotisation | du 01/05/2020 | au 31/05/2020
|
||||
valeur cumulée à partir d'une date: >-
|
||||
SMIC: 1521.22 €/mois
|
||||
|
||||
cotisation en juin:
|
||||
formule: cotisation | du 01/06/2020 | au 30/06/2020
|
||||
primes:
|
||||
formule:
|
||||
somme:
|
||||
- 2470 €/mois | du 01/01/2019 | au 31/01/2019
|
||||
- 1470 €/mois | du 01/05/2019 | au 31/05/2019
|
||||
|
||||
cotisation en novembre:
|
||||
formule: cotisation | du 01/11/2020 | au 30/11/2020
|
||||
rémunération:
|
||||
formule:
|
||||
somme:
|
||||
- 1530 €/mois | du 01/01/2019 | au 31/12/2019
|
||||
- primes
|
||||
|
||||
réduction sur AC au 1er janvier:
|
||||
formule: non
|
||||
|
||||
réduction générale chômage . coefficient:
|
||||
formule:
|
||||
arrondi:
|
||||
valeur: (0.0405 / 0.60 ) * (1.60 * SMIC / rémunération - 1)
|
||||
décimales: 4
|
||||
|
||||
réduction générale chômage . réduction sans régularisation:
|
||||
formule:
|
||||
encadrement:
|
||||
plancher: 0
|
||||
valeur:
|
||||
arrondi:
|
||||
valeur:
|
||||
multiplication:
|
||||
assiette: rémunération
|
||||
taux: coefficient
|
||||
décimales: 2
|
||||
|
||||
réduction générale chômage:
|
||||
formule:
|
||||
régularisation:
|
||||
règle: réduction sans régularisation
|
||||
valeurs cumulées:
|
||||
- 'SMIC | à partir du 01/10/2019'
|
||||
- 'rémunération | à partir du 01/10/2019'
|
||||
|
||||
recalcul:
|
||||
description: >-
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
mergeAllMissing
|
||||
} from 'Engine/evaluation'
|
||||
import { Node } from 'Engine/mecanismViews/common'
|
||||
import { mapTemporal, pureTemporal, temporalAverage } from 'Engine/temporal'
|
||||
import { EvaluatedRule } from 'Engine/types'
|
||||
import { has } from 'ramda'
|
||||
import React from 'react'
|
||||
|
@ -64,15 +65,20 @@ function evaluate<Names extends string>(
|
|||
const value = evaluateAttribute(node.explanation.value)
|
||||
const decimals = evaluateAttribute(node.explanation.decimals)
|
||||
|
||||
const nodeValue =
|
||||
typeof value.nodeValue === 'number'
|
||||
? roundWithPrecision(value.nodeValue, decimals.nodeValue)
|
||||
: value.nodeValue
|
||||
const temporalValue = mapTemporal(
|
||||
(val: number | false | null) =>
|
||||
typeof val === 'number'
|
||||
? roundWithPrecision(val, decimals.nodeValue)
|
||||
: val,
|
||||
value.temporalValue ?? pureTemporal(value.nodeValue)
|
||||
)
|
||||
|
||||
const nodeValue = temporalAverage(temporalValue, value.unit)
|
||||
return {
|
||||
...node,
|
||||
unit: value.unit,
|
||||
nodeValue,
|
||||
...(temporalValue.length > 1 && { temporalValue }),
|
||||
missingVariables: mergeAllMissing([value, decimals]),
|
||||
explanation: { value, decimals }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { typeWarning } from 'Engine/error'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
evaluateObject,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
parseObject
|
||||
} from 'Engine/evaluation'
|
||||
import { Node } from 'Engine/mecanismViews/common'
|
||||
|
@ -60,48 +59,38 @@ const objectShape = {
|
|||
plancher: defaultNode(-Infinity)
|
||||
}
|
||||
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
const evaluateAttribute = evaluateNode.bind(
|
||||
null,
|
||||
cache,
|
||||
situation,
|
||||
parsedRules
|
||||
)
|
||||
const valeur = evaluateAttribute(node.explanation.valeur)
|
||||
let plafond = evaluateAttribute(node.explanation.plafond)
|
||||
if (plafond.nodeValue === false || plafond.nodeValue === null) {
|
||||
plafond = objectShape.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 evaluate = evaluateObject(
|
||||
objectShape,
|
||||
({ valeur, plafond, plancher }, cache) => {
|
||||
if (plafond.nodeValue === false || plafond.nodeValue === null) {
|
||||
plafond = objectShape.plafond
|
||||
}
|
||||
}
|
||||
|
||||
const nodeValue = Math.max(
|
||||
plancher.nodeValue,
|
||||
Math.min(plafond.nodeValue, valeur.nodeValue)
|
||||
)
|
||||
return {
|
||||
...node,
|
||||
nodeValue,
|
||||
missingVariables: mergeAllMissing([valeur, plafond, plancher]),
|
||||
unit: valeur.unit,
|
||||
explanation: {
|
||||
valeur,
|
||||
plafond,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
return {
|
||||
nodeValue:
|
||||
typeof valeur.nodeValue !== 'number'
|
||||
? valeur.nodeValue
|
||||
: Math.max(
|
||||
plancher.nodeValue,
|
||||
Math.min(plafond.nodeValue, valeur.nodeValue)
|
||||
),
|
||||
unit: valeur.unit
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default (recurse, k, v) => {
|
||||
const explanation = parseObject(recurse, objectShape, v)
|
||||
|
||||
|
|
|
@ -12,9 +12,15 @@ import {
|
|||
temporalCumul
|
||||
} from 'Engine/temporal'
|
||||
import { Unit } from 'Engine/units'
|
||||
import { DottedName } from 'Rules'
|
||||
import { coerceArray } from '../../utils'
|
||||
import { DottedName } from './../../rules/index'
|
||||
|
||||
function stripTemporalTransform(node) {
|
||||
if (!node?.explanation?.period) {
|
||||
return node
|
||||
}
|
||||
return stripTemporalTransform(node.explanation.value)
|
||||
}
|
||||
export default function parse(parse, k, v) {
|
||||
const rule = parse(v.règle)
|
||||
if (!v['valeurs cumulées']) {
|
||||
|
@ -23,16 +29,22 @@ export default function parse(parse, k, v) {
|
|||
)
|
||||
}
|
||||
|
||||
const variables = coerceArray(v['valeurs cumulées']).map(parse) as Array<{
|
||||
dottedName: DottedName
|
||||
category: string
|
||||
name: 'string'
|
||||
}>
|
||||
if (variables.some(({ category }) => category !== 'reference')) {
|
||||
throw new Error(
|
||||
'Le mécanisme régularisation attend des noms de règles sous la clé `valeurs cumulées`'
|
||||
)
|
||||
}
|
||||
const variables = coerceArray(v['valeurs cumulées']).map(variable => {
|
||||
if (typeof variable !== 'string') {
|
||||
throw new Error(
|
||||
`Les \`valeurs cumulées\` du mécanisme de régularisation doivent être des noms de règles existantes`
|
||||
)
|
||||
}
|
||||
|
||||
const value = parse(variable)
|
||||
const reference = stripTemporalTransform(value)
|
||||
if (reference.category !== 'reference') {
|
||||
throw new Error(
|
||||
'Le mécanisme régularisation attend des noms de règles existantes dans les `valeurs cumulées`'
|
||||
)
|
||||
}
|
||||
return { value, dottedName: reference.dottedName }
|
||||
}) as Array<{ dottedName: DottedName; value: Object }>
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
|
@ -94,17 +106,17 @@ function evaluate(
|
|||
|
||||
const currentYear = getYear(temporalEvaluation[0].start as string)
|
||||
const cumulatedVariables = node.explanation.variables.reduce(
|
||||
(acc, parsedVariable) => {
|
||||
const evaluation = evaluate(parsedVariable)
|
||||
(acc, { dottedName, value }) => {
|
||||
const evaluation = evaluate(value)
|
||||
if (!evaluation.unit.denominators.some(unit => unit === 'mois')) {
|
||||
evaluationError(
|
||||
cache._meta.contextRule,
|
||||
`Dans le mécanisme régularisation, la valeur cumulée '${parsedVariable.name}' n'est pas une variable numérique définie sur le mois`
|
||||
`Dans le mécanisme régularisation, la valeur cumulée '${dottedName}' n'est pas une variable numérique définie sur le mois`
|
||||
)
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[parsedVariable.dottedName]: getMonthlyCumulatedValuesOverYear(
|
||||
[dottedName]: getMonthlyCumulatedValuesOverYear(
|
||||
currentYear,
|
||||
evaluation.temporalValue ?? pureTemporal(evaluation.nodeValue),
|
||||
evaluation.unit
|
||||
|
@ -133,19 +145,18 @@ function evaluate(
|
|||
}
|
||||
|
||||
const evaluation = evaluate(node.explanation.rule)
|
||||
|
||||
console.log(evaluation)
|
||||
const temporalValue = evaluation.temporalValue
|
||||
const evaluationWithRegularisation = groupByYear(
|
||||
temporalValue as Temporal<Evaluation<number>>
|
||||
)
|
||||
.map(regulariseYear)
|
||||
.flat()
|
||||
|
||||
return {
|
||||
...node,
|
||||
temporalValue: evaluationWithRegularisation,
|
||||
explanation: evaluation,
|
||||
nodeValue: temporalAverage(temporalValue),
|
||||
nodeValue: temporalAverage(evaluationWithRegularisation, evaluation.unit),
|
||||
missingVariables: evaluation.missingVariables,
|
||||
unit: evaluation.unit
|
||||
}
|
||||
|
|
|
@ -27,10 +27,9 @@ function evaluate(
|
|||
evaluateAttribute(node.explanation.period.end)
|
||||
const value = evaluateAttribute(node.explanation.value)
|
||||
const period = {
|
||||
start: start?.nodeValue ?? null,
|
||||
end: end?.nodeValue ?? null
|
||||
start: start?.nodeValue || null,
|
||||
end: end?.nodeValue || null
|
||||
}
|
||||
|
||||
const temporalValue = value.temporalValue
|
||||
? narrowTemporalValue(period, value.temporalValue)
|
||||
: createTemporalEvaluation(value.nodeValue, period)
|
||||
|
|
|
@ -143,12 +143,15 @@ let simplify = (
|
|||
|
||||
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
|
||||
'jour/trimestre': (365 / 12) * 3,
|
||||
'€/k€': 10 ** 3,
|
||||
'g/kg': 10 ** 3,
|
||||
'mg/g': 10 ** 3,
|
||||
'mg/kg': 10 ** 6
|
||||
}
|
||||
function singleUnitConversionFactor(
|
||||
from: string,
|
||||
|
@ -225,7 +228,8 @@ export function convertUnit(
|
|||
|
||||
const convertibleUnitClasses = [
|
||||
['mois', 'an', 'jour', 'trimestre'],
|
||||
['€', 'k€']
|
||||
['€', 'k€'],
|
||||
['g', 'kg', 'mg']
|
||||
]
|
||||
function areSameClass(a: string, b: string) {
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
SMIC:
|
||||
titre.en: '[automatic] SMIC'
|
||||
titre.fr: SMIC
|
||||
SMIC horaire:
|
||||
titre.en: hourly minimum wage (SMIC)
|
||||
titre.fr: SMIC horaire
|
||||
|
@ -4711,6 +4714,9 @@ dirigeant . rémunération totale:
|
|||
résumé.fr: Dépensé par l'entreprise
|
||||
titre.en: Director total income
|
||||
titre.fr: rémunération totale
|
||||
effectif 20 salarié ou plus:
|
||||
titre.en: '[automatic] 20 or more employees'
|
||||
titre.fr: effectif 20 salarié ou plus
|
||||
entreprise:
|
||||
description.en: The contract binds a company and an employee
|
||||
description.fr: |
|
||||
|
@ -5623,6 +5629,9 @@ plafond sécurité sociale temps plein:
|
|||
prendre en compte pour le calcul de certaines cotisations.
|
||||
titre.en: full-time social security ceiling
|
||||
titre.fr: plafond sécurité sociale temps plein
|
||||
primes:
|
||||
titre.en: '[automatic] premiums'
|
||||
titre.fr: primes
|
||||
protection sociale:
|
||||
description.en: >
|
||||
Social protection in France is composed of 5 main branches: sickness,
|
||||
|
@ -6011,6 +6020,48 @@ revenus net de cotisations:
|
|||
résumé.fr: Avant impôt
|
||||
titre.en: net contribution income
|
||||
titre.fr: revenus net de cotisations
|
||||
réduction générale:
|
||||
titre.en: '[automatic] overall decrease'
|
||||
titre.fr: réduction générale
|
||||
réduction générale . chômage:
|
||||
titre.en: '[automatic] unemployment'
|
||||
titre.fr: chômage
|
||||
réduction générale . chômage . coefficient:
|
||||
titre.en: '[automatic] coefficient'
|
||||
titre.fr: coefficient
|
||||
réduction générale . chômage . constante:
|
||||
titre.en: '[automatic] constant'
|
||||
titre.fr: constante
|
||||
réduction générale . chômage . réduction sans régularisation:
|
||||
titre.en: '[automatic] unregulated reduction'
|
||||
titre.fr: réduction sans régularisation
|
||||
réduction générale . début:
|
||||
titre.en: '[automatic] start'
|
||||
titre.fr: début
|
||||
réduction générale . urssaf:
|
||||
titre.en: '[automatic] urssaf'
|
||||
titre.fr: urssaf
|
||||
réduction générale . urssaf . coefficient:
|
||||
titre.en: '[automatic] coefficient'
|
||||
titre.fr: coefficient
|
||||
réduction générale . urssaf . constante:
|
||||
titre.en: '[automatic] constant'
|
||||
titre.fr: constante
|
||||
réduction générale . urssaf . réduction sans régularisation:
|
||||
titre.en: '[automatic] unregulated reduction'
|
||||
titre.fr: réduction sans régularisation
|
||||
réduction lodeom:
|
||||
titre.en: '[automatic] lodeom reduction'
|
||||
titre.fr: réduction lodeom
|
||||
réduction lodeom . constante:
|
||||
titre.en: '[automatic] constant'
|
||||
titre.fr: constante
|
||||
réduction sur AC au 1er janvier:
|
||||
titre.en: '[automatic] reduction on AC on January 1st'
|
||||
titre.fr: réduction sur AC au 1er janvier
|
||||
rémunération:
|
||||
titre.en: '[automatic] remuneration'
|
||||
titre.fr: rémunération
|
||||
situation personnelle:
|
||||
titre.en: personal situation
|
||||
titre.fr: situation personnelle
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
SMIC: 1521.22 €/mois
|
||||
|
||||
primes:
|
||||
formule:
|
||||
somme:
|
||||
- 2470 €/mois | du 01/01/2019 | au 31/01/2019
|
||||
- 1470 €/mois | du 01/05/2019 | au 31/05/2019
|
||||
|
||||
rémunération:
|
||||
formule:
|
||||
somme:
|
||||
- 1530 €/mois | du 01/01/2019 | au 31/12/2019
|
||||
- primes
|
||||
|
||||
effectif 20 salarié ou plus:
|
||||
formule: oui
|
||||
|
||||
réduction sur AC au 1er janvier:
|
||||
formule: non
|
||||
|
||||
réduction générale . urssaf . constante:
|
||||
formule:
|
||||
variations:
|
||||
- si: effectif 20 salarié ou plus
|
||||
alors: 0.284
|
||||
- sinon: 0.2809
|
||||
|
||||
réduction générale . urssaf . coefficient:
|
||||
formule:
|
||||
arrondi:
|
||||
valeur: (constante / 0.60 ) * (1.60 * SMIC / rémunération - 1)
|
||||
décimales: 4
|
||||
|
||||
réduction générale . urssaf . réduction sans régularisation:
|
||||
formule:
|
||||
encadrement:
|
||||
plancher: 0
|
||||
valeur:
|
||||
arrondi:
|
||||
valeur:
|
||||
multiplication:
|
||||
assiette: rémunération
|
||||
taux: coefficient
|
||||
décimales: 2
|
||||
|
||||
réduction générale . urssaf:
|
||||
formule:
|
||||
régularisation:
|
||||
règle: réduction sans régularisation
|
||||
valeurs cumulées:
|
||||
- SMIC
|
||||
- rémunération
|
||||
|
||||
réduction générale . chômage . constante:
|
||||
formule: 0.0405
|
||||
|
||||
réduction générale . chômage . coefficient:
|
||||
formule:
|
||||
arrondi:
|
||||
valeur: (constante / 0.60 ) * (1.60 * SMIC / rémunération - 1)
|
||||
décimales: 4
|
||||
|
||||
réduction générale . chômage . réduction sans régularisation:
|
||||
formule:
|
||||
encadrement:
|
||||
plancher: 0
|
||||
valeur:
|
||||
arrondi:
|
||||
valeur:
|
||||
multiplication:
|
||||
assiette: rémunération
|
||||
taux: coefficient
|
||||
décimales: 2
|
||||
|
||||
réduction générale . début:
|
||||
non applicable si: réduction sur AC au 1er janvier
|
||||
formule: 01/10/2019
|
||||
|
||||
réduction générale . chômage:
|
||||
formule:
|
||||
régularisation:
|
||||
règle: réduction sans régularisation
|
||||
valeurs cumulées:
|
||||
- 'SMIC | à partir de : début'
|
||||
- 'rémunération | à partir de : début'
|
||||
|
||||
réduction générale:
|
||||
formule: urssaf + chômage
|
||||
|
||||
réduction lodeom . constante:
|
||||
formule: 0.3214
|
||||
|
||||
réduction lodeom:
|
||||
formule:
|
||||
régularisation:
|
||||
valeurs cumulées:
|
||||
- SMIC
|
||||
- rémunération
|
||||
règle:
|
||||
encadrement:
|
||||
plancher: 0
|
||||
valeur:
|
||||
arrondi:
|
||||
décimales: 2
|
||||
valeur:
|
||||
produit:
|
||||
assiette: rémunération
|
||||
facteur [ref coefficient]:
|
||||
arrondi:
|
||||
décimales: 4
|
||||
valeur:
|
||||
grille:
|
||||
assiette: rémunération
|
||||
multiplicateur: SMIC
|
||||
tranches:
|
||||
- montant: constante
|
||||
plafond: 1.7
|
||||
- montant: constante * 1.7 * SMIC / rémunération
|
||||
plafond: 2.5
|
||||
- montant: 1.7 * constante * (3.5 * SMIC / rémunération - 1)
|
|
@ -178,6 +178,7 @@ protection sociale . revenu moyen:
|
|||
|
||||
formule:
|
||||
le maximum de:
|
||||
- 0
|
||||
- dirigeant . indépendant . revenu professionnel
|
||||
- dirigeant . auto-entrepreneur . impôt . revenu abattu
|
||||
- contrat salarié . rémunération . brut
|
||||
|
|
|
@ -1,10 +1,34 @@
|
|||
import React, { Suspense } from 'react'
|
||||
import { Header } from './Header'
|
||||
let Studio = React.lazy(() => import('./Studio'))
|
||||
|
||||
export default function LazyStudio() {
|
||||
return (
|
||||
<Suspense fallback={<div>Chargement du code source...</div>}>
|
||||
<Studio />
|
||||
</Suspense>
|
||||
<div
|
||||
css={`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
`}
|
||||
>
|
||||
<div className="ui__ container">
|
||||
<Header noSubtitle sectionName="Studio" />
|
||||
</div>
|
||||
<Suspense
|
||||
fallback={
|
||||
<p
|
||||
className="ui__ lead"
|
||||
css={`
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
`}
|
||||
>
|
||||
Chargement du code source...
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<Studio />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,96 +1,114 @@
|
|||
import { ControlledEditor } from '@monaco-editor/react'
|
||||
// import { ControlledEditor } from '@monaco-editor/react'
|
||||
import { formatValue } from 'Engine/format'
|
||||
import Engine from 'Engine/react'
|
||||
import { safeLoad } from 'js-yaml'
|
||||
import { last } from 'ramda'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import MonacoEditor from 'react-monaco-editor'
|
||||
import { useLocation } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
import { Header } from './Header'
|
||||
const EXAMPLE_CODE = `
|
||||
# Bienvenu dans le bac à sable du langage publicode !
|
||||
# Pour en savoir plus sur le langage, consultez le tutoriel :
|
||||
# => https://publi.codes
|
||||
|
||||
let initialInput = `a:
|
||||
formule: 10€
|
||||
b:
|
||||
formule: a + 18€
|
||||
c:
|
||||
formule:
|
||||
produit:
|
||||
assiette: 2000€
|
||||
taux: 3%
|
||||
d:
|
||||
formule: a + b + c
|
||||
prix . carottes: 2€/kg
|
||||
prix . champignons: 5€/kg
|
||||
prix . avocat: 2€/avocat
|
||||
|
||||
dépenses primeur:
|
||||
formule:
|
||||
somme:
|
||||
- prix . carottes * 1.5 kg
|
||||
- prix . champignons * 500g
|
||||
- prix . avocat * 3 avocat
|
||||
`
|
||||
|
||||
function useDebounce(value, delay) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value)
|
||||
useEffect(
|
||||
() => {
|
||||
// Update debounced value after delay
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value)
|
||||
}, delay)
|
||||
|
||||
// Cancel the timeout if value changes (also on delay change or unmount)
|
||||
// This is how we prevent debounced value from updating if value is changed ...
|
||||
// .. within the delay period. Timeout gets cleared and restarted.
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
},
|
||||
[value, delay] // Only re-call effect if value or delay changes
|
||||
)
|
||||
return debouncedValue
|
||||
}
|
||||
export default function Studio() {
|
||||
const search = new URLSearchParams(useLocation().search ?? '')
|
||||
const code = search.get('code')
|
||||
const [editorValue, setEditorValue] = useState(code ? code : initialInput)
|
||||
const [targets, setTargets] = useState<string[]>([])
|
||||
const [rules, setRules] = useState(editorValue)
|
||||
const handleShare = useCallback(() => {
|
||||
navigator.clipboard.writeText(
|
||||
`https://publi.codes/studio?code=${encodeURIComponent(editorValue)}`
|
||||
)
|
||||
}, [editorValue])
|
||||
const search = useLocation().search
|
||||
const initialValue = useMemo(() => {
|
||||
const code = new URLSearchParams(search ?? '').get('code')
|
||||
return code ? code : EXAMPLE_CODE
|
||||
}, [search])
|
||||
const [editorValue, setEditorValue] = useState(initialValue)
|
||||
const debouncedEditorValue = useDebounce(editorValue, 1000)
|
||||
|
||||
const targets = useMemo(() => {
|
||||
try {
|
||||
return Object.keys(safeLoad(debouncedEditorValue) ?? {})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return []
|
||||
}
|
||||
}, [debouncedEditorValue])
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
setTargets(Object.keys(safeLoad(editorValue) ?? {}))
|
||||
} catch {}
|
||||
}, [editorValue])
|
||||
history.replaceState(
|
||||
null,
|
||||
'',
|
||||
`${window.location.pathname}?code=${encodeURIComponent(
|
||||
debouncedEditorValue
|
||||
)}`
|
||||
)
|
||||
}, [debouncedEditorValue])
|
||||
|
||||
const handleShare = useCallback(() => {
|
||||
navigator.clipboard.writeText(window.location.href)
|
||||
}, [window.location.href])
|
||||
|
||||
return (
|
||||
<Engine.Provider rules={rules}>
|
||||
<div
|
||||
<Layout>
|
||||
<MonacoEditor
|
||||
language="yaml"
|
||||
height="90vh"
|
||||
width="55%"
|
||||
defaultValue={editorValue}
|
||||
onChange={newValue => setEditorValue(newValue ?? '')}
|
||||
options={{
|
||||
minimap: { enabled: false }
|
||||
}}
|
||||
/>
|
||||
<section
|
||||
css={`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
padding: 0 1rem;
|
||||
flex: 1;
|
||||
`}
|
||||
>
|
||||
<div className="ui__ container">
|
||||
<Header noSubtitle sectionName="Studio" />
|
||||
</div>
|
||||
<Layout>
|
||||
<section>
|
||||
<ControlledEditor
|
||||
css={`
|
||||
height: 100%;
|
||||
`}
|
||||
language="yaml"
|
||||
value={editorValue}
|
||||
onChange={(_ev, newValue) => setEditorValue(newValue ?? '')}
|
||||
options={{ minimap: { enabled: false } }}
|
||||
/>
|
||||
</section>
|
||||
<section
|
||||
css={`
|
||||
padding: 30px 20px;
|
||||
`}
|
||||
>
|
||||
<Results
|
||||
targets={targets}
|
||||
onClickUpdate={() => setRules(editorValue)}
|
||||
onClickShare={handleShare}
|
||||
/>
|
||||
</section>
|
||||
</Layout>
|
||||
</div>
|
||||
</Engine.Provider>
|
||||
<Engine.Provider rules={debouncedEditorValue}>
|
||||
<Results targets={targets} onClickShare={handleShare} />
|
||||
</Engine.Provider>
|
||||
</section>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
type ResultsProps = {
|
||||
targets: string[]
|
||||
onClickUpdate: React.MouseEventHandler
|
||||
onClickShare: React.MouseEventHandler
|
||||
}
|
||||
|
||||
export const Results = ({
|
||||
targets,
|
||||
onClickUpdate,
|
||||
onClickShare
|
||||
}: ResultsProps) => {
|
||||
export const Results = ({ targets, onClickShare }: ResultsProps) => {
|
||||
const [rule, setCurrentTarget] = useState<string>()
|
||||
const currentTarget = rule ?? (last(targets) as string)
|
||||
const error = Engine.useError()
|
||||
|
@ -111,11 +129,6 @@ export const Results = ({
|
|||
`}
|
||||
>
|
||||
{nl2br(error)}
|
||||
<br />
|
||||
<br />
|
||||
<button className="ui__ button small" onClick={onClickUpdate}>
|
||||
{emoji('▶️')} Ré-essayer
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
@ -126,7 +139,9 @@ export const Results = ({
|
|||
border-radius: 5px;
|
||||
`}
|
||||
>
|
||||
<label htmlFor="objectif">Que voulez-vous calculer ? </label>
|
||||
<p className="ui__ notice">
|
||||
<label htmlFor="objectif">Que voulez-vous calculer ? </label>
|
||||
</p>
|
||||
<select
|
||||
id="objectif"
|
||||
onChange={e => {
|
||||
|
@ -149,9 +164,6 @@ export const Results = ({
|
|||
<br />
|
||||
<br />
|
||||
<div className="ui__ answer-group">
|
||||
<button className="ui__ plain button small" onClick={onClickUpdate}>
|
||||
{emoji('▶️')} Calculer
|
||||
</button>
|
||||
<button className="ui__ button small" onClick={onClickShare}>
|
||||
{emoji('🔗')} Copier le lien
|
||||
</button>
|
||||
|
@ -174,7 +186,25 @@ export const Results = ({
|
|||
{analysis.isApplicable === false ? (
|
||||
<>{emoji('❌')} Cette règle n'est pas applicable</>
|
||||
) : (
|
||||
<Engine.Evaluation expression={currentTarget} />
|
||||
<>
|
||||
<p>
|
||||
<strong>
|
||||
<Engine.Evaluation expression={currentTarget} />
|
||||
</strong>
|
||||
</p>
|
||||
<br />
|
||||
{analysis.temporalValue
|
||||
?.filter(({ value }) => value !== false)
|
||||
.map(({ start: du, end: au, value }) => (
|
||||
<span key={du}>
|
||||
<small>
|
||||
Du <em>{du}</em> au <em>{au}</em> :{' '}
|
||||
</small>
|
||||
<code>{formatValue({ value, unit: analysis.unit })}</code>{' '}
|
||||
<br />
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -202,10 +232,6 @@ const Layout = styled.div`
|
|||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
section {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-env node */
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const CopyPlugin = require('copy-webpack-plugin')
|
||||
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
|
||||
const { EnvironmentPlugin } = require('webpack')
|
||||
const path = require('path')
|
||||
|
||||
|
@ -31,6 +32,8 @@ module.exports.default = {
|
|||
globalObject: 'self'
|
||||
},
|
||||
plugins: [
|
||||
new MonacoWebpackPlugin(),
|
||||
|
||||
new EnvironmentPlugin({
|
||||
EN_SITE: '/infrance${path}',
|
||||
FR_SITE: '/mon-entreprise${path}',
|
||||
|
@ -138,6 +141,10 @@ module.exports.commonLoaders = ({ legacy = false } = {}) => {
|
|||
{
|
||||
test: /\.md$/,
|
||||
use: ['raw-loader']
|
||||
},
|
||||
{
|
||||
test: /\.ttf$/,
|
||||
use: ['file-loader']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ describe('conversation', function() {
|
|||
})
|
||||
})
|
||||
describe('real conversation', function() {
|
||||
it('should not have more than X questions', function() {
|
||||
it('should not have more than X questions', function(done) {
|
||||
let state = merge(baseState, {
|
||||
rules,
|
||||
simulation: {
|
||||
|
@ -163,5 +163,6 @@ describe('real conversation', function() {
|
|||
|
||||
expect(nextSteps.length).to.be.below(30) // If this breaks, that's good news
|
||||
expect(nextSteps.length).to.be.above(10)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -240,3 +240,18 @@ variable temporelle numérique . grille:
|
|||
formule: cotisation spéciale | du 01/01/2019 | au 31/12/2019
|
||||
exemples:
|
||||
- valeur attendue: 567.438
|
||||
|
||||
condition:
|
||||
date applicable:
|
||||
applicable si: condition
|
||||
formule: 10/01/2019
|
||||
|
||||
variable temporelle . date non applicable:
|
||||
formule: '(30 | à partir de : date applicable) | le 09/01/2019'
|
||||
exemples:
|
||||
- situation:
|
||||
condition: oui
|
||||
valeur attendue: false
|
||||
- situation:
|
||||
condition: non
|
||||
valeur attendue: 30
|
||||
|
|
|
@ -64,11 +64,11 @@ Contrats Madelin:
|
|||
dirigeant . indépendant . contrats madelin . retraite . montant: 300
|
||||
# Cas global madelin grand (plafonds calculés différemment)
|
||||
- dirigeant . rémunération totale: 300000
|
||||
entreprise . charges: 15000
|
||||
entreprise . charges: 15000
|
||||
dirigeant . indépendant . contrats madelin . mutuelle . montant: 1500
|
||||
dirigeant . indépendant . contrats madelin . retraite . montant: 5000
|
||||
# Cas charges plus faibles que total madelin
|
||||
- dirigeant . rémunération totale: 20000
|
||||
entreprise . charges: 500
|
||||
entreprise . charges: 500
|
||||
dirigeant . indépendant . contrats madelin . mutuelle . montant: 300
|
||||
dirigeant . indépendant . contrats madelin . retraite . montant: 300
|
||||
|
|
Loading…
Reference in New Issue