🔨 ⚙️ Première version des tests (exemples) sur /règle
Les 4 éléments du CDD sont testées. Il reste du boulot, voir le TODO des tests de majoration chômagepull/2/head
parent
819e9b04af
commit
75354efdc5
|
@ -19,3 +19,21 @@
|
|||
|
||||
références:
|
||||
Code du travail - Article L6322-37 : https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000022234996&cidTexte=LEGITEXT000006072050
|
||||
|
||||
exemples:
|
||||
- nom: SMIC
|
||||
situation:
|
||||
assiette cotisations sociales: 1480
|
||||
valeur attendue: 14.8
|
||||
|
||||
- nom: salaire médian
|
||||
situation:
|
||||
motif . classique . usage: oui
|
||||
assiette cotisations sociales: 2300
|
||||
valeur attendue: 23
|
||||
|
||||
- nom: motif saisonnier -> non applicable
|
||||
situation:
|
||||
motif . classique . saisonnier: oui
|
||||
assiette cotisations sociales: 2300
|
||||
valeur attendue: null
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
# TODO aspect temporel
|
||||
# L'indemnité est versée à la fin du contrat, sauf si le CDD se poursuit par un CDI.
|
||||
|
||||
#TODO avoir un vrai mécanisme de calcul temporel pour faire les conversions
|
||||
#TODO cette formule pourrait être clarifiée,
|
||||
# probablement grâce à un vrai mécanisme de calcul temporel pour faire les conversions
|
||||
formule:
|
||||
le maximum de:
|
||||
- description: Méthode "du dixième"
|
||||
|
@ -46,6 +47,30 @@
|
|||
facteur: 1 / 21
|
||||
|
||||
|
||||
exemples:
|
||||
- nom: pas de congés non pris
|
||||
situation:
|
||||
salaire brut: 2300
|
||||
prime fin de contrat: 0
|
||||
congés non pris: 0
|
||||
durée contrat: 12
|
||||
valeur attendue: 0
|
||||
- nom: 10 jours non pris
|
||||
situation:
|
||||
salaire brut: 2300
|
||||
prime fin de contrat: 0
|
||||
congés non pris: 10
|
||||
durée contrat: 12
|
||||
valeur attendue: 92
|
||||
- nom: 3 jours non pris
|
||||
situation:
|
||||
salaire brut: 2300
|
||||
prime fin de contrat: 0
|
||||
congés non pris: 3
|
||||
durée contrat: 6
|
||||
valeur attendue: 54.76
|
||||
|
||||
|
||||
notes: |
|
||||
|
||||
À noter, la loi El Khomri modifie l'article L3141-12:
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
- Dans les faits, les CDD Senior perçoivent une indemnité d’un montant équivalent à l’indemnité de précarité : [line](https://www.easycdd.com/LEGISLATION-CDD/Fin-ou-rupture-du-contrat-CDD/La-prime-de-precarite/La-prime-de-precarite-n-est-pas-due-si)
|
||||
|
||||
|
||||
non applicable si:
|
||||
une de ces conditions:
|
||||
# Evènements particuliers
|
||||
|
@ -37,6 +36,19 @@
|
|||
assiette: salaire brut
|
||||
taux: 10%
|
||||
|
||||
exemples:
|
||||
- nom: salaire médian
|
||||
situation:
|
||||
salaire brut: 2300
|
||||
valeur attendue: 230
|
||||
|
||||
- nom: CDD d'usage -> non applicable
|
||||
situation:
|
||||
motif . classique . usage: oui
|
||||
événement . refus CDI avantageux: oui
|
||||
salaire brut: 2300
|
||||
valeur attendue: null
|
||||
|
||||
|
||||
références:
|
||||
Code du travail - Article L1243-8: https://www.legifrance.gouv.fr/affichCode.do?idSectionTA=LEGISCTA000006189459&cidTexte=LEGITEXT000006072050
|
||||
|
|
|
@ -25,6 +25,25 @@
|
|||
durée contrat <= 3: 0.5%
|
||||
|
||||
|
||||
exemples:
|
||||
- nom: salaire médian, CDD d'usage, contrat de 2 mois
|
||||
situation:
|
||||
assiette cotisations sociales: 2300
|
||||
motif . classique . accroissement activité: oui
|
||||
durée contrat: 1
|
||||
valeur attendue: 69
|
||||
|
||||
#TODO on ne peut aujourd'hui tester 'classique . usage' : l'évaluation n'aura pas la valeur de la première branche de la logique numérique, motif . classique . accroissement activité, et va donc s'arrêter en renvoyant un 'null'
|
||||
# solution possible : un mode d'évaluation 'shallow' ou non renseigné = faux (null -> false)
|
||||
|
||||
- nom: durée de contrat de 4 mois -> non applicable
|
||||
situation:
|
||||
assiette cotisations sociales: 2300
|
||||
durée contrat: 4
|
||||
valeur attendue: null
|
||||
|
||||
|
||||
|
||||
|
||||
références:
|
||||
La mojoration de la contribution chômage: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/lassurance-chomage-et-lags/la-majoration-de-la-contribution.html
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
références:
|
||||
Code du travail - Article L1242-2: https://www.legifrance.gouv.fr/affichCodeArticle.do;jsessionid=714D2E2B814371F4F1D5AA88472CD621.tpdila20v_1?idArticle=LEGIARTI000033024658&cidTexte=LEGITEXT000006072050&dateTexte=20170420
|
||||
|
||||
|
||||
- espace: contrat salarié . CDD . motif . classique
|
||||
nom: saisonnier
|
||||
titre: Saisonnier
|
||||
|
|
|
@ -3,7 +3,7 @@ import {connect} from 'react-redux'
|
|||
import {findRuleByDottedName} from '../engine/rules'
|
||||
import './Aide.css'
|
||||
import {EXPLAIN_VARIABLE} from '../actions'
|
||||
import References from './References'
|
||||
import References from './rule/References'
|
||||
import marked from '../engine/marked'
|
||||
|
||||
@connect(
|
||||
|
|
|
@ -25,7 +25,7 @@ export let AttachDictionary = dictionary => Decorated =>
|
|||
render(){
|
||||
let {explanation, term} = this.state
|
||||
return (
|
||||
<div className="dictionaryWrapper">
|
||||
<div style={{display: 'inline-block'}} className="dictionaryWrapper">
|
||||
<Decorated ref={decorated => this.decorated = decorated} {...this.props} explain={this.explain}/>
|
||||
{explanation &&
|
||||
<div className="dictionaryPanelWrapper" onClick={() => this.setState({term: null, explanation: null})}>
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, {Component} from 'react'
|
|||
import './HomeSyso.css'
|
||||
import {searchRules, encodeRuleName} from '../engine/rules.js'
|
||||
import {Link} from 'react-router-dom'
|
||||
import '../components/Rule.css'
|
||||
import R from 'ramda'
|
||||
|
||||
export default class Home extends Component {
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {decodeRuleName} from '../engine/rules.js'
|
||||
import './Rule.css'
|
||||
import JSONTree from 'react-json-tree'
|
||||
import R from 'ramda'
|
||||
import PageTypeIcon from './PageTypeIcon'
|
||||
import {connect} from 'react-redux'
|
||||
import mockSituation from '../engine/mockSituation.yaml'
|
||||
import {START_CONVERSATION} from '../actions'
|
||||
import classNames from 'classnames'
|
||||
import possiblesDestinataires from '../../règles/ressources/destinataires/destinataires.yaml'
|
||||
import {capitalise0} from '../utils'
|
||||
import knownMecanisms from '../engine/known-mecanisms.yaml'
|
||||
import marked from '../engine/marked'
|
||||
import References from './References'
|
||||
import {AttachDictionary} from './AttachDictionary'
|
||||
import {analyseSituation} from '../engine/traverse'
|
||||
import {formValueSelector} from 'redux-form'
|
||||
|
||||
|
||||
// situationGate function useful for testing :
|
||||
let testingSituationGate = v => // eslint-disable-line no-unused-vars
|
||||
R.path(v.split('.'))(mockSituation)
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
situationGate: name => formValueSelector('conversation')(state, name),
|
||||
form: state.form
|
||||
}),
|
||||
dispatch => ({
|
||||
startConversation: rootVariable => dispatch({type: START_CONVERSATION, rootVariable}),
|
||||
})
|
||||
)
|
||||
export default class Rule extends Component {
|
||||
render() {
|
||||
let {
|
||||
match: {params: {name: encodedName}},
|
||||
situationGate,
|
||||
form
|
||||
} = this.props,
|
||||
name = decodeRuleName(encodedName)
|
||||
|
||||
let rule = analyseSituation(name)(situationGate)
|
||||
console.log('rule', rule)
|
||||
// if (!rule) {
|
||||
// this.props.router.push('/404')
|
||||
// return null
|
||||
// }
|
||||
|
||||
let
|
||||
situationExists = !R.isEmpty(form)
|
||||
|
||||
let destinataire = R.path([rule.type, 'destinataire'])(rule),
|
||||
destinataireData = possiblesDestinataires[destinataire]
|
||||
|
||||
|
||||
return (
|
||||
<div id="rule">
|
||||
<PageTypeIcon type="comprendre"/>
|
||||
<h1>
|
||||
<span className="rule-type">{rule.type}</span>
|
||||
<span className="rule-name">{capitalise0(name)}</span>
|
||||
</h1>
|
||||
<section id="rule-meta">
|
||||
<div id="meta-paragraph">
|
||||
<p>
|
||||
{rule.description}
|
||||
</p>
|
||||
</div>
|
||||
<div id="destinataire">
|
||||
<h2>Destinataire</h2>
|
||||
{!destinataireData ?
|
||||
<p>Non renseigné</p>
|
||||
: <div>
|
||||
<a href={destinataireData.lien} target="_blank">
|
||||
{destinataireData.image &&
|
||||
<img src={require('../../règles/ressources/destinataires/' + destinataireData.image)} /> }
|
||||
{!destinataireData.image &&
|
||||
<div id="calligraphy">{destinataire}</div>
|
||||
}
|
||||
</a>
|
||||
{destinataireData.nom && <div id="destinataireName">{destinataireData.nom}</div>}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h2>Références</h2>
|
||||
{this.renderReferences(rule)}
|
||||
</div>
|
||||
</section>
|
||||
<Algorithm {...{rule, situationExists}}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderReferences({'références': refs}) {
|
||||
if (!refs) return <p>Cette règle manque de références.</p>
|
||||
|
||||
return <References refs={refs}/>
|
||||
}
|
||||
}
|
||||
|
||||
@AttachDictionary(knownMecanisms)
|
||||
class Algorithm extends React.Component {
|
||||
state = {
|
||||
showValues: false
|
||||
}
|
||||
render(){
|
||||
let {rule, situationExists, explain} = this.props,
|
||||
showValues = situationExists && this.state.showValues
|
||||
return (
|
||||
<div id="algorithm">
|
||||
<section id="rule-rules" className={classNames({showValues})}>
|
||||
{ do {
|
||||
// TODO ce let est incompréhensible !
|
||||
let [,cond] =
|
||||
R.toPairs(rule).find(([,v]) => v && v.rulePropType == 'cond') || []
|
||||
cond != null &&
|
||||
<section id="declenchement">
|
||||
<h2>Conditions de déclenchement</h2>
|
||||
{cond.jsx}
|
||||
</section>
|
||||
}}
|
||||
<section id="formule">
|
||||
<h2>Calcul</h2>
|
||||
{rule['formule'].jsx}
|
||||
</section>
|
||||
</section>
|
||||
{situationExists && <div>
|
||||
<button id="showValues" onClick={() => this.setState({showValues: !this.state.showValues})}>
|
||||
<i className="fa fa-rocket" aria-hidden="true"></i> {!showValues ? 'Injecter votre situation' : 'Cacher votre situation'}
|
||||
</button>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -59,8 +59,8 @@ export default class extends React.Component {
|
|||
sim = path =>
|
||||
R.path(R.unless(R.is(Array), R.of)(path))(this.rule.simulateur || {}),
|
||||
reinitalise = () => {
|
||||
this.props.resetForm(name);
|
||||
this.props.startConversation(name);
|
||||
this.props.resetForm(this.name);
|
||||
this.props.startConversation(this.name);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import R from 'ramda'
|
||||
import {AttachDictionary} from '../AttachDictionary'
|
||||
import knownMecanisms from 'Engine/known-mecanisms.yaml'
|
||||
import marked from 'Engine/marked'
|
||||
|
||||
@AttachDictionary(knownMecanisms)
|
||||
export default class Algorithm extends React.Component {
|
||||
state = {
|
||||
showValues: false
|
||||
}
|
||||
render(){
|
||||
let {traversedRule: rule, showValues} = this.props
|
||||
return (
|
||||
<div id="algorithm">
|
||||
<section id="rule-rules" className={classNames({showValues})}>
|
||||
{ do {
|
||||
// TODO ce let est incompréhensible !
|
||||
let [,cond] =
|
||||
R.toPairs(rule).find(([,v]) => v && v.rulePropType == 'cond') || []
|
||||
cond != null &&
|
||||
<section id="declenchement">
|
||||
<h2>Conditions de déclenchement</h2>
|
||||
{cond.jsx}
|
||||
</section>
|
||||
}}
|
||||
<section id="formule">
|
||||
<h2>Calcul</h2>
|
||||
{rule['formule'].jsx}
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
#examples {
|
||||
min-width: 30%;
|
||||
}
|
||||
|
||||
#examples h2.subtitled {
|
||||
margin-bottom: .1em;
|
||||
}
|
||||
#examples .subtitle {
|
||||
font-size: 85%;
|
||||
font-style: italic;
|
||||
margin-top: 0.3em;
|
||||
margin-bottom: 1.6em
|
||||
}
|
||||
|
||||
#injectSituation {
|
||||
background: rgb(74, 137, 220);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 100%;
|
||||
padding: .3em .6em;
|
||||
margin-bottom: .6em;
|
||||
}
|
||||
|
||||
#injectSituation.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
#examples ul {
|
||||
padding-left: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
#examples .example {
|
||||
font-weight: 500;
|
||||
margin: .6em;
|
||||
cursor: pointer;
|
||||
}
|
||||
#examples .example.selected .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#examples i {
|
||||
margin-right: .6em;
|
||||
}
|
||||
|
||||
#examples .example i {
|
||||
font-size: 200%;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
#examples .example:not(.ok) {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
#examples .example .ko {
|
||||
font-size: 80%;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#examples .expected {
|
||||
font-weight: 700;
|
||||
color: #031bd6;
|
||||
}
|
||||
|
||||
#examples .example.ok i {
|
||||
color: #2ecc71
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import React, { Component } from "react"
|
||||
import R from "ramda"
|
||||
import classNames from "classnames"
|
||||
import {
|
||||
decodeRuleName,
|
||||
findRuleByName,
|
||||
disambiguateRuleReference
|
||||
} from "Engine/rules.js"
|
||||
import { analyseSituation } from "Engine/traverse"
|
||||
import "./Examples.css"
|
||||
|
||||
export default class Examples extends Component {
|
||||
runExamples() {
|
||||
let { rule } = this.props,
|
||||
exemples = rule.exemples || []
|
||||
|
||||
|
||||
return exemples.map(ex => {
|
||||
// les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle,
|
||||
// comme dans sa formule
|
||||
let exempleSituation = R.pipe(
|
||||
R.toPairs,
|
||||
R.map(([k, v]) => [disambiguateRuleReference(rule, k), v]),
|
||||
R.fromPairs
|
||||
)(ex.situation)
|
||||
|
||||
let runExemple = analyseSituation(rule.name)(v => exempleSituation[v]),
|
||||
exempleCalculatedValue = runExemple["non applicable si"] &&
|
||||
runExemple["non applicable si"].nodeValue
|
||||
? null
|
||||
: runExemple.formule.nodeValue
|
||||
|
||||
return {
|
||||
...ex,
|
||||
ok: Math.abs( ex['valeur attendue'] - exempleCalculatedValue ) < .1, //TODO on peut sûrement faire mieux...
|
||||
rule: runExemple
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let examples = this.runExamples(),
|
||||
focusedExample = R.path(['focusedExample', 'nom'])(this.props),
|
||||
{clearInjection, inject, situationExists , showValues} = this.props
|
||||
|
||||
return (
|
||||
<div id="examples">
|
||||
<h2 className="subtitled">Examples de calcul</h2>
|
||||
<p className="subtitle">Cliquez sur un exemple pour le visualiser</p>
|
||||
{situationExists && <div>
|
||||
<button
|
||||
className={classNames({selected: !focusedExample && showValues})}
|
||||
id="injectSituation"
|
||||
onClick={() => showValues ? clearInjection(): inject()}>
|
||||
<i className="fa fa-rocket" aria-hidden="true"></i> {focusedExample || !showValues ? 'Injecter votre situation' : 'Cacher votre situation'}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
{R.isEmpty(examples) ?
|
||||
<p><i className="fa fa-exclamation-triangle" aria-hidden="true"></i><em>Cette règle manque d'exemples...</em></p>
|
||||
: <ul>{
|
||||
examples.map(({nom, ok, rule, 'valeur attendue': expected}) =>
|
||||
<li key={nom} className={classNames("example", {ok, selected: focusedExample == nom})}
|
||||
onClick={() => focusedExample == nom ? clearInjection() : inject({nom, ok, rule})}
|
||||
>
|
||||
<span> {
|
||||
ok ?
|
||||
<i className="fa fa-check-circle" aria-hidden="true"></i>
|
||||
: <i className="fa fa-times" aria-hidden="true"></i>
|
||||
}</span>
|
||||
<span className="name">{nom}</span>
|
||||
{!ok &&
|
||||
<div className="ko">
|
||||
Ce test ne passe pas
|
||||
{showValues && <span>
|
||||
: la valeur attendue était {' '}
|
||||
<span className="expected">{expected}</span>
|
||||
</span>}
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import references from '../../règles/ressources/références/références.yaml'
|
||||
import './References.css'
|
||||
import R from 'ramda'
|
||||
import references from 'Règles/ressources/références/références.yaml'
|
||||
import './References.css'
|
||||
|
||||
export default ({refs}) => (
|
||||
<ul className="references">
|
||||
|
@ -18,7 +18,7 @@ export default ({refs}) => (
|
|||
<span className="url">
|
||||
{domain}
|
||||
{refData.image &&
|
||||
<img src={require('../../règles/ressources/références/' + refData.image)}/> }
|
||||
<img src={require('Règles/ressources/références/' + refData.image)}/> }
|
||||
</span>
|
||||
</span>
|
||||
<a href={link} target="_blank">
|
|
@ -30,7 +30,7 @@
|
|||
}
|
||||
|
||||
#rule h2 {
|
||||
font-size: 100%;
|
||||
font-size: 140%;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #4B4B66;
|
||||
display: inline-block;
|
||||
|
@ -105,27 +105,17 @@
|
|||
#rule-rules .situationValue {
|
||||
display: none;
|
||||
padding-left: 1em;
|
||||
font-weight: 600;
|
||||
color: #4A89DC;
|
||||
font-weight: 700;
|
||||
color: #031bd6;
|
||||
}
|
||||
|
||||
#rule-rules.showValues .situationValue {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#showValues {
|
||||
background: rgb(74, 137, 220);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 100%;
|
||||
padding: .4em;
|
||||
margin: auto;
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
#algorithm {
|
||||
margin-top: 3em;
|
||||
width: 100%;
|
||||
margin-right: 10em;
|
||||
}
|
||||
.dictionaryPanelWrapper {
|
||||
position: fixed;
|
||||
|
@ -170,11 +160,10 @@
|
|||
#rule-rules section {
|
||||
margin: 1em 0 1em 3em;
|
||||
font-weight: 500;
|
||||
font-size: 90%;
|
||||
color: #444
|
||||
}
|
||||
#rule-rules section h2 {
|
||||
font-size: 130%;
|
||||
#rule-rules section > div {
|
||||
font-size: 90%;
|
||||
}
|
||||
.node {
|
||||
padding-left: 1em;
|
||||
|
@ -350,3 +339,12 @@
|
|||
font-size: 160%;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#rule-calc {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
import React, { Component } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {connect} from 'react-redux'
|
||||
import {formValueSelector} from 'redux-form'
|
||||
import R from 'ramda'
|
||||
import './Rule.css'
|
||||
import PageTypeIcon from '../PageTypeIcon'
|
||||
import {decodeRuleName, findRuleByName, disambiguateRuleReference} from 'Engine/rules.js'
|
||||
import mockSituation from 'Engine/mockSituation.yaml'
|
||||
import {analyseSituation} from 'Engine/traverse'
|
||||
import {START_CONVERSATION} from '../../actions'
|
||||
import possiblesDestinataires from 'Règles/ressources/destinataires/destinataires.yaml'
|
||||
import {capitalise0} from '../../utils'
|
||||
import References from './References'
|
||||
import Algorithm from './Algorithm'
|
||||
import Examples from './Examples'
|
||||
|
||||
// situationGate function useful for testing :
|
||||
let testingSituationGate = v => // eslint-disable-line no-unused-vars
|
||||
R.path(v.split('.'))(mockSituation)
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
situationGate: name => formValueSelector('conversation')(state, name),
|
||||
form: state.form
|
||||
}),
|
||||
dispatch => ({
|
||||
startConversation: rootVariable => dispatch({type: START_CONVERSATION, rootVariable}),
|
||||
})
|
||||
)
|
||||
export default class Rule extends Component {
|
||||
state = {
|
||||
example: null, showValues: false
|
||||
}
|
||||
componentWillReceiveProps(nextProps){
|
||||
let get = R.path(['match', 'params', 'name'])
|
||||
if (get(nextProps) !== get(this.props))
|
||||
this.setRule(get(nextProps))
|
||||
}
|
||||
setRule(name){
|
||||
this.rule = analyseSituation(decodeRuleName(name))(this.props.situationGate)
|
||||
}
|
||||
componentWillMount(){
|
||||
let {
|
||||
match: {params: {name}},
|
||||
situationGate
|
||||
} = this.props
|
||||
|
||||
this.setRule(name)
|
||||
}
|
||||
render() {
|
||||
|
||||
// if (!rule) {
|
||||
// this.props.router.push('/404')
|
||||
// return null
|
||||
// }
|
||||
|
||||
let
|
||||
situationExists = !R.isEmpty(this.props.form)
|
||||
|
||||
let
|
||||
{type, name, description} = this.rule,
|
||||
destinataire = R.path([type, 'destinataire'])(this.rule),
|
||||
destinataireData = possiblesDestinataires[destinataire]
|
||||
|
||||
return (
|
||||
<div id="rule">
|
||||
<PageTypeIcon type="comprendre"/>
|
||||
<h1>
|
||||
<span className="rule-type">{type}</span>
|
||||
<span className="rule-name">{capitalise0(name)}</span>
|
||||
</h1>
|
||||
<section id="rule-meta">
|
||||
<div id="meta-paragraph">
|
||||
<p>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<div id="destinataire">
|
||||
<h2>Destinataire</h2>
|
||||
{!destinataireData ?
|
||||
<p>Non renseigné</p>
|
||||
: <div>
|
||||
<a href={destinataireData.lien} target="_blank">
|
||||
{destinataireData.image &&
|
||||
<img src={require('Règles/ressources/destinataires/' + destinataireData.image)} /> }
|
||||
{!destinataireData.image &&
|
||||
<div id="calligraphy">{destinataire}</div>
|
||||
}
|
||||
</a>
|
||||
{destinataireData.nom && <div id="destinataireName">{destinataireData.nom}</div>}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h2>Références</h2>
|
||||
{this.renderReferences(this.rule)}
|
||||
</div>
|
||||
</section>
|
||||
<section id="rule-calc">
|
||||
<Algorithm {...{traversedRule: R.path(['example', 'rule'])(this.state) || this.rule, showValues: this.state.showValues}}/>
|
||||
<Examples
|
||||
situationExists={situationExists}
|
||||
rule={this.rule}
|
||||
focusedExample={this.state.example}
|
||||
showValues={this.state.showValues}
|
||||
clearInjection={() => this.setState({example: null, showValues: false})}
|
||||
inject={example => this.setState({example, showValues: true})}/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderReferences({'références': refs}) {
|
||||
if (!refs) return <p>Cette règle manque de références.</p>
|
||||
|
||||
return <References refs={refs}/>
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import './reset.css'
|
|||
import {Link, Route, BrowserRouter as Router, Switch} from 'react-router-dom'
|
||||
import HomeEmbauche from '../components/HomeEmbauche'
|
||||
import HomeSyso from '../components/HomeSyso'
|
||||
import Rule from '../components/Rule'
|
||||
import Rule from '../components/rule/Rule'
|
||||
import Route404 from '../components/Route404'
|
||||
import Contact from '../components/Contact'
|
||||
import Simulateur from '../components/Simulateur'
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# La description peut être rédigée en markdown :-)
|
||||
|
||||
une de ces conditions:
|
||||
type: boolean
|
||||
description: |
|
||||
C'est un `ou` logique.
|
||||
|
||||
|
@ -9,6 +10,7 @@ une de ces conditions:
|
|||
|
||||
Renvoie vrai si l'une des conditions est vraie.
|
||||
toutes ces conditions:
|
||||
type: boolean
|
||||
description: |
|
||||
C'est un `et` logique.
|
||||
|
||||
|
@ -17,6 +19,7 @@ toutes ces conditions:
|
|||
Renvoie vrai si toutes les conditions vraies.
|
||||
|
||||
logique numérique:
|
||||
type: numeric
|
||||
description: |
|
||||
Contient une liste de couples condition-conséquence.
|
||||
|
||||
|
@ -27,10 +30,12 @@ logique numérique:
|
|||
Si aucune condition n'est vraie, alors ce mécanisme renvoie implicitement `non applicable` (ce qui peut se traduire par la valeur `0` si nous sommes dans un contexte numérique).
|
||||
|
||||
taux:
|
||||
type: numeric
|
||||
description: |
|
||||
C'est tout simplement une valeur numérique exprimée en pourcentage.
|
||||
|
||||
multiplication:
|
||||
type: numeric
|
||||
description: |
|
||||
C'est une multiplication un peu améliorée, très utile pour exprimer les cotisations.
|
||||
|
||||
|
@ -39,6 +44,7 @@ multiplication:
|
|||
La multiplication peut être plafonnée : ce plafond sépare l'assiette en deux, et la partie au-dessus du plafond est tout simplement ignorée. Dans ce cas, elle se comporte comme une barème en taux marginaux à deux tranches, la deuxième au taux nul et allant de `plafond` à l'infini.
|
||||
|
||||
le maximum de:
|
||||
type: numeric
|
||||
description: |
|
||||
Renvoie l'élément de la liste de propositions fournie qui la la plus grande valeur.
|
||||
|
||||
|
@ -47,6 +53,7 @@ le maximum de:
|
|||
Il est conseillé de renseigner une description de chaque proposition par exemple quand elles représentent des méthodes de calcul alternatives parmi lesquelles il faut en choisir une.
|
||||
|
||||
somme:
|
||||
type: numeric
|
||||
description: |
|
||||
C'est tout simplement la somme de chaque terme de la liste.
|
||||
|
||||
|
@ -71,12 +78,14 @@ non applicable si:
|
|||
La formule de calcul peut donc être ignorée, quel que soit son montant.
|
||||
|
||||
barème:
|
||||
type: numeric
|
||||
description: |
|
||||
C'est un barème en taux marginaux, mécanisme de calcul connu son utilisation dans le calcul de l'impôt sur le revenu.
|
||||
L'assiette est décomposée en plusieurs tranches, qui sont multipliées par un taux spécifique.
|
||||
Les tranches sont très souvent exprimées sous forme de facteurs (par exemple [1, 2, 4]) d'une variable que l'on appelle multiplicateur, par exemple le plafond de la sécurité sociale.
|
||||
|
||||
composantes:
|
||||
type: numeric
|
||||
description: |
|
||||
Beaucoup de cotisations sont composées de deux parties qui partage la méthode de calcul mais diffèrent par des paramètres différents.
|
||||
|
||||
|
|
|
@ -60,17 +60,23 @@ let fillVariableNode = (rule, situationGate) => (parseResult) => {
|
|||
variablePartialName = fragments.join(' . '),
|
||||
dottedName = disambiguateRuleReference(rule, variablePartialName),
|
||||
variable = findRuleByDottedName(dottedName),
|
||||
variableIsRule = variable.formule != null,
|
||||
variableIsCalculable = variable.formule != null,
|
||||
//TODO perf : mettre un cache sur les variables !
|
||||
// On le fait pas pour l'instant car ça peut compliquer les fonctionnalités futures
|
||||
// et qu'il n'y a aucun problème de perf aujourd'hui
|
||||
parsedRule = variableIsRule && treatRuleRoot(
|
||||
parsedRule = variableIsCalculable && treatRuleRoot(
|
||||
situationGate,
|
||||
variable
|
||||
),
|
||||
|
||||
nodeValue = variableIsRule ? parsedRule.nodeValue : evaluateVariable(situationGate, dottedName, variable.format),
|
||||
missingVariables = variableIsRule ? [] : (nodeValue == null ? [dottedName] : [])
|
||||
situationValue = evaluateVariable(situationGate, dottedName, variable),
|
||||
nodeValue = situationValue
|
||||
!= null ? situationValue
|
||||
: !variableIsCalculable
|
||||
? null
|
||||
: parsedRule.nodeValue,
|
||||
explanation = parsedRule,
|
||||
missingVariables = variableIsCalculable ? [] : (nodeValue == null ? [dottedName] : [])
|
||||
|
||||
return {
|
||||
nodeValue,
|
||||
|
@ -394,13 +400,14 @@ let treat = (situationGate, rule) => rawNode => {
|
|||
}
|
||||
|
||||
if (k === 'taux') {
|
||||
//TODO gérer les taux historisés
|
||||
if (R.is(String)(v))
|
||||
let reg = /^(\d+(\.\d+)?)\%$/
|
||||
console.log('taux, v', v)
|
||||
if (R.test(reg)(v))
|
||||
return {
|
||||
category: 'percentage',
|
||||
type: 'numeric',
|
||||
percentage: v,
|
||||
nodeValue: transformPercentage(v),
|
||||
nodeValue: R.match(reg)(v)[1]/100,
|
||||
explanation: null,
|
||||
jsx:
|
||||
<span className="percentage" >
|
||||
|
@ -409,7 +416,7 @@ let treat = (situationGate, rule) => rawNode => {
|
|||
}
|
||||
// Si c'est une liste historisée de pourcentages
|
||||
// TODO revoir le test avant le bug de l'an 2100
|
||||
else if ( R.all(R.test(/(19|20)\d\d(-\d\d)?(-\d\d)?/))(R.keys(v)) ) {
|
||||
else if ( R.is(Array)(v) && R.all(R.test(/(19|20)\d\d(-\d\d)?(-\d\d)?/))(R.keys(v)) ) {
|
||||
//TODO sélectionner la date de la simulation en cours
|
||||
let lazySelection = R.first(R.values(v))
|
||||
return {
|
||||
|
@ -497,7 +504,7 @@ let treat = (situationGate, rule) => rawNode => {
|
|||
name="multiplication"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
<ul className="properties">
|
||||
<li key="assiette">
|
||||
<span className="key">assiette: </span>
|
||||
<span className="value">{assiette.jsx}</span>
|
||||
|
|
|
@ -20,10 +20,18 @@ let evaluateBottomUp = situationGate => startingFragments => {
|
|||
}
|
||||
|
||||
|
||||
export let evaluateVariable = (situationGate, variableName, format) => {
|
||||
/* Evalue la valeur d'une variable
|
||||
en utilisant la fonction situationGate qui donne accès à la situation courante*/
|
||||
export let evaluateVariable = (situationGate, variableName, rule) => {
|
||||
// test rec
|
||||
let value = situationGate(variableName)
|
||||
return format != null ?
|
||||
(value == undefined ? null : value)
|
||||
: evaluateBottomUp(situationGate)(splitName(variableName))
|
||||
|
||||
return rule.format != null ?
|
||||
value
|
||||
: !rule.formule ?
|
||||
// c'est une variante, eg. motifs . classique . accroissement d'activité
|
||||
evaluateBottomUp(situationGate)(splitName(variableName))
|
||||
: rule.formule['une possibilité'] ?
|
||||
evaluateBottomUp(situationGate)(splitName(variableName))
|
||||
: value
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var webpack = require('webpack'),
|
||||
autoprefixer = require('autoprefixer'),
|
||||
path = require('path'),
|
||||
prodEnv = process.env.NODE_ENV == 'production' // eslint-disable-line no-undef
|
||||
|
||||
module.exports = {
|
||||
|
@ -15,10 +16,16 @@ module.exports = {
|
|||
'./source/entry.js'
|
||||
],
|
||||
output: {
|
||||
path: require('path').resolve('./dist/'),
|
||||
path: path.resolve('./dist/'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/dist/'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
Engine: path.resolve('source/engine/'),
|
||||
Règles: path.resolve('règles/')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
loaders: [ {
|
||||
test: /\.css$/,
|
||||
|
|
Loading…
Reference in New Issue