mirror of
https://github.com/betagouv/mon-entreprise
synced 2025-02-09 04:05:01 +00:00
Grâce aux récentes évolutions de Publicode, ce retour utilisateur fréquent trouve une solution particulièrement simple et élégante en remplaçant la valeur littérale du SMIC par une référence vers le SMIC contractuel. Cela procure les avantages suivants : - Le montant du SMIC est défini une seule fois dans la base de règles, et on y fait référence partout où on l'utilise - Vu que c'est la référence qui est stockée dans la "situation" on peut répondre à la question "temps partiel" après avoir cliqué sur "SMIC" dans la case "salaire brut" et le montant correspondant va automatiquement se recalculer. A nécessité quelques menues modifications du code qui gère les références dans la situation pour corriger des bugs. Fixes #649
158 lines
3.9 KiB
TypeScript
158 lines
3.9 KiB
TypeScript
/* eslint-disable @typescript-eslint/ban-types */
|
|
import { mapObjIndexed } from 'ramda'
|
|
import { evaluationError, warning } from './error'
|
|
import { evaluateNode } from './evaluation'
|
|
import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits'
|
|
import { parse } from './parse'
|
|
import parseRules from './parseRules'
|
|
import { EvaluatedNode, EvaluatedRule, ParsedRules, Rules } from './types'
|
|
import { parseUnit } from './units'
|
|
import * as utils from './ruleUtils'
|
|
|
|
const emptyCache = () => ({
|
|
_meta: { contextRule: [] }
|
|
})
|
|
|
|
type Cache = {
|
|
_meta: {
|
|
contextRule: Array<string>
|
|
inversionFail?: {
|
|
given: string
|
|
estimated: string
|
|
}
|
|
}
|
|
}
|
|
|
|
type EvaluatedSituation<Names extends string> = Partial<
|
|
Record<Names, EvaluatedNode<Names>>
|
|
>
|
|
|
|
export type EvaluationOptions = Partial<{
|
|
unit: string
|
|
}>
|
|
|
|
export * from './components'
|
|
export { formatValue, serializeValue } from './format'
|
|
export { default as translateRules } from './translateRules'
|
|
export { default as cyclesLib } from './cyclesLib/index'
|
|
export * from './types'
|
|
export { parseRules }
|
|
export { utils }
|
|
|
|
export default class Engine<Names extends string> {
|
|
parsedRules: ParsedRules<Names>
|
|
situation: EvaluatedSituation<Names> = {}
|
|
private cache: Cache
|
|
private warnings: Array<string> = []
|
|
|
|
constructor(rules: string | Rules<Names> | ParsedRules<Names>) {
|
|
this.cache = emptyCache()
|
|
this.parsedRules =
|
|
typeof rules === 'string' || !(Object.values(rules)[0] as any)?.dottedName
|
|
? parseRules(rules)
|
|
: (rules as ParsedRules<Names>)
|
|
}
|
|
|
|
private resetCache() {
|
|
this.cache = emptyCache()
|
|
}
|
|
|
|
private evaluateExpression(
|
|
expression: string,
|
|
context: string
|
|
): EvaluatedRule<Names> {
|
|
// EN ATTENDANT d'AVOIR une meilleure gestion d'erreur, on va mocker
|
|
// console.warn
|
|
const originalWarn = console.warn
|
|
console.warn = (warning: string) => {
|
|
this.warnings.push(warning)
|
|
originalWarn(warning)
|
|
}
|
|
const result = simplifyNodeUnit(
|
|
evaluateNode(
|
|
this.cache,
|
|
this.situation,
|
|
this.parsedRules,
|
|
parse(
|
|
this.parsedRules,
|
|
{ dottedName: context },
|
|
this.parsedRules
|
|
)(expression)
|
|
)
|
|
)
|
|
console.warn = originalWarn
|
|
|
|
if (Object.keys(result.defaultValue?.missingVariable ?? {}).length) {
|
|
throw evaluationError(
|
|
context,
|
|
"Impossible d'évaluer l'expression car celle ci fait appel à des variables manquantes"
|
|
)
|
|
}
|
|
return result
|
|
}
|
|
|
|
setSituation(
|
|
situation: Partial<Record<Names, string | number | object>> = {}
|
|
) {
|
|
this.resetCache()
|
|
this.situation = mapObjIndexed(
|
|
(value, name) =>
|
|
typeof value === 'string'
|
|
? this.evaluateExpression(value, `[situation] ${name}`)
|
|
: value,
|
|
situation
|
|
) as EvaluatedSituation<Names>
|
|
this.resetCache()
|
|
return this
|
|
}
|
|
|
|
evaluate(expression: Names, options?: EvaluationOptions): EvaluatedRule<Names>
|
|
evaluate(
|
|
expression: string,
|
|
options?: EvaluationOptions
|
|
): EvaluatedNode<Names> | EvaluatedRule<Names>
|
|
evaluate(expression: string, options?: EvaluationOptions) {
|
|
let result = this.evaluateExpression(
|
|
expression,
|
|
`[evaluation] ${expression}`
|
|
)
|
|
if (result.category === 'reference' && result.explanation) {
|
|
result = {
|
|
...result.explanation,
|
|
nodeValue: result.nodeValue,
|
|
missingVariables: result.missingVariables,
|
|
...('unit' in result && { unit: result.unit }),
|
|
...('temporalValue' in result && {
|
|
temporalValue: result.temporalValue
|
|
}),
|
|
dottedName: result.dottedName
|
|
} as EvaluatedRule<Names>
|
|
}
|
|
if (options?.unit) {
|
|
try {
|
|
return convertNodeToUnit(
|
|
parseUnit(options.unit),
|
|
result as EvaluatedNode<Names, number>
|
|
)
|
|
} catch (e) {
|
|
warning(
|
|
`[evaluation] ${expression}`,
|
|
"L'unité demandée est incompatible avec l'expression évaluée"
|
|
)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
getWarnings() {
|
|
return this.warnings
|
|
}
|
|
|
|
inversionFail(): boolean {
|
|
return !!this.cache._meta.inversionFail
|
|
}
|
|
|
|
getParsedRules(): ParsedRules<Names> {
|
|
return this.parsedRules
|
|
}
|
|
}
|