🔥 Mise à jour du site mon-entreprise suite aux refacto de evaluateRule
parent
ba01ae2d4f
commit
ab02bbb5f5
|
@ -71,7 +71,9 @@ export const validateStepWithValue = (
|
|||
dottedName: DottedName,
|
||||
value: unknown
|
||||
): ThunkResult<void> => dispatch => {
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
if (value !== undefined) {
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
}
|
||||
dispatch({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import { max } from 'ramda'
|
||||
import { useContext } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
@ -14,11 +14,11 @@ export default function Distribution() {
|
|||
const targetUnit = useSelector(targetUnitSelector)
|
||||
const engine = useContext(EngineContext)
|
||||
const distribution = (getCotisationsBySection(
|
||||
useContext(EngineContext).getParsedRules()
|
||||
useEngine().getParsedRules()
|
||||
).map(([section, cotisations]) => [
|
||||
section,
|
||||
cotisations
|
||||
.map(c => engine.evaluate(c, { unit: targetUnit }))
|
||||
.map(c => engine.evaluate({ valeur: c, unité: targetUnit }))
|
||||
.reduce(
|
||||
(acc, evaluation) => acc + ((evaluation?.nodeValue as number) || 0),
|
||||
0
|
||||
|
@ -65,8 +65,8 @@ export function DistributionBranch({
|
|||
value={value}
|
||||
maximum={maximum}
|
||||
title={<RuleLink dottedName={dottedName} />}
|
||||
icon={icon ?? branche.icons}
|
||||
description={branche.summary}
|
||||
icon={icon ?? branche.rawNode.icônes}
|
||||
description={branche.rawNode.résumé}
|
||||
unit="€"
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -4,42 +4,44 @@ import { useTranslation } from 'react-i18next'
|
|||
import { DottedName } from 'Rules'
|
||||
import { coerceArray } from '../utils'
|
||||
import RuleLink from './RuleLink'
|
||||
import { EngineContext } from './utils/EngineContext'
|
||||
import { EngineContext, useEngine } from './utils/EngineContext'
|
||||
|
||||
export type ValueProps = {
|
||||
export type ValueProps<Names extends string> = {
|
||||
expression: string
|
||||
unit?: string
|
||||
engine?: Engine<Names>
|
||||
displayedUnit?: string
|
||||
precision?: number
|
||||
engine?: Engine<DottedName>
|
||||
linkToRule?: boolean
|
||||
} & React.HTMLProps<HTMLSpanElement>
|
||||
|
||||
export default function Value({
|
||||
export default function Value<Names extends string>({
|
||||
expression,
|
||||
unit,
|
||||
engine,
|
||||
displayedUnit,
|
||||
precision,
|
||||
engine,
|
||||
linkToRule = true,
|
||||
...props
|
||||
}: ValueProps) {
|
||||
}: ValueProps<Names>) {
|
||||
const { language } = useTranslation().i18n
|
||||
if (expression === null) {
|
||||
throw new TypeError('expression cannot be null')
|
||||
}
|
||||
const evaluation = (engine ?? useContext(EngineContext)).evaluate(
|
||||
expression,
|
||||
{ unit }
|
||||
)
|
||||
const e = engine ?? useEngine()
|
||||
const isRule = expression in e.getParsedRules()
|
||||
const evaluation = e.evaluate({
|
||||
valeur: expression,
|
||||
...(unit && { unité: unit })
|
||||
})
|
||||
const value = formatValue(evaluation, {
|
||||
displayedUnit,
|
||||
language,
|
||||
precision
|
||||
})
|
||||
if ('dottedName' in evaluation && linkToRule) {
|
||||
if (isRule && linkToRule) {
|
||||
return (
|
||||
<RuleLink dottedName={evaluation.dottedName}>
|
||||
<RuleLink dottedName={expression as DottedName}>
|
||||
<span {...props}>{value}</span>
|
||||
</RuleLink>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { hideNotification } from 'Actions/actions'
|
||||
import animate from 'Components/ui/animate'
|
||||
import { useInversionFail, EngineContext } from 'Components/utils/EngineContext'
|
||||
import {
|
||||
useInversionFail,
|
||||
EngineContext,
|
||||
useEngine
|
||||
} from 'Components/utils/EngineContext'
|
||||
import { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -9,7 +13,7 @@ import { RootState } from 'Reducers/rootReducer'
|
|||
import './Notifications.css'
|
||||
import { Markdown } from './utils/markdown'
|
||||
import { ScrollToElement } from './utils/Scroll'
|
||||
import { EvaluatedRule } from 'publicodes'
|
||||
import Engine, { EvaluatedRule, ASTNode } from 'publicodes'
|
||||
|
||||
// To add a new notification to a simulator, you should create a publicode rule
|
||||
// with the "type: notification" attribute. The display can be customized with
|
||||
|
@ -20,17 +24,20 @@ type Notification = Pick<EvaluatedRule, 'dottedName' | 'description'> & {
|
|||
sévérité: 'avertissement' | 'information'
|
||||
}
|
||||
|
||||
export function getNotifications(engine: Engine) {
|
||||
return Object.values(engine.getParsedRules())
|
||||
.filter(
|
||||
(rule: ASTNode & { nodeKind: 'rule' }) =>
|
||||
rule.rawNode['type'] === 'notification'
|
||||
)
|
||||
.map(node => engine.evaluateNode(node))
|
||||
.filter(node => !!node.nodeValue)
|
||||
.map(node => node.rawNode)
|
||||
}
|
||||
export default function Notifications() {
|
||||
const { t } = useTranslation()
|
||||
const engine = useContext(EngineContext)
|
||||
const notifications = Object.values(engine.getParsedRules())
|
||||
.filter(rule => rule['type'] === 'notification')
|
||||
.filter(
|
||||
notification =>
|
||||
![null, false].includes(
|
||||
engine.evaluate(notification.dottedName).isApplicable
|
||||
)
|
||||
)
|
||||
const engine = useEngine()
|
||||
|
||||
const inversionFail = useInversionFail()
|
||||
const hiddenNotifications = useSelector(
|
||||
(state: RootState) => state.simulation?.hiddenNotifications
|
||||
|
@ -49,7 +56,7 @@ export default function Notifications() {
|
|||
sévérité: 'avertissement'
|
||||
}
|
||||
]
|
||||
: ((notifications as any) as Array<Notification>)
|
||||
: ((getNotifications(engine) as any) as Array<Notification>)
|
||||
if (!messages?.length) return null
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import Value from 'Components/EngineValue'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import { EngineContext, useEvaluation } from 'Components/utils/EngineContext'
|
||||
import { formatValue, ParsedRule, ParsedRules } from 'publicodes'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import {
|
||||
ASTNode,
|
||||
EvaluatedNode,
|
||||
formatValue,
|
||||
ParsedRules,
|
||||
reduceAST
|
||||
} from 'publicodes'
|
||||
import { Fragment, useContext } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { DottedName } from 'Rules'
|
||||
|
@ -21,9 +27,9 @@ export const SECTION_ORDER = [
|
|||
|
||||
type Section = typeof SECTION_ORDER[number]
|
||||
|
||||
function getSection(rule: ParsedRule): Section {
|
||||
function getSection(rule: ASTNode & { nodeKind: 'rule' }): Section {
|
||||
const section = ('protection sociale . ' +
|
||||
rule.cotisation?.branche) as Section
|
||||
rule.rawNode.cotisation?.branche) as Section
|
||||
if (SECTION_ORDER.includes(section)) {
|
||||
return section
|
||||
}
|
||||
|
@ -31,24 +37,43 @@ function getSection(rule: ParsedRule): Section {
|
|||
}
|
||||
|
||||
export function getCotisationsBySection(
|
||||
parsedRules: ParsedRules
|
||||
parsedRules: ParsedRules<DottedName>
|
||||
): Array<[Section, DottedName[]]> {
|
||||
const cotisations = [
|
||||
...parsedRules['contrat salarié . cotisations . patronales'].formule
|
||||
.explanation.explanation,
|
||||
...parsedRules['contrat salarié . cotisations . salariales'].formule
|
||||
.explanation.explanation
|
||||
]
|
||||
function findCotisations(dottedName: DottedName) {
|
||||
return reduceAST<Array<ASTNode & { nodeKind: 'reference' }>>(
|
||||
(acc, node) => {
|
||||
if (
|
||||
node.nodeKind === 'reference' &&
|
||||
node.dottedName !== 'contrat salarié . cotisations' &&
|
||||
node.dottedName?.startsWith('contrat salarié . ') &&
|
||||
node.dottedName !==
|
||||
'contrat salarié . cotisations . patronales . réductions de cotisations'
|
||||
) {
|
||||
return [...acc, node]
|
||||
}
|
||||
},
|
||||
[],
|
||||
parsedRules[dottedName]
|
||||
)
|
||||
}
|
||||
|
||||
const cotisations = ([
|
||||
...findCotisations('contrat salarié . cotisations . patronales'),
|
||||
...findCotisations('contrat salarié . cotisations . salariales')
|
||||
] as Array<ASTNode & { dottedName: DottedName } & { nodeKind: 'reference' }>)
|
||||
.map(cotisation => cotisation.dottedName)
|
||||
.filter(Boolean)
|
||||
.map(
|
||||
dottedName =>
|
||||
dottedName.replace(/ . (salarié|employeur)$/, '') as DottedName
|
||||
)
|
||||
.reduce((acc, cotisation: DottedName) => {
|
||||
const sectionName = getSection(parsedRules[cotisation])
|
||||
return {
|
||||
...acc,
|
||||
[sectionName]: (acc[sectionName] ?? new Set()).add(cotisation)
|
||||
}
|
||||
}, {}) as Record<Section, Set<DottedName>>
|
||||
|
||||
}, {} as Record<Section, Set<DottedName>>)
|
||||
return Object.entries(cotisations)
|
||||
.map(([section, dottedNames]) => [section, [...dottedNames.values()]])
|
||||
.sort(
|
||||
|
@ -59,7 +84,7 @@ export function getCotisationsBySection(
|
|||
}
|
||||
|
||||
export default function PaySlip() {
|
||||
const parsedRules = useContext(EngineContext).getParsedRules()
|
||||
const parsedRules = useEngine().getParsedRules()
|
||||
const cotisationsBySection = getCotisationsBySection(parsedRules)
|
||||
|
||||
return (
|
||||
|
@ -122,10 +147,12 @@ export default function PaySlip() {
|
|||
</div>
|
||||
<Value
|
||||
expression="- contrat salarié . cotisations . patronales . réductions de cotisations"
|
||||
unit="€/mois"
|
||||
displayedUnit="€"
|
||||
/>
|
||||
<Value
|
||||
expression="- contrat salarié . cotisations . salariales . réductions de cotisations"
|
||||
unit="€/mois"
|
||||
displayedUnit="€"
|
||||
/>
|
||||
{/* Total cotisation */}
|
||||
|
@ -152,18 +179,34 @@ export default function PaySlip() {
|
|||
)
|
||||
}
|
||||
|
||||
function findReferenceInNode(dottedName: DottedName, node: EvaluatedNode) {
|
||||
return reduceAST<(EvaluatedNode & { nodeKind: 'reference' }) | null>(
|
||||
(acc, node) => {
|
||||
if (
|
||||
node.nodeKind === 'reference' &&
|
||||
node.dottedName?.startsWith(dottedName)
|
||||
) {
|
||||
return node as EvaluatedNode & { nodeKind: 'reference' }
|
||||
} else if (node.nodeKind === 'reference') {
|
||||
return acc
|
||||
}
|
||||
},
|
||||
null,
|
||||
node
|
||||
)
|
||||
}
|
||||
function Cotisation({ dottedName }: { dottedName: DottedName }) {
|
||||
const language = useTranslation().i18n.language
|
||||
const partSalariale = useEvaluation(
|
||||
'contrat salarié . cotisations . salariales'
|
||||
)?.formule.explanation.explanation.find(
|
||||
(cotisation: ParsedRule) => cotisation.dottedName === dottedName
|
||||
const engine = useContext(EngineContext)
|
||||
const cotisationsSalariales = engine.evaluateNode(
|
||||
engine.getParsedRules()['contrat salarié . cotisations . salariales']
|
||||
)
|
||||
const partPatronale = useEvaluation(
|
||||
'contrat salarié . cotisations . patronales'
|
||||
)?.formule.explanation.explanation.find(
|
||||
(cotisation: ParsedRule) => cotisation.dottedName === dottedName
|
||||
const cotisationsPatronales = engine.evaluateNode(
|
||||
engine.getParsedRules()['contrat salarié . cotisations . patronales']
|
||||
)
|
||||
const partSalariale = findReferenceInNode(dottedName, cotisationsSalariales)
|
||||
const partPatronale = findReferenceInNode(dottedName, cotisationsPatronales)
|
||||
|
||||
if (!partPatronale?.nodeValue && !partSalariale?.nodeValue) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ export const SalaireNetSection = () => {
|
|||
type LineProps = {
|
||||
rule: DottedName
|
||||
negative?: boolean
|
||||
} & Omit<ValueProps, 'expression'>
|
||||
} & Omit<ValueProps<DottedName>, 'expression'>
|
||||
|
||||
export function Line({
|
||||
rule,
|
||||
|
|
|
@ -24,7 +24,8 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||
import { situationSelector } from 'Selectors/simulationSelectors'
|
||||
import InfoBulle from 'Components/ui/InfoBulle'
|
||||
import './SchemeComparaison.css'
|
||||
import { EngineContext, useEvaluation } from './utils/EngineContext'
|
||||
import { EngineContext, useEngine } from './utils/EngineContext'
|
||||
import { DottedName } from 'Rules'
|
||||
|
||||
type SchemeComparaisonProps = {
|
||||
hideAutoEntrepreneur?: boolean
|
||||
|
@ -39,9 +40,11 @@ export default function SchemeComparaison({
|
|||
useEffect(() => {
|
||||
dispatch(setSimulationConfig(dirigeantComparaison))
|
||||
}, [])
|
||||
const plafondAutoEntrepreneurDépassé = useEvaluation(
|
||||
'dirigeant . auto-entrepreneur . contrôle seuil de CA dépassé'
|
||||
).isApplicable
|
||||
const engine = useEngine()
|
||||
const plafondAutoEntrepreneurDépassé =
|
||||
engine.evaluate(
|
||||
'dirigeant . auto-entrepreneur . contrôle seuil de CA dépassé'
|
||||
).nodeValue === true
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
const [conversationStarted, setConversationStarted] = useState(
|
||||
|
@ -51,13 +54,13 @@ export default function SchemeComparaison({
|
|||
setConversationStarted
|
||||
])
|
||||
|
||||
const parsedRules = useContext(EngineContext).getParsedRules()
|
||||
const parsedRules = engine.getParsedRules()
|
||||
const situation = useSelector(situationSelector)
|
||||
const displayResult =
|
||||
useSelector(situationSelector)['entreprise . charges'] != undefined
|
||||
const assimiléEngine = useMemo(
|
||||
() =>
|
||||
new Engine(parsedRules).setSituation({
|
||||
new Engine<DottedName>(parsedRules).setSituation({
|
||||
...situation,
|
||||
dirigeant: "'assimilé salarié'"
|
||||
}),
|
||||
|
@ -65,7 +68,7 @@ export default function SchemeComparaison({
|
|||
)
|
||||
const autoEntrepreneurEngine = useMemo(
|
||||
() =>
|
||||
new Engine(parsedRules).setSituation({
|
||||
new Engine<DottedName>(parsedRules).setSituation({
|
||||
...situation,
|
||||
dirigeant: "'auto-entrepreneur'"
|
||||
}),
|
||||
|
@ -73,7 +76,7 @@ export default function SchemeComparaison({
|
|||
)
|
||||
const indépendantEngine = useMemo(
|
||||
() =>
|
||||
new Engine(parsedRules).setSituation({
|
||||
new Engine<DottedName>(parsedRules).setSituation({
|
||||
...situation,
|
||||
dirigeant: "'indépendant'"
|
||||
}),
|
||||
|
|
|
@ -4,7 +4,7 @@ import { DottedName } from 'Rules'
|
|||
import Worker from 'worker-loader!./SearchBar.worker.js'
|
||||
import RuleLink from './RuleLink'
|
||||
import './SearchBar.css'
|
||||
import { EngineContext } from './utils/EngineContext'
|
||||
import { EngineContext, useEngine } from './utils/EngineContext'
|
||||
import { utils } from 'publicodes'
|
||||
|
||||
const worker = new Worker()
|
||||
|
@ -62,7 +62,7 @@ function highlightMatches(str: string, matches: Matches) {
|
|||
export default function SearchBar({
|
||||
showListByDefault = false
|
||||
}: SearchBarProps) {
|
||||
const rules = useContext(EngineContext).getParsedRules()
|
||||
const rules = useEngine().getParsedRules()
|
||||
const [input, setInput] = useState('')
|
||||
const [results, setResults] = useState<
|
||||
Array<{
|
||||
|
@ -78,8 +78,8 @@ export default function SearchBar({
|
|||
.filter(utils.ruleWithDedicatedDocumentationPage)
|
||||
.map(rule => ({
|
||||
title:
|
||||
rule.title ??
|
||||
rule.name + (rule.acronyme ? ` (${rule.acronyme})` : ''),
|
||||
rule.title +
|
||||
(rule.rawNode.acronyme ? ` (${rule.rawNode.acronyme})` : ''),
|
||||
dottedName: rule.dottedName,
|
||||
espace: rule.dottedName.split(' . ').reverse()
|
||||
})),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import RuleLink from 'Components/RuleLink'
|
||||
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
|
||||
import { EvaluatedRule, Evaluation, Types } from 'publicodes'
|
||||
import { EvaluatedNode, EvaluatedRule } from 'publicodes'
|
||||
import React from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
import { DottedName } from 'Rules'
|
||||
|
@ -82,7 +82,7 @@ export function roundedPercentages(values: Array<number>) {
|
|||
type StackedBarChartProps = {
|
||||
data: Array<{
|
||||
color?: string
|
||||
value: Evaluation<Types>
|
||||
value: EvaluatedNode['nodeValue']
|
||||
legend: React.ReactNode
|
||||
key: string
|
||||
}>
|
||||
|
|
|
@ -7,12 +7,18 @@ import AnimatedTargetValue from 'Components/ui/AnimatedTargetValue'
|
|||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import {
|
||||
EngineContext,
|
||||
useEvaluation,
|
||||
useEngine,
|
||||
useInversionFail
|
||||
} from 'Components/utils/EngineContext'
|
||||
import { SitePathsContext } from 'Components/utils/SitePathsContext'
|
||||
import { EvaluatedNode } from 'publicodes'
|
||||
import { EvaluatedRule, formatValue } from 'publicodes'
|
||||
import {
|
||||
ASTNode,
|
||||
EvaluatedNode,
|
||||
EvaluatedRule,
|
||||
evaluateRule,
|
||||
formatValue,
|
||||
reduceAST
|
||||
} from 'publicodes'
|
||||
import { isNil } from 'ramda'
|
||||
import { Fragment, useCallback, useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
|
@ -20,11 +26,8 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { DottedName, ParsedRule } from 'Rules'
|
||||
import {
|
||||
situationSelector,
|
||||
targetUnitSelector
|
||||
} from 'Selectors/simulationSelectors'
|
||||
import { DottedName } from 'Rules'
|
||||
import { targetUnitSelector } from 'Selectors/simulationSelectors'
|
||||
import CurrencyInput from './CurrencyInput/CurrencyInput'
|
||||
import './TargetSelection.css'
|
||||
|
||||
|
@ -84,13 +87,15 @@ type TargetProps = {
|
|||
}
|
||||
const Target = ({ dottedName }: TargetProps) => {
|
||||
const activeInput = useSelector((state: RootState) => state.activeTargetInput)
|
||||
const target = useEvaluation(dottedName, {
|
||||
unit: useSelector(targetUnitSelector)
|
||||
const engine = useEngine()
|
||||
const target = evaluateRule(engine, dottedName, {
|
||||
unité: useSelector(targetUnitSelector),
|
||||
arrondi: 'oui'
|
||||
})
|
||||
const dispatch = useDispatch()
|
||||
const onSuggestionClick = useCallback(
|
||||
value => {
|
||||
dispatch(updateSituation(target.dottedName, value))
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
},
|
||||
[target.dottedName, dispatch]
|
||||
)
|
||||
|
@ -103,7 +108,6 @@ const Target = ({ dottedName }: TargetProps) => {
|
|||
return null
|
||||
}
|
||||
const isActiveInput = activeInput === target.dottedName
|
||||
|
||||
return (
|
||||
<li
|
||||
key={target.dottedName}
|
||||
|
@ -142,7 +146,6 @@ const Target = ({ dottedName }: TargetProps) => {
|
|||
<InputSuggestions
|
||||
suggestions={target.suggestions}
|
||||
onFirstClick={onSuggestionClick}
|
||||
unit={target.unit}
|
||||
/>
|
||||
</div>
|
||||
</Animate.fromTop>
|
||||
|
@ -153,7 +156,7 @@ const Target = ({ dottedName }: TargetProps) => {
|
|||
)
|
||||
}
|
||||
|
||||
const Header = ({ target }: { target: ParsedRule }) => {
|
||||
const Header = ({ target }: { target: EvaluatedRule<DottedName> }) => {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const { t } = useTranslation()
|
||||
const { pathname } = useLocation()
|
||||
|
@ -166,11 +169,11 @@ const Header = ({ target }: { target: ParsedRule }) => {
|
|||
<span className="texts">
|
||||
<span className="optionTitle">
|
||||
<RuleLink dottedName={target.dottedName}>
|
||||
{target.title || target.name}
|
||||
{target.title}
|
||||
{hackyShowPeriod && ' ' + t('mensuel')}
|
||||
</RuleLink>
|
||||
</span>
|
||||
<p className="ui__ notice">{target.summary}</p>
|
||||
<p className="ui__ notice">{target.résumé}</p>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
|
@ -190,26 +193,23 @@ function TargetInputOrValue({
|
|||
const { language } = useTranslation().i18n
|
||||
const colors = useContext(ThemeColorsContext)
|
||||
const dispatch = useDispatch()
|
||||
const situationValue = useSelector(situationSelector)[target.dottedName]
|
||||
const targetUnit = useSelector(targetUnitSelector)
|
||||
const engine = useContext(EngineContext)
|
||||
const value =
|
||||
typeof situationValue === 'string'
|
||||
? Math.round(
|
||||
engine.evaluate(situationValue, { unit: targetUnit })
|
||||
.nodeValue as number
|
||||
)
|
||||
: situationValue != null
|
||||
? situationValue
|
||||
: target?.nodeValue != null
|
||||
? Math.round(+target.nodeValue)
|
||||
: undefined
|
||||
(engine.evaluate({
|
||||
valeur: target.dottedName,
|
||||
unité: targetUnit,
|
||||
arrondi: 'oui'
|
||||
}).nodeValue as number) ?? undefined
|
||||
const blurValue = useInversionFail() && !isActiveInput
|
||||
|
||||
const onChange = useCallback(
|
||||
evt =>
|
||||
dispatch(
|
||||
updateSituation(target.dottedName, +evt.target.value + ' ' + targetUnit)
|
||||
updateSituation(target.dottedName, {
|
||||
valeur: evt.target.value,
|
||||
unité: targetUnit
|
||||
})
|
||||
),
|
||||
[targetUnit, target, dispatch]
|
||||
)
|
||||
|
@ -267,11 +267,17 @@ function TargetInputOrValue({
|
|||
}
|
||||
function TitreRestaurant() {
|
||||
const targetUnit = useSelector(targetUnitSelector)
|
||||
const titresRestaurant = useEvaluation(
|
||||
'contrat salarié . frais professionnels . titres-restaurant . montant',
|
||||
{ unit: targetUnit }
|
||||
)
|
||||
const { language } = useTranslation().i18n
|
||||
|
||||
const titresRestaurant = evaluateRule(
|
||||
useEngine(),
|
||||
'contrat salarié . frais professionnels . titres-restaurant . montant',
|
||||
{
|
||||
unité: targetUnit,
|
||||
arrondi: 'oui'
|
||||
}
|
||||
)
|
||||
|
||||
if (!titresRestaurant?.nodeValue) return null
|
||||
return (
|
||||
<Animate.fromTop>
|
||||
|
@ -279,10 +285,7 @@ function TitreRestaurant() {
|
|||
<RuleLink dottedName={titresRestaurant.dottedName}>
|
||||
+{' '}
|
||||
<strong>
|
||||
{formatValue(titresRestaurant, {
|
||||
displayedUnit: '€',
|
||||
language
|
||||
})}
|
||||
{formatValue(titresRestaurant, { displayedUnit: '€', language })}
|
||||
</strong>{' '}
|
||||
<Trans>en titres-restaurant</Trans> {emoji(' 🍽')}
|
||||
</RuleLink>
|
||||
|
@ -292,35 +295,46 @@ function TitreRestaurant() {
|
|||
}
|
||||
function AidesGlimpse() {
|
||||
const targetUnit = useSelector(targetUnitSelector)
|
||||
const aides = useEvaluation('contrat salarié . aides employeur', {
|
||||
unit: targetUnit
|
||||
})
|
||||
const { language } = useTranslation().i18n
|
||||
const dottedName = 'contrat salarié . aides employeur'
|
||||
const engine = useEngine()
|
||||
const aides = evaluateRule(engine, dottedName, {
|
||||
unité: targetUnit,
|
||||
arrondi: 'oui'
|
||||
})
|
||||
|
||||
if (!aides?.nodeValue) return null
|
||||
|
||||
// Dans le cas où il n'y a qu'une seule aide à l'embauche qui s'applique, nous
|
||||
// faisons un lien direct vers cette aide, plutôt qu'un lien vers la liste qui
|
||||
// est une somme des aides qui sont toutes nulle sauf l'aide active.
|
||||
const aidesDetail = aides?.formule.explanation.explanation
|
||||
const aidesNotNul = aidesDetail?.filter(
|
||||
(node: EvaluatedNode) => node.nodeValue !== false
|
||||
const aideLink = reduceAST(
|
||||
(acc, node) => {
|
||||
if (node.nodeKind === 'somme') {
|
||||
const aidesNotNul = (node.explanation as EvaluatedNode[]).filter(
|
||||
({ nodeValue }) => nodeValue !== false
|
||||
)
|
||||
console.log('aidesNotNul', aidesNotNul, node.explanation)
|
||||
if (aidesNotNul.length === 1) {
|
||||
return (aidesNotNul[0] as ASTNode & { nodeKind: 'reference' })
|
||||
.dottedName as DottedName
|
||||
} else {
|
||||
return acc
|
||||
}
|
||||
}
|
||||
},
|
||||
aides.dottedName,
|
||||
engine.evaluateNode(engine.getParsedRules()[dottedName])
|
||||
)
|
||||
const aideLink = aidesNotNul?.length === 1 ? aidesNotNul[0] : aides
|
||||
|
||||
if (!aides?.nodeValue) return null
|
||||
return (
|
||||
<Animate.fromTop>
|
||||
<div className="aidesGlimpse">
|
||||
<RuleLink dottedName={aideLink.dottedName}>
|
||||
<RuleLink dottedName={aideLink as DottedName}>
|
||||
<Trans>en incluant</Trans>{' '}
|
||||
<strong>
|
||||
<span>
|
||||
{formatValue(aides, {
|
||||
displayedUnit: '€',
|
||||
language
|
||||
})}
|
||||
</span>
|
||||
<span>{formatValue(aides, { displayedUnit: '€', language })}</span>
|
||||
</strong>{' '}
|
||||
<Trans>d'aides</Trans> {emoji(aides?.icons ?? '')}
|
||||
<Trans>d'aides</Trans> {emoji(aides.icônes ?? '')}
|
||||
</RuleLink>
|
||||
</div>
|
||||
</Animate.fromTop>
|
||||
|
|
|
@ -19,8 +19,8 @@ export default function Aide() {
|
|||
if (!explained) return null
|
||||
|
||||
const rule = rules[explained],
|
||||
text = rule.description,
|
||||
refs = rule.références
|
||||
text = rule.rawNode.description,
|
||||
refs = rule.rawNode.références
|
||||
|
||||
return (
|
||||
<Overlay onClose={stopExplaining}>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { goToQuestion, resetSimulation } from 'Actions/actions'
|
||||
import Overlay from 'Components/Overlay'
|
||||
import { useEvaluation } from 'Components/utils/EngineContext'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import { useNextQuestions } from 'Components/utils/useNextQuestion'
|
||||
import { formatValue } from 'publicodes'
|
||||
import { evaluateRule, formatValue } from 'publicodes'
|
||||
import { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
@ -68,13 +69,14 @@ function StepsTable({
|
|||
onClose: () => void
|
||||
}) {
|
||||
const dispatch = useDispatch()
|
||||
const evaluatedRules = useEvaluation(rules)
|
||||
const engine = useEngine()
|
||||
const evaluatedRules = rules.map(rule => evaluateRule(engine, rule))
|
||||
const language = useTranslation().i18n.language
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
{evaluatedRules
|
||||
.filter(rule => rule.isApplicable !== false)
|
||||
.filter(rule => rule.nodeValue !== false)
|
||||
.map(rule => (
|
||||
<tr
|
||||
key={rule.dottedName}
|
||||
|
|
|
@ -36,14 +36,15 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
|
|||
dispatch(goToQuestion(currentQuestion))
|
||||
}
|
||||
}, [dispatch, currentQuestion])
|
||||
console.log(currentQuestion)
|
||||
const setDefault = () =>
|
||||
dispatch(
|
||||
// TODO: Skiping a question shouldn't be equivalent to answering the
|
||||
// default value (for instance the question shouldn't appear in the
|
||||
// answered questions).
|
||||
validateStepWithValue(
|
||||
currentQuestion,
|
||||
rules[currentQuestion].defaultValue
|
||||
currentQuestion, //TODO
|
||||
undefined
|
||||
)
|
||||
)
|
||||
const goToPrevious = () =>
|
||||
|
@ -77,7 +78,7 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
|
|||
<Animate.fadeIn>
|
||||
<div className="step">
|
||||
<h3>
|
||||
{rules[currentQuestion].question}{' '}
|
||||
{rules[currentQuestion].rawNode.question}{' '}
|
||||
<ExplicableRule dottedName={currentQuestion} />
|
||||
</h3>
|
||||
|
||||
|
@ -87,7 +88,6 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
|
|||
value={situation[currentQuestion]}
|
||||
onChange={onChange}
|
||||
onSubmit={submit}
|
||||
rules={rules}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { RuleInputProps } from 'Components/conversation/RuleInput'
|
||||
import { Rule } from 'publicodes'
|
||||
import { EvaluatedRule } from 'publicodes'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import InputSuggestions from './InputSuggestions'
|
||||
|
@ -9,7 +9,7 @@ type DateInputProps = {
|
|||
id: RuleInputProps['id']
|
||||
onSubmit: RuleInputProps['onSubmit']
|
||||
value: RuleInputProps['value']
|
||||
suggestions: Rule['suggestions']
|
||||
suggestions: EvaluatedRule['suggestions']
|
||||
}
|
||||
|
||||
export default function DateInput({
|
||||
|
|
|
@ -18,7 +18,7 @@ export function ExplicableRule({ dottedName }: { dottedName: DottedName }) {
|
|||
if (dottedName == null) return null
|
||||
const rule = rules[dottedName]
|
||||
|
||||
if (rule.description == null) return null
|
||||
if (rule.rawNode.description == null) return null
|
||||
|
||||
//TODO montrer les variables de type 'une possibilité'
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { formatValue, Unit } from 'publicodes'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { formatValue } from 'publicodes'
|
||||
import { Unit } from 'publicodes/dist/types/AST/types'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NumberFormat from 'react-number-format'
|
||||
import { serialize } from 'storage/serializeSimulation'
|
||||
import { currencyFormat, debounce } from '../../utils'
|
||||
import InputSuggestions from './InputSuggestions'
|
||||
import { InputCommonProps } from './RuleInput'
|
||||
|
@ -13,14 +15,19 @@ export default function Input({
|
|||
onSubmit,
|
||||
id,
|
||||
value,
|
||||
defaultValue,
|
||||
autoFocus,
|
||||
unit
|
||||
}: InputCommonProps & { unit?: Unit; onSubmit: (source: string) => void }) {
|
||||
missing,
|
||||
unit,
|
||||
autoFocus
|
||||
}: InputCommonProps & {
|
||||
onSubmit: (source: string) => void
|
||||
unit: Unit | undefined
|
||||
}) {
|
||||
const debouncedOnChange = useCallback(debounce(550, onChange), [])
|
||||
const { language } = useTranslation().i18n
|
||||
const unité = formatValue({ nodeValue: value ?? 0, unit }, { language })
|
||||
.replace(/[\d,.]/g, '')
|
||||
.trim()
|
||||
const { thousandSeparator, decimalSeparator } = currencyFormat(language)
|
||||
|
||||
// const [currentValue, setCurrentValue] = useState(value)
|
||||
return (
|
||||
<div className="step input">
|
||||
|
@ -31,13 +38,12 @@ export default function Input({
|
|||
onChange(value)
|
||||
}}
|
||||
onSecondClick={() => onSubmit?.('suggestion')}
|
||||
unit={unit}
|
||||
/>
|
||||
<NumberFormat
|
||||
autoFocus={autoFocus}
|
||||
className="suffixed ui__"
|
||||
id={id}
|
||||
placeholder={defaultValue?.nodeValue ?? defaultValue}
|
||||
placeholder={missing && value}
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalSeparator={decimalSeparator}
|
||||
allowEmptyFormatting={true}
|
||||
|
@ -45,18 +51,13 @@ export default function Input({
|
|||
// re-render with a new "value" prop from the outside.
|
||||
onValueChange={({ floatValue }) => {
|
||||
if (floatValue !== value) {
|
||||
debouncedOnChange(floatValue)
|
||||
debouncedOnChange({ valeur: floatValue, unité })
|
||||
}
|
||||
}}
|
||||
value={value}
|
||||
value={!missing && value}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<span className="suffix">
|
||||
{formatValue({ nodeValue: value ?? 0, unit }, { language }).replace(
|
||||
/[\d,.]*/g,
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
<span className="suffix"> {unité}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
import { serializeValue } from 'publicodes'
|
||||
import { ASTNode } from 'publicodes'
|
||||
import { toPairs } from 'ramda'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Unit } from 'publicodes'
|
||||
|
||||
type InputSuggestionsProps = {
|
||||
suggestions?: Record<string, number>
|
||||
onFirstClick: (val: string) => void
|
||||
onSecondClick?: (val: string) => void
|
||||
unit?: Unit
|
||||
suggestions?: Record<string, ASTNode>
|
||||
onFirstClick: (val: ASTNode) => void
|
||||
onSecondClick?: (val: ASTNode) => void
|
||||
}
|
||||
|
||||
export default function InputSuggestions({
|
||||
suggestions = {},
|
||||
onSecondClick = x => x,
|
||||
onFirstClick,
|
||||
unit
|
||||
onFirstClick
|
||||
}: InputSuggestionsProps) {
|
||||
const [suggestion, setSuggestion] = useState<string | number>()
|
||||
const [suggestion, setSuggestion] = useState<ASTNode>()
|
||||
const { t, i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
|
@ -30,18 +27,11 @@ export default function InputSuggestions({
|
|||
margin-bottom: 0.4rem;
|
||||
`}
|
||||
>
|
||||
{toPairs(suggestions).map(([text, value]: [string, number]) => {
|
||||
const valueWithUnit: string = serializeValue(
|
||||
{
|
||||
nodeValue: value,
|
||||
unit
|
||||
},
|
||||
{ language: i18n.language }
|
||||
)
|
||||
{toPairs(suggestions).map(([text, value]: [string, ASTNode]) => {
|
||||
return (
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
key={value}
|
||||
key={text}
|
||||
css={`
|
||||
margin: 0 0.4rem !important;
|
||||
:first-child {
|
||||
|
@ -49,9 +39,9 @@ export default function InputSuggestions({
|
|||
}
|
||||
`}
|
||||
onClick={() => {
|
||||
onFirstClick(valueWithUnit)
|
||||
if (suggestion !== value) setSuggestion(valueWithUnit)
|
||||
else onSecondClick && onSecondClick(valueWithUnit)
|
||||
onFirstClick(value)
|
||||
if (suggestion !== value) setSuggestion(value)
|
||||
else onSecondClick && onSecondClick(value)
|
||||
}}
|
||||
title={t('cliquez pour insérer cette suggestion')}
|
||||
>
|
||||
|
|
|
@ -6,7 +6,7 @@ export default function ParagrapheInput({
|
|||
onChange,
|
||||
value,
|
||||
id,
|
||||
defaultValue,
|
||||
missing,
|
||||
autoFocus
|
||||
}: InputCommonProps) {
|
||||
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
|
||||
|
@ -19,14 +19,11 @@ export default function ParagrapheInput({
|
|||
rows={6}
|
||||
style={{ resize: 'none' }}
|
||||
id={id}
|
||||
placeholder={(defaultValue?.nodeValue ?? defaultValue)?.replace(
|
||||
'\\n',
|
||||
'\n'
|
||||
)}
|
||||
placeholder={missing && value?.replace('\\n', '\n')}
|
||||
onChange={({ target }) => {
|
||||
debouncedOnChange(`'${target.value.replace(/\n/g, '\\n')}'`)
|
||||
}}
|
||||
defaultValue={value?.replace('\\n', '\n')}
|
||||
defaultValue={!missing && value?.replace('\\n', '\n')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import classnames from 'classnames'
|
||||
import { Markdown } from 'Components/utils/markdown'
|
||||
import { ASTNode, References } from 'publicodes'
|
||||
import { Rule } from 'publicodes/dist/types/rule'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Explicable } from './Explicable'
|
||||
import { References, ParsedRule, Rule } from 'publicodes'
|
||||
import { binaryQuestion, InputCommonProps, RuleInputProps } from './RuleInput'
|
||||
|
||||
/* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans
|
||||
|
@ -23,7 +24,7 @@ import { binaryQuestion, InputCommonProps, RuleInputProps } from './RuleInput'
|
|||
|
||||
*/
|
||||
|
||||
export type Choice = ParsedRule & {
|
||||
export type Choice = ASTNode & { nodeKind: 'rule' } & {
|
||||
canGiveUp?: boolean
|
||||
children: Array<Choice>
|
||||
}
|
||||
|
@ -37,10 +38,13 @@ export default function Question({
|
|||
choices,
|
||||
onSubmit,
|
||||
dottedName: questionDottedName,
|
||||
missing,
|
||||
onChange,
|
||||
value: currentValue
|
||||
}: QuestionProps) {
|
||||
const [currentSelection, setCurrentSelection] = useState(currentValue)
|
||||
const [currentSelection, setCurrentSelection] = useState(
|
||||
missing ? null : `'${currentValue}'`
|
||||
)
|
||||
const handleChange = useCallback(
|
||||
value => {
|
||||
setCurrentSelection(value)
|
||||
|
@ -55,7 +59,6 @@ export default function Question({
|
|||
},
|
||||
[onSubmit, onChange, setCurrentSelection]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSelection != null) {
|
||||
const timeoutId = setTimeout(() => onChange(currentSelection), 300)
|
||||
|
@ -116,7 +119,12 @@ export default function Question({
|
|||
)}
|
||||
{choices.children &&
|
||||
choices.children.map(
|
||||
({ title, dottedName, description, children, icons, références }) =>
|
||||
({
|
||||
title,
|
||||
dottedName,
|
||||
rawNode: { description, icônes, références },
|
||||
children
|
||||
}) =>
|
||||
children ? (
|
||||
<li key={dottedName} className="variant">
|
||||
<div>{title}</div>
|
||||
|
@ -131,7 +139,7 @@ export default function Question({
|
|||
dottedName,
|
||||
currentSelection,
|
||||
name: questionDottedName,
|
||||
icons,
|
||||
icônes,
|
||||
onSubmit: handleSubmit,
|
||||
description,
|
||||
références,
|
||||
|
@ -194,8 +202,8 @@ type RadioLabelContentProps = {
|
|||
value: string
|
||||
label: string
|
||||
name: string
|
||||
currentSelection?: string
|
||||
icons?: string
|
||||
currentSelection?: null | string
|
||||
icônes?: string
|
||||
onChange: RuleInputProps['onChange']
|
||||
onSubmit: (src: string, value: string) => void
|
||||
}
|
||||
|
@ -205,7 +213,7 @@ function RadioLabelContent({
|
|||
label,
|
||||
name,
|
||||
currentSelection,
|
||||
icons,
|
||||
icônes,
|
||||
onChange,
|
||||
onSubmit
|
||||
}: RadioLabelContentProps) {
|
||||
|
@ -231,7 +239,7 @@ function RadioLabelContent({
|
|||
checked={selected}
|
||||
/>
|
||||
<span>
|
||||
{icons && <>{emoji(icons)} </>}
|
||||
{icônes && <>{emoji(icônes)} </>}
|
||||
<Trans>{label}</Trans>
|
||||
</span>
|
||||
</label>
|
||||
|
|
|
@ -6,18 +6,23 @@ import CurrencyInput from 'Components/CurrencyInput/CurrencyInput'
|
|||
import PercentageField from 'Components/PercentageField'
|
||||
import ToggleSwitch from 'Components/ui/ToggleSwitch'
|
||||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { ParsedRule, ParsedRules } from 'publicodes'
|
||||
import {
|
||||
ASTNode,
|
||||
EvaluatedRule,
|
||||
evaluateRule,
|
||||
ParsedRules,
|
||||
reduceAST,
|
||||
} from 'publicodes'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DottedName } from 'Rules'
|
||||
import DateInput from './DateInput'
|
||||
import TextInput from './TextInput'
|
||||
import SelectEuropeCountry from './select/SelectEuropeCountry'
|
||||
import ParagrapheInput from './ParagrapheInput'
|
||||
import SelectEuropeCountry from './select/SelectEuropeCountry'
|
||||
import TextInput from './TextInput'
|
||||
|
||||
type Value = any
|
||||
export type RuleInputProps<Name extends string = DottedName> = {
|
||||
rules: ParsedRules<Name>
|
||||
dottedName: Name
|
||||
onChange: (value: Value | null) => void
|
||||
useSwitch?: boolean
|
||||
|
@ -29,22 +34,20 @@ export type RuleInputProps<Name extends string = DottedName> = {
|
|||
onSubmit?: (source: string) => void
|
||||
}
|
||||
|
||||
export type InputCommonProps = Pick<
|
||||
RuleInputProps<string>,
|
||||
export type InputCommonProps<Name extends string = string> = Pick<
|
||||
RuleInputProps<Name>,
|
||||
'dottedName' | 'value' | 'onChange' | 'autoFocus' | 'className'
|
||||
> &
|
||||
Pick<
|
||||
ParsedRule<string>,
|
||||
'title' | 'question' | 'defaultValue' | 'suggestions'
|
||||
> & {
|
||||
Pick<EvaluatedRule<Name>, 'title' | 'question' | 'suggestions'> & {
|
||||
key: string
|
||||
id: string
|
||||
missing: boolean
|
||||
required: boolean
|
||||
}
|
||||
|
||||
export const binaryQuestion = [
|
||||
{ value: 'oui', label: 'Oui' },
|
||||
{ value: 'non', label: 'Non' }
|
||||
{ value: 'non', label: 'Non' },
|
||||
] as const
|
||||
|
||||
// This function takes the unknown rule and finds which React component should
|
||||
|
@ -52,41 +55,39 @@ export const binaryQuestion = [
|
|||
// That's not great, but we won't invest more time until we have more diverse
|
||||
// input components and a better type system.
|
||||
export default function RuleInput<Name extends string = DottedName>({
|
||||
rules,
|
||||
dottedName,
|
||||
onChange,
|
||||
value,
|
||||
useSwitch = false,
|
||||
id,
|
||||
isTarget = false,
|
||||
autoFocus = false,
|
||||
className,
|
||||
onSubmit = () => null
|
||||
onSubmit = () => null,
|
||||
}: RuleInputProps<Name>) {
|
||||
const rule = rules[dottedName]
|
||||
const unit = rule.unit
|
||||
const language = useTranslation().i18n.language
|
||||
const engine = useContext(EngineContext)
|
||||
const commonProps: InputCommonProps = {
|
||||
const rule = evaluateRule(engine, dottedName)
|
||||
const language = useTranslation().i18n.language
|
||||
const value = rule.nodeValue
|
||||
const commonProps: InputCommonProps<Name> = {
|
||||
key: dottedName,
|
||||
dottedName,
|
||||
value,
|
||||
missing: !!rule.missingVariables[dottedName],
|
||||
onChange,
|
||||
autoFocus,
|
||||
className,
|
||||
title: rule.title,
|
||||
id: id ?? dottedName,
|
||||
question: rule.question,
|
||||
defaultValue: rule.defaultValue,
|
||||
suggestions: rule.suggestions,
|
||||
required: true
|
||||
required: true,
|
||||
}
|
||||
if (getVariant(rule)) {
|
||||
if (getVariant(engine.getParsedRules()[dottedName])) {
|
||||
return (
|
||||
<Question
|
||||
{...commonProps}
|
||||
onSubmit={onSubmit}
|
||||
choices={buildVariantTree(rules, dottedName)}
|
||||
choices={buildVariantTree(engine.getParsedRules(), dottedName)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -111,10 +112,14 @@ export default function RuleInput<Name extends string = DottedName>({
|
|||
)
|
||||
}
|
||||
|
||||
if (unit == null && (rule.type === 'booléen' || rule.type == undefined)) {
|
||||
if (
|
||||
rule.unit == null &&
|
||||
(rule.type === 'booléen' || rule.type == undefined) &&
|
||||
typeof rule.nodeValue !== 'number'
|
||||
) {
|
||||
return useSwitch ? (
|
||||
<ToggleSwitch
|
||||
defaultChecked={value === 'oui' || rule.defaultValue === 'oui'}
|
||||
defaultChecked={value === 'oui'}
|
||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChange(evt.target.checked ? 'oui' : 'non')
|
||||
}
|
||||
|
@ -124,7 +129,7 @@ export default function RuleInput<Name extends string = DottedName>({
|
|||
{...commonProps}
|
||||
choices={[
|
||||
{ value: 'oui', label: 'Oui' },
|
||||
{ value: 'non', label: 'Non' }
|
||||
{ value: 'non', label: 'Non' },
|
||||
]}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
|
@ -136,7 +141,7 @@ export default function RuleInput<Name extends string = DottedName>({
|
|||
? engine.evaluate(commonProps.value as DottedName).nodeValue
|
||||
: commonProps.value
|
||||
|
||||
if (unit?.numerators.includes('€') && isTarget) {
|
||||
if (rule.unit?.numerators.includes('€') && isTarget) {
|
||||
return (
|
||||
<>
|
||||
<CurrencyInput
|
||||
|
@ -146,12 +151,12 @@ export default function RuleInput<Name extends string = DottedName>({
|
|||
value={value as string}
|
||||
name={dottedName}
|
||||
className="targetInput"
|
||||
onChange={evt => onChange(evt.target.value)}
|
||||
onChange={(evt) => onChange(evt.target.value)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (unit?.numerators.includes('%') && isTarget) {
|
||||
if (rule.unit?.numerators.includes('%') && isTarget) {
|
||||
return <PercentageField {...commonProps} debounce={600} />
|
||||
}
|
||||
|
||||
|
@ -162,32 +167,38 @@ export default function RuleInput<Name extends string = DottedName>({
|
|||
return <ParagrapheInput {...commonProps} />
|
||||
}
|
||||
|
||||
return <Input {...commonProps} unit={unit} onSubmit={onSubmit} />
|
||||
return <Input {...commonProps} onSubmit={onSubmit} unit={rule.unit} />
|
||||
}
|
||||
|
||||
const getVariant = (rule: ParsedRule) =>
|
||||
rule?.formule?.explanation['possibilités']
|
||||
|
||||
const getVariant = (node: ASTNode & { nodeKind: 'rule' }) =>
|
||||
reduceAST<false | (ASTNode & { nodeKind: 'une possibilité' })>(
|
||||
(_, node) => {
|
||||
if (node.nodeKind === 'une possibilité') {
|
||||
return node
|
||||
}
|
||||
},
|
||||
false,
|
||||
node
|
||||
)
|
||||
export const buildVariantTree = <Name extends string>(
|
||||
allRules: ParsedRules<Name>,
|
||||
path: Name
|
||||
): Choice => {
|
||||
const rec = (path: Name) => {
|
||||
const node = allRules[path]
|
||||
if (!node) throw new Error(`La règle ${path} est introuvable`)
|
||||
const variant = getVariant(node)
|
||||
const variants = variant && node.formule.explanation['possibilités']
|
||||
const canGiveUp =
|
||||
variant && node.formule.explanation['choix obligatoire'] !== 'oui'
|
||||
return Object.assign(
|
||||
node,
|
||||
variant
|
||||
? {
|
||||
canGiveUp,
|
||||
children: variants.map((v: string) => rec(`${path} . ${v}` as Name))
|
||||
}
|
||||
: null
|
||||
) as Choice
|
||||
}
|
||||
return rec(path)
|
||||
const node = allRules[path]
|
||||
if (!node) throw new Error(`La règle ${path} est introuvable`)
|
||||
const variant = getVariant(node)
|
||||
const canGiveUp = variant && !variant['choix obligatoire']
|
||||
return Object.assign(
|
||||
node,
|
||||
variant
|
||||
? {
|
||||
canGiveUp,
|
||||
children: (variant.explanation as (ASTNode & {
|
||||
nodeKind: 'reference'
|
||||
})[]).map(({ dottedName }) =>
|
||||
buildVariantTree(allRules, dottedName as Name)
|
||||
),
|
||||
}
|
||||
: null
|
||||
) as Choice
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ export default function TextInput({
|
|||
onChange,
|
||||
value,
|
||||
id,
|
||||
defaultValue,
|
||||
missing,
|
||||
autoFocus
|
||||
}: InputCommonProps) {
|
||||
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
|
||||
|
@ -18,11 +18,11 @@ export default function TextInput({
|
|||
className="ui__"
|
||||
type="text"
|
||||
id={id}
|
||||
placeholder={defaultValue?.nodeValue ?? defaultValue}
|
||||
placeholder={missing && value}
|
||||
onChange={({ target }) => {
|
||||
debouncedOnChange(`'${target.value}'`)
|
||||
}}
|
||||
defaultValue={value}
|
||||
defaultValue={!missing && value}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -99,12 +99,14 @@ export default function Select({ onChange, value, id }: InputCommonProps) {
|
|||
// await
|
||||
// serialize to not mix our data schema and the API response's
|
||||
onChange({
|
||||
...commune,
|
||||
...(taux != null
|
||||
? {
|
||||
'taux du versement transport': taux
|
||||
}
|
||||
: {})
|
||||
objet: {
|
||||
...commune,
|
||||
...(taux != null
|
||||
? {
|
||||
'taux du versement transport': taux
|
||||
}
|
||||
: {})
|
||||
}
|
||||
})
|
||||
},
|
||||
[setSearchResults, setName]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import StackedBarChart from 'Components/StackedBarChart'
|
||||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import { evaluateRule } from 'publicodes'
|
||||
import { default as React, useContext } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
@ -8,11 +9,11 @@ import { targetUnitSelector } from 'Selectors/simulationSelectors'
|
|||
import AidesCovid from './AidesCovid'
|
||||
|
||||
export default function AutoEntrepreneurExplanation() {
|
||||
const engine = useContext(EngineContext)
|
||||
const engine = useEngine()
|
||||
const { t } = useTranslation()
|
||||
const { palettes } = useContext(ThemeColorsContext)
|
||||
const targetUnit = useSelector(targetUnitSelector)
|
||||
const impôt = engine.evaluate('impôt', { unit: targetUnit })
|
||||
const impôt = evaluateRule(engine, 'impôt', { unité: targetUnit })
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
@ -24,9 +25,10 @@ export default function AutoEntrepreneurExplanation() {
|
|||
<StackedBarChart
|
||||
data={[
|
||||
{
|
||||
...engine.evaluate(
|
||||
...evaluateRule(
|
||||
engine,
|
||||
'dirigeant . auto-entrepreneur . net après impôt',
|
||||
{ unit: targetUnit }
|
||||
{ unité: targetUnit }
|
||||
),
|
||||
title: t("Revenu (incluant les dépenses liées à l'activité)"),
|
||||
color: palettes[0][0]
|
||||
|
@ -36,9 +38,10 @@ export default function AutoEntrepreneurExplanation() {
|
|||
? [{ ...impôt, title: t('impôt'), color: palettes[1][0] }]
|
||||
: []),
|
||||
{
|
||||
...engine.evaluate(
|
||||
...evaluateRule(
|
||||
engine,
|
||||
'dirigeant . auto-entrepreneur . cotisations et contributions',
|
||||
{ unit: targetUnit }
|
||||
{ unité: targetUnit }
|
||||
),
|
||||
title: t('Cotisations'),
|
||||
color: palettes[1][1]
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import BarChartBranch from 'Components/BarChart'
|
||||
import 'Components/Distribution.css'
|
||||
import Value, { Condition } from 'Components/EngineValue'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import StackedBarChart from 'Components/StackedBarChart'
|
||||
import * as Animate from 'Components/ui/animate'
|
||||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import Emoji from 'Components/utils/Emoji'
|
||||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import assuranceMaladieSrc from 'Images/assurance-maladie.svg'
|
||||
import * as logosSrc from 'Images/logos-cnavpl'
|
||||
import urssafSrc from 'Images/urssaf.svg'
|
||||
import { evaluateRule } from 'publicodes'
|
||||
import { max } from 'ramda'
|
||||
import { useContext } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -14,14 +18,10 @@ import { useSelector } from 'react-redux'
|
|||
import { DottedName } from 'Rules'
|
||||
import { targetUnitSelector } from 'Selectors/simulationSelectors'
|
||||
import styled from 'styled-components'
|
||||
import BarChartBranch from 'Components/BarChart'
|
||||
import 'Components/Distribution.css'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import AidesCovid from './AidesCovid'
|
||||
// import Distribution from 'Components/Distribution'
|
||||
|
||||
export default function IndépendantExplanation() {
|
||||
const engine = useContext(EngineContext)
|
||||
const engine = useEngine()
|
||||
const { t } = useTranslation()
|
||||
const { palettes } = useContext(ThemeColorsContext)
|
||||
|
||||
|
@ -37,13 +37,14 @@ export default function IndépendantExplanation() {
|
|||
<StackedBarChart
|
||||
data={[
|
||||
{
|
||||
...engine.evaluate('revenu net après impôt'),
|
||||
...evaluateRule(engine, 'revenu net après impôt'),
|
||||
title: t('Revenu disponible'),
|
||||
color: palettes[0][0]
|
||||
},
|
||||
{ ...engine.evaluate('impôt'), color: palettes[1][0] },
|
||||
{ ...evaluateRule(engine, 'impôt'), color: palettes[1][0] },
|
||||
{
|
||||
...engine.evaluate(
|
||||
...evaluateRule(
|
||||
engine,
|
||||
'dirigeant . indépendant . cotisations et contributions'
|
||||
),
|
||||
title: t('Cotisations'),
|
||||
|
@ -127,7 +128,7 @@ function PLExplanation() {
|
|||
}
|
||||
|
||||
function CaisseRetraite() {
|
||||
const engine = useContext(EngineContext)
|
||||
const engine = useEngine()
|
||||
const unit = useSelector(targetUnitSelector)
|
||||
const caisses = [
|
||||
'CARCDSF',
|
||||
|
@ -142,7 +143,7 @@ function CaisseRetraite() {
|
|||
<>
|
||||
{caisses.map(caisse => {
|
||||
const dottedName = `dirigeant . indépendant . PL . ${caisse}` as DottedName
|
||||
const { description, références } = engine.evaluate(dottedName)
|
||||
const { description, références } = evaluateRule(engine, dottedName)
|
||||
return (
|
||||
<Condition expression={dottedName} key={caisse}>
|
||||
<div className="ui__ card box">
|
||||
|
@ -215,7 +216,7 @@ function Distribution() {
|
|||
).map(([section, cotisations]) => [
|
||||
section,
|
||||
(cotisations as string[])
|
||||
.map(c => engine.evaluate(c, { unit: targetUnit }))
|
||||
.map(c => engine.evaluate({ valeur: c, unité: targetUnit }))
|
||||
.reduce(
|
||||
(acc, evaluation) => acc + ((evaluation?.nodeValue as number) || 0),
|
||||
0
|
||||
|
@ -262,8 +263,8 @@ function DistributionBranch({
|
|||
value={value}
|
||||
maximum={maximum}
|
||||
title={<RuleLink dottedName={dottedName} />}
|
||||
icon={icon ?? branche.icons}
|
||||
description={branche.summary}
|
||||
icon={icon ?? branche.rawNode.icônes}
|
||||
description={branche.rawNode.résumé}
|
||||
unit="€"
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -2,13 +2,19 @@ import Distribution from 'Components/Distribution'
|
|||
import PaySlip from 'Components/PaySlip'
|
||||
import StackedBarChart from 'Components/StackedBarChart'
|
||||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import { useEvaluation, useInversionFail } from 'Components/utils/EngineContext'
|
||||
import {
|
||||
EngineContext,
|
||||
useEngine,
|
||||
useInversionFail
|
||||
} from 'Components/utils/EngineContext'
|
||||
import { useContext, useRef } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as Animate from 'Components/ui/animate'
|
||||
import { answeredQuestionsSelector } from 'Selectors/simulationSelectors'
|
||||
import { evaluateRule } from 'publicodes'
|
||||
import { DottedName } from 'Rules'
|
||||
|
||||
export default function SalaryExplanation() {
|
||||
const showDistributionFirst = !useSelector(answeredQuestionsSelector).length
|
||||
|
@ -75,14 +81,13 @@ export default function SalaryExplanation() {
|
|||
function RevenueRepatitionSection() {
|
||||
const { t } = useTranslation()
|
||||
const { palettes } = useContext(ThemeColorsContext)
|
||||
const data = useEvaluation(
|
||||
[
|
||||
'contrat salarié . rémunération . net après impôt',
|
||||
'impôt',
|
||||
'contrat salarié . cotisations'
|
||||
],
|
||||
{ unit: '€/mois' }
|
||||
)
|
||||
const engine = useEngine()
|
||||
const data = ([
|
||||
'contrat salarié . rémunération . net après impôt',
|
||||
'impôt',
|
||||
'contrat salarié . cotisations'
|
||||
] as DottedName[]).map(r => evaluateRule(engine, r, { unité: '€/mois' }))
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h2>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import Engine, { EvaluatedRule, EvaluationOptions } from 'publicodes'
|
||||
import Engine from 'publicodes'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
import { DottedName } from 'Rules'
|
||||
|
||||
export const EngineContext = createContext<Engine<DottedName>>(null as any)
|
||||
|
||||
export const EngineContext = createContext<Engine>(new Engine({}))
|
||||
export const EngineProvider = EngineContext.Provider
|
||||
|
||||
export function useEngine(): Engine<DottedName> {
|
||||
return useContext(EngineContext) as Engine<DottedName>
|
||||
}
|
||||
|
||||
type SituationProviderProps = {
|
||||
children: React.ReactNode
|
||||
situation: Partial<
|
||||
|
@ -22,26 +25,6 @@ export function SituationProvider({
|
|||
<EngineContext.Provider value={engine}>{children}</EngineContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useEvaluation(
|
||||
rule: DottedName,
|
||||
options?: EvaluationOptions
|
||||
): EvaluatedRule<DottedName>
|
||||
export function useEvaluation(
|
||||
rule: DottedName[],
|
||||
options?: EvaluationOptions
|
||||
): EvaluatedRule<DottedName>[]
|
||||
export function useEvaluation(
|
||||
rule: Array<DottedName> | DottedName,
|
||||
options?: EvaluationOptions
|
||||
): Array<EvaluatedRule<DottedName>> | EvaluatedRule<DottedName> {
|
||||
const engine = useContext(EngineContext)
|
||||
if (Array.isArray(rule)) {
|
||||
return rule.map(name => engine.evaluate(name, options))
|
||||
}
|
||||
return engine.evaluate(rule, options)
|
||||
}
|
||||
|
||||
export function useInversionFail() {
|
||||
return useContext(EngineContext).inversionFail()
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
toPairs,
|
||||
zipWith
|
||||
} from 'ramda'
|
||||
import { useMemo } from 'react'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Simulation, SimulationConfig } from 'Reducers/rootReducer'
|
||||
import { DottedName } from 'Rules'
|
||||
|
@ -33,7 +33,7 @@ import {
|
|||
objectifsSelector,
|
||||
situationSelector
|
||||
} from 'Selectors/simulationSelectors'
|
||||
import { useEvaluation } from './EngineContext'
|
||||
import { EngineContext } from './EngineContext'
|
||||
|
||||
type MissingVariables = Partial<Record<DottedName, number>>
|
||||
export function getNextSteps(
|
||||
|
@ -123,8 +123,9 @@ export const useNextQuestions = function(): Array<DottedName> {
|
|||
const currentQuestion = useSelector(currentQuestionSelector)
|
||||
const questionsConfig = useSelector(configSelector).questions ?? {}
|
||||
const situation = useSelector(situationSelector)
|
||||
const missingVariables = useEvaluation(objectifs).map(
|
||||
node => node.missingVariables ?? {}
|
||||
const engine = useContext(EngineContext)
|
||||
const missingVariables = objectifs.map(
|
||||
node => engine.evaluate(node).missingVariables ?? {}
|
||||
)
|
||||
const nextQuestions = useMemo(() => {
|
||||
return getNextQuestions(
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
période:
|
||||
|
||||
période: oui
|
||||
période . jours ouvrés moyen par mois:
|
||||
formule: 21 jour ouvré/mois
|
||||
note: On retient 21 comme nombre de jours ouvrés moyen par mois
|
||||
|
|
|
@ -28,7 +28,7 @@ contrat salarié . convention collective . BTP . catégorie du salarié . etam:
|
|||
icônes: 👷♂️
|
||||
|
||||
contrat salarié . convention collective . BTP . catégorie du salarié . cadre:
|
||||
applicable si: catégorie du salarié = 'cadre'
|
||||
formule: catégorie du salarié = 'cadre'
|
||||
titre: Cadre
|
||||
icônes: 👩💼
|
||||
remplace:
|
||||
|
@ -36,6 +36,7 @@ contrat salarié . convention collective . BTP . catégorie du salarié . cadre:
|
|||
par: oui
|
||||
|
||||
contrat salarié . convention collective . BTP . retraite complémentaire:
|
||||
valeur: oui
|
||||
non applicable si: catégorie du salarié = 'etam'
|
||||
remplace:
|
||||
- règle: retraite complémentaire . employeur . taux tranche 1
|
||||
|
@ -48,6 +49,8 @@ contrat salarié . convention collective . BTP . retraite complémentaire:
|
|||
par: 8.64%
|
||||
|
||||
contrat salarié . convention collective . BTP . retraite complémentaire . etam:
|
||||
valeur: oui
|
||||
|
||||
applicable si: catégorie du salarié = 'etam'
|
||||
description: >-
|
||||
Répartition conventionnelle fixée par l’article 5 de l’Accord du BTP du 13 décembre 1990.
|
||||
|
@ -61,9 +64,10 @@ contrat salarié . convention collective . BTP . retraite complémentaire . etam
|
|||
- règle: retraite complémentaire . salarié . taux tranche 2
|
||||
par: 8.89%
|
||||
|
||||
contrat salarié . convention collective . BTP . prévoyance complémentaire:
|
||||
contrat salarié . convention collective . BTP . prévoyance complémentaire: oui
|
||||
|
||||
contrat salarié . convention collective . BTP . prévoyance complémentaire . ouvrier:
|
||||
valeur: oui
|
||||
applicable si: catégorie du salarié = 'ouvrier'
|
||||
remplace:
|
||||
- règle: prévoyance . employeur
|
||||
|
@ -82,6 +86,7 @@ contrat salarié . convention collective . BTP . prévoyance complémentaire . o
|
|||
plafond: 3 * plafond sécurité sociale
|
||||
|
||||
contrat salarié . convention collective . BTP . prévoyance complémentaire . etam:
|
||||
valeur: oui
|
||||
applicable si: catégorie du salarié = 'etam'
|
||||
remplace:
|
||||
- règle: prévoyance . employeur
|
||||
|
@ -100,6 +105,7 @@ contrat salarié . convention collective . BTP . prévoyance complémentaire . e
|
|||
plafond: 3 * plafond sécurité sociale
|
||||
|
||||
contrat salarié . convention collective . BTP . prévoyance complémentaire . cadre:
|
||||
valeur: oui
|
||||
applicable si: catégorie du salarié = 'cadre'
|
||||
remplace:
|
||||
- règle: prévoyance . employeur
|
||||
|
|
|
@ -56,9 +56,8 @@ contrat salarié . convention collective . SVP . prévoyance:
|
|||
contrat salarié . intermittents du spectacle:
|
||||
applicable si:
|
||||
toutes ces conditions:
|
||||
- CDD . motif . classique . usage
|
||||
- une de ces conditions:
|
||||
- convention collective . SVP
|
||||
- CDD . motif = 'classique . usage'
|
||||
- convention collective . SVP
|
||||
question: A quel statut d'intermittent est rattaché l'employé ?
|
||||
par défaut: "'technicien'"
|
||||
formule:
|
||||
|
@ -91,6 +90,7 @@ contrat salarié . intermittents du spectacle . retraite complémentaire technic
|
|||
une de ces conditions:
|
||||
- statut cadre
|
||||
- technicien
|
||||
formule: oui
|
||||
remplace:
|
||||
- règle: retraite complémentaire . employeur . taux tranche 1
|
||||
par: 3.94%
|
||||
|
@ -103,7 +103,7 @@ contrat salarié . intermittents du spectacle . technicien:
|
|||
formule: intermittents du spectacle = 'technicien'
|
||||
|
||||
contrat salarié . intermittents du spectacle . technicien . non cadre:
|
||||
applicable si: statut cadre = non
|
||||
formule: statut cadre = non
|
||||
remplace:
|
||||
- règle: retraite complémentaire . employeur . taux tranche 2
|
||||
par: 10.80%
|
||||
|
@ -140,7 +140,7 @@ contrat salarié . intermittents du spectacle . artiste:
|
|||
Article L7121-2: https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000032859810&cidTexte=LEGITEXT000006072050&dateTexte=20160709
|
||||
|
||||
contrat salarié . intermittents du spectacle . artiste . non cadre:
|
||||
applicable si: statut cadre = non
|
||||
formule: statut cadre = non
|
||||
remplace:
|
||||
- règle: plafond sécurité sociale
|
||||
par: plafond sécurité sociale temps plein
|
||||
|
|
|
@ -6,7 +6,7 @@ contrat salarié . convention collective . sport:
|
|||
L'entreprise dépend de la convention collective nationale des sportifs (CCNS)
|
||||
Les disciplines concernées sont tous les sports pour lesquels il existe une fédération française agréée par le ministère de la Jeunesse et des Sports.
|
||||
|
||||
contrat salarié . convention collective . sport . cotisations:
|
||||
contrat salarié . convention collective . sport . cotisations: oui
|
||||
|
||||
contrat salarié . convention collective . sport . cotisations . patronales:
|
||||
titre: cotisations conventionnelles
|
||||
|
@ -177,6 +177,7 @@ contrat salarié . convention collective . sport . exonération cotisation AT:
|
|||
remplace:
|
||||
règle: ATMP
|
||||
par: non
|
||||
formule: oui
|
||||
|
||||
contrat salarié . convention collective . sport . exonération cotisation AT . refus:
|
||||
titre: refus exonération AT
|
||||
|
|
|
@ -67,7 +67,7 @@ dirigeant . assimilé salarié . réduction ACRE . taux:
|
|||
- plafond: 100%
|
||||
taux: 0%
|
||||
dirigeant . assimilé salarié . réduction ACRE . notification taux annuel:
|
||||
applicable si: entreprise . ACRE
|
||||
formule: oui
|
||||
type: notification
|
||||
description: |
|
||||
Le taux ACRE utilisé est une moyenne annuelle. Le
|
||||
|
@ -87,12 +87,12 @@ dirigeant . auto-entrepreneur . base des cotisations:
|
|||
dirigeant . auto-entrepreneur . contrôle seuil de CA dépassé:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: base des cotisations > plafond
|
||||
formule: base des cotisations > plafond
|
||||
description: Le seuil annuel de chiffre d'affaires pour le régime de l'auto-entreprise est dépassé. [En savoir plus](/documentation/dirigeant/auto‑entrepreneur/plafond)
|
||||
|
||||
dirigeant . auto-entrepreneur . contrôle seuil de TVA dépassé:
|
||||
type: notification
|
||||
applicable si: entreprise . chiffre d'affaires > entreprise . franchise de TVA
|
||||
formule: entreprise . chiffre d'affaires > entreprise . franchise de TVA
|
||||
description: Le seuil annuel de chiffre d'affaires pour la franchise de TVA est dépassé. [En savoir plus](/documentation/entreprise/franchise-de-TVA)
|
||||
|
||||
dirigeant . auto-entrepreneur . plafond:
|
||||
|
@ -304,15 +304,14 @@ dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . pla
|
|||
formule: plafond sécurité sociale temps plein / impôt . abattement . taux inversé
|
||||
|
||||
dirigeant . auto-entrepreneur . notification calcul ACRE annuel:
|
||||
applicable si: entreprise . ACRE
|
||||
formule: entreprise . ACRE
|
||||
type: notification
|
||||
description: |
|
||||
Le taux ACRE utilisé est celui correspondant au mois courant. Le
|
||||
simulateur ne prends pas encore en compte le chevauchement de 2 période
|
||||
d'acre sur une meme année.
|
||||
|
||||
dirigeant . auto-entrepreneur . impôt:
|
||||
|
||||
dirigeant . auto-entrepreneur . impôt: oui
|
||||
dirigeant . auto-entrepreneur . impôt . abattement:
|
||||
formule:
|
||||
produit:
|
||||
|
@ -345,7 +344,7 @@ dirigeant . auto-entrepreneur . impôt . versement libératoire:
|
|||
|
||||
dirigeant . auto-entrepreneur . impôt . versement libératoire . contrôle seuil:
|
||||
type: notification
|
||||
applicable si:
|
||||
formule:
|
||||
toutes ces conditions:
|
||||
- impôt . foyer fiscal . revenu fiscal de référence > 27086 €/an
|
||||
- versement libératoire
|
||||
|
@ -442,7 +441,7 @@ dirigeant . indépendant:
|
|||
|
||||
dirigeant . indépendant . avertissement base forfaitaire:
|
||||
type: notification
|
||||
applicable si:
|
||||
formule:
|
||||
toutes ces conditions:
|
||||
- entreprise . durée d'activité . en fin d'année < 2 ans
|
||||
- entreprise . date de création != 02/01/2020
|
||||
|
@ -833,17 +832,18 @@ dirigeant . indépendant . cotisations et contributions . déduction tabac . rev
|
|||
|
||||
dirigeant . indépendant . contrats madelin:
|
||||
titre: Contrats Madelin
|
||||
|
||||
question: Avez-vous souscrit à des contrats de complémentaire privée dits ("contrats Madelins")
|
||||
par défaut: non
|
||||
dirigeant . indépendant . contrats madelin . montant:
|
||||
titre: Somme des cotisations à contrats Madelin
|
||||
formule:
|
||||
somme:
|
||||
- mutuelle . montant
|
||||
- retraite . montant
|
||||
- mutuelle
|
||||
- retraite
|
||||
|
||||
dirigeant . indépendant . contrats madelin . contrôle montant charges:
|
||||
type: notification
|
||||
applicable si: entreprise . charges < montant
|
||||
formule: entreprise . charges < montant
|
||||
description: >-
|
||||
Le montant de l'ensemble des cotisations à vos contrats Madelin
|
||||
doit être inclus dans vos charges de fonctionnement, or vous
|
||||
|
@ -853,9 +853,9 @@ dirigeant . indépendant . contrats madelin . part déductible fiscalement:
|
|||
titre: Part de la cotisation à contrat Madelin qui est déductible fiscalement
|
||||
formule:
|
||||
somme:
|
||||
- valeur: mutuelle . montant
|
||||
- valeur: mutuelle
|
||||
plafond: mutuelle . plafond
|
||||
- valeur: retraite . montant
|
||||
- valeur: retraite
|
||||
plafond: retraite . plafond
|
||||
|
||||
dirigeant . indépendant . contrats madelin . part non-déductible fiscalement:
|
||||
|
@ -863,9 +863,6 @@ dirigeant . indépendant . contrats madelin . part non-déductible fiscalement:
|
|||
formule: montant - part déductible fiscalement
|
||||
|
||||
dirigeant . indépendant . contrats madelin . mutuelle:
|
||||
titre: Contrat Madelin mutuelle
|
||||
|
||||
dirigeant . indépendant . contrats madelin . mutuelle . montant:
|
||||
titre: Souscription à un contrat de mutuelle Madelin
|
||||
question: Quel est le montant que vous versez à un contrat de mutuelle Madelin ?
|
||||
description: |
|
||||
|
@ -876,7 +873,7 @@ dirigeant . indépendant . contrats madelin . mutuelle . montant:
|
|||
Fiche impôts: https://www.impots.gouv.fr/portail/particulier/questions/je-cotise-un-contrat-madelin-quel-est-mon-avantage-fiscal
|
||||
Bofip (contrats d'assurance de groupe): https://bofip.impots.gouv.fr/bofip/4639-PGP.html
|
||||
Article de loi: https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000029042287&cidTexte=LEGITEXT000006069577&dateTexte=20140530&fastReqId=1900907951&nbResultRech=1
|
||||
par défaut: 0 €/an
|
||||
par défaut: 50 €/mois
|
||||
|
||||
dirigeant . indépendant . contrats madelin . mutuelle . plafond:
|
||||
unité: €/an
|
||||
|
@ -897,9 +894,6 @@ dirigeant . indépendant . contrats madelin . mutuelle . plafond:
|
|||
Réassurez-moi: https://reassurez-moi.fr/guide/pro/tns/plafond#le_plafond_de_deduction_madelin_pour_une_mutuelle_santenbsp
|
||||
|
||||
dirigeant . indépendant . contrats madelin . retraite:
|
||||
titre: Contrat Madelin retraite
|
||||
|
||||
dirigeant . indépendant . contrats madelin . retraite . montant:
|
||||
titre: Souscription à une retraite Madelin
|
||||
question: Quel est le montant que vous versez à votre contrat Madelin retraite ?
|
||||
description: |
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
entreprise:
|
||||
valeur: oui
|
||||
description: |
|
||||
Le contrat lie une entreprise, identifiée par un code SIREN, et un employé.
|
||||
|
||||
|
@ -23,13 +24,13 @@ entreprise . date de création:
|
|||
entreprise . date de création . contrôle date future:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: date de création > 01/2021
|
||||
formule: date de création > 01/2021
|
||||
description: Nous ne pouvons voir aussi loin dans le futur
|
||||
|
||||
entreprise . date de création . contrôle date passée:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: date de création < 01/1900
|
||||
formule: date de création < 01/1900
|
||||
description: Il s'agit d'une très vieille entreprise ! Êtes-vous sûr de ne pas vous être trompé dans la saisie ?
|
||||
|
||||
entreprise . durée d'activité:
|
||||
|
@ -228,7 +229,7 @@ entreprise . effectif:
|
|||
alors: 49 employés
|
||||
- si: entreprise . effectif . seuil = 'moins de 250'
|
||||
alors: 249 employés
|
||||
- si: entreprise . effectif . seuil = '251 et plus'
|
||||
- si: entreprise . effectif . seuil = 'plus de 250'
|
||||
alors: 251 employés
|
||||
|
||||
entreprise . effectif . seuil:
|
||||
|
@ -249,7 +250,7 @@ entreprise . effectif . seuil:
|
|||
- moins de 20
|
||||
- moins de 50
|
||||
- moins de 250
|
||||
- 251 et plus
|
||||
- plus de 250
|
||||
par défaut: "'moins de 5'"
|
||||
|
||||
entreprise . effectif . seuil . moins de 5:
|
||||
|
@ -257,7 +258,8 @@ entreprise . effectif . seuil . moins de 11:
|
|||
entreprise . effectif . seuil . moins de 20:
|
||||
entreprise . effectif . seuil . moins de 50:
|
||||
entreprise . effectif . seuil . moins de 250:
|
||||
entreprise . effectif . seuil . 251 et plus:
|
||||
entreprise . effectif . seuil . plus de 250:
|
||||
titre: 251 et plus
|
||||
|
||||
entreprise . ratio alternants:
|
||||
question: Quelle est la fraction de contrats d'alternance dans l'effectif moyen de l'entreprise ?
|
||||
|
@ -445,7 +447,8 @@ entreprise . auto entreprise impossible:
|
|||
- rattachée à la CIPAV != oui
|
||||
note: D'autres conditions d'exclusions existent, il faudra les compléter, mais la question de la catégorie d'activité doit avant être complétée.
|
||||
|
||||
établissement:
|
||||
établissement:
|
||||
formule: oui
|
||||
description: |
|
||||
Le salarié travaille dans un établissement de l'entreprise, identifié par un code SIRET.
|
||||
|
||||
|
|
|
@ -378,7 +378,7 @@ impôt . foyer fiscal . revenu imposable . revenu d'activité abattu:
|
|||
- contrat salarié . rémunération . net imposable
|
||||
- dirigeant . indépendant . résultat fiscal
|
||||
abattement:
|
||||
valeur: 0.1 * assiette
|
||||
valeur: 10% * assiette
|
||||
plafond: 12502 €/an
|
||||
plancher: 437 €/an
|
||||
références:
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
// Currenty we systematically bundle all the rules even if we only need a
|
||||
// sub-section of them. We might support "code-splitting" the rules in the
|
||||
// future.
|
||||
import {
|
||||
EvaluatedRule as GenericEvaluatedRule,
|
||||
ParsedRule as GenericParsedRule,
|
||||
ParsedRules as GenericParsedRules,
|
||||
Rules as GenericRules
|
||||
} from 'publicodes'
|
||||
import jsonRules from '../types/dottednames.json'
|
||||
import artisteAuteur from './artiste-auteur.yaml'
|
||||
import base from './base.yaml'
|
||||
import chômagePartiel from './chômage-partiel.yaml'
|
||||
|
@ -17,23 +12,17 @@ import CCOptique from './conventions-collectives/optique.yaml'
|
|||
import CCSpectacleVivant from './conventions-collectives/spectacle-vivant.yaml'
|
||||
import CCSport from './conventions-collectives/sport.yaml'
|
||||
import dirigeant from './dirigeant.yaml'
|
||||
import jsonRules from '../types/dottednames.json'
|
||||
import déclarationIndépendant from './déclaration-revenu-indépendant.yaml'
|
||||
import professionLibérale from './profession-libérale.yaml'
|
||||
import entrepriseEtablissement from './entreprise-établissement.yaml'
|
||||
import impot from './impôt.yaml'
|
||||
import professionLibérale from './profession-libérale.yaml'
|
||||
import protectionSociale from './protection-sociale.yaml'
|
||||
import salarié from './salarié.yaml'
|
||||
import situationPersonnelle from './situation-personnelle.yaml'
|
||||
|
||||
export type DottedName = keyof typeof jsonRules
|
||||
export type Rules = GenericRules<DottedName>
|
||||
export type ParsedRules = GenericParsedRules<DottedName>
|
||||
export type ParsedRule = GenericParsedRule<DottedName>
|
||||
export type EvaluatedRule = GenericEvaluatedRule<DottedName>
|
||||
export type Situation = Partial<Record<DottedName, string>>
|
||||
|
||||
const rules: Rules = {
|
||||
const rules = {
|
||||
...base,
|
||||
// TODO: rule order shouldn't matter but there is a bug if "impot" is after
|
||||
// "dirigeant".
|
||||
|
@ -44,7 +33,7 @@ const rules: Rules = {
|
|||
...professionLibérale,
|
||||
...entrepriseEtablissement,
|
||||
...protectionSociale,
|
||||
... salarié,
|
||||
...salarié,
|
||||
...CCBatiment,
|
||||
...CCHotels,
|
||||
...CCOptique,
|
||||
|
|
|
@ -396,7 +396,7 @@ dirigeant . indépendant . PL . PAMC . proportion recette activité non conventi
|
|||
dirigeant . indépendant . PL . PAMC . proportion recette activité non conventionnée . notification:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: proportion recette activité non conventionnée > 100%
|
||||
formule: proportion recette activité non conventionnée > 100%
|
||||
description: |
|
||||
La proportion ne peut pas être supérieure à 100%
|
||||
|
||||
|
@ -589,7 +589,7 @@ dirigeant . indépendant . PL . PAMC . assiette participation chirurgien-dentist
|
|||
par défaut: 1
|
||||
|
||||
dirigeant . indépendant . PL . PAMC . assiette participation chirurgien-dentiste . taux Urssaf . notification:
|
||||
applicable si: taux Urssaf >= 100
|
||||
formule: taux Urssaf >= 100
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
description: Le taux Urssaf doit être inférieur à 100
|
||||
|
@ -881,13 +881,13 @@ dirigeant . indépendant . PL . CARCDSF . retraite complémentaire . cotisation
|
|||
arrondi: oui
|
||||
|
||||
dirigeant . indépendant . PL . CARCDSF . retraite complémentaire . cotisation forfaitaire . réduction applicable:
|
||||
applicable si: assiette des cotisations < 85% * plafond sécurité sociale temps plein
|
||||
formule: assiette des cotisations < 85% * plafond sécurité sociale temps plein
|
||||
description: |
|
||||
Vous avez la possibilité de bénéficier d'une réduction de cotisation
|
||||
pour la retraite complémentaire si vous en faites la demande. [En savoir
|
||||
plus](/documentation/dirigeant/indépendant/PL/CARCDSF/retraite-complémentaire/cotisation-forfaitaire/taux-réduction)
|
||||
type: notification
|
||||
formule: oui
|
||||
|
||||
|
||||
dirigeant . indépendant . PL . CARCDSF . retraite complémentaire . cotisation forfaitaire . taux réduction:
|
||||
applicable si: réduction applicable
|
||||
|
@ -957,8 +957,7 @@ dirigeant . indépendant . PL . CARCDSF . chirurgien-dentiste . PCV . participat
|
|||
|
||||
dirigeant . indépendant . PL . CARCDSF . chirurgien-dentiste . exonération PCV:
|
||||
type: notification
|
||||
applicable si: (assiette des cotisations / prix d'une consultation) <= 500 consultation/an
|
||||
formule: oui
|
||||
formule: (assiette des cotisations / prix d'une consultation) <= 500 consultation/an
|
||||
description: >-
|
||||
Vous avez la possibilité de bénéficier d'une exonération totale de
|
||||
cotisation pour la prestation complémentaire de vieillesse (PCV) si vous en
|
||||
|
@ -1043,8 +1042,7 @@ dirigeant . indépendant . PL . CARCDSF . sage-femme . PCV:
|
|||
|
||||
dirigeant . indépendant . PL . CARCDSF . sage-femme . exonération PCV:
|
||||
type: notification
|
||||
applicable si: assiette des cotisations <= 3120 €/an
|
||||
formule: oui
|
||||
formule: assiette des cotisations <= 3120 €/an
|
||||
description: >-
|
||||
Vous avez la possibilité de bénéficier d'une exonération totale de
|
||||
cotisation pour la prestation complémentaire de vieillesse (PCV) si vous en
|
||||
|
|
|
@ -101,7 +101,7 @@ contrat salarié . frais professionnels . titres-restaurant . montant:
|
|||
formule:
|
||||
produit:
|
||||
assiette: montant unitaire
|
||||
facteur: titres-restaurant par mois
|
||||
facteur: nombre
|
||||
composantes:
|
||||
- attributs:
|
||||
nom: employeur
|
||||
|
@ -116,12 +116,12 @@ contrat salarié . frais professionnels . titres-restaurant . part déductible:
|
|||
valeur: montant . employeur
|
||||
plafond:
|
||||
produit:
|
||||
assiette: titres-restaurant par mois
|
||||
assiette: nombre
|
||||
facteur: 5.55 €/titres-restaurant
|
||||
références:
|
||||
urssaf.fr: https://www.urssaf.fr/portail/home/taux-et-baremes/frais-professionnels/les-titres-restaurant.html
|
||||
|
||||
contrat salarié . frais professionnels . titres-restaurant . titres-restaurant par mois:
|
||||
contrat salarié . frais professionnels . titres-restaurant . nombre:
|
||||
question: Combien de titres-restaurant sont distribués au salarié ?
|
||||
par défaut: 19 titres-restaurant/mois
|
||||
|
||||
|
@ -150,13 +150,13 @@ contrat salarié . frais professionnels . titres-restaurant . taux participation
|
|||
contrat salarié . frais professionnels . titres-restaurant . contrôle taux employeur min:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: taux participation employeur < 50%
|
||||
formule: taux participation employeur < 50%
|
||||
description: La part employeur du titre-restaurant doit être de 50% au minimum
|
||||
|
||||
contrat salarié . frais professionnels . titres-restaurant . contrôle taux employeur max:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: taux participation employeur > 60%
|
||||
formule: taux participation employeur > 60%
|
||||
description: La part employeur du titre-restaurant doit être de 60% au maximum
|
||||
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo:
|
||||
|
@ -253,7 +253,7 @@ contrat salarié . activité partielle . heures travaillées:
|
|||
contrat salarié . activité partielle . heures travaillées . contrôle temps de travail:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: heures travaillées > temps de travail . temps contractuel
|
||||
formule: heures travaillées > temps de travail . temps contractuel
|
||||
description: >-
|
||||
Dans le cadre de l'activité partielle, le temps de travail doit être inférieur
|
||||
à celui inscrit dans le contrat de travail.
|
||||
|
@ -471,7 +471,7 @@ contrat salarié . CDD . taxe forfaitaire sur les CDD d'usage:
|
|||
|
||||
Certains secteurs d'activités définis dans le code du travail ne sont pas
|
||||
concernés par cette taxe.
|
||||
applicable si: motif . classique . usage
|
||||
applicable si: motif = 'classique . usage'
|
||||
# TODO: cette formule ne fonctionne pas pour des contrats dont la durée est
|
||||
# inférieure à un mois
|
||||
formule: 10 € / durée contrat
|
||||
|
@ -489,7 +489,7 @@ contrat salarié . CDD . CPF:
|
|||
- événement . poursuite du CDD en CDI
|
||||
- apprentissage
|
||||
- contrat jeune vacances
|
||||
- motif . classique . saisonnier
|
||||
- motif = 'classique . saisonnier'
|
||||
- motif . contrat aidé
|
||||
formule:
|
||||
produit:
|
||||
|
@ -641,8 +641,8 @@ contrat salarié . CDD . prime de fin de contrat:
|
|||
- événement . rupture pour faute grave ou force majeure
|
||||
- événement . rupture pendant période essai
|
||||
|
||||
- motif . classique . usage
|
||||
- motif . classique . saisonnier
|
||||
- motif = 'classique . usage'
|
||||
- motif = 'classique . saisonnier'
|
||||
- motif . complément formation
|
||||
- motif . contrat aidé
|
||||
|
||||
|
@ -972,7 +972,7 @@ contrat salarié . CDD . congés non pris:
|
|||
contrat salarié . CDD . contrôle congés non pris max:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: congés non pris > congés dus en jours ouvrés
|
||||
formule: congés non pris > congés dus en jours ouvrés
|
||||
description: Un salarié acquiert normalement 2.08 jours de congés ouvrés par mois.
|
||||
|
||||
contrat salarié . CDD . contrat jeune vacances:
|
||||
|
@ -1043,13 +1043,12 @@ contrat salarié . apprentissage . ancienneté . moins de trois ans:
|
|||
|
||||
contrat salarié . apprentissage . ancienneté . moins de quatre ans:
|
||||
formule: ancienneté = 'moins de quatre ans'
|
||||
|
||||
contrat salarié . apprentissage . ancienneté . moins de quatre ans . information:
|
||||
type: notification
|
||||
description: >-
|
||||
La durée maximale du contrat peut être portée à 4 ans lorsque la qualité de
|
||||
travailleur handicapé est reconnue à l'apprenti.
|
||||
|
||||
|
||||
contrat salarié . professionnalisation:
|
||||
description: |
|
||||
Le contrat de professionnalisation est un contrat de travail en alternance
|
||||
|
@ -1130,6 +1129,7 @@ contrat salarié . CDD:
|
|||
|
||||
contrat salarié . CDD . information:
|
||||
type: notification
|
||||
formule: oui
|
||||
description: >-
|
||||
Rappelez-vous qu'un CDD doit toujours correspondre à un besoin temporaire de l'entreprise.
|
||||
[Code du travail - Article L1242-1](https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000006901194&cidTexte=LEGITEXT000006072050)
|
||||
|
@ -1214,12 +1214,12 @@ contrat salarié . rémunération . brut de base:
|
|||
contrat salarié . rémunération . contrôle smic:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: assiette de vérification du SMIC < SMIC contractuel
|
||||
formule: assiette de vérification du SMIC < SMIC contractuel
|
||||
description: Le salaire saisi est inférieur au SMIC.
|
||||
|
||||
contrat salarié . rémunération . contrôle salaire élevé:
|
||||
type: notification
|
||||
applicable si:
|
||||
formule:
|
||||
toutes ces conditions:
|
||||
- brut de base >= 10000 €/mois
|
||||
- dirigeant = non
|
||||
|
@ -1670,6 +1670,7 @@ contrat salarié . cotisations . patronales:
|
|||
- (- réductions de cotisations)
|
||||
|
||||
contrat salarié . rémunération:
|
||||
formule: oui
|
||||
description: La rémunération se distingue du salaire en incluant les avantages non monétaires versés en contrepartie du travail. Elle est donc plus large que les sommes d'argent versées au salarié.
|
||||
|
||||
contrat salarié . rémunération . net de cotisations:
|
||||
|
@ -1715,17 +1716,13 @@ contrat salarié . rémunération . net imposable . base:
|
|||
- CSG et CRDS . non déductible
|
||||
|
||||
contrat salarié . rémunération . net imposable . heures supplémentaires et complémentaires défiscalisées:
|
||||
unité: €/mois
|
||||
formule:
|
||||
valeur:
|
||||
somme:
|
||||
- heures supplémentaires
|
||||
- heures complémentaires
|
||||
plafond: plafond brut
|
||||
références:
|
||||
DSN: https://dsn-info.custhelp.com/app/answers/detail/a_id/2110
|
||||
|
||||
contrat salarié . rémunération . net imposable . heures supplémentaires et complémentaires défiscalisées . plafond brut:
|
||||
formule: 5358 €/an
|
||||
plafond: 5358 €/an
|
||||
références:
|
||||
DSN: https://dsn-info.custhelp.com/app/answers/detail/a_id/2110
|
||||
|
||||
|
@ -1931,11 +1928,6 @@ contrat salarié . aides employeur . aide à l'embauche d'apprentis:
|
|||
- entreprise . effectif < 250
|
||||
- apprentissage
|
||||
- apprentissage . diplôme préparé . niveau bac ou moins
|
||||
|
||||
# HACK: "apprentissage . ancienneté" n'est pas détecté par le moteur dans les dépendances ("missingVariables") de cette aide.
|
||||
# On l'ajoute ici uniquement pour faire remonter la question au bon niveau, mais ça ne devrait pas être nécessaire.
|
||||
- apprentissage . ancienneté
|
||||
|
||||
formule:
|
||||
variations:
|
||||
- si: apprentissage . ancienneté = 'moins d'un an'
|
||||
|
@ -2115,13 +2107,13 @@ contrat salarié . temps de travail . temps partiel . heures par semaine:
|
|||
contrat salarié . temps de travail . temps partiel . contrôle temps min:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: heures par semaine < 24
|
||||
formule: heures par semaine < 24 heures/semaine
|
||||
description: Le nombre minimum d'heures par semaine est 24. Il est possible de descendre plus bas dans certains cas seulement. [Plus d'infos](https://www.service-public.fr/particuliers/vosdroits/F32428).
|
||||
|
||||
contrat salarié . temps de travail . temps partiel . contrôle temps max:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: heures par semaine >= base légale
|
||||
formule: heures par semaine >= base légale
|
||||
description: Un temps partiel doit être en dessous de la durée de travail légale (35h)
|
||||
|
||||
contrat salarié . temps de travail . quotité de travail:
|
||||
|
@ -2152,7 +2144,7 @@ contrat salarié . temps de travail . heures supplémentaires:
|
|||
|
||||
contrat salarié . temps de travail . contrôle 44h max:
|
||||
type: notification
|
||||
applicable si:
|
||||
formule:
|
||||
toutes ces conditions:
|
||||
- heures supplémentaires > 9 heures/semaine * période . semaines par mois
|
||||
- heures supplémentaires <= 13 heures/semaine * période . semaines par mois
|
||||
|
@ -2161,7 +2153,7 @@ contrat salarié . temps de travail . contrôle 44h max:
|
|||
contrat salarié . temps de travail . contrôle 48h max:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: heures supplémentaires > 13 heures/semaine * période . semaines par mois
|
||||
formule: heures supplémentaires > 13 heures/semaine * période . semaines par mois
|
||||
description: La durée hebdomadaire maximale de travail ne peut pas dépasser 48h
|
||||
|
||||
contrat salarié . temps de travail . heures supplémentaires . majoration:
|
||||
|
@ -2195,14 +2187,14 @@ contrat salarié . temps de travail . heures complémentaires:
|
|||
|
||||
contrat salarié . temps de travail . contrôle heures complémentaires 10 pourcents:
|
||||
type: notification
|
||||
applicable si: heures complémentaires > heures complémentaires . seuil légal
|
||||
formule: heures complémentaires > heures complémentaires . seuil légal
|
||||
description: Sauf disposition conventionnelle, le nombre d'heures complémentaires ne peut être supérieur à un dixième de la durée contractuelle du temps partiel.
|
||||
|
||||
# TODO: Le système d'unité ne fait pas la conversion mois/semaines automatiquement donc nous devons ajouter un terme "semaines par mois" manuellement
|
||||
contrat salarié . temps de travail . contrôle heures complémentaires max:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: heures complémentaires + temps partiel . heures par semaine * période . semaines par mois >= base légale * période . semaines par mois
|
||||
formule: heures complémentaires + temps partiel . heures par semaine * période . semaines par mois >= base légale * période . semaines par mois
|
||||
description: Les heures complémentaires ne doivent pas amener le salarié à travailler pour une durée supérieure ou égale à la durée légale du travail (35h)
|
||||
|
||||
contrat salarié . temps de travail . heures complémentaires . majoration:
|
||||
|
@ -2476,15 +2468,11 @@ contrat salarié . retraite complémentaire:
|
|||
contrat salarié . retraite supplémentaire:
|
||||
formule:
|
||||
somme:
|
||||
- employeur
|
||||
- salarié
|
||||
- nom: employeur
|
||||
valeur: 0€/mois
|
||||
- nom: salarié
|
||||
valeur: 0€/mois
|
||||
|
||||
contrat salarié . retraite supplémentaire . employeur:
|
||||
titre: Retraite supplémentaire employeur
|
||||
formule: 0€/mois
|
||||
|
||||
contrat salarié . retraite supplémentaire . salarié:
|
||||
formule: 0€/mois
|
||||
|
||||
contrat salarié . retraite supplémentaire . part déductible:
|
||||
formule:
|
||||
|
@ -2646,7 +2634,7 @@ contrat salarié . complémentaire santé . part employeur:
|
|||
contrat salarié . complémentaire santé . part employeur min:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: part employeur < 50%
|
||||
formule: part employeur < 50%
|
||||
description: La part employeur de la complémentaire santé doit être de 50% au minimum
|
||||
|
||||
contrat salarié . complémentaire santé . part salarié:
|
||||
|
@ -2683,7 +2671,7 @@ contrat salarié . complémentaire santé . forfait:
|
|||
contrat salarié . complémentaire santé . contrôle min:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: complémentaire santé . forfait < 15 €/mois
|
||||
formule: complémentaire santé . forfait < 15 €/mois
|
||||
description: Vérifiez bien qu'une complémentaire santé si peu chère couvre le panier de soin minimal défini dans la loi.
|
||||
|
||||
contrat salarié . régime alsace moselle:
|
||||
|
@ -2708,8 +2696,8 @@ contrat salarié . contribution au dialogue social:
|
|||
Anciennement 'contribution patronale au financement des organisations syndicales'
|
||||
|
||||
références:
|
||||
- https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-contribution-patronale-au-dia.html
|
||||
- https://www.service-public.fr/professionnels-entreprises/vosdroits/F33308
|
||||
urssaf.fr: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-contribution-patronale-au-dia.html
|
||||
service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F33308
|
||||
|
||||
formule:
|
||||
produit:
|
||||
|
@ -3022,7 +3010,8 @@ contrat salarié . maladie:
|
|||
nom: employeur
|
||||
composantes:
|
||||
- attributs:
|
||||
nom: maladie, maternité, invalidité, décès
|
||||
titre: maladie, maternité, invalidité, décès
|
||||
nom: base
|
||||
taux: taux employeur
|
||||
- attributs:
|
||||
nom: contribution solidarité autonomie
|
||||
|
@ -3097,15 +3086,11 @@ contrat salarié . participation effort de construction:
|
|||
contrat salarié . prévoyance:
|
||||
formule:
|
||||
somme:
|
||||
- employeur
|
||||
- salarié
|
||||
- nom: employeur
|
||||
formule: 0 €/mois
|
||||
- nom: salarié
|
||||
formule: 0 €/mois
|
||||
|
||||
contrat salarié . prévoyance . employeur:
|
||||
titré: Prévoyance employeur
|
||||
formule: 0 €/mois
|
||||
|
||||
contrat salarié . prévoyance . salarié:
|
||||
formule: 0 €/mois
|
||||
|
||||
contrat salarié . prévoyance . part déductible:
|
||||
formule:
|
||||
|
@ -3293,6 +3278,7 @@ contrat salarié . profession spécifique:
|
|||
- pilote de ligne ou personnel navigant
|
||||
|
||||
contrat salarié . profession spécifique . journaliste:
|
||||
formule: contrat salarié . profession spécifique = 'journaliste'
|
||||
icônes: ✒
|
||||
description: >-
|
||||
Concerne les journalistes, rédacteurs, photographes, directeurs de journaux
|
||||
|
@ -3502,6 +3488,7 @@ contrat salarié . maladie . taux domiciliation fiscale étranger:
|
|||
formule: 5.50%
|
||||
|
||||
contrat salarié . lodeom:
|
||||
valeur: oui
|
||||
description: |
|
||||
Un ensemble assez complexe de réductions de cotisation est disponible pour les salariés d'outre-mer.
|
||||
Leur fonctionnement est similaire à celui de la réduction générale sur les bas salaires : pour un certain salaire donné, 100% de réduction.
|
||||
|
@ -3739,10 +3726,7 @@ contrat salarié . convention collective:
|
|||
contrat salarié . convention collective . contrôle décharge:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si:
|
||||
toutes ces conditions:
|
||||
- convention collective != non
|
||||
- convention collective != 'droit commun'
|
||||
formule: convention collective != 'droit commun'
|
||||
description: >-
|
||||
Attention, l'implémentation des conventions collective est encore partielle
|
||||
et non vérifiée. Néanmoins, cela permet d'obtenir une première estimation,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
situation personnelle:
|
||||
situation personnelle: oui
|
||||
|
||||
situation personnelle . RSA:
|
||||
titre: bénéficiaire RSA ou prime d'activité
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DottedName, Situation } from '../rules/index'
|
||||
import { DottedName } from '../rules/index'
|
||||
import { createSelector } from 'reselect'
|
||||
import { RootState, SimulationConfig } from 'Reducers/rootReducer'
|
||||
|
||||
|
@ -16,7 +16,7 @@ export const objectifsSelector = createSelector([configSelector], config => {
|
|||
return objectifs
|
||||
})
|
||||
|
||||
const emptySituation: Situation = {}
|
||||
const emptySituation: Partial<Record<DottedName, string | number | Object>> = {}
|
||||
|
||||
export const situationSelector = (state: RootState) =>
|
||||
state.simulation?.situation ?? emptySituation
|
||||
|
|
|
@ -8,13 +8,14 @@ import {
|
|||
import { SitePathsContext } from 'Components/utils/SitePathsContext'
|
||||
import 'iframe-resizer'
|
||||
import Engine from 'publicodes'
|
||||
import { Rule } from 'publicodes/dist/types/rule'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import createSentryMiddleware from 'redux-sentry-middleware'
|
||||
import { Rules } from 'Rules'
|
||||
import { DottedName } from 'Rules'
|
||||
import {
|
||||
configSituationSelector,
|
||||
situationSelector
|
||||
|
@ -81,7 +82,7 @@ const middlewares = [
|
|||
|
||||
type RootProps = {
|
||||
basename: ProviderProps['basename']
|
||||
rules: Rules
|
||||
rules: Record<DottedName, Rule>
|
||||
}
|
||||
|
||||
export default function Root({ basename, rules }: RootProps) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { goBackToSimulation } from 'Actions/actions'
|
||||
import SearchButton from 'Components/SearchButton'
|
||||
import * as Animate from 'Components/ui/animate'
|
||||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import { ScrollToTop } from 'Components/utils/Scroll'
|
||||
import { SitePathsContext } from 'Components/utils/SitePathsContext'
|
||||
import { Documentation, getDocumentationSiteMap } from 'publicodes'
|
||||
|
@ -16,7 +16,7 @@ export default function RulePage() {
|
|||
const currentSimulation = useSelector(
|
||||
(state: RootState) => !!state.simulation?.url
|
||||
)
|
||||
const engine = useContext(EngineContext)
|
||||
const engine = useEngine()
|
||||
const documentationPath = useContext(SitePathsContext).documentation.index
|
||||
const { pathname } = useLocation()
|
||||
const documentationSitePaths = useMemo(
|
||||
|
|
|
@ -3,11 +3,11 @@ import Aide from 'Components/conversation/Aide'
|
|||
import { Explicable, ExplicableRule } from 'Components/conversation/Explicable'
|
||||
import 'Components/TargetSelection.css'
|
||||
import Warning from 'Components/ui/WarningBlock'
|
||||
import { useEvaluation, EngineContext } from 'Components/utils/EngineContext'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import { ScrollToTop } from 'Components/utils/Scroll'
|
||||
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
|
||||
import RuleInput from 'Components/conversation/RuleInput'
|
||||
import { ParsedRule } from 'publicodes'
|
||||
import { EvaluatedRule, evaluateRule } from 'publicodes'
|
||||
import { Fragment, useCallback, useEffect, useState, useContext } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
@ -111,7 +111,6 @@ export default function AideDéclarationIndépendant() {
|
|||
</Trans>
|
||||
<BigInput>
|
||||
<RuleInput
|
||||
rules={rules}
|
||||
dottedName="dirigeant . rémunération totale"
|
||||
onChange={setCurrentIncome}
|
||||
value={currentIncome}
|
||||
|
@ -320,7 +319,10 @@ function SubSection({
|
|||
...(Object.keys(situation) as Array<DottedName>),
|
||||
...nextSteps
|
||||
].filter(nextStep => {
|
||||
const { dottedName, question } = parsedRules[nextStep]
|
||||
const {
|
||||
dottedName,
|
||||
rawNode: { question }
|
||||
} = parsedRules[nextStep]
|
||||
return !!question && dottedName.startsWith(sectionDottedName)
|
||||
})
|
||||
|
||||
|
@ -336,13 +338,13 @@ function SubSection({
|
|||
|
||||
type SimpleFieldProps = {
|
||||
dottedName: DottedName
|
||||
summary?: ParsedRule['summary']
|
||||
question?: ParsedRule['question']
|
||||
summary?: EvaluatedRule['résumé']
|
||||
question?: EvaluatedRule['question']
|
||||
}
|
||||
function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
|
||||
const dispatch = useDispatch()
|
||||
const evaluatedRule = useEvaluation(dottedName)
|
||||
const rules = useContext(EngineContext).getParsedRules()
|
||||
const engine = useContext(EngineContext)
|
||||
const evaluatedRule = evaluateRule(engine, dottedName)
|
||||
const value = useSelector(situationSelector)[dottedName]
|
||||
const [currentValue, setCurrentValue] = useState(value)
|
||||
|
||||
|
@ -365,8 +367,11 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
|
|||
setCurrentValue(value)
|
||||
}, [value])
|
||||
if (
|
||||
evaluatedRule.isApplicable === false ||
|
||||
evaluatedRule.isApplicable === null
|
||||
// TODO
|
||||
// evaluatedRule.isApplicable === false ||
|
||||
// evaluatedRule.isApplicable === null
|
||||
evaluatedRule.nodeValue === false ||
|
||||
evaluatedRule.nodeValue === null
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
@ -388,10 +393,9 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
|
|||
{question ?? evaluatedRule.question}
|
||||
<ExplicableRule dottedName={dottedName} />
|
||||
</p>
|
||||
<p className="ui__ notice">{summary ?? evaluatedRule.summary}</p>
|
||||
<p className="ui__ notice">{summary ?? evaluatedRule.résumé}</p>
|
||||
</div>
|
||||
<RuleInput
|
||||
rules={rules}
|
||||
dottedName={dottedName}
|
||||
onChange={update}
|
||||
value={currentValue}
|
||||
|
@ -403,9 +407,9 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
|
|||
}
|
||||
|
||||
function Results() {
|
||||
const results = useEvaluation(
|
||||
simulationConfig.objectifs as Array<DottedName>,
|
||||
{ unit: '€/an' }
|
||||
const engine = useEngine()
|
||||
const results = (simulationConfig.objectifs as DottedName[]).map(objectif =>
|
||||
evaluateRule(engine, objectif, { unité: '€/an' })
|
||||
)
|
||||
const onGoingComputation = !results.filter(node => node.nodeValue != null)
|
||||
.length
|
||||
|
@ -434,7 +438,7 @@ function Results() {
|
|||
{results.map(r => (
|
||||
<Fragment key={r.title}>
|
||||
<h4>
|
||||
{r.title} <small>{r.summary}</small>
|
||||
{r.title} <small>{r.résumé}</small>
|
||||
</h4>
|
||||
{r.description && <p className="ui__ notice">{r.description}</p>}
|
||||
<p className="ui__ lead" css="margin-bottom: 1rem;">
|
||||
|
|
|
@ -231,14 +231,14 @@ activité transfrontalière simultanée . activité non salariée . nombre:
|
|||
activité transfrontalière simultanée . activité non salariée . nombre max:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: nombre > 2
|
||||
formule: nombre > 2
|
||||
description: >
|
||||
Ce formulaire ne permet pas de déclarer une activité dans plus de 3 pays
|
||||
|
||||
activité transfrontalière simultanée . activité non salariée . nombre min:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
applicable si: nombre < 1
|
||||
formule: nombre < 1
|
||||
description: >
|
||||
Vous devez déclarer un pays au moins
|
||||
|
||||
|
|
|
@ -2,10 +2,18 @@ import { Explicable } from 'Components/conversation/Explicable'
|
|||
import RuleInput from 'Components/conversation/RuleInput'
|
||||
import * as Animate from 'Components/ui/animate'
|
||||
import Emoji from 'Components/utils/Emoji'
|
||||
import { EngineContext, EngineProvider } from 'Components/utils/EngineContext'
|
||||
import { Markdown } from 'Components/utils/markdown'
|
||||
import { usePersistingState } from 'Components/utils/persistState'
|
||||
import Engine from 'publicodes'
|
||||
import { lazy, createElement, Suspense, useCallback, useState } from 'react'
|
||||
import Engine, { evaluateRule } from 'publicodes'
|
||||
import {
|
||||
lazy,
|
||||
createElement,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useState,
|
||||
useContext
|
||||
} from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { hash } from '../../../../../utils'
|
||||
import formulaire from './formulaire-détachement.yaml'
|
||||
|
@ -15,7 +23,7 @@ const LazyEndBlock = lazy(() => import('./EndBlock'))
|
|||
export default function FormulaireMobilitéIndépendant() {
|
||||
const engine = new Engine(formulaire)
|
||||
return (
|
||||
<>
|
||||
<EngineProvider value={engine}>
|
||||
<h1>Demande de mobilité en Europe pour travailleur indépendant</h1>
|
||||
<h2>
|
||||
<small>
|
||||
|
@ -74,25 +82,29 @@ export default function FormulaireMobilitéIndépendant() {
|
|||
</strong>{' '}
|
||||
de 9h00 à 12h00 et de 13h00 à 16h00.
|
||||
</p>
|
||||
<FormulairePublicodes engine={engine} />
|
||||
</>
|
||||
<FormulairePublicodes />
|
||||
</EngineProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const useFields = (engine: Engine<string>, fieldNames: Array<string>) => {
|
||||
const fields = fieldNames
|
||||
.map(name => engine.evaluate(name))
|
||||
.map(name => evaluateRule(engine, name))
|
||||
.filter(
|
||||
node =>
|
||||
node.isApplicable !== false &&
|
||||
node.isApplicable !== null &&
|
||||
// TODO
|
||||
// node.isApplicable !== false &&
|
||||
// node.isApplicable !== null &&
|
||||
node.nodeValue !== false &&
|
||||
node.nodeValue !== null &&
|
||||
(node.question || node.type || node.API)
|
||||
)
|
||||
return fields
|
||||
}
|
||||
|
||||
const VERSION = hash(JSON.stringify(formulaire))
|
||||
function FormulairePublicodes({ engine }: { engine: Engine<string> }) {
|
||||
function FormulairePublicodes() {
|
||||
const engine = useContext(EngineContext)
|
||||
const [situation, setSituation] = usePersistingState<Record<string, string>>(
|
||||
`formulaire-détachement:${VERSION}`,
|
||||
{}
|
||||
|
@ -159,7 +171,6 @@ function FormulairePublicodes({ engine }: { engine: Engine<string> }) {
|
|||
<RuleInput
|
||||
id={field.dottedName}
|
||||
dottedName={field.dottedName}
|
||||
rules={engine.getParsedRules()}
|
||||
value={situation[field.dottedName]}
|
||||
onChange={value => onChange(field.dottedName, value)}
|
||||
/>
|
||||
|
|
|
@ -6,7 +6,8 @@ import SimulateurWarning from 'Components/SimulateurWarning'
|
|||
import AidesCovid from 'Components/simulationExplanation/AidesCovid'
|
||||
import 'Components/TargetSelection.css'
|
||||
import Animate from 'Components/ui/animate'
|
||||
import { EngineContext, useEvaluation } from 'Components/utils/EngineContext'
|
||||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { evaluateRule } from 'publicodes'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
@ -59,13 +60,13 @@ type SimpleFieldProps = {
|
|||
|
||||
function SimpleField({ dottedName }: SimpleFieldProps) {
|
||||
const dispatch = useDispatch()
|
||||
const rule = useEvaluation(dottedName)
|
||||
const rule = evaluateRule(useContext(EngineContext), dottedName)
|
||||
const initialRender = useContext(InitialRenderContext)
|
||||
const parsedRules = useContext(EngineContext).getParsedRules()
|
||||
const value = useSelector(situationSelector)[dottedName]
|
||||
if (rule.isApplicable === false || rule.isApplicable === null) {
|
||||
return null
|
||||
}
|
||||
const value = rule.nodeValue
|
||||
// TODO
|
||||
// if (rule.isApplicable === false || rule.isApplicable === null) {
|
||||
// return null
|
||||
// }
|
||||
|
||||
return (
|
||||
<li>
|
||||
|
@ -73,7 +74,7 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
|
|||
<div className="main">
|
||||
<div className="header">
|
||||
<label htmlFor={dottedName}>
|
||||
<span className="optionTitle">{rule.question || rule.titre}</span>
|
||||
<span className="optionTitle">{rule.question || rule.title}</span>
|
||||
<p className="ui__ notice">{rule.résumé}</p>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -82,9 +83,8 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
|
|||
className="targetInput"
|
||||
isTarget
|
||||
dottedName={dottedName}
|
||||
rules={parsedRules}
|
||||
value={value}
|
||||
onChange={x => dispatch(updateSituation(dottedName, x))}
|
||||
onChange={(x) => dispatch(updateSituation(dottedName, x))}
|
||||
useSwitch
|
||||
/>
|
||||
</div>
|
||||
|
@ -99,7 +99,7 @@ type WarningProps = {
|
|||
}
|
||||
|
||||
function Warning({ dottedName }: WarningProps) {
|
||||
const warning = useEvaluation(dottedName)
|
||||
const warning = evaluateRule(useContext(EngineContext), dottedName)
|
||||
if (!warning.nodeValue) {
|
||||
return null
|
||||
}
|
||||
|
@ -160,32 +160,32 @@ function CotisationsResult() {
|
|||
const branches = [
|
||||
{
|
||||
dottedName: 'artiste-auteur . cotisations . vieillesse',
|
||||
icon: '👵'
|
||||
icon: '👵',
|
||||
},
|
||||
{
|
||||
dottedName: 'artiste-auteur . cotisations . CSG-CRDS',
|
||||
icon: '🏛'
|
||||
icon: '🏛',
|
||||
},
|
||||
{
|
||||
dottedName: 'artiste-auteur . cotisations . formation professionnelle',
|
||||
icon: '👷♂️'
|
||||
}
|
||||
icon: '👷♂️',
|
||||
},
|
||||
] as const
|
||||
|
||||
function RepartitionCotisations() {
|
||||
const engine = useContext(EngineContext)
|
||||
const cotisations = branches.map(branch => ({
|
||||
const cotisations = branches.map((branch) => ({
|
||||
...branch,
|
||||
value: engine.evaluate(branch.dottedName).nodeValue as number
|
||||
value: engine.evaluate(branch.dottedName).nodeValue as number,
|
||||
}))
|
||||
const maximum = Math.max(...cotisations.map(x => x.value))
|
||||
const maximum = Math.max(...cotisations.map((x) => x.value))
|
||||
return (
|
||||
<section>
|
||||
<h2>
|
||||
<Trans>À quoi servent mes cotisations ?</Trans>
|
||||
</h2>
|
||||
<div className="distribution-chart__container">
|
||||
{cotisations.map(cotisation => (
|
||||
{cotisations.map((cotisation) => (
|
||||
<DistributionBranch
|
||||
key={cotisation.dottedName}
|
||||
maximum={maximum}
|
||||
|
|
|
@ -3,8 +3,8 @@ import Simulation from 'Components/Simulation'
|
|||
import Animate from 'Components/ui/animate'
|
||||
import Warning from 'Components/ui/WarningBlock'
|
||||
import { IsEmbeddedContext } from 'Components/utils/embeddedContext'
|
||||
import { useEvaluation } from 'Components/utils/EngineContext'
|
||||
import { EvaluatedRule, formatValue } from 'publicodes'
|
||||
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
|
||||
import { EvaluatedRule, evaluateRule, formatValue } from 'publicodes'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DottedName } from 'Rules'
|
||||
|
@ -72,10 +72,18 @@ function ExplanationSection() {
|
|||
t
|
||||
} = useTranslation()
|
||||
|
||||
const net = useEvaluation('contrat salarié . rémunération . net')
|
||||
const netHabituel = useEvaluation('chômage partiel . revenu net habituel')
|
||||
const totalEntreprise = useEvaluation('contrat salarié . prix du travail')
|
||||
const totalEntrepriseHabituel = useEvaluation(
|
||||
const engine = useEngine()
|
||||
const net = evaluateRule(engine, 'contrat salarié . rémunération . net')
|
||||
const netHabituel = evaluateRule(
|
||||
engine,
|
||||
'chômage partiel . revenu net habituel'
|
||||
)
|
||||
const totalEntreprise = evaluateRule(
|
||||
engine,
|
||||
'contrat salarié . prix du travail'
|
||||
)
|
||||
const totalEntrepriseHabituel = evaluateRule(
|
||||
engine,
|
||||
'chômage partiel . coût employeur habituel'
|
||||
)
|
||||
if (
|
||||
|
@ -248,7 +256,7 @@ function ValueWithLink(rule: EvaluatedRule<DottedName>) {
|
|||
)
|
||||
}
|
||||
|
||||
function RowLabel(target: EvaluatedRule) {
|
||||
function RowLabel(target: EvaluatedRule<DottedName>) {
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
|
@ -259,7 +267,7 @@ function RowLabel(target: EvaluatedRule) {
|
|||
>
|
||||
{target.title}
|
||||
</div>
|
||||
<p className="ui__ notice">{target.summary}</p>
|
||||
<p className="ui__ notice">{target.résumé}</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ questions:
|
|||
Type d'activité: entreprise . catégorie d'activité
|
||||
Date de création: entreprise . date de création
|
||||
ACRE: entreprise . ACRE
|
||||
Contrats Madelins: dirigeant . indépendant . contrats madelin
|
||||
Conjoint collaborateur: dirigeant . indépendant . conjoint collaborateur
|
||||
Impôt sur le revenu: impôt . méthode de calcul
|
||||
liste noire:
|
||||
- entreprise . charges
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { formatValue } from 'publicodes'
|
||||
import Engine, { ASTNode, EvaluatedNode, formatValue } from 'publicodes'
|
||||
import { DottedName } from './rules'
|
||||
|
||||
export function capitalise0(name: undefined): undefined
|
||||
export function capitalise0(name: string): string
|
||||
|
|
|
@ -10,6 +10,7 @@ describe('conversation', function() {
|
|||
it('should start with the first missing variable', function() {
|
||||
const missingVariables = new Engine({
|
||||
// TODO - this won't work without the indirection, figure out why
|
||||
'top': 'oui',
|
||||
'top . startHere': { formule: { somme: ['a', 'b'] } },
|
||||
'top . a': { formule: 'aa' },
|
||||
'top . b': { formule: 'bb' },
|
||||
|
|
|
@ -7,7 +7,7 @@ describe('DottedNames graph', () => {
|
|||
let cyclesDependencies = cyclesLib.cyclicDependencies(rules)
|
||||
|
||||
expect(
|
||||
cyclesDependencies.reverse(),
|
||||
cyclesDependencies,
|
||||
`\nThe cycles have been found in the rules dependencies graph.\nSee below for a representation of each cycle.\n⬇️ is a node of the cycle.\n↘️ is each of the dependencies of this node.\n\t- ${cyclesDependencies
|
||||
.map(
|
||||
(cycleDependencies, idx) =>
|
||||
|
@ -15,13 +15,16 @@ describe('DottedNames graph', () => {
|
|||
idx +
|
||||
':\n\t\t⬇️ ' +
|
||||
cycleDependencies
|
||||
.map(
|
||||
([ruleName, dependencies]) =>
|
||||
ruleName + '\n\t\t\t↘️ ' + dependencies.join('\n\t\t\t↘️ ')
|
||||
)
|
||||
// .map(
|
||||
// ([ruleName, dependencies]) =>
|
||||
// ruleName + '\n\t\t\t↘️ ' + dependencies.join('\n\t\t\t↘️ ')
|
||||
// )
|
||||
.join('\n\t\t⬇️ ')
|
||||
)
|
||||
.join('\n\t- ')}\n\n`
|
||||
).to.be.an('array').that.is.empty
|
||||
).to.be.an('array').of.length(1)
|
||||
// We have one cycle that we are aware of, but that doesn't occur at runtime
|
||||
// see contrat salarié . activité partielle . indemnités . complémentaire
|
||||
|
||||
})
|
||||
})
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
|
||||
effectif:
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
entreprise . effectif: 10
|
||||
entreprise . effectif: 10 employés
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
entreprise . effectif: 20
|
||||
entreprise . effectif: 20 employés
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
entreprise . effectif: 50
|
||||
entreprise . effectif: 50 employés
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
entreprise . effectif: 100
|
||||
entreprise . effectif: 100 employés
|
||||
|
||||
inversions:
|
||||
- contrat salarié . prix du travail: 2000 €/mois
|
||||
|
@ -65,21 +65,21 @@ cdd:
|
|||
contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
- contrat salarié: "'CDD'"
|
||||
contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . CDD . durée contrat: 6
|
||||
contrat salarié . CDD . congés non pris: 3
|
||||
contrat salarié . CDD . durée contrat: 6 mois
|
||||
contrat salarié . CDD . congés non pris: 3 jours
|
||||
- contrat salarié: "'CDD'"
|
||||
contrat salarié . rémunération . brut de base: 2400 €/mois
|
||||
contrat salarié . CDD . durée contrat: 10
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5
|
||||
contrat salarié . CDD . durée contrat: 10 mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5 heures/mois
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo: oui
|
||||
contrat salarié . rémunération . avantages en nature . montant: 200
|
||||
contrat salarié . rémunération . avantages en nature . montant: 200 €/mois
|
||||
- contrat salarié: "'CDD'"
|
||||
contrat salarié . rémunération . brut de base: 2400 €/mois
|
||||
contrat salarié . convention collective: "'BTP'"
|
||||
|
||||
atmp:
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . ATMP . taux collectif ATMP: 5
|
||||
contrat salarié . ATMP . taux collectif ATMP: 5%
|
||||
|
||||
assimilé salarié:
|
||||
- dirigeant: "'assimilé salarié'"
|
||||
|
@ -102,7 +102,7 @@ aides:
|
|||
contrat salarié . ancienneté . date d'embauche: 01/09/2020
|
||||
- contrat salarié: "'CDD'"
|
||||
contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . CDD . durée contrat: 6
|
||||
contrat salarié . CDD . durée contrat: 6 mois
|
||||
contrat salarié . aides employeur . emploi franc . éligible: oui
|
||||
contrat salarié . ancienneté . date d'embauche: 01/09/2020
|
||||
# emploi franc+
|
||||
|
@ -131,20 +131,20 @@ temps partiel:
|
|||
contrat salarié . temps de travail . temps partiel: oui
|
||||
- contrat salarié . rémunération . brut de base . équivalent temps plein: 2500€/mois
|
||||
contrat salarié . temps de travail . temps partiel: oui
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 26
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 26 heures/semaine
|
||||
- contrat salarié . rémunération . brut de base: 1000 €/mois
|
||||
contrat salarié . temps de travail . temps partiel: oui
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 20
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 20 heures/semaine
|
||||
|
||||
treizième mois:
|
||||
- contrat salarié . rémunération . brut de base: 2300 €/mois
|
||||
contrat salarié . rémunération . primes . fin d'année . treizième mois: oui
|
||||
- contrat salarié . rémunération . brut de base: 2300 €/mois
|
||||
contrat salarié . rémunération . primes . activité . base: 200
|
||||
contrat salarié . rémunération . primes . activité . base: 200 €/mois
|
||||
contrat salarié . rémunération . primes . fin d'année . treizième mois: oui
|
||||
contrat salarié . temps de travail . temps partiel: oui
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 26
|
||||
contrat salarié . temps de travail . heures complémentaires: 5
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 26 heures/semaine
|
||||
contrat salarié . temps de travail . heures complémentaires: 5 heures/mois
|
||||
- contrat salarié . rémunération . brut de base: 2300 €/mois
|
||||
contrat salarié . rémunération . primes . fin d'année . prime de fin d'année en mois: 2
|
||||
|
||||
|
@ -171,7 +171,7 @@ impôt sur le revenu:
|
|||
établissement . localisation . département: "'Mayotte'"
|
||||
- contrat salarié . rémunération . brut de base: 3000 €/mois
|
||||
impôt . méthode de calcul: "'taux personnalisé'"
|
||||
impôt . taux personnalisé: 10
|
||||
impôt . taux personnalisé: 10%
|
||||
|
||||
impôt sur le revenu - quotient familial:
|
||||
- impôt . méthode de calcul: "'barème standard'"
|
||||
|
@ -211,44 +211,44 @@ impôt sur le revenu - quotient familial:
|
|||
|
||||
heures supplémentaires et complémentaires:
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5 heures/mois
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 30
|
||||
contrat salarié . temps de travail . heures supplémentaires: 3 heures/mois
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5
|
||||
entreprise . effectif: 100
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5 heures/mois
|
||||
entreprise . effectif: 100 employés
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5
|
||||
contrat salarié . temps de travail . heures supplémentaires: 5 heures/mois
|
||||
contrat salarié . convention collective: "'HCR'"
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 30
|
||||
contrat salarié . temps de travail . heures supplémentaires: 3 heures/mois
|
||||
contrat salarié . convention collective: "'HCR'"
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . heures supplémentaires: 30
|
||||
contrat salarié . temps de travail . heures supplémentaires: 3 heures/mois
|
||||
contrat salarié . convention collective: "'compta'"
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . temps partiel: oui
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 24
|
||||
contrat salarié . temps de travail . heures complémentaires: 20
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 24 heures/semaine
|
||||
contrat salarié . temps de travail . heures complémentaires: 20 heures/mois
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . temps de travail . temps partiel: oui
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 26
|
||||
contrat salarié . temps de travail . heures complémentaires: 20
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 26 heures/semaine
|
||||
contrat salarié . temps de travail . heures complémentaires: 20 heures/mois
|
||||
|
||||
avantages:
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . rémunération . avantages en nature: oui
|
||||
contrat salarié . rémunération . avantages en nature . montant: 100
|
||||
contrat salarié . rémunération . avantages en nature . montant: 100€/mois
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . rémunération . avantages en nature: oui
|
||||
contrat salarié . rémunération . avantages en nature . autres: oui
|
||||
contrat salarié . rémunération . avantages en nature . autres . montant: 100
|
||||
contrat salarié . rémunération . avantages en nature . ntic . coût appareils: 400
|
||||
contrat salarié . rémunération . avantages en nature . ntic . abonnements: 20
|
||||
contrat salarié . rémunération . avantages en nature . autres . montant: 100€/mois
|
||||
contrat salarié . rémunération . avantages en nature . ntic . coût appareils: 400€
|
||||
contrat salarié . rémunération . avantages en nature . ntic . abonnements: 20€/mois
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . rémunération . avantages en nature: oui
|
||||
contrat salarié . rémunération . avantages en nature . nourriture: oui
|
||||
contrat salarié . rémunération . avantages en nature . nourriture . repas par mois: 10
|
||||
contrat salarié . rémunération . avantages en nature . nourriture . repas par mois: 10 repas/mois
|
||||
|
||||
JEI:
|
||||
- contrat salarié . rémunération . brut de base: 3000 €/mois
|
||||
|
@ -262,22 +262,22 @@ JEI:
|
|||
frais pro - titres restaurant:
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . frais professionnels . titres-restaurant: oui
|
||||
contrat salarié . frais professionnels . titres-restaurant . titres-restaurant par mois: 10
|
||||
contrat salarié . frais professionnels . titres-restaurant . nombre: 10 titres-restaurant
|
||||
- contrat salarié . rémunération . brut de base: 3000 €/mois
|
||||
contrat salarié . frais professionnels . titres-restaurant: oui
|
||||
contrat salarié . frais professionnels . titres-restaurant . titres-restaurant par mois: 20
|
||||
contrat salarié . frais professionnels . titres-restaurant . montant unitaire: 20
|
||||
contrat salarié . frais professionnels . titres-restaurant . nombre: 20 titres-restaurant
|
||||
contrat salarié . frais professionnels . titres-restaurant . montant unitaire: 20€/titre-restaurant
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . frais professionnels . titres-restaurant: oui
|
||||
contrat salarié . frais professionnels . titres-restaurant . taux participation employeur: 55
|
||||
contrat salarié . frais professionnels . titres-restaurant . taux participation employeur: 55%
|
||||
|
||||
frais pro - IKV:
|
||||
- contrat salarié . rémunération . brut de base: 3200 €/mois
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo: oui
|
||||
- contrat salarié . rémunération . brut de base: 3200 €/mois
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo . distance mensuelle: 200
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo . distance mensuelle: 200 km/mois
|
||||
- contrat salarié . rémunération . net après impôt: 1630 €/mois
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo . distance mensuelle: 30
|
||||
contrat salarié . frais professionnels . indemnité kilométrique vélo . distance mensuelle: 30 km/mois
|
||||
|
||||
frais pro - DFS:
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
|
@ -306,14 +306,14 @@ activité partielle:
|
|||
contrat salarié . activité partielle: oui
|
||||
- contrat salarié . rémunération . brut de base: 4000 €/mois
|
||||
contrat salarié . activité partielle: oui
|
||||
contrat salarié . activité partielle . heures travaillées: 30.33331
|
||||
contrat salarié . activité partielle . heures travaillées: 30.33331 heures/mois
|
||||
- contrat salarié . rémunération . brut de base: 4000 €/mois
|
||||
contrat salarié . activité partielle: oui
|
||||
contrat salarié . activité partielle . heures travaillées: 75.833275
|
||||
contrat salarié . activité partielle . heures travaillées: 75.833275 heures/mois
|
||||
- contrat salarié . rémunération . brut de base: 3000 €/mois
|
||||
contrat salarié . activité partielle: oui
|
||||
contrat salarié . temps de travail . temps partiel: oui
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 28
|
||||
contrat salarié . temps de travail . temps partiel . heures par semaine: 28 heures/semaine
|
||||
- contrat salarié . rémunération . brut de base: 4000 €/mois
|
||||
contrat salarié . activité partielle: oui
|
||||
contrat salarié . profession spécifique: "'journaliste'"
|
||||
|
@ -321,7 +321,7 @@ activité partielle:
|
|||
contrat salarié . activité partielle: oui
|
||||
contrat salarié . activité partielle . convention syntec: oui
|
||||
- contrat salarié . rémunération . brut de base: 2000 €/mois
|
||||
contrat salarié . activité partielle . heures travaillées: 75.833275
|
||||
contrat salarié . activité partielle . heures travaillées: 75.833275 heures/mois
|
||||
contrat salarié . activité partielle: oui
|
||||
contrat salarié . activité partielle . convention syntec: oui
|
||||
- contrat salarié . rémunération . brut de base: 6000 €/mois
|
||||
|
@ -379,17 +379,17 @@ lodeom innovation et croissance:
|
|||
|
||||
taux spécifiques retraite complémentaire:
|
||||
- contrat salarié . rémunération . brut de base: 1521.22 €/mois
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 5.59
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 2.28
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 5.59%
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 2.28%
|
||||
- contrat salarié . rémunération . brut de base: 2500 €/mois
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 5.59
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 2.28
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 5.59%
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 2.28%
|
||||
- contrat salarié . rémunération . brut de base: 1521.22 €/mois
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 3.94
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 3.93
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 3.94%
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 3.93%
|
||||
- contrat salarié . rémunération . brut de base: 2500 €/mois
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 3.94
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 3.93
|
||||
contrat salarié . retraite complémentaire . employeur . taux tranche 1: 3.94%
|
||||
contrat salarié . retraite complémentaire . salarié . taux tranche 1: 3.93%
|
||||
|
||||
CCN batiment:
|
||||
- contrat salarié . rémunération . brut de base: 2500 €/mois
|
||||
|
@ -405,15 +405,15 @@ CCN batiment:
|
|||
CCN compta:
|
||||
- contrat salarié . rémunération . brut de base: 2500 €/mois
|
||||
contrat salarié . convention collective: "'compta'"
|
||||
contrat salarié . temps de travail . heures supplémentaires: 30
|
||||
contrat salarié . temps de travail . heures supplémentaires: 3 heures/mois
|
||||
|
||||
CCN HCR:
|
||||
- contrat salarié . rémunération . brut de base: 2500 €/mois
|
||||
contrat salarié . convention collective: "'HCR'"
|
||||
contrat salarié . temps de travail . heures supplémentaires: 30
|
||||
contrat salarié . temps de travail . heures supplémentaires: 3 heures/mois
|
||||
contrat salarié . rémunération . avantages en nature: oui
|
||||
contrat salarié . rémunération . avantages en nature . nourriture: oui
|
||||
contrat salarié . rémunération . avantages en nature . nourriture . repas par mois: 10
|
||||
contrat salarié . rémunération . avantages en nature . nourriture . repas par mois: 10 repas/mois
|
||||
|
||||
CCN optique:
|
||||
- contrat salarié . rémunération . brut de base: 2500 €/mois
|
||||
|
|
|
@ -40,14 +40,13 @@ const runSimulations = (situations, targets, baseSituation = {}) =>
|
|||
const res = targets.map(target => engine.evaluate(target).nodeValue)
|
||||
|
||||
const evaluatedNotifications = Object.values(engine.getParsedRules())
|
||||
.filter(rule => rule['type'] === 'notification')
|
||||
.filter(
|
||||
notification =>
|
||||
![null, false].includes(
|
||||
engine.evaluate(notification.dottedName).isApplicable
|
||||
)
|
||||
)
|
||||
.map(notification => notification.dottedName)
|
||||
.filter(
|
||||
(rule) =>
|
||||
rule.rawNode['type'] === 'notification'
|
||||
)
|
||||
.map(node => engine.evaluateNode(node))
|
||||
.filter(node => !!node.nodeValue)
|
||||
.map(node => node.dottedName)
|
||||
|
||||
const snapshotedDisplayedNotifications = evaluatedNotifications.length
|
||||
? `\nNotifications affichées : ${evaluatedNotifications.join(', ')}`
|
||||
|
@ -61,7 +60,7 @@ const runSimulations = (situations, targets, baseSituation = {}) =>
|
|||
})
|
||||
)
|
||||
|
||||
it('calculate simulations-salarié', () => {
|
||||
it.only('calculate simulations-salarié', () => {
|
||||
runSimulations(
|
||||
employeeSituations,
|
||||
employeeConfig.objectifs,
|
||||
|
|
|
@ -12,14 +12,14 @@ progressivement le résultat affiché, et d'exposer une documentation du calcul
|
|||
|
||||
## Projets phares
|
||||
|
||||
- **[mon-entreprise.fr](https://mon-entreprise.fr/simulateurs)** utilise publicodes
|
||||
pour spécifier l'ensemble des calculs relatifs à la législation socio-fiscale
|
||||
en France. Le site permet entre autre de simuler une fiche de paie complète,
|
||||
de calculer les cotisations sociales pour un indépendant ou encore connaître
|
||||
le montant du chômage partiel.
|
||||
- **[futur.eco](https://futur.eco/)** utilise publicodes pour calculer les bilans
|
||||
carbone d'un grand nombre d'activités, plats, transports ou biens.
|
||||
- **[Nos Gestes Climat](https://ecolab.ademe.fr/apps/climat)** utilise publicodes pour proposer un calculateur d'empreinte climat personnel de référence complètement ouvert
|
||||
- **[mon-entreprise.fr](https://mon-entreprise.fr/simulateurs)** utilise publicodes
|
||||
pour spécifier l'ensemble des calculs relatifs à la législation socio-fiscale
|
||||
en France. Le site permet entre autre de simuler une fiche de paie complète,
|
||||
de calculer les cotisations sociales pour un indépendant ou encore connaître
|
||||
le montant du chômage partiel.
|
||||
- **[futur.eco](https://futur.eco/)** utilise publicodes pour calculer les bilans
|
||||
carbone d'un grand nombre d'activités, plats, transports ou biens.
|
||||
- **[Nos Gestes Climat](https://ecolab.ademe.fr/apps/climat)** utilise publicodes pour proposer un calculateur d'empreinte climat personnel de référence complètement ouvert
|
||||
|
||||
## Syntaxe
|
||||
|
||||
|
@ -33,7 +33,7 @@ possédant une _formule de calcul_ :
|
|||
|
||||
```yaml
|
||||
prix d'un repas:
|
||||
formule: 10 €
|
||||
formule: 10 €
|
||||
```
|
||||
|
||||
Une formule de calcul peut faire _référence_ à d'autres règles.
|
||||
|
@ -41,14 +41,13 @@ Dans l'exemple suivant la règle `prix total` aura pour valeur 50 (= 5 \* 10)
|
|||
|
||||
```yaml
|
||||
prix d'un repas:
|
||||
formule: 10 €
|
||||
formule: 10 €
|
||||
|
||||
prix total:
|
||||
formule: 5 * prix d'un repas
|
||||
formule: 5 * prix d'un repas
|
||||
```
|
||||
|
||||
Il s'agit d'un langage déclaratif : comme dans une formule d'un tableur le `prix
|
||||
total` sera recalculé automatiquement si le prix d'un repas change. L'ordre de
|
||||
Il s'agit d'un langage déclaratif : comme dans une formule d'un tableur le `prix total` sera recalculé automatiquement si le prix d'un repas change. L'ordre de
|
||||
définition des règles n'a pas d'importance.
|
||||
|
||||
### Unités
|
||||
|
@ -58,13 +57,13 @@ l'unité des valeurs littérales :
|
|||
|
||||
```yaml
|
||||
prix d'un repas:
|
||||
formule: 10 €/repas
|
||||
formule: 10 €/repas
|
||||
|
||||
nombre de repas:
|
||||
formule: 5 repas
|
||||
formule: 5 repas
|
||||
|
||||
prix total:
|
||||
formule: nombre de repas * prix d'un repas
|
||||
formule: nombre de repas * prix d'un repas
|
||||
```
|
||||
|
||||
Le calcul est inchangé mais on a indiqué que le "prix d'un repas" s'exprime en
|
||||
|
@ -77,16 +76,16 @@ automatiquement des formules incohérentes :
|
|||
|
||||
```yaml
|
||||
prix d'un repas:
|
||||
formule: 10 €/repas
|
||||
formule: 10 €/repas
|
||||
|
||||
nombre de repas:
|
||||
formule: 5 repas
|
||||
formule: 5 repas
|
||||
|
||||
frais de réservation:
|
||||
formule: 1 €/repas
|
||||
formule: 1 €/repas
|
||||
|
||||
prix total:
|
||||
formule: nombre de repas * prix d'un repas + frais de réservation
|
||||
formule: nombre de repas * prix d'un repas + frais de réservation
|
||||
# Erreur:
|
||||
# La formule de "prix total" est invalide.
|
||||
```
|
||||
|
@ -99,7 +98,7 @@ de factoriser la variable "nombre de repas" dans la formule du "prix total".
|
|||
|
||||
```yaml
|
||||
prix total:
|
||||
formule: nombre de repas * (prix d'un repas + frais de réservation)
|
||||
formule: nombre de repas * (prix d'un repas + frais de réservation)
|
||||
```
|
||||
|
||||
> **Attention :** Il ne faut pas insérer d'espace autour de la barre oblique dans
|
||||
|
@ -111,30 +110,25 @@ Publicode convertit automatiquement les unités si besoin.
|
|||
|
||||
```yaml
|
||||
salaire:
|
||||
formule: 1500 €/mois
|
||||
formule: 1500 €/mois
|
||||
|
||||
prime faible salaire:
|
||||
applicable si: salaire < 20 k€/an
|
||||
formule: 300€
|
||||
applicable si: salaire < 20 k€/an
|
||||
formule: 300€
|
||||
```
|
||||
|
||||
<<<<<<< HEAD
|
||||
On peut forcer la conversion des unités via la propriété `unité`, ou la notation
|
||||
suffixée `[...]`.
|
||||
=======
|
||||
On peut forcer la conversion des unités via la propriété `unité`
|
||||
>>>>>>> 30d2971e (:WIP: Délimitation des pistes de refacto (on y va à la masse de destruction))
|
||||
|
||||
```yaml
|
||||
salaire:
|
||||
formule: 3200 €/mois
|
||||
unité: €/an
|
||||
formule: 3200 €/mois
|
||||
unité: €/an
|
||||
```
|
||||
|
||||
**Types de base disponibles pour la conversion :**
|
||||
|
||||
- `jour` / `mois` / `an`
|
||||
- `€` / `k€`
|
||||
- `jour` / `mois` / `an`
|
||||
- `€` / `k€`
|
||||
|
||||
### Pages d'explications
|
||||
|
||||
|
@ -146,27 +140,27 @@ mise en regard des calculs eux-mêmes.
|
|||
|
||||
Plusieurs propriétés sont reprises dans ces pages d'explications :
|
||||
|
||||
- le **titre**, qui s'affiche en haut de la page. Par défaut on utilise le nom
|
||||
de la règle, mais la propriété `titre` permet de choisir un titre plus
|
||||
approprié ;
|
||||
- la **description** qui peut être rédigée en Markdown et est généralement
|
||||
affichée comme paragraphe d'introduction sur la page. On utilise le caractère
|
||||
`>` pour indiquer au parseur Yaml que la description utilise du Markdown ;
|
||||
- les **références** externes (documentation utile) affichées en
|
||||
bas de page et qui sont constituées d'une liste de liens avec une description.
|
||||
- le **titre**, qui s'affiche en haut de la page. Par défaut on utilise le nom
|
||||
de la règle, mais la propriété `titre` permet de choisir un titre plus
|
||||
approprié ;
|
||||
- la **description** qui peut être rédigée en Markdown et est généralement
|
||||
affichée comme paragraphe d'introduction sur la page. On utilise le caractère
|
||||
`>` pour indiquer au parseur Yaml que la description utilise du Markdown ;
|
||||
- les **références** externes (documentation utile) affichées en
|
||||
bas de page et qui sont constituées d'une liste de liens avec une description.
|
||||
|
||||
```yaml
|
||||
ticket resto:
|
||||
titre: Prise en charge des titres-restaurants
|
||||
formule: 4 €/repas
|
||||
description: >
|
||||
L'employeur peut remettre des titres restaurants sous plusieurs formats:
|
||||
- ticket *papier*
|
||||
- carte à *puce*
|
||||
- appli *mobile*
|
||||
références:
|
||||
Fiche service public: https://www.service-public.fr/professionnels-entreprises/vosdroits/F21059
|
||||
Fiche Urssaf: https://www.urssaf.fr/portail/home/taux-et-baremes/frais-professionnels/les-titres-restaurant.html
|
||||
titre: Prise en charge des titres-restaurants
|
||||
formule: 4 €/repas
|
||||
description: >
|
||||
L'employeur peut remettre des titres restaurants sous plusieurs formats:
|
||||
- ticket *papier*
|
||||
- carte à *puce*
|
||||
- appli *mobile*
|
||||
références:
|
||||
Fiche service public: https://www.service-public.fr/professionnels-entreprises/vosdroits/F21059
|
||||
Fiche Urssaf: https://www.urssaf.fr/portail/home/taux-et-baremes/frais-professionnels/les-titres-restaurant.html
|
||||
```
|
||||
|
||||
Voir aussi la rubrique sur les mécanismes.
|
||||
|
@ -178,10 +172,10 @@ utilise le `.` pour exprimer la hiérarchie des noms.
|
|||
|
||||
```yaml
|
||||
prime de vacances:
|
||||
formule: taux * 1000 €
|
||||
formule: taux * 1000 €
|
||||
|
||||
prime de vacances . taux:
|
||||
formule: 6%
|
||||
formule: 6%
|
||||
```
|
||||
|
||||
Ici `prime de vacances` est à la fois une règle et un espace de noms. La variable
|
||||
|
@ -196,7 +190,7 @@ différent, sans que cela entre en conflit:
|
|||
```yaml
|
||||
# Ceci n'entre pas dans le calcul de `prime de vacances` définie plus haut
|
||||
autre prime . taux:
|
||||
formule: 19%
|
||||
formule: 19%
|
||||
```
|
||||
|
||||
On dit que la formule de la règle `prime de vacances` fait référence à la
|
||||
|
@ -207,7 +201,7 @@ nom complet de cette règle:
|
|||
|
||||
```yaml
|
||||
prime de vacances v2:
|
||||
formule: autre prime . taux * 1000 €
|
||||
formule: autre prime . taux * 1000 €
|
||||
```
|
||||
|
||||
Dans le cas d'espaces de noms imbriqués (à plus qu'un étage), le nom inscrit
|
||||
|
@ -216,10 +210,10 @@ espaces de nom jusqu'à la racine.
|
|||
|
||||
```yaml
|
||||
contrat salarié . rémunération . primes . prime de vacances:
|
||||
formule: taux générique * 1000 €
|
||||
formule: taux générique * 1000 €
|
||||
|
||||
contrat salarié . rémunération . taux générique:
|
||||
formule: 10%
|
||||
formule: 10%
|
||||
```
|
||||
|
||||
Ici `contrat salarié . rémunération . primes . prime de vacances` va faire
|
||||
|
@ -236,40 +230,40 @@ Par exemple on a un mécanisme `barème`:
|
|||
|
||||
```yaml
|
||||
revenu imposable:
|
||||
formule: 54126 €
|
||||
formule: 54126 €
|
||||
|
||||
impôt sur le revenu:
|
||||
formule:
|
||||
barème:
|
||||
assiette: revenu imposable
|
||||
tranches:
|
||||
- taux: 0%
|
||||
plafond: 9807 €
|
||||
- taux: 14%
|
||||
plafond: 27086 €
|
||||
- taux: 30%
|
||||
plafond: 72617 €
|
||||
- taux: 41%
|
||||
plafond: 153783 €
|
||||
- taux: 45%
|
||||
formule:
|
||||
barème:
|
||||
assiette: revenu imposable
|
||||
tranches:
|
||||
- taux: 0%
|
||||
plafond: 9807 €
|
||||
- taux: 14%
|
||||
plafond: 27086 €
|
||||
- taux: 30%
|
||||
plafond: 72617 €
|
||||
- taux: 41%
|
||||
plafond: 153783 €
|
||||
- taux: 45%
|
||||
```
|
||||
|
||||
La syntaxe hiérarchique de Yaml permet d'imbriquer les mécanismes :
|
||||
|
||||
```yaml
|
||||
prime . fixe:
|
||||
formule: 1000€
|
||||
formule: 1000€
|
||||
|
||||
prime . taux du bonus:
|
||||
formule: 20%
|
||||
formule: 20%
|
||||
|
||||
prime:
|
||||
formule:
|
||||
somme:
|
||||
- fixe
|
||||
- produit:
|
||||
assiette: fixe
|
||||
taux: taux du bonus
|
||||
formule:
|
||||
somme:
|
||||
- fixe
|
||||
- produit:
|
||||
assiette: fixe
|
||||
taux: taux du bonus
|
||||
```
|
||||
|
||||
> **[Aller à la liste des mécanismes existants](./mécanismes)**
|
||||
|
@ -280,17 +274,17 @@ On peut définir des conditions d'applicabilité des règles :
|
|||
|
||||
```yaml
|
||||
date de début:
|
||||
formule: 12/02/2020
|
||||
formule: 12/02/2020
|
||||
|
||||
ancienneté en fin d'année:
|
||||
formule:
|
||||
durée:
|
||||
depuis: date de début
|
||||
jusqu'à: 31/12/2020
|
||||
formule:
|
||||
durée:
|
||||
depuis: date de début
|
||||
jusqu'à: 31/12/2020
|
||||
|
||||
prime de vacances:
|
||||
applicable si: ancienneté en fin d'année > 1 an
|
||||
formule: 200€
|
||||
applicable si: ancienneté en fin d'année > 1 an
|
||||
formule: 200€
|
||||
```
|
||||
|
||||
Ici si l'ancienneté est inférieure à un an la prime de vacances ne sera pas
|
||||
|
@ -302,9 +296,9 @@ La syntaxe suivante est également valable:
|
|||
|
||||
```yaml
|
||||
dirigeant . assimilé salarié:
|
||||
formule: dirigeant = 'assimilé salarié'
|
||||
rend non applicable:
|
||||
- contrat salarié . convention collective
|
||||
formule: dirigeant = 'assimilé salarié'
|
||||
rend non applicable:
|
||||
- contrat salarié . convention collective
|
||||
```
|
||||
|
||||
### Remplacement
|
||||
|
@ -319,41 +313,41 @@ quelle règle existante sans avoir besoin de la modifier :
|
|||
|
||||
```yaml
|
||||
frais de repas:
|
||||
formule: 5 €/repas
|
||||
formule: 5 €/repas
|
||||
|
||||
convention hôtels cafés restaurants:
|
||||
formule: oui
|
||||
formule: oui
|
||||
|
||||
convention hôtels cafés restaurants . frais de repas:
|
||||
remplace: frais de repas
|
||||
formule: 6 €/repas
|
||||
remplace: frais de repas
|
||||
formule: 6 €/repas
|
||||
|
||||
montant repas mensuels:
|
||||
formule: 20 repas * frais de repas
|
||||
formule: 20 repas * frais de repas
|
||||
```
|
||||
|
||||
On peut également choisir de remplacer uniquement dans un contexte donné:
|
||||
|
||||
```yaml
|
||||
a:
|
||||
formule: 10 min
|
||||
formule: 10 min
|
||||
|
||||
b:
|
||||
formule: 20 min
|
||||
formule: 20 min
|
||||
|
||||
règle nulle:
|
||||
remplace:
|
||||
- règle: a
|
||||
sauf dans: somme originale
|
||||
- règle: b
|
||||
dans: somme avec remplacements
|
||||
formule: 0
|
||||
remplace:
|
||||
- règle: a
|
||||
sauf dans: somme originale
|
||||
- règle: b
|
||||
dans: somme avec remplacements
|
||||
formule: 0
|
||||
|
||||
somme originale:
|
||||
formule: a + b
|
||||
formule: a + b
|
||||
|
||||
somme avec remplacements:
|
||||
formule: a + b
|
||||
formule: a + b
|
||||
```
|
||||
|
||||
### Références de paramètres
|
||||
|
@ -369,17 +363,17 @@ l'extérieur :
|
|||
|
||||
```yaml
|
||||
prime:
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux: taux
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux: taux
|
||||
|
||||
prime . taux:
|
||||
formule: 5%
|
||||
formule: 5%
|
||||
|
||||
super-prime:
|
||||
remplace: prime . taux
|
||||
formule: 10%
|
||||
remplace: prime . taux
|
||||
formule: 10%
|
||||
```
|
||||
|
||||
Ce code fonctionne mais il nous oblige a créer une règle `prime . taux` qui
|
||||
|
@ -395,14 +389,14 @@ veut accéder depuis l'extérieur avec le mot clé `[ref]` :
|
|||
|
||||
```yaml
|
||||
prime:
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux [ref]: 5%
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux [ref]: 5%
|
||||
|
||||
super-prime:
|
||||
remplace: prime . taux
|
||||
formule: 10%
|
||||
remplace: prime . taux
|
||||
formule: 10%
|
||||
```
|
||||
|
||||
Par défaut le paramètre est référencé avec son nom dans l'espace de nom de la
|
||||
|
@ -410,14 +404,14 @@ règle, ici `prime . taux`. Il est possible de choisir un nom personnalisé :
|
|||
|
||||
```yaml
|
||||
prime:
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux [ref taux bonus]: 5%
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux [ref taux bonus]: 5%
|
||||
|
||||
super-prime:
|
||||
remplace: prime . taux bonus
|
||||
formule: 10%
|
||||
remplace: prime . taux bonus
|
||||
formule: 10%
|
||||
```
|
||||
|
||||
Lors d'une relecture future de la règle `prime` le mot clé `[ref]` indique
|
||||
|
@ -428,16 +422,16 @@ La syntaxe suivante est équivalente :
|
|||
|
||||
```yaml
|
||||
prime:
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux:
|
||||
définition: taux bonus
|
||||
formule: 5%
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1000€
|
||||
taux:
|
||||
définition: taux bonus
|
||||
formule: 5%
|
||||
|
||||
super-prime:
|
||||
remplace: prime . taux bonus
|
||||
formule: 10%
|
||||
remplace: prime . taux bonus
|
||||
formule: 10%
|
||||
```
|
||||
|
||||
## Évaluation
|
||||
|
|
|
@ -3,17 +3,16 @@ import * as R from 'ramda'
|
|||
import parsePublicodes from '../parsePublicodes'
|
||||
import { RuleNode } from '../rule'
|
||||
import { reduceAST } from './index'
|
||||
|
||||
type RulesDependencies = Array<[string, Array<string>]>
|
||||
type GraphCycles = Array<Array<string>>
|
||||
type GraphCyclesWithDependencies = Array<RulesDependencies>
|
||||
|
||||
export function buildRulesDependencies(
|
||||
function buildRulesDependencies(
|
||||
parsedRules: Record<string, RuleNode>
|
||||
): RulesDependencies {
|
||||
return Object.entries(parsedRules).map(([name, node]) => [
|
||||
name,
|
||||
buildRuleDependancies(node)
|
||||
R.uniq(buildRuleDependancies(node))
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -25,11 +24,20 @@ function buildRuleDependancies(rule: RuleNode): Array<string> {
|
|||
case 'inversion':
|
||||
case 'une possibilité':
|
||||
return acc
|
||||
case 'recalcul':
|
||||
node.explanation.amendedSituation.forEach(s => fn(s[1]))
|
||||
return
|
||||
case 'reference':
|
||||
return [...acc, node.dottedName as string]
|
||||
case 'rule':
|
||||
// Cycle from parent dependancies are ignored at runtime
|
||||
// Cycle from parent dependancies are ignored at runtime,
|
||||
// so we don' detect them statically
|
||||
return fn(rule.explanation.valeur)
|
||||
case 'variations':
|
||||
// a LOT of cycles with replacements... we disactivate them until we see clearer,
|
||||
if (node.rawNode && typeof node.rawNode === 'string') {
|
||||
return [...acc, node.rawNode]
|
||||
}
|
||||
}
|
||||
},
|
||||
[],
|
||||
|
@ -38,7 +46,7 @@ function buildRuleDependancies(rule: RuleNode): Array<string> {
|
|||
}
|
||||
|
||||
function buildDependenciesGraph(rulesDeps: RulesDependencies): graphlib.Graph {
|
||||
const g = new graphlib.Graph()
|
||||
const g = new (graphlib as any).Graph()
|
||||
rulesDeps.forEach(([ruleDottedName, dependencies]) => {
|
||||
dependencies.forEach(depDottedName => {
|
||||
g.setEdge(ruleDottedName, depDottedName)
|
||||
|
@ -47,16 +55,15 @@ function buildDependenciesGraph(rulesDeps: RulesDependencies): graphlib.Graph {
|
|||
return g
|
||||
}
|
||||
|
||||
type ArgsType<T> = T extends (...args: infer U) => any ? U : never
|
||||
type RawRules = ArgsType<typeof parsePublicodes>[0]
|
||||
type RawRules = Parameters<typeof parsePublicodes>[0]
|
||||
|
||||
export function cyclesInDependenciesGraph(rawRules: RawRules): GraphCycles {
|
||||
const parsedRules = parsePublicodes(rawRules)
|
||||
const rulesDependencies = buildRulesDependencies(parsedRules)
|
||||
const dependenciesGraph = buildDependenciesGraph(rulesDependencies)
|
||||
const cycles = graphlib.alg.findCycles(dependenciesGraph)
|
||||
const cycles = (graphlib as any).alg.findCycles(dependenciesGraph)
|
||||
|
||||
return cycles
|
||||
return cycles.map(c => c.reverse())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,11 +79,22 @@ export function cyclicDependencies<Names extends string>(
|
|||
const parsedRules = parsePublicodes(rawRules)
|
||||
const rulesDependencies = buildRulesDependencies(parsedRules)
|
||||
const dependenciesGraph = buildDependenciesGraph(rulesDependencies)
|
||||
const cycles = graphlib.alg.findCycles(dependenciesGraph)
|
||||
|
||||
const cycles = (graphlib as any).alg.findCycles(dependenciesGraph)
|
||||
const rulesDependenciesObject = R.fromPairs(rulesDependencies)
|
||||
|
||||
return cycles.map(cycle =>
|
||||
cycle.map(ruleName => [ruleName, rulesDependenciesObject[ruleName]])
|
||||
)
|
||||
return cycles.map(cycle => {
|
||||
const c = cycle.reverse()
|
||||
|
||||
return c.reduce((acc, current) => {
|
||||
const previous = acc.slice(-1)[0]
|
||||
if (previous && !rulesDependenciesObject[previous].includes(current)) {
|
||||
return acc
|
||||
}
|
||||
return [...acc, current]
|
||||
}, [])
|
||||
// .map(name => [
|
||||
// name,
|
||||
// rulesDependenciesObject[name].filter(name => c.includes(name))
|
||||
// ])
|
||||
})
|
||||
}
|
||||
|
|
|
@ -67,8 +67,11 @@ export type ASTNode = (
|
|||
| VariationNode
|
||||
| ConstantNode
|
||||
| ReplacementNode
|
||||
) &
|
||||
(EvaluationDecoration | {}) // TODO : separate type for evaluated AST Tree
|
||||
) & {
|
||||
isDefault?: boolean
|
||||
rawNode?: string | Object
|
||||
} & (EvaluationDecoration<Types> | {}) // TODO : separate type for evaluated AST Tree
|
||||
|
||||
export type MecanismNode = Exclude<
|
||||
ASTNode,
|
||||
RuleNode | ConstantNode | ReferenceNode
|
||||
|
@ -90,14 +93,15 @@ export type Unit = {
|
|||
denominators: Array<BaseUnit>
|
||||
}
|
||||
|
||||
export type Types = number | boolean | string | Object
|
||||
|
||||
// Idée : une évaluation est un n-uple : (value, unit, missingVariable, isApplicable)
|
||||
// Une temporalEvaluation est une liste d'evaluation sur chaque période. : [(Evaluation, Period)]
|
||||
export type Evaluation<T extends Types = Types> = T | false | null
|
||||
export type EvaluationDecoration<T extends Types = Types> = {
|
||||
type EvaluationDecoration<T extends Types> = {
|
||||
nodeValue: Evaluation<T>
|
||||
missingVariables: Partial<Record<string, number>>
|
||||
unit?: Unit
|
||||
temporalValue?: Temporal<Evaluation>
|
||||
}
|
||||
export type Types = number | boolean | string | Object
|
||||
export type Evaluation<T extends Types = Types> = T | false | null
|
||||
export type EvaluatedNode<T extends Types = Types> = ASTNode &
|
||||
EvaluationDecoration<T>
|
||||
|
|
|
@ -30,6 +30,7 @@ export function Documentation({
|
|||
i18n.changeLanguage(language)
|
||||
}
|
||||
}, [language])
|
||||
|
||||
return (
|
||||
<EngineContext.Provider value={engine}>
|
||||
<BasepathContext.Provider value={documentationPath}>
|
||||
|
|
|
@ -17,7 +17,6 @@ export default function Composantes({ nodeValue, explanation, unit }) {
|
|||
value={nodeValue}
|
||||
unit={unit}
|
||||
>
|
||||
<StyledComponent>
|
||||
<div
|
||||
css={`
|
||||
font-weight: bold;
|
||||
|
@ -32,7 +31,7 @@ export default function Composantes({ nodeValue, explanation, unit }) {
|
|||
</div>
|
||||
<ol>
|
||||
{explanation.map((c, i) => [
|
||||
<li className="composante" key={JSON.stringify(c.composante)}>
|
||||
<li key={JSON.stringify(c.composante)}>
|
||||
<ul
|
||||
className="composanteAttributes"
|
||||
style={{
|
||||
|
@ -68,22 +67,6 @@ export default function Composantes({ nodeValue, explanation, unit }) {
|
|||
</li>
|
||||
])}
|
||||
</ol>
|
||||
</StyledComponent>
|
||||
</Mecanism>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledComponent = styled.div`
|
||||
> ol {
|
||||
list-style: none;
|
||||
counter-reset: li;
|
||||
padding-left: 1em;
|
||||
}
|
||||
> ol > li > ul > li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.composanteAttributes {
|
||||
display: inline-block;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
import { EvaluatedNode } from '../../AST/types'
|
||||
import { makeJsx } from '../../evaluation'
|
||||
import { Mecanism } from './common'
|
||||
|
||||
export default function ProductView({ nodeValue, explanation, unit }) {
|
||||
export default function Product(node: EvaluatedNode & { nodeKind: 'produit' }) {
|
||||
return (
|
||||
// The rate and factor and threshold are given defaut neutral values. If there is nothing to explain, don't display them at all
|
||||
<Mecanism name="produit" value={nodeValue} unit={unit}>
|
||||
<Mecanism name="produit" value={node.nodeValue} unit={node.unit}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
@ -14,8 +13,8 @@ export default function ProductView({ nodeValue, explanation, unit }) {
|
|||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
{makeJsx(explanation.assiette)}
|
||||
{!explanation.plafond.isDefault && (
|
||||
{makeJsx(node.explanation.assiette)}
|
||||
{!node.explanation.plafond.isDefault && (
|
||||
<small
|
||||
css={`
|
||||
display: flex;
|
||||
|
@ -27,11 +26,11 @@ export default function ProductView({ nodeValue, explanation, unit }) {
|
|||
<span>
|
||||
<Trans>Plafonnée à :</Trans>
|
||||
</span>
|
||||
{makeJsx(explanation.plafond)}
|
||||
{makeJsx(node.explanation.plafond)}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
{!explanation.facteur.isDefault && (
|
||||
{!node.explanation.facteur.isDefault && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
@ -41,10 +40,10 @@ export default function ProductView({ nodeValue, explanation, unit }) {
|
|||
}}
|
||||
>
|
||||
<div style={{ margin: '0 0.6rem' }}> × </div>
|
||||
<div>{makeJsx(explanation.facteur)}</div>
|
||||
<div>{makeJsx(node.explanation.facteur)}</div>
|
||||
</div>
|
||||
)}
|
||||
{!explanation.taux.isDefault && (
|
||||
{!node.explanation.taux.isDefault && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
@ -54,7 +53,7 @@ export default function ProductView({ nodeValue, explanation, unit }) {
|
|||
}}
|
||||
>
|
||||
<div style={{ margin: '0 0.6rem' }}> × </div>
|
||||
{makeJsx(explanation.taux)}
|
||||
{makeJsx(node.explanation.taux)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
|
@ -10,6 +10,7 @@ import styled from 'styled-components'
|
|||
export default function Variations({ nodeValue, explanation, unit }) {
|
||||
let [expandedVariation, toggleVariation] = useState(null)
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
<StyledComponent>
|
||||
<Mecanism
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
import styled, { StyledConfig } from 'styled-components'
|
||||
import mecanismsDoc from '../../../docs/mecanisms.yaml'
|
||||
import { makeJsx } from '../../evaluation'
|
||||
import { formatValue } from '../../format'
|
||||
|
@ -9,7 +9,7 @@ import {
|
|||
ASTNode,
|
||||
ConstantNode,
|
||||
Evaluation,
|
||||
EvaluationDecoration,
|
||||
EvaluatedNode,
|
||||
Types,
|
||||
Unit
|
||||
} from '../../AST/types'
|
||||
|
@ -20,9 +20,11 @@ import mecanismColors from './colors'
|
|||
import MecanismExplanation from './Explanation'
|
||||
import { ReferenceNode } from '../../reference'
|
||||
import { RuleNode } from '../../rule'
|
||||
import { EngineContext } from '../contexts'
|
||||
import { InternalError } from '../../error'
|
||||
type NodeValuePointerProps = {
|
||||
data: Evaluation<Types>
|
||||
unit: Unit
|
||||
unit: Unit | undefined
|
||||
}
|
||||
|
||||
export const NodeValuePointer = ({ data, unit }: NodeValuePointerProps) => (
|
||||
|
@ -49,7 +51,7 @@ export const NodeValuePointer = ({ data, unit }: NodeValuePointerProps) => (
|
|||
type NodeProps = {
|
||||
name: string
|
||||
value: Evaluation<Types>
|
||||
unit: Unit
|
||||
unit?: Unit
|
||||
children: React.ReactNode
|
||||
displayName?: boolean
|
||||
}
|
||||
|
@ -105,11 +107,13 @@ export function Mecanism({
|
|||
export const InfixMecanism = ({
|
||||
value,
|
||||
prefixed,
|
||||
children
|
||||
children,
|
||||
dimValue
|
||||
}: {
|
||||
value: ASTNode & EvaluationDecoration
|
||||
value: EvaluatedNode
|
||||
children: React.ReactNode
|
||||
prefixed?: boolean
|
||||
dimValue?: boolean
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
|
@ -125,7 +129,9 @@ export const InfixMecanism = ({
|
|||
`}
|
||||
>
|
||||
{prefixed && children}
|
||||
<div className="value">{makeJsx(value)}</div>
|
||||
<div className="value" css={dimValue ? `opacity: 0.5` : ''}>
|
||||
{makeJsx(value)}
|
||||
</div>
|
||||
{!prefixed && children}
|
||||
</div>
|
||||
)
|
||||
|
@ -232,18 +238,30 @@ const StyledMecanismName = styled.button<{ name: string; inline?: boolean }>`
|
|||
|
||||
// Un élément du graphe de calcul qui a une valeur interprétée (à afficher)
|
||||
export function Leaf(
|
||||
node: ReferenceNode &
|
||||
EvaluationDecoration & { explanation: RuleNode; dottedName: string }
|
||||
node: ReferenceNode & {
|
||||
dottedName: string
|
||||
} & EvaluatedNode
|
||||
) {
|
||||
const { dottedName, name, nodeValue, explanation: rule, unit } = node
|
||||
|
||||
const engine = useContext(EngineContext)
|
||||
const { dottedName, nodeValue, unit } = node
|
||||
const rule = engine?.getParsedRules()[node.dottedName]
|
||||
if (!rule) {
|
||||
throw new InternalError(node)
|
||||
}
|
||||
const inlineRule =
|
||||
node.dottedName === node.contextDottedName + ' . ' + node.name &&
|
||||
!node.name.includes(' . ') &&
|
||||
rule.virtualRule
|
||||
if (inlineRule) {
|
||||
return makeJsx(rule)
|
||||
}
|
||||
return (
|
||||
<span className="variable filtered leaf">
|
||||
<span className="nodeHead">
|
||||
<RuleLinkWithContext dottedName={dottedName}>
|
||||
<span className="name">
|
||||
{rule.rawRule.acronyme ? (
|
||||
<abbr title={rule.title}>{rule.rawRule.acronyme}</abbr>
|
||||
{rule.rawNode.acronyme ? (
|
||||
<abbr title={rule.title}>{rule.rawNode.acronyme}</abbr>
|
||||
) : (
|
||||
rule.title
|
||||
)}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { any, identity, path } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { makeJsx } from '../../evaluation'
|
||||
|
||||
const Conditions = ({
|
||||
'rendu non applicable': disabledBy,
|
||||
parentDependencies,
|
||||
'applicable si': applicable,
|
||||
'non applicable si': notApplicable
|
||||
}: any) => {
|
||||
const listElements = [
|
||||
...parentDependencies.map(
|
||||
parentDependency =>
|
||||
parentDependency.nodeValue === false && (
|
||||
<ShowIfDisabled
|
||||
dependency={parentDependency}
|
||||
key={parentDependency.dottedName}
|
||||
/>
|
||||
)
|
||||
),
|
||||
...disabledBy?.explanation?.isDisabledBy?.map(
|
||||
(dependency: any, i: number) =>
|
||||
dependency?.nodeValue === true && (
|
||||
<ShowIfDisabled dependency={dependency} key={`dependency ${i}`} />
|
||||
)
|
||||
),
|
||||
applicable && <li key="applicable">{makeJsx(applicable)}</li>,
|
||||
notApplicable && <li key="non applicable">{makeJsx(notApplicable)}</li>
|
||||
]
|
||||
|
||||
return any(identity, listElements) ? (
|
||||
<>
|
||||
<h2>
|
||||
<Trans>Déclenchement</Trans>
|
||||
</h2>
|
||||
<ul
|
||||
css={`
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
`}
|
||||
>
|
||||
{listElements}
|
||||
</ul>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
|
||||
function ShowIfDisabled({ dependency }: { dependency: any }) {
|
||||
return (
|
||||
<li>
|
||||
<span style={{ background: 'var(--lighterColor)', fontWeight: 'bold' }}>
|
||||
<Trans>Désactivée</Trans>
|
||||
</span>{' '}
|
||||
<Trans>car dépend de</Trans> {makeJsx(dependency)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Algorithm({ rule }: { rule: any }) {
|
||||
const formula =
|
||||
rule.formule ||
|
||||
(rule.category === 'variable' && rule.explanation.formule),
|
||||
displayFormula =
|
||||
formula &&
|
||||
!!Object.keys(formula).length &&
|
||||
!path(['formule', 'explanation', 'une possibilité'], rule) &&
|
||||
!(formula.explanation.constant && rule.nodeValue)
|
||||
return (
|
||||
<>
|
||||
<Conditions {...rule} />
|
||||
{displayFormula && (
|
||||
<>
|
||||
<h2>Comment cette donnée est-elle calculée ?</h2>
|
||||
<div
|
||||
className={
|
||||
formula.explanation.constant || formula.explanation.operator
|
||||
? 'mecanism'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{makeJsx(formula)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import Engine from '../..'
|
||||
import Engine, { EvaluatedNode } from '../..'
|
||||
import { makeJsx } from '../../evaluation'
|
||||
import { formatValue } from '../../format'
|
||||
import { ReferenceNode } from '../../reference'
|
||||
import { RuleNode } from '../../rule'
|
||||
import { ruleWithDedicatedDocumentationPage } from '../../ruleUtils'
|
||||
import { serializeUnit } from '../../units'
|
||||
import { simplifyNodeUnit } from '../../nodeUnits'
|
||||
import { Markdown } from '../Markdown'
|
||||
import { RuleLinkWithContext } from '../RuleLink'
|
||||
import Algorithm from './Algorithm'
|
||||
import RuleHeader from './Header'
|
||||
import References from './References'
|
||||
import RuleSource from './RuleSource'
|
||||
|
@ -15,12 +18,13 @@ export default function Rule({ dottedName, engine, language }) {
|
|||
if (!engine.getParsedRules()[dottedName]) {
|
||||
return <p>Cette règle est introuvable dans la base</p>
|
||||
}
|
||||
const rule = engine.evaluate(dottedName)
|
||||
const rule = engine.evaluateNode(
|
||||
engine.getParsedRules()[dottedName]
|
||||
) as EvaluatedNode & RuleNode
|
||||
// TODO affichage inline vs page
|
||||
|
||||
const isSetInStituation = engine.parsedSituation[dottedName] !== undefined
|
||||
const { description, question } = rule
|
||||
|
||||
const { description, question } = rule.rawNode
|
||||
const { parent, valeur } = rule.explanation
|
||||
return (
|
||||
<div id="documentationRuleRoot">
|
||||
<RuleHeader dottedName={dottedName} />
|
||||
|
@ -28,7 +32,7 @@ export default function Rule({ dottedName, engine, language }) {
|
|||
<Markdown source={description || question} />
|
||||
</section>
|
||||
|
||||
{(rule.nodeValue || rule.defaultValue || rule.unit) && (
|
||||
{(rule.nodeValue || rule.unit) && (
|
||||
<>
|
||||
<p
|
||||
className="ui__ lead card light-bg"
|
||||
|
@ -37,64 +41,57 @@ export default function Rule({ dottedName, engine, language }) {
|
|||
padding: '1rem'
|
||||
}}
|
||||
>
|
||||
{((rule.defaultValue?.nodeValue == null &&
|
||||
rule.nodeValue != null) ||
|
||||
(rule.defaultValue?.nodeValue != null && isSetInStituation)) && (
|
||||
<>
|
||||
{formatValue(rule, { language })}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{rule.defaultValue?.nodeValue != null && (
|
||||
<>
|
||||
<small>
|
||||
Valeur par défaut :{' '}
|
||||
{formatValue(rule.defaultValue, {
|
||||
language
|
||||
})}
|
||||
</small>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{rule.nodeValue == null && !rule.defaultValue?.unit && rule.unit && (
|
||||
<>
|
||||
<small>Unité : {serializeUnit(rule.unit)}</small>
|
||||
</>
|
||||
{formatValue(simplifyNodeUnit(rule), { language })}
|
||||
<br />
|
||||
|
||||
{rule.nodeValue == null && rule.unit && (
|
||||
<small>Unité : {serializeUnit(rule.unit)}</small>
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Algorithm rule={rule} />
|
||||
<RuleSource key={dottedName} dottedName={dottedName} engine={engine} />
|
||||
{rule['rend non applicable'] && (
|
||||
{parent && 'nodeValue' in parent && parent.nodeValue === false && (
|
||||
<>
|
||||
<h2>
|
||||
<Trans>Rend non applicable les règles suivantes</Trans> :{' '}
|
||||
</h2>
|
||||
<h3>Parent non applicable</h3>
|
||||
<p>
|
||||
Cette règle est non applicable car{' '}
|
||||
<RuleLinkWithContext
|
||||
dottedName={(parent as ReferenceNode).dottedName as string}
|
||||
/>{' '}
|
||||
est non applicable.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<h2>Comment cette donnée est-elle calculée ?</h2>
|
||||
{makeJsx(valeur)}
|
||||
|
||||
<RuleSource key={dottedName} dottedName={dottedName} engine={engine} />
|
||||
{!!rule.replacements.length && (
|
||||
<>
|
||||
<h3>Effets </h3>
|
||||
<ul>
|
||||
{rule['rend non applicable'].map(ruleName => (
|
||||
<li key={ruleName}>
|
||||
<RuleLinkWithContext dottedName={ruleName} />
|
||||
</li>
|
||||
{rule.replacements.map(replacement => (
|
||||
<li>{makeJsx(replacement)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{rule.note && (
|
||||
{rule.rawNode.note && (
|
||||
<>
|
||||
<h3>Note</h3>
|
||||
<div className="ui__ notice">
|
||||
<Markdown source={rule.note} />
|
||||
<Markdown source={rule.rawNode.note} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{rule.références && (
|
||||
{rule.rawNode.références && (
|
||||
<>
|
||||
<h2>
|
||||
<Trans>Références</Trans>
|
||||
</h2>
|
||||
<References refs={rule.références} />
|
||||
<References refs={rule.rawNode.références} />
|
||||
</>
|
||||
)}
|
||||
{/* <Examples
|
||||
|
|
|
@ -1,71 +1,86 @@
|
|||
import { mapAccum, scan } from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import yaml from 'yaml'
|
||||
import Engine, { formatValue } from '../../index'
|
||||
import yaml, { parse } from 'yaml'
|
||||
import { reduceAST } from '../../AST'
|
||||
import { ASTNode } from '../../AST/types'
|
||||
import Engine, { EvaluatedNode, formatValue } from '../../index'
|
||||
import PublicodesBlock from '../PublicodesBlock'
|
||||
|
||||
type Props = { dottedName: string; engine: Engine }
|
||||
export default function RuleSource({ engine, dottedName }: Props) {
|
||||
const [showSource, setShowSource] = useState(false)
|
||||
const { rawRule, dependencies } = engine.getParsedRules()[dottedName]
|
||||
// When we import a rule in the Publicode Studio, we need to provide a
|
||||
// simplified definition of its dependencies to avoid undefined references.
|
||||
// We use the current situation value as their simplified definition.
|
||||
const dependenciesValues = Object.fromEntries(
|
||||
dependencies.map(dottedNameDependency => [
|
||||
dottedNameDependency,
|
||||
formatValueForStudio(engine.evaluate(dottedNameDependency as string))
|
||||
])
|
||||
const rule = engine.evaluateNode(engine.getParsedRules()[dottedName])
|
||||
const dependencies = reduceAST<
|
||||
Array<
|
||||
ASTNode & {
|
||||
nodeKind: 'reference'
|
||||
}
|
||||
>
|
||||
>(
|
||||
(acc, node) => {
|
||||
if (node.nodeKind === 'reference') {
|
||||
return [...acc, node]
|
||||
}
|
||||
if (node.nodeKind === 'variations' && typeof node.rawNode === 'string') {
|
||||
// We don't take replacement into account
|
||||
const originNode = node.explanation.slice(-1)[0].consequence
|
||||
return originNode.nodeKind === 'reference' ? [...acc, originNode] : acc
|
||||
}
|
||||
},
|
||||
[],
|
||||
rule
|
||||
)
|
||||
|
||||
const source = yaml
|
||||
.stringify({
|
||||
...dependenciesValues,
|
||||
[dottedName]: rawRule
|
||||
// When we import a rule in the Publicode Studio, we need to provide a
|
||||
// simplified definition of its dependencies to avoid undefined references.
|
||||
const dependenciesValues = Object.fromEntries(
|
||||
dependencies.map(reference => [
|
||||
reference.dottedName,
|
||||
formatValueForStudio(reference as EvaluatedNode)
|
||||
])
|
||||
)
|
||||
const getParents = dottedName => scan(
|
||||
(acc, part) => [acc, part].filter(Boolean).join(' . '),
|
||||
'', dottedName.split(' . ') as Array<string>).filter(Boolean)
|
||||
|
||||
const values = dependencies.reduce((acc, dep) => {
|
||||
getParents(dep.dottedName).forEach(name => {
|
||||
acc[name] ??= 'oui'
|
||||
})
|
||||
return acc
|
||||
}, {
|
||||
...dependenciesValues,
|
||||
[dottedName]: rule.rawNode
|
||||
})
|
||||
|
||||
const source = yaml
|
||||
.stringify(values)
|
||||
// For clarity add a break line before the main rule
|
||||
.replace(`${dottedName}:`, `\n${dottedName}:`)
|
||||
|
||||
return showSource ? (
|
||||
<section>
|
||||
<h3>Source publicode</h3>
|
||||
<p className="ui__ notice">
|
||||
Ci-dessous la règle d'origine, écrite en publicodes. Publicodes est un
|
||||
langage déclaratif développé par beta.gouv.fr en partenariat avec
|
||||
l'Acoss pour encoder les algorithmes d'intérêt public.{' '}
|
||||
<a href="https://publi.codes">En savoir plus.</a>
|
||||
</p>
|
||||
<PublicodesBlock source={source} />
|
||||
|
||||
<p
|
||||
css={`
|
||||
text-align: right;
|
||||
`}
|
||||
>
|
||||
<button
|
||||
className="ui__ simple small button"
|
||||
onClick={() => setShowSource(false)}
|
||||
>
|
||||
{emoji('❌')} Cacher la règle publicodes
|
||||
</button>
|
||||
</p>
|
||||
</section>
|
||||
) : (
|
||||
<p
|
||||
const baseURL =
|
||||
location.hostname === 'localhost' ? '/publicodes' : 'https://publi.codes'
|
||||
return (
|
||||
<div
|
||||
css={`
|
||||
text-align: right;
|
||||
`}
|
||||
>
|
||||
<button
|
||||
<a
|
||||
className="ui__ simple small button"
|
||||
onClick={() => setShowSource(true)}
|
||||
target="_blank"
|
||||
href={`${baseURL}/studio/${encodeRuleName(dottedName)}?code=${encodeURIComponent(source)}`}
|
||||
>
|
||||
{emoji('✍️')} Voir la règle publicodes
|
||||
</button>
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const encodeRuleName = name =>
|
||||
name
|
||||
?.replace(/\s\.\s/g, '/')
|
||||
.replace(/-/g, '\u2011') // replace with a insecable tiret to differenciate from space
|
||||
.replace(/\s/g, '-')
|
||||
// TODO: This formating function should be in the core code. We need to think
|
||||
// about the different options of the formatting options and our use cases
|
||||
// (putting a value in the URL #1169, importing a value in the Studio, showing a value
|
||||
|
|
|
@ -48,13 +48,14 @@ export function typeWarning(
|
|||
message: string,
|
||||
originalError?: Error
|
||||
) {
|
||||
console.warn(
|
||||
`\n[ Erreur de type ]
|
||||
➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\`
|
||||
✖️ ${message}
|
||||
${originalError ? originalError.message : ''}
|
||||
`
|
||||
)
|
||||
// DESACTIVE EN ATTENDANT L'INFÉRENCE DE TYPE
|
||||
// console.warn(
|
||||
// `\n[ Erreur de type ]
|
||||
// ➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\`
|
||||
// ✖️ ${message}
|
||||
// ${originalError ? originalError.message : ''}
|
||||
// `
|
||||
// )
|
||||
}
|
||||
|
||||
export function warning(
|
||||
|
@ -62,13 +63,13 @@ export function warning(
|
|||
message: string,
|
||||
solution?: string
|
||||
) {
|
||||
console.warn(
|
||||
`\n[ Avertissement ]
|
||||
➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\`
|
||||
⚠️ ${message}
|
||||
💡 ${solution ? solution : ''}
|
||||
`
|
||||
)
|
||||
// console.warn(
|
||||
// `\n[ Avertissement ]
|
||||
// ➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\`
|
||||
// ⚠️ ${message}
|
||||
// 💡 ${solution ? solution : ''}
|
||||
// `
|
||||
// )
|
||||
}
|
||||
|
||||
export class InternalError extends EngineError {
|
||||
|
|
|
@ -14,10 +14,10 @@ import {
|
|||
ASTNode,
|
||||
ConstantNode,
|
||||
Evaluation,
|
||||
EvaluationDecoration,
|
||||
EvaluatedNode,
|
||||
NodeKind
|
||||
} from './AST/types'
|
||||
import { typeWarning } from './error'
|
||||
import { InternalError, typeWarning } from './error'
|
||||
import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits'
|
||||
import parse from './parse'
|
||||
import {
|
||||
|
@ -32,6 +32,9 @@ import {
|
|||
|
||||
export const makeJsx = (node: ASTNode): JSX.Element => {
|
||||
const Component = node.jsx
|
||||
if (!Component) {
|
||||
throw new InternalError(node)
|
||||
}
|
||||
return <Component {...node} />
|
||||
}
|
||||
|
||||
|
@ -115,7 +118,7 @@ export const defaultNode = (nodeValue: Evaluation) =>
|
|||
nodeValue,
|
||||
type: typeof nodeValue,
|
||||
// eslint-disable-next-line
|
||||
jsx: ({ nodeValue }: ASTNode & EvaluationDecoration) => (
|
||||
jsx: ({ nodeValue }: EvaluatedNode) => (
|
||||
<span className="value">{nodeValue}</span>
|
||||
),
|
||||
isDefault: true,
|
||||
|
@ -165,7 +168,7 @@ export function evaluateObject<NodeName extends NodeKind>(
|
|||
}, temporalExplanations)
|
||||
|
||||
const sameUnitTemporalExplanation: Temporal<ASTNode &
|
||||
EvaluationDecoration & { nodeValue: number }> = convertNodesToSameUnit(
|
||||
EvaluatedNode & { nodeValue: number }> = convertNodesToSameUnit(
|
||||
temporalExplanation.map(x => x.value),
|
||||
this.cache._meta.contextRule,
|
||||
node.nodeKind
|
||||
|
@ -189,7 +192,7 @@ export function evaluateObject<NodeName extends NodeKind>(
|
|||
if (sameUnitTemporalExplanation.length === 1) {
|
||||
return {
|
||||
...baseEvaluation,
|
||||
explanation: (sameUnitTemporalExplanation[0] as any).value
|
||||
explanation: (sameUnitTemporalExplanation[0] as any).value.explanation
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -18,7 +18,7 @@ const letter = '[a-zA-Z\u00C0-\u017F€$%]';
|
|||
const letterOrNumber = '[a-zA-Z\u00C0-\u017F0-9\'°]';
|
||||
const word = `${letter}(?:[-']?${letterOrNumber}+)*`;
|
||||
const wordOrNumber = `(?:${word}|${letterOrNumber}+)`
|
||||
const words = `${word}(?:[\\s]?${wordOrNumber}+)*`
|
||||
const words = `${word}(?:[,\\s]?${wordOrNumber}+)*`
|
||||
const periodWord = `\\| ${word}(?:[\\s]${word})*`
|
||||
|
||||
const numberRegExp = '-?(?:[1-9][0-9]+|[0-9])(?:\\.[0-9]+)?';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
import { map } from 'ramda'
|
||||
import { ASTNode, EvaluationDecoration, NodeKind } from './AST/types'
|
||||
import { ASTNode, EvaluatedNode, NodeKind } from './AST/types'
|
||||
import { evaluationFunctions } from './evaluationFunctions'
|
||||
import { simplifyNodeUnit } from './nodeUnits'
|
||||
import parse from './parse'
|
||||
import parsePublicodes, { disambiguateReference } from './parsePublicodes'
|
||||
import { Rule, RuleNode } from './rule'
|
||||
|
@ -30,18 +31,21 @@ export type EvaluationOptions = Partial<{
|
|||
unit: string
|
||||
}>
|
||||
|
||||
// export { default as cyclesLib } from './AST/index'
|
||||
export * as cyclesLib from './AST/graph'
|
||||
export { reduceAST, updateAST } from './AST'
|
||||
export * from './components'
|
||||
export { formatValue, serializeValue } from './format'
|
||||
export { formatValue } from './format'
|
||||
export { default as translateRules } from './translateRules'
|
||||
export { ASTNode, EvaluatedNode }
|
||||
export { parsePublicodes }
|
||||
export { utils }
|
||||
export { Rule }
|
||||
|
||||
export type evaluationFunction<Kind extends NodeKind = NodeKind> = (
|
||||
this: Engine,
|
||||
node: ASTNode & { nodeKind: Kind }
|
||||
) => ASTNode & { nodeKind: Kind } & EvaluationDecoration
|
||||
type ParsedRules<Name extends string> = Record<
|
||||
) => ASTNode & { nodeKind: Kind } & EvaluatedNode
|
||||
export type ParsedRules<Name extends string> = Record<
|
||||
Name,
|
||||
RuleNode & { dottedName: Name }
|
||||
>
|
||||
|
@ -51,7 +55,7 @@ export default class Engine<Name extends string = string> {
|
|||
cache: Cache
|
||||
private warnings: Array<string> = []
|
||||
|
||||
constructor(rules: string | Record<string, Rule> | Record<string, RuleNode>) {
|
||||
constructor(rules: string | Record<string, Rule> | ParsedRules<Name>) {
|
||||
this.cache = emptyCache()
|
||||
this.resetCache()
|
||||
if (typeof rules === 'string') {
|
||||
|
@ -69,6 +73,7 @@ export default class Engine<Name extends string = string> {
|
|||
this.parsedRules = parsePublicodes(
|
||||
rules as Record<string, Rule>
|
||||
) as ParsedRules<Name>
|
||||
|
||||
}
|
||||
|
||||
private resetCache() {
|
||||
|
@ -76,24 +81,25 @@ export default class Engine<Name extends string = string> {
|
|||
}
|
||||
|
||||
setSituation(
|
||||
situation: Partial<Record<Name, string | number | object>> = {}
|
||||
situation: Partial<Record<Name, string | number | object | ASTNode>> = {}
|
||||
) {
|
||||
this.resetCache()
|
||||
this.parsedSituation = map(value => {
|
||||
if (value && typeof value === 'object' && 'nodeKind' in value) {
|
||||
return value as ASTNode
|
||||
}
|
||||
return disambiguateReference(this.parsedRules)(
|
||||
parse(value, {
|
||||
dottedName: "'''situation",
|
||||
dottedName: "situation'''",
|
||||
parsedRules: {}
|
||||
})
|
||||
)
|
||||
}, situation)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
evaluate(
|
||||
expression: Name
|
||||
): RuleNode & EvaluationDecoration & { dottedName: Name }
|
||||
evaluate(expression: string): ASTNode & EvaluationDecoration {
|
||||
evaluate(expression: string | Object): EvaluatedNode {
|
||||
/*
|
||||
TODO
|
||||
EN ATTENDANT d'AVOIR une meilleure gestion d'erreur, on va mocker console.warn
|
||||
|
@ -104,14 +110,11 @@ export default class Engine<Name extends string = string> {
|
|||
this.warnings.push(warning)
|
||||
originalWarn(warning)
|
||||
}
|
||||
if (this.parsedRules[expression]) {
|
||||
// TODO : No replacement here. Is this what we want ?
|
||||
return this.evaluateNode(this.parsedRules[expression])
|
||||
}
|
||||
const result = this.evaluateNode(
|
||||
// TODO : No replacement here. Is this what we want ?
|
||||
disambiguateReference(this.parsedRules)(
|
||||
parse(expression, {
|
||||
dottedName: "'''evaluation",
|
||||
dottedName: "evaluation'''",
|
||||
parsedRules: {}
|
||||
})
|
||||
)
|
||||
|
@ -128,11 +131,11 @@ export default class Engine<Name extends string = string> {
|
|||
return !!this.cache._meta.inversionFail
|
||||
}
|
||||
|
||||
getParsedRules(): Record<string, RuleNode> {
|
||||
getParsedRules(): ParsedRules<Name> {
|
||||
return this.parsedRules
|
||||
}
|
||||
|
||||
evaluateNode<N extends ASTNode = ASTNode>(node: N): N & EvaluationDecoration {
|
||||
evaluateNode<N extends ASTNode = ASTNode>(node: N): N & EvaluatedNode {
|
||||
if (!node.nodeKind) {
|
||||
throw Error('The provided node must have a "nodeKind" attribute')
|
||||
} else if (!evaluationFunctions[node.nodeKind]) {
|
||||
|
@ -142,3 +145,31 @@ export default class Engine<Name extends string = string> {
|
|||
return evaluationFunctions[node.nodeKind].call(this, node)
|
||||
}
|
||||
}
|
||||
|
||||
// This function is an util for allowing smother migration to the new Engine API
|
||||
export function evaluateRule<DottedName extends string = string>(
|
||||
engine: Engine<DottedName>,
|
||||
dottedName: DottedName,
|
||||
modifiers: Object = {}
|
||||
): EvaluatedRule<DottedName> {
|
||||
const evaluation = simplifyNodeUnit(
|
||||
engine.evaluate({ valeur: dottedName, ...modifiers })
|
||||
)
|
||||
const rule = engine.getParsedRules()[dottedName] as RuleNode & { dottedName: DottedName }
|
||||
return {
|
||||
...rule.rawNode,
|
||||
...rule,
|
||||
...evaluation
|
||||
} as EvaluatedRule<DottedName>
|
||||
}
|
||||
|
||||
export type EvaluatedRule<Name extends string = string> = EvaluatedNode &
|
||||
Omit<
|
||||
(ASTNode & {
|
||||
nodeKind: 'rule'
|
||||
}) &
|
||||
(ASTNode & {
|
||||
nodeKind: 'rule'
|
||||
})['rawNode'] & { dottedName: Name },
|
||||
'nodeKind'
|
||||
>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import { evaluationFunction } from '..'
|
||||
import parse from '../parse'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { InfixMecanism, Mecanism } from '../components/mecanisms/common'
|
||||
import { bonus, makeJsx, mergeMissing } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import { ASTNode } from '../AST/types'
|
||||
|
@ -18,10 +18,10 @@ export type ApplicableSiNode = {
|
|||
function MecanismApplicable({ explanation }) {
|
||||
return (
|
||||
<InfixMecanism prefixed value={explanation.valeur}>
|
||||
<p>
|
||||
<strong>Applicable si : </strong>
|
||||
<Mecanism name="applicable si" value={explanation.condition.nodeValue}>
|
||||
{makeJsx(explanation.condition)}
|
||||
</p>
|
||||
</Mecanism>
|
||||
<br />
|
||||
</InfixMecanism>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -64,12 +64,12 @@ export const evaluateInversion: evaluationFunction<'inversion'> = function(
|
|||
}
|
||||
this.parsedSituation[node.explanation.ruleToInverse] = {
|
||||
unit: unit,
|
||||
jsx: null,
|
||||
jsx: () => n,
|
||||
nodeKind: 'unité',
|
||||
explanation: {
|
||||
nodeKind: 'constant',
|
||||
nodeValue: n,
|
||||
jsx: null,
|
||||
jsx: () => n,
|
||||
type: 'number'
|
||||
} as ConstantNode
|
||||
} as UnitéNode
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { evaluationFunction } from '..'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { InfixMecanism, Mecanism } from '../components/mecanisms/common'
|
||||
import { ASTNode } from '../AST/types'
|
||||
import { bonus, makeJsx, mergeMissing } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
|
@ -16,10 +16,13 @@ export type NonApplicableSiNode = {
|
|||
function MecanismNonApplicable({ explanation }) {
|
||||
return (
|
||||
<InfixMecanism prefixed value={explanation.valeur}>
|
||||
<p>
|
||||
<strong>Non applicable si : </strong>
|
||||
{makeJsx(explanation.applicable)}
|
||||
</p>
|
||||
<Mecanism
|
||||
name="non applicable si"
|
||||
value={explanation.condition.nodeValue}
|
||||
>
|
||||
{makeJsx(explanation.condition)}
|
||||
</Mecanism>
|
||||
<br />
|
||||
</InfixMecanism>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { ASTNode } from '../AST/types'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { makeJsx } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import parse from '../parse'
|
||||
import { Context } from '../parsePublicodes'
|
||||
|
@ -21,12 +23,20 @@ export const mecanismOnePossibility = (v, context: Context) => {
|
|||
...v,
|
||||
explanation: v.possibilités.map(p => parse(p, context)),
|
||||
nodeKind: 'une possibilité',
|
||||
jsx: (node: PossibilityNode) => (
|
||||
<Mecanism name="une possibilité parmis" value={null}>
|
||||
<ul>
|
||||
{node.explanation.map(node => (
|
||||
<li>{makeJsx(node)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Mecanism>
|
||||
),
|
||||
context: context.dottedName
|
||||
} as PossibilityNode
|
||||
}
|
||||
registerEvaluationFunction<'une possibilité'>('une possibilité', node => ({
|
||||
...node,
|
||||
nodeValue: null,
|
||||
jsx: null,
|
||||
missingVariables: { [node.context]: 1 }
|
||||
}))
|
||||
|
|
|
@ -10,7 +10,7 @@ import { registerEvaluationFunction } from '../evaluationFunctions'
|
|||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import parse from '../parse'
|
||||
import { liftTemporal2, pureTemporal, temporalAverage } from '../temporal'
|
||||
import { EvaluationDecoration } from '../AST/types'
|
||||
import { EvaluatedNode } from '../AST/types'
|
||||
import { inferUnit, serializeUnit } from '../units'
|
||||
|
||||
const knownOperations = {
|
||||
|
@ -57,8 +57,8 @@ const parseOperation = (k, symbol) => (v, context) => {
|
|||
|
||||
const evaluate: evaluationFunction<'operation'> = function(node) {
|
||||
const explanation = node.explanation.map(node => this.evaluateNode(node)) as [
|
||||
ASTNode & EvaluationDecoration,
|
||||
ASTNode & EvaluationDecoration
|
||||
EvaluatedNode,
|
||||
EvaluatedNode
|
||||
]
|
||||
let [node1, node2] = explanation
|
||||
const missingVariables = mergeAllMissing([node1, node2])
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ASTNode } from '../AST/types'
|
|||
import { bonus, makeJsx, mergeMissing } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import parse from '../parse'
|
||||
import { EvaluationDecoration } from '../AST/types'
|
||||
import { EvaluatedNode } from '../AST/types'
|
||||
|
||||
export type ParDéfautNode = {
|
||||
explanation: {
|
||||
|
@ -17,7 +17,10 @@ export type ParDéfautNode = {
|
|||
}
|
||||
function ParDéfautComponent({ explanation }) {
|
||||
return (
|
||||
<InfixMecanism prefixed value={explanation.valeur}>
|
||||
<InfixMecanism
|
||||
value={explanation.valeur}
|
||||
dimValue={explanation.valeur.nodeValue === null}
|
||||
>
|
||||
<p>
|
||||
<strong>Par défaut : </strong>
|
||||
{makeJsx(explanation.parDéfaut)}
|
||||
|
@ -40,7 +43,7 @@ const evaluate: evaluationFunction<'par défaut'> = function(node) {
|
|||
nodeValue: valeur.nodeValue,
|
||||
explanation,
|
||||
missingVariables: mergeMissing(
|
||||
(explanation.valeur as EvaluationDecoration).missingVariables,
|
||||
(explanation.valeur as EvaluatedNode).missingVariables,
|
||||
'missingVariables' in explanation.parDéfaut
|
||||
? bonus(explanation.parDéfaut.missingVariables)
|
||||
: {}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { makeJsx, mergeAllMissing } from '../evaluation'
|
|||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import { ASTNode } from '../AST/types'
|
||||
import { EvaluationDecoration } from '../AST/types'
|
||||
import { EvaluatedNode } from '../AST/types'
|
||||
|
||||
function MecanismPlafond({ explanation }) {
|
||||
return (
|
||||
|
@ -43,10 +43,7 @@ const evaluate: evaluationFunction<'plafond'> = function(node) {
|
|||
plafond = this.evaluateNode(plafond)
|
||||
if (valeur.unit) {
|
||||
try {
|
||||
plafond = convertNodeToUnit(
|
||||
valeur.unit,
|
||||
plafond as ASTNode & EvaluationDecoration
|
||||
)
|
||||
plafond = convertNodeToUnit(valeur.unit, plafond as EvaluatedNode)
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
this.cache._meta.contextRule,
|
||||
|
|
|
@ -7,7 +7,7 @@ import { makeJsx, mergeAllMissing } from '../evaluation'
|
|||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import parse from '../parse'
|
||||
import { EvaluationDecoration } from '../AST/types'
|
||||
import { EvaluatedNode } from '../AST/types'
|
||||
|
||||
function MecanismPlancher({ explanation }) {
|
||||
return (
|
||||
|
@ -41,10 +41,7 @@ const evaluate: evaluationFunction<'plancher'> = function(node) {
|
|||
plancher = this.evaluateNode(plancher)
|
||||
if (valeur.unit) {
|
||||
try {
|
||||
plancher = convertNodeToUnit(
|
||||
valeur.unit,
|
||||
plancher as ASTNode & EvaluationDecoration
|
||||
)
|
||||
plancher = convertNodeToUnit(valeur.unit, plancher as EvaluatedNode)
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
this.cache._meta.contextRule,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { registerEvaluationFunction } from '../evaluationFunctions'
|
|||
import parse from '../parse'
|
||||
import { ReferenceNode } from '../reference'
|
||||
import { disambiguateRuleReference } from '../ruleUtils'
|
||||
import { EvaluationDecoration } from '../AST/types'
|
||||
import { EvaluatedNode } from '../AST/types'
|
||||
import { serializeUnit } from '../units'
|
||||
|
||||
export type RecalculNode = {
|
||||
|
@ -20,7 +20,7 @@ export type RecalculNode = {
|
|||
|
||||
const evaluateRecalcul: evaluationFunction<'recalcul'> = function(node) {
|
||||
if (this.cache._meta.inRecalcul) {
|
||||
return (defaultNode(false) as any) as RecalculNode & EvaluationDecoration
|
||||
return (defaultNode(false) as any) as RecalculNode & EvaluatedNode
|
||||
}
|
||||
|
||||
const amendedSituation = node.explanation.amendedSituation
|
||||
|
@ -32,9 +32,7 @@ const evaluateRecalcul: evaluationFunction<'recalcul'> = function(node) {
|
|||
([originRule, replacement]) =>
|
||||
originRule.nodeValue !== replacement.nodeValue ||
|
||||
serializeUnit(originRule.unit) !== serializeUnit(replacement.unit)
|
||||
) as Array<
|
||||
[ReferenceNode & EvaluationDecoration, ASTNode & EvaluationDecoration]
|
||||
>
|
||||
) as Array<[ReferenceNode & EvaluatedNode, EvaluatedNode]>
|
||||
|
||||
const originalCache = { ...this.cache }
|
||||
const originalSituation = { ...this.parsedSituation }
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import { isEmpty } from 'ramda'
|
||||
import { ASTNode, EvaluationDecoration } from '../AST/types'
|
||||
import { ASTNode, EvaluatedNode } from '../AST/types'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { makeJsx, mergeAllMissing } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import parse from '../parse'
|
||||
|
||||
function MecanismSituation({ explanation, nodeValue, unit }) {
|
||||
// TODO : vue différente selon si valeur depuis la situation ou calculée
|
||||
return makeJsx({ ...explanation.valeur, nodeValue, unit })
|
||||
function MecanismSituation({ explanation }) {
|
||||
return explanation.situationValeur ? (
|
||||
<InfixMecanism prefixed value={explanation.valeur} dimValue>
|
||||
<p>
|
||||
<strong>Valeur renseignée dans la simulation : </strong>
|
||||
{makeJsx(explanation.situationValeur)}
|
||||
</p>
|
||||
</InfixMecanism>
|
||||
) : (
|
||||
makeJsx(explanation.valeur)
|
||||
)
|
||||
}
|
||||
|
||||
export type SituationNode = {
|
||||
|
@ -36,7 +45,7 @@ parseSituation.nom = 'nom dans la situation' as const
|
|||
registerEvaluationFunction(parseSituation.nom, function evaluate(node) {
|
||||
const explanation = { ...node.explanation }
|
||||
const situationKey = explanation.situationKey
|
||||
let valeur: ASTNode & EvaluationDecoration
|
||||
let valeur: EvaluatedNode
|
||||
if (situationKey in this.parsedSituation) {
|
||||
valeur = this.evaluateNode(this.parsedSituation[situationKey])
|
||||
explanation.situationValeur = valeur
|
||||
|
|
|
@ -4,10 +4,7 @@ import { evaluateArray } from '../evaluation'
|
|||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import parse from '../parse'
|
||||
|
||||
const evaluate = evaluateArray<'somme'>(
|
||||
(x: any, y: any) => (x === false && y === false ? false : x + y),
|
||||
false
|
||||
)
|
||||
const evaluate = evaluateArray<'somme'>((x: any, y: any) => x + y, 0)
|
||||
|
||||
export type SommeNode = {
|
||||
explanation: Array<ASTNode>
|
||||
|
|
|
@ -6,10 +6,8 @@ import parse from '../parse'
|
|||
import { convertUnit, inferUnit } from '../units'
|
||||
|
||||
type TrancheNode = { taux: ASTNode } | { montant: ASTNode }
|
||||
export type TrancheNodes = [
|
||||
...Array<TrancheNode & { plafond: ASTNode }>,
|
||||
TrancheNode & { plafond?: ASTNode }
|
||||
]
|
||||
export type TrancheNodes = Array<TrancheNode & { plafond?: ASTNode }>
|
||||
|
||||
export const parseTranches = (tranches, context): TrancheNodes => {
|
||||
return tranches
|
||||
.map((t, i) => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { ASTNode, Unit } from '../AST/types'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { typeWarning } from '../error'
|
||||
import { makeJsx } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
import { formatValue } from '../format'
|
||||
import parse from '../parse'
|
||||
import { convertUnit, parseUnit } from '../units'
|
||||
import { convertUnit, parseUnit, serializeUnit } from '../units'
|
||||
|
||||
export type UnitéNode = {
|
||||
unit: Unit
|
||||
|
@ -11,8 +13,22 @@ export type UnitéNode = {
|
|||
jsx: any
|
||||
nodeKind: 'unité'
|
||||
}
|
||||
function MecanismUnité({ explanation, nodeValue, unit }) {
|
||||
return makeJsx({ ...explanation, nodeValue, unit })
|
||||
function MecanismUnité(node) {
|
||||
return node.explanation.nodeKind === 'constant' ||
|
||||
node.explanation.nodeKind === 'reference' ? (
|
||||
<>
|
||||
{makeJsx(node.explanation)} {serializeUnit(node.unit)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<InfixMecanism value={node.explanation}>
|
||||
<p>
|
||||
<strong>Unité : </strong>
|
||||
{serializeUnit(node.unit)}
|
||||
</p>
|
||||
</InfixMecanism>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function parseUnité(v, context): UnitéNode {
|
||||
|
|
|
@ -59,7 +59,7 @@ export default function parseVariableTemporelle(
|
|||
const explanation = parse(v.explanation, context)
|
||||
return {
|
||||
nodeKind: 'variable temporelle',
|
||||
jsx: null,
|
||||
jsx: () => 'variable temporelle',
|
||||
explanation: {
|
||||
period: {
|
||||
start: v.period.start && parse(v.period.start, context),
|
||||
|
|
|
@ -20,9 +20,10 @@ export type VariationNode = {
|
|||
explanation: Array<{
|
||||
condition: ASTNode
|
||||
consequence: ASTNode
|
||||
satisfied?: boolean
|
||||
}>
|
||||
nodeKind: 'variations'
|
||||
jsx: any
|
||||
jsx: Function
|
||||
}
|
||||
|
||||
export const devariate = (k, v, context): ASTNode => {
|
||||
|
@ -51,14 +52,13 @@ export const devariate = (k, v, context): ASTNode => {
|
|||
return explanation
|
||||
}
|
||||
|
||||
export default function parseVariations(v, context) {
|
||||
export default function parseVariations(v, context): VariationNode {
|
||||
const explanation = v.map(({ si, alors, sinon }) =>
|
||||
sinon !== undefined
|
||||
? { consequence: parse(sinon, context), condition: defaultNode(true) }
|
||||
: { consequence: parse(alors, context), condition: parse(si, context) }
|
||||
)
|
||||
|
||||
// TODO - find an appropriate representation
|
||||
return {
|
||||
explanation,
|
||||
jsx: Variations,
|
||||
|
@ -163,15 +163,14 @@ const evaluate: evaluationFunction<'variations'> = function(node) {
|
|||
[]
|
||||
)
|
||||
)
|
||||
|
||||
return simplifyNodeUnit({
|
||||
return {
|
||||
...node,
|
||||
nodeValue,
|
||||
...(unit !== undefined && { unit }),
|
||||
explanation,
|
||||
missingVariables,
|
||||
...(temporalValue.length > 1 && { temporalValue })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('variations', evaluate)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { mapTemporal } from './temporal'
|
||||
import { convertUnit, simplifyUnit } from './units'
|
||||
import { ASTNode, EvaluationDecoration, Unit } from './AST/types'
|
||||
import { convertUnit, serializeUnit, simplifyUnit } from './units'
|
||||
import { ASTNode, EvaluatedNode, Unit } from './AST/types'
|
||||
|
||||
export function simplifyNodeUnit(node) {
|
||||
if (!node.unit) {
|
||||
|
@ -11,10 +11,10 @@ export function simplifyNodeUnit(node) {
|
|||
return convertNodeToUnit(unit, node)
|
||||
}
|
||||
|
||||
export function convertNodeToUnit(
|
||||
export function convertNodeToUnit<Node extends EvaluatedNode = EvaluatedNode>(
|
||||
to: Unit | undefined,
|
||||
node: ASTNode & EvaluationDecoration
|
||||
) {
|
||||
node: Node
|
||||
): Node {
|
||||
const temporalValue =
|
||||
node.temporalValue && node.unit
|
||||
? mapTemporal(
|
||||
|
|
|
@ -59,7 +59,10 @@ Utilisez leur contrepartie française : 'oui' / 'non'`
|
|||
return parseRule(node, context)
|
||||
}
|
||||
|
||||
return parseChainedMecanisms(node, context)
|
||||
return {
|
||||
...parseChainedMecanisms(node, context),
|
||||
rawNode
|
||||
}
|
||||
}
|
||||
|
||||
const compiledGrammar = Grammar.fromCompiled(grammar)
|
||||
|
@ -106,7 +109,7 @@ Cela vient probablement d'une erreur dans l'indentation
|
|||
)
|
||||
}
|
||||
if (isEmpty(rawNode)) {
|
||||
return { nodeKind: 'constant', nodeValue: null }
|
||||
return { nodeKind: 'constant', nodeValue: null, jsx: () => null }
|
||||
}
|
||||
|
||||
const mecanismName = Object.keys(rawNode)[0]
|
||||
|
@ -146,11 +149,11 @@ const chainableMecanisms = [
|
|||
applicable,
|
||||
nonApplicable,
|
||||
parDéfaut,
|
||||
situation,
|
||||
arrondi,
|
||||
unité,
|
||||
plancher,
|
||||
plafond,
|
||||
unité,
|
||||
arrondi
|
||||
situation
|
||||
]
|
||||
function parseChainedMecanisms(rawNode, context: Context): ASTNode {
|
||||
const parseFn = chainableMecanisms.find(fn => fn.nom in rawNode)
|
||||
|
@ -195,7 +198,12 @@ const parseFunctions = {
|
|||
objet: v => ({
|
||||
type: 'objet',
|
||||
nodeValue: v,
|
||||
nodeKind: 'constant'
|
||||
nodeKind: 'constant',
|
||||
jsx: () => (
|
||||
<code>
|
||||
<pre>{JSON.stringify(v, null, 2)}</pre>
|
||||
</code>
|
||||
)
|
||||
}),
|
||||
constant: v => ({
|
||||
type: v.type,
|
||||
|
|
|
@ -97,13 +97,16 @@ function transpileRef(object: Record<string, any> | string | Array<any>) {
|
|||
export const disambiguateReference = (parsedRules: Record<string, RuleNode>) =>
|
||||
updateAST(node => {
|
||||
if (node.nodeKind === 'reference') {
|
||||
const dottedName = disambiguateRuleReference(
|
||||
parsedRules,
|
||||
node.contextDottedName,
|
||||
node.name
|
||||
)
|
||||
return {
|
||||
...node,
|
||||
dottedName: disambiguateRuleReference(
|
||||
parsedRules,
|
||||
node.contextDottedName,
|
||||
node.name
|
||||
)
|
||||
dottedName,
|
||||
title: parsedRules[dottedName].title,
|
||||
acronym: parsedRules[dottedName].rawNode.acronyme
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EvaluationDecoration } from './AST/types'
|
||||
import { EvaluatedNode } from './AST/types'
|
||||
import { Leaf } from './components/mecanisms/common'
|
||||
import { InternalError } from './error'
|
||||
import { registerEvaluationFunction } from './evaluationFunctions'
|
||||
|
@ -8,7 +8,7 @@ import { RuleNode } from './rule'
|
|||
export type ReferenceNode = {
|
||||
nodeKind: 'reference'
|
||||
name: string
|
||||
explanation?: RuleNode & EvaluationDecoration
|
||||
explanation?: RuleNode & EvaluatedNode
|
||||
contextDottedName: string
|
||||
dottedName?: string
|
||||
jsx: any
|
||||
|
|
|
@ -2,8 +2,10 @@ import { groupBy } from 'ramda'
|
|||
import { AST } from 'yaml'
|
||||
import { traverseParsedRules, updateAST } from './AST'
|
||||
import { ASTNode } from './AST/types'
|
||||
import Variations from './components/mecanisms/Variations'
|
||||
import { InternalError, warning } from './error'
|
||||
import { defaultNode } from './evaluation'
|
||||
import { defaultNode, makeJsx } from './evaluation'
|
||||
import { VariationNode } from './mecanisms/variations'
|
||||
import parse from './parse'
|
||||
import { Context } from './parsePublicodes'
|
||||
import { RuleNode } from './rule'
|
||||
|
@ -17,6 +19,7 @@ export type ReplacementNode = {
|
|||
replacementNode: ASTNode
|
||||
whiteListedNames: Array<ASTNode & { nodeKind: 'reference' }>
|
||||
jsx: any
|
||||
rawNode: any
|
||||
blackListedNames: Array<ASTNode & { nodeKind: 'reference' }>
|
||||
}
|
||||
|
||||
|
@ -27,27 +30,39 @@ export function parseReplacements(
|
|||
if (!replacements) {
|
||||
return []
|
||||
}
|
||||
return coerceArray(replacements).map(reference => {
|
||||
if (typeof reference === 'string') {
|
||||
reference = { règle: reference }
|
||||
return coerceArray(replacements).map(replacement => {
|
||||
if (typeof replacement === 'string') {
|
||||
replacement = { règle: replacement }
|
||||
}
|
||||
|
||||
const replacedReference = parse(reference.règle, context)
|
||||
let replacementNode = parse(reference.par ?? context.dottedName, context)
|
||||
const replacedReference = parse(replacement.règle, context)
|
||||
let replacementNode = parse(replacement.par ?? context.dottedName, context)
|
||||
|
||||
const [whiteListedNames, blackListedNames] = [
|
||||
reference.dans ?? [],
|
||||
reference['sauf dans'] ?? []
|
||||
replacement.dans ?? [],
|
||||
replacement['sauf dans'] ?? []
|
||||
]
|
||||
.map(dottedName => coerceArray(dottedName))
|
||||
.map(refs => refs.map(ref => parse(ref, context)))
|
||||
|
||||
return {
|
||||
nodeKind: 'replacement',
|
||||
rawNode: replacement,
|
||||
definitionRule: parse(context.dottedName, context),
|
||||
replacedReference,
|
||||
replacementNode,
|
||||
jsx: null,
|
||||
jsx: (node: ReplacementNode) => (
|
||||
<span>
|
||||
Remplace {makeJsx(node.replacedReference)}{' '}
|
||||
{node.rawNode.par && <>par {makeJsx(node.replacementNode)}</>}
|
||||
{node.rawNode.dans && (
|
||||
<>dans {node.whiteListedNames.map(makeJsx).join(', ')}</>
|
||||
)}
|
||||
{node.rawNode['sauf dans'] && (
|
||||
<>sauf dans {node.blackListedNames.map(makeJsx).join(', ')}</>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
whiteListedNames,
|
||||
blackListedNames
|
||||
} as ReplacementNode
|
||||
|
@ -58,10 +73,13 @@ export function parseRendNonApplicable(
|
|||
rules: Rule['rend non applicable'],
|
||||
context: Context
|
||||
): Array<ReplacementNode> {
|
||||
return parseReplacements(rules, context).map(replacement => ({
|
||||
...replacement,
|
||||
replacementNode: defaultNode(false)
|
||||
}))
|
||||
return parseReplacements(rules, context).map(
|
||||
replacement =>
|
||||
({
|
||||
...replacement,
|
||||
replacementNode: defaultNode(false)
|
||||
} as ReplacementNode)
|
||||
)
|
||||
}
|
||||
|
||||
export function inlineReplacements(
|
||||
|
@ -78,9 +96,14 @@ export function inlineReplacements(
|
|||
)
|
||||
return traverseParsedRules(
|
||||
updateAST(node => {
|
||||
if (node.nodeKind === 'replacement') {
|
||||
if (
|
||||
node.nodeKind === 'replacement' ||
|
||||
node.nodeKind === 'inversion' ||
|
||||
node.nodeKind === 'une possibilité' ||
|
||||
node.nodeKind === 'recalcul'
|
||||
) {
|
||||
// We don't want to replace references in replacements...
|
||||
// Nor in ammended situation of recalcul and inversion (TODO)
|
||||
// Nor in ammended situation of recalcul and inversion (for now)
|
||||
return false
|
||||
}
|
||||
if (node.nodeKind === 'reference') {
|
||||
|
@ -129,7 +152,9 @@ function replace(
|
|||
+!!r2.blackListedNames.length - +!!r1.blackListedNames.length
|
||||
return criterion1 || criterion2
|
||||
})
|
||||
|
||||
if (!applicableReplacements.length) {
|
||||
return node
|
||||
}
|
||||
if (applicableReplacements.length > 1) {
|
||||
warning(
|
||||
node.contextDottedName,
|
||||
|
@ -143,22 +168,27 @@ ${applicableReplacements.map(
|
|||
`
|
||||
)
|
||||
}
|
||||
return applicableReplacements.reduceRight<ASTNode>(
|
||||
(replacedNode, replacement) => {
|
||||
return {
|
||||
nodeKind: 'variations',
|
||||
explanation: [
|
||||
{
|
||||
condition: replacement.definitionRule,
|
||||
consequence: replacement.replacementNode
|
||||
},
|
||||
{
|
||||
condition: defaultNode(true),
|
||||
consequence: replacedNode
|
||||
}
|
||||
]
|
||||
} as ASTNode & { nodeKind: 'variations' }
|
||||
},
|
||||
node
|
||||
)
|
||||
|
||||
return {
|
||||
nodeKind: 'variations',
|
||||
rawNode: node.rawNode,
|
||||
jsx: Replacement,
|
||||
explanation: [
|
||||
...applicableReplacements.map(replacement => ({
|
||||
condition: replacement.definitionRule,
|
||||
consequence: replacement.replacementNode
|
||||
})),
|
||||
{
|
||||
condition: defaultNode(true),
|
||||
consequence: node
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function Replacement(node: VariationNode) {
|
||||
const applicableReplacement = node.explanation.find(ex => ex.satisfied)
|
||||
?.consequence
|
||||
const replacedNode = node.explanation.slice(-1)[0].consequence
|
||||
return makeJsx(applicableReplacement || replacedNode)
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { filter, map, mapObjIndexed, pick } from 'ramda'
|
||||
import { ASTNode, EvaluationDecoration } from './AST/types'
|
||||
import RuleComponent from './components/rule/Rule'
|
||||
import { bonus, mergeMissing } from './evaluation'
|
||||
import { filter, mapObjIndexed, pick } from 'ramda'
|
||||
import { ASTNode, EvaluatedNode } from './AST/types'
|
||||
import { bonus, makeJsx, mergeMissing } from './evaluation'
|
||||
import { registerEvaluationFunction } from "./evaluationFunctions"
|
||||
import parseNonApplicable from './mecanisms/nonApplicable'
|
||||
import parse, { mecanismKeys } from './parse'
|
||||
import { Context } from './parsePublicodes'
|
||||
import { ReferenceNode } from './reference'
|
||||
import { parseRendNonApplicable, parseReplacements, ReplacementNode } from './replacement'
|
||||
import { nameLeaf, ruleParents } from './ruleUtils'
|
||||
import { capitalise0 } from './utils'
|
||||
|
@ -21,13 +20,18 @@ export type Rule = {
|
|||
résumé?: string
|
||||
'icônes'?: string
|
||||
titre?: string
|
||||
cotisation?: {
|
||||
branche: string
|
||||
}
|
||||
type?: string
|
||||
note?: string
|
||||
remplace?: RendNonApplicable | Array<RendNonApplicable>
|
||||
'rend non applicable'?: Remplace | Array<string>
|
||||
suggestions?: Record<string, string | number | object>
|
||||
références?: { [source: string]: string }
|
||||
API?: string
|
||||
}
|
||||
|
||||
type Remplace = {
|
||||
règle: string
|
||||
par?: Object | string | number
|
||||
|
@ -41,6 +45,7 @@ export type RuleNode = {
|
|||
title: string
|
||||
nodeKind: "rule"
|
||||
jsx: any
|
||||
virtualRule: boolean,
|
||||
rawNode: Rule,
|
||||
replacements: Array<ReplacementNode>
|
||||
explanation: {
|
||||
|
@ -48,13 +53,12 @@ export type RuleNode = {
|
|||
valeur: ASTNode
|
||||
}
|
||||
suggestions: Record<string, ASTNode>
|
||||
dependencies: Array<string>
|
||||
}
|
||||
|
||||
export default function parseRule(
|
||||
rawRule: Rule,
|
||||
context: Context
|
||||
): RuleNode {
|
||||
): ReferenceNode {
|
||||
const dottedName = [context.dottedName, rawRule.nom]
|
||||
.filter(Boolean)
|
||||
.join(' . ')
|
||||
|
@ -70,7 +74,10 @@ export default function parseRule(
|
|||
}
|
||||
|
||||
const ruleContext = { ...context, dottedName }
|
||||
const name = nameLeaf(dottedName)
|
||||
let name = nameLeaf(dottedName)
|
||||
if (context.dottedName) {
|
||||
name = `${nameLeaf(context.dottedName)} (${name})`
|
||||
}
|
||||
const [parent] = ruleParents(dottedName)
|
||||
const explanation = {
|
||||
valeur: parse(ruleValue, ruleContext),
|
||||
|
@ -85,13 +92,18 @@ export default function parseRule(
|
|||
title: capitalise0(rawRule['titre'] || name),
|
||||
suggestions: mapObjIndexed(node => parse(node, ruleContext), rawRule.suggestions ?? {}),
|
||||
nodeKind: "rule",
|
||||
jsx: RuleComponent,
|
||||
jsx: node => <>
|
||||
<code className="ui__ light-bg">{capitalise0(node.rawNode.nom)}</code>
|
||||
{makeJsx(node.explanation.valeur)}
|
||||
</>,
|
||||
explanation,
|
||||
rawNode: rawRule,
|
||||
dependencies: [] as Array<string> // TODO
|
||||
virtualRule: !!context.dottedName
|
||||
}) as RuleNode
|
||||
|
||||
return context.parsedRules[dottedName]
|
||||
// We return the parsedReference
|
||||
return parse(rawRule.nom, context) as ReferenceNode
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,19 +112,19 @@ registerEvaluationFunction('rule', function evaluate(node) {
|
|||
return this.cache[node.dottedName]
|
||||
}
|
||||
const explanation = { ...node.explanation }
|
||||
|
||||
this.cache._meta.contextRule.push(node.dottedName)
|
||||
this.cache._meta.parentEvaluationStack ??= []
|
||||
|
||||
let parent: ASTNode & EvaluationDecoration | null = null
|
||||
let parent: EvaluatedNode | null = null
|
||||
if (explanation.parent && !this.cache._meta.parentEvaluationStack.includes(node.dottedName)) {
|
||||
this.cache._meta.parentEvaluationStack.push(node.dottedName)
|
||||
parent = this.evaluateNode(explanation.parent) as ASTNode & EvaluationDecoration
|
||||
parent = this.evaluateNode(explanation.parent) as EvaluatedNode
|
||||
explanation.parent = parent
|
||||
this.cache._meta.parentEvaluationStack.pop()
|
||||
}
|
||||
let valeur: ASTNode & EvaluationDecoration | null = null
|
||||
let valeur: EvaluatedNode | null = null
|
||||
if (!parent || parent.nodeValue !== false) {
|
||||
valeur = this.evaluateNode(explanation.valeur) as ASTNode & EvaluationDecoration
|
||||
valeur = this.evaluateNode(explanation.valeur) as EvaluatedNode
|
||||
explanation.valeur = valeur
|
||||
}
|
||||
const evaluation = {
|
||||
|
@ -122,7 +134,7 @@ registerEvaluationFunction('rule', function evaluate(node) {
|
|||
missingVariables: mergeMissing(valeur?.missingVariables, bonus(parent?.missingVariables)),
|
||||
...(valeur && 'unit' in valeur && { unit: valeur.unit }),
|
||||
}
|
||||
|
||||
this.cache._meta.contextRule.pop()
|
||||
this.cache[node.dottedName] = evaluation;
|
||||
return evaluation;
|
||||
})
|
|
@ -6,13 +6,7 @@ import {
|
|||
getRelativeDate,
|
||||
getYear
|
||||
} from './date'
|
||||
import {
|
||||
Unit,
|
||||
Evaluation,
|
||||
Types,
|
||||
ASTNode,
|
||||
EvaluationDecoration
|
||||
} from './AST/types'
|
||||
import { Unit, Evaluation, Types, ASTNode, EvaluatedNode } from './AST/types'
|
||||
|
||||
export type Period<T> = {
|
||||
start: T | null
|
||||
|
@ -69,9 +63,7 @@ export function parsePeriod<Date>(word: string, date: Date): Period<Date> {
|
|||
throw new Error('Non implémenté')
|
||||
}
|
||||
|
||||
export type TemporalNode = Temporal<
|
||||
ASTNode & EvaluationDecoration & { nodeValue: number }
|
||||
>
|
||||
export type TemporalNode = Temporal<EvaluatedNode & { nodeValue: number }>
|
||||
export type Temporal<T> = Array<Period<string> & { value: T }>
|
||||
|
||||
export function narrowTemporalValue<T extends Types>(
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { assoc, mapObjIndexed } from 'ramda'
|
||||
import { RuleNode } from './rule'
|
||||
import { Rule } from './rule'
|
||||
|
||||
type Translation = Record<string, string>
|
||||
type translateAttribute = (
|
||||
prop: string,
|
||||
rule: RuleNode,
|
||||
rule: Rule,
|
||||
translation: Translation,
|
||||
lang: string
|
||||
) => RuleNode
|
||||
) => Rule
|
||||
|
||||
/* Traduction */
|
||||
const translateSuggestion: translateAttribute = (
|
||||
|
@ -41,7 +41,7 @@ export const attributesToTranslate = [
|
|||
]
|
||||
|
||||
const translateProp = (lang: string, translation: Translation) => (
|
||||
rule: RuleNode,
|
||||
rule: Rule,
|
||||
prop: string
|
||||
) => {
|
||||
if (prop === 'suggestions' && rule?.suggestions) {
|
||||
|
@ -56,8 +56,8 @@ function translateRule<Names extends string>(
|
|||
lang: string,
|
||||
translations: { [Name in Names]: Translation },
|
||||
name: Names,
|
||||
rule: RuleNode
|
||||
): RuleNode {
|
||||
rule: Rule
|
||||
): Rule {
|
||||
const ruleTrans = translations[name]
|
||||
if (!ruleTrans) {
|
||||
return rule
|
||||
|
@ -71,11 +71,10 @@ function translateRule<Names extends string>(
|
|||
export default function translateRules(
|
||||
lang: string,
|
||||
translations: Record<string, Translation>,
|
||||
rules: Record<string, RuleNode>
|
||||
): Record<string, RuleNode> {
|
||||
rules: Record<string, Rule>
|
||||
): Record<string, Rule> {
|
||||
const translatedRules = mapObjIndexed(
|
||||
(rule: RuleNode, name: string) =>
|
||||
translateRule(lang, translations, name, rule),
|
||||
(rule: Rule, name: string) => translateRule(lang, translations, name, rule),
|
||||
rules
|
||||
)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import i18n from './i18n'
|
|||
import { Evaluation, Unit } from './AST/types'
|
||||
|
||||
export const parseUnit = (string: string, lng = 'fr'): Unit => {
|
||||
const [a, ...b] = string.split('/'),
|
||||
const [a, ...b] = string.split('/').map(u => u.trim()),
|
||||
result = {
|
||||
numerators: a
|
||||
.split('.')
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('Cyclic dependencies detectron 3000 ™', () => {
|
|||
formule: b + 1
|
||||
`
|
||||
const cycles = cyclesInDependenciesGraph(rules)
|
||||
expect(cycles).to.deep.equal([['d', 'c', 'b', 'a']])
|
||||
expect(cycles).to.deep.equal([['a','b','c','d']])
|
||||
})
|
||||
|
||||
|
||||
|
@ -51,6 +51,6 @@ describe('Cyclic dependencies detectron 3000 ™', () => {
|
|||
formule: a
|
||||
`
|
||||
const cycles = cyclesInDependenciesGraph(rules)
|
||||
expect(cycles).to.deep.equal([["a . c", "a"]])
|
||||
expect(cycles).to.deep.equal([["a", "a . c"]])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/types",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
|
|
|
@ -22,4 +22,4 @@
|
|||
"strictPropertyInitialization": true,
|
||||
"types": ["webpack-env"]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue