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égularisation
pull/973/head
Johan Girod 2020-04-09 17:12:36 +02:00
parent 07564964c5
commit 78e97f0f37
18 changed files with 1263 additions and 773 deletions

View File

@ -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",

View File

@ -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,

View File

@ -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 lanné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: >-

View File

@ -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 }
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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 (

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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>
)
}

View File

@ -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;

View File

@ -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']
}
]
}

View File

@ -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()
})
})

View File

@ -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

View File

@ -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

1333
yarn.lock

File diff suppressed because it is too large Load Diff