mirror of
https://github.com/betagouv/mon-entreprise
synced 2025-02-09 05:15:02 +00:00
Ce n'est pas très propre, mais difficile de faire autrement sans revoir l'architecture des filtres, du cache, des sélecteurs de la fiche de paie et du l'explicaiton des cotisations...
272 lines
6.9 KiB
JavaScript
272 lines
6.9 KiB
JavaScript
import React from 'react'
|
||
import { Parser } from 'nearley'
|
||
import Grammar from './grammar.ne'
|
||
import {
|
||
contains,
|
||
propEq,
|
||
curry,
|
||
cond,
|
||
equals,
|
||
divide,
|
||
multiply,
|
||
map,
|
||
intersection,
|
||
keys,
|
||
propOr,
|
||
always,
|
||
head,
|
||
gte,
|
||
lte,
|
||
lt,
|
||
gt,
|
||
add,
|
||
subtract
|
||
} from 'ramda'
|
||
import { evaluateNode, rewriteNode, makeJsx, mergeMissing } from './evaluation'
|
||
import { Node } from './mecanismViews/common'
|
||
import {
|
||
treatVariable,
|
||
treatNegatedVariable,
|
||
treatVariableTransforms
|
||
} from './treatVariable'
|
||
import { treat } from './traverse'
|
||
import knownMecanisms from './known-mecanisms.yaml'
|
||
import {
|
||
mecanismOneOf,
|
||
mecanismAllOf,
|
||
mecanismNumericalSwitch,
|
||
mecanismSum,
|
||
mecanismProduct,
|
||
mecanismScale,
|
||
mecanismLinearScale,
|
||
mecanismMax,
|
||
mecanismMin,
|
||
mecanismError,
|
||
mecanismComplement,
|
||
mecanismSelection,
|
||
mecanismInversion,
|
||
mecanismReduction,
|
||
mecanismVariations,
|
||
mecanismSynchronisation
|
||
} from './mecanisms'
|
||
|
||
let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart)
|
||
|
||
export let treatString = (rules, rule) => rawNode => {
|
||
/* On a affaire à un string, donc à une expression infixe.
|
||
Elle sera traité avec le parser obtenu grâce à NearleyJs et notre grammaire `grammar.ne`.
|
||
On obtient un objet de type Variable (avec potentiellement un 'modifier', par exemple temporel (TODO)), CalcExpression ou Comparison.
|
||
Cet objet est alors rebalancé à 'treat'.
|
||
*/
|
||
|
||
let [parseResult, ...additionnalResults] = nearley().feed(rawNode).results
|
||
|
||
if (
|
||
additionnalResults &&
|
||
additionnalResults.length > 0 &&
|
||
parseResult.category !== 'boolean'
|
||
) {
|
||
// booleans, 'oui' and 'non', have an exceptional resolving precedence
|
||
throw new Error(
|
||
"Attention ! L'expression <" +
|
||
rawNode +
|
||
'> ne peut être traitée de façon univoque'
|
||
)
|
||
}
|
||
|
||
if (
|
||
!contains(parseResult.category)([
|
||
'variable',
|
||
'calcExpression',
|
||
'filteredVariable',
|
||
'comparison',
|
||
'negatedVariable',
|
||
'percentage',
|
||
'boolean'
|
||
])
|
||
)
|
||
throw new Error(
|
||
"Attention ! Erreur de traitement de l'expression : " + rawNode
|
||
)
|
||
|
||
if (parseResult.category == 'variable')
|
||
return treatVariableTransforms(rules, rule)(parseResult)
|
||
if (parseResult.category == 'negatedVariable')
|
||
return treatNegatedVariable(
|
||
treatVariable(rules, rule)(parseResult.variable)
|
||
)
|
||
|
||
if (parseResult.category == 'boolean') {
|
||
return {
|
||
nodeValue: parseResult.nodeValue,
|
||
// eslint-disable-next-line
|
||
jsx: () => <span className="boolean">{rawNode}</span>
|
||
}
|
||
}
|
||
|
||
// We don't need to handle category == 'value' because YAML then returns it as
|
||
// numerical value, not a String: it goes to treatNumber
|
||
if (parseResult.category == 'percentage')
|
||
return {
|
||
nodeValue: parseResult.nodeValue,
|
||
category: 'percentage',
|
||
// eslint-disable-next-line
|
||
jsx: () => <span className="value">{rawNode.split('%')[0]} %</span>
|
||
//on ajoute l'espace nécessaire en français avant le pourcentage
|
||
}
|
||
|
||
if (
|
||
parseResult.category == 'calcExpression' ||
|
||
parseResult.category == 'comparison'
|
||
) {
|
||
let evaluate = (cache, situation, parsedRules, node) => {
|
||
let operatorFunction = {
|
||
'*': multiply,
|
||
'/': divide,
|
||
'+': add,
|
||
'-': subtract,
|
||
'<': lt,
|
||
'<=': lte,
|
||
'>': gt,
|
||
'>=': gte,
|
||
'=': equals,
|
||
'!=': (a, b) => !equals(a, b)
|
||
}[node.operator],
|
||
explanation = map(
|
||
curry(evaluateNode)(cache, situation, parsedRules),
|
||
node.explanation
|
||
),
|
||
value1 = explanation[0].nodeValue,
|
||
value2 = explanation[1].nodeValue,
|
||
nodeValue =
|
||
value1 == null || value2 == null
|
||
? null
|
||
: operatorFunction(value1, value2),
|
||
missingVariables = mergeMissing(
|
||
explanation[0].missingVariables,
|
||
explanation[1].missingVariables
|
||
)
|
||
|
||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||
}
|
||
|
||
let explanation = parseResult.explanation.map(
|
||
cond([
|
||
[
|
||
propEq('category', 'variable'),
|
||
treatVariableTransforms(rules, rule)
|
||
],
|
||
[
|
||
propEq('category', 'value'),
|
||
node => ({
|
||
nodeValue: node.nodeValue,
|
||
// eslint-disable-next-line
|
||
jsx: nodeValue => <span className="value">{nodeValue}</span>
|
||
})
|
||
],
|
||
[
|
||
propEq('category', 'percentage'),
|
||
node => ({
|
||
nodeValue: node.nodeValue,
|
||
// eslint-disable-next-line
|
||
jsx: nodeValue => (
|
||
<span className="value">{nodeValue * 100}%</span>
|
||
)
|
||
//the best would be to display the original text before parsing, but nearley does'nt let us access it
|
||
})
|
||
]
|
||
])
|
||
),
|
||
operator = parseResult.operator
|
||
let operatorToUnicode = operator =>
|
||
({
|
||
'>=': '≥',
|
||
'<=': '≤',
|
||
'!=': '≠',
|
||
'*': '∗',
|
||
'/': '∕',
|
||
'-': '−'
|
||
}[operator] || operator)
|
||
let jsx = (nodeValue, explanation) => (
|
||
<Node
|
||
classes={'inlineExpression ' + parseResult.category}
|
||
value={nodeValue}
|
||
child={
|
||
<span className="nodeContent">
|
||
<span className="fa fa" />
|
||
{makeJsx(explanation[0])}
|
||
<span className="operator">
|
||
{operatorToUnicode(parseResult.operator)}
|
||
</span>
|
||
{makeJsx(explanation[1])}
|
||
</span>
|
||
}
|
||
/>
|
||
)
|
||
|
||
return {
|
||
evaluate,
|
||
jsx,
|
||
operator,
|
||
text: rawNode,
|
||
category: parseResult.category,
|
||
type: parseResult.category == 'calcExpression' ? 'numeric' : 'boolean',
|
||
explanation
|
||
}
|
||
}
|
||
}
|
||
|
||
export let treatNumber = rawNode => ({
|
||
text: '' + rawNode,
|
||
category: 'number',
|
||
nodeValue: rawNode,
|
||
type: 'numeric',
|
||
jsx: <span className="number">{rawNode}</span>
|
||
})
|
||
|
||
export let treatOther = rawNode => {
|
||
throw new Error(
|
||
'Cette donnée : ' + rawNode + ' doit être un Number, String ou Object'
|
||
)
|
||
}
|
||
export let treatObject = (rules, rule, treatOptions) => rawNode => {
|
||
let mecanisms = intersection(keys(rawNode), keys(knownMecanisms))
|
||
|
||
if (mecanisms.length != 1) {
|
||
throw new Error(`OUPS : On ne devrait reconnaître que un et un seul mécanisme dans cet objet
|
||
Objet YAML : ${JSON.stringify(rawNode)}
|
||
Mécanismes implémentés correspondants : ${JSON.stringify(mecanisms)}
|
||
Cette liste doit avoir un et un seul élément.
|
||
Vérifier que le mécanisme est dans l'objet 'dispatch' et dans les'knownMecanisms.yaml'
|
||
`)
|
||
}
|
||
|
||
let k = head(mecanisms),
|
||
v = rawNode[k]
|
||
|
||
let dispatch = {
|
||
'une de ces conditions': mecanismOneOf,
|
||
'toutes ces conditions': mecanismAllOf,
|
||
'aiguillage numérique': mecanismNumericalSwitch,
|
||
somme: mecanismSum,
|
||
multiplication: mecanismProduct,
|
||
barème: mecanismScale,
|
||
'barème linéaire': mecanismLinearScale,
|
||
'le maximum de': mecanismMax,
|
||
'le minimum de': mecanismMin,
|
||
complément: mecanismComplement,
|
||
sélection: mecanismSelection,
|
||
'une possibilité': always({
|
||
...v,
|
||
'une possibilité': 'oui',
|
||
missingVariables: { [rule.dottedName]: 1 }
|
||
}),
|
||
inversion: mecanismInversion(rule.dottedName),
|
||
allègement: mecanismReduction,
|
||
variations: mecanismVariations,
|
||
synchronisation: mecanismSynchronisation
|
||
},
|
||
action = propOr(mecanismError, k, dispatch)
|
||
|
||
return action(treat(rules, rule, treatOptions), k, v)
|
||
}
|