2019-06-19 09:54:47 +00:00
// Reference to a variable
2019-11-04 10:03:01 +00:00
import parseRule from 'Engine/parseRule' ;
import React from 'react' ;
import { evaluateApplicability } from './evaluateRule' ;
import { evaluateNode } from './evaluation' ;
import { getSituationValue } from './getSituationValue' ;
import { Leaf } from './mecanismViews/common' ;
import { disambiguateRuleReference , findParentDependency , findRuleByDottedName } from './rules' ;
2019-06-13 16:17:22 +00:00
2019-11-04 10:03:01 +00:00
const ruleHasConditions = ( rule , rules ) =>
rule [ 'applicable si' ] != null ||
rule [ 'non applicable si' ] != null ||
rule . isDisabledBy ? . length > 1 ||
findParentDependency ( rules , rule )
2019-06-13 16:17:22 +00:00
2019-10-30 15:52:51 +00:00
let evaluateReference = ( filter ) => ( cache , situation , rules , node ) => {
let rule = rules [ node . dottedName ]
2019-07-19 17:51:36 +00:00
2019-11-04 10:03:01 +00:00
2019-10-30 15:52:51 +00:00
// 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 applicableReplacements = rule . replacedBy
2019-11-04 10:03:01 +00:00
. filter ( ( { referenceNode } ) => {
const isApplicable =
! ruleHasConditions ( rules [ referenceNode . dottedName ] , rules ) ||
evaluateApplicability ( cache , situation , rules , rules [ referenceNode . dottedName ] ) . nodeValue === true
return isApplicable
}
)
. map ( ( { referenceNode , replacementNode } ) =>
replacementNode != null ? evaluateNode ( cache , situation , rules , replacementNode ) : evaluateReference ( filter ) ( cache , situation , rules , referenceNode )
)
2019-06-19 09:54:47 +00:00
2019-10-30 15:52:51 +00:00
if ( applicableReplacements . length ) {
2019-11-04 10:03:01 +00:00
return applicableReplacements [ 0 ]
2019-10-30 15:52:51 +00:00
}
2019-11-04 10:03:01 +00:00
2019-10-30 15:52:51 +00:00
let dottedName = node . dottedName ,
// On va vérifier dans le cache courant, dict, si la variable n'a pas été déjà évaluée
// En effet, l'évaluation dans le cas d'une variable qui a une formule, est coûteuse !
cacheName = dottedName + ( filter ? '.' + filter : '' ) ,
cached = cache [ cacheName ]
if ( cached ) return cached
2019-11-04 10:03:01 +00:00
2019-10-30 15:52:51 +00:00
let variableHasFormula = rule . formule != null ,
2019-11-04 10:03:01 +00:00
variableHasCond = ruleHasConditions ( rule , rules ) ,
2019-10-30 15:52:51 +00:00
situationValue = getSituationValue ( situation , dottedName , rule ) ,
needsEvaluation =
situationValue == null && ( variableHasCond || variableHasFormula )
let explanation = needsEvaluation
? evaluateNode ( cache , situation , rules , rule )
: rule
let cacheAndNode = ( nodeValue , missingVariables , customExplanation ) => {
cache [ cacheName ] = {
... node ,
nodeValue ,
explanation : customExplanation || explanation ,
missingVariables
2019-07-03 16:49:31 +00:00
}
2019-10-30 15:52:51 +00:00
return cache [ cacheName ]
}
const variableScore = rule . defaultValue ? 1 : 2
// SITUATION 1 : La variable est directement renseignée
if ( situationValue != null ) {
return cacheAndNode (
situationValue ,
{ } ,
{ ... explanation , nodeValue : situationValue }
)
}
2018-06-19 09:06:30 +00:00
2019-10-30 15:52:51 +00:00
// SITUATION 2 : La variable est calculée
if ( situationValue == null && variableHasFormula )
return cacheAndNode ( explanation . nodeValue , explanation . missingVariables )
2018-06-19 09:06:30 +00:00
2019-10-30 15:52:51 +00:00
// SITUATION 3 : La variable est une question sans condition dont la valeur n'a pas été renseignée
if ( situationValue == null && ! variableHasFormula && ! variableHasCond )
return cacheAndNode ( null , { [ dottedName ] : variableScore } )
2018-06-19 09:06:30 +00:00
2019-10-30 15:52:51 +00:00
// SITUATION 4 : La variable est une question avec conditions
if ( situationValue == null && ! variableHasFormula && variableHasCond ) {
// SITUATION 4.1 : La condition est connue et vrai
if ( explanation . isApplicable )
return rule . question
? cacheAndNode ( null , { [ dottedName ] : variableScore } )
: cacheAndNode ( true , { } )
2018-06-19 09:06:30 +00:00
2019-10-30 15:52:51 +00:00
// SITUATION 4.2 : La condition est connue et fausse
if ( explanation . isApplicable === false ) return cacheAndNode ( false , { } )
2019-06-20 13:53:30 +00:00
2019-10-30 15:52:51 +00:00
// SITUATION 4.3 : La condition n'est pas connue
return cacheAndNode ( null , explanation . missingVariables )
2018-06-19 08:25:53 +00:00
}
2019-10-30 15:52:51 +00:00
}
export let parseReference = ( rules , rule , parsedRules , filter ) => (
partialReference
) => {
let dottedName = disambiguateRuleReference ( rules , rule , partialReference )
let inInversionFormula = rule . formule ? . [ 'inversion numérique' ]
let parsedRule =
parsedRules [ dottedName ] ||
// 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 ) )
2018-06-19 08:25:53 +00:00
return {
2019-10-30 15:52:51 +00:00
evaluate : evaluateReference ( filter , parsedRule ) ,
2018-06-19 09:06:30 +00:00
//eslint-disable-next-line react/display-name
2018-06-19 08:25:53 +00:00
jsx : nodeValue => (
2019-07-20 16:30:42 +00:00
< >
< Leaf
classes = "variable filtered"
filter = { filter }
2019-10-30 15:52:51 +00:00
name = { partialReference }
2019-07-20 16:30:42 +00:00
dottedName = { dottedName }
nodeValue = { nodeValue }
unit = { parsedRule . unit }
/ >
< / >
2018-06-19 08:25:53 +00:00
) ,
2019-06-13 16:17:22 +00:00
name : partialReference ,
2019-07-01 15:59:57 +00:00
category : 'reference' ,
2019-10-30 15:52:51 +00:00
partialReference ,
2019-06-19 09:54:47 +00:00
dottedName ,
unit : parsedRule . unit
2018-06-19 08:25:53 +00:00
}
}
2018-10-01 15:41:55 +00:00
// 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
// - filters on the variable to select one part of the variable's 'composantes'
2019-10-30 15:52:51 +00:00
const evaluateTransforms = ( originalEval , rule , parseResult ) => (
cache ,
situation ,
parsedRules ,
node
) => {
// Filter transformation
let filteringSituation = name =>
name == 'sys.filter' ? parseResult . filter : situation ( name )
let filteredNode = originalEval (
2018-06-19 08:25:53 +00:00
cache ,
2019-10-30 15:52:51 +00:00
parseResult . filter ? filteringSituation : situation ,
2018-06-19 08:25:53 +00:00
parsedRules ,
node
2019-10-30 15:52:51 +00:00
)
if ( ! filteredNode . explanation ) {
return filteredNode
}
2018-10-01 15:41:55 +00:00
2019-10-30 15:52:51 +00:00
let nodeValue = filteredNode . nodeValue
2018-11-13 15:14:57 +00:00
2019-10-30 15:52:51 +00:00
// Temporal transformation
let supportedPeriods = [ 'mois' , 'année' , 'flexible' ]
if ( nodeValue == null ) return filteredNode
let ruleToTransform = findRuleByDottedName (
parsedRules ,
filteredNode . explanation . dottedName
)
2018-11-13 15:14:57 +00:00
2019-10-30 15:52:51 +00:00
let inlinePeriodTransform = { mensuel : 'mois' , annuel : 'année' } [
parseResult . temporalTransform
]
2019-03-04 14:33:51 +00:00
2019-10-30 15:52:51 +00:00
// Exceptions
if ( ! rule . période && ! inlinePeriodTransform ) {
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 !
2018-11-07 16:55:42 +00:00
2019-10-30 15:52:51 +00:00
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 .
`
)
2019-02-14 17:00:52 +00:00
2019-10-30 15:52:51 +00:00
return filteredNode
2018-06-19 08:25:53 +00:00
}
2019-10-30 15:52:51 +00:00
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
}
export let parseReferenceTransforms = (
rules ,
rule ,
parsedRules
) => parseResult => {
const referenceName = parseResult . variable . fragments . join ( ' . ' )
2019-06-13 16:17:22 +00:00
let node = parseReference ( rules , rule , parsedRules , parseResult . filter ) (
2019-10-30 15:52:51 +00:00
referenceName
2018-10-01 15:41:55 +00:00
)
2018-06-19 08:25:53 +00:00
return {
... node ,
2018-10-01 15:41:55 +00:00
// Decorate node with the composante filter (either who is paying, either tax free)
... ( parseResult . filter
? {
2019-10-30 15:52:51 +00:00
cotisation : {
... node . cotisation ,
'dû par' : parseResult . filter ,
'impôt sur le revenu' : parseResult . filter
}
}
2018-10-01 15:41:55 +00:00
: { } ) ,
2019-10-30 15:52:51 +00:00
evaluate : evaluateTransforms ( node . evaluate , rule , parseResult )
2018-06-19 08:25:53 +00:00
}
}