commit
b8c46f3e69
|
@ -83,8 +83,8 @@
|
|||
"fantasy-land": "^3.3.0",
|
||||
"fantasy-tuples": "^1.0.0",
|
||||
"file-loader": "^1.1.6",
|
||||
"hard-source-webpack-plugin": "^0.5.18",
|
||||
"html-loader": "^0.5.1",
|
||||
"img-loader": "^2.0.0",
|
||||
"jsdom": "^11.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"nearley-loader": "0.0.2",
|
||||
|
|
|
@ -6,6 +6,10 @@ export function stepAction(name, step, source) {
|
|||
return { type: STEP_ACTION, name, step, source }
|
||||
}
|
||||
|
||||
export function setExample(name, situation) {
|
||||
return { type: 'SET_EXAMPLE', situation, name }
|
||||
}
|
||||
|
||||
export const START_CONVERSATION = 'START_CONVERSATION'
|
||||
|
||||
// Reset the form
|
||||
|
|
|
@ -26,7 +26,8 @@ import { capitalise0, humanFigure } from '../utils'
|
|||
import { nameLeaf, encodeRuleName } from 'Engine/rules'
|
||||
|
||||
// Filtered variables and rules can't be filtered in a uniform way, for now
|
||||
let paidBy = pathEq(['explanation', 'cotisation', 'dû par'])
|
||||
let paidBy = payer => item =>
|
||||
pathEq(['explanation', item.explanation.type, 'dû par'], payer, item)
|
||||
let filteredBy = pathEq(['cotisation', 'dû par'])
|
||||
export let byName = groupBy(prop('dottedName'))
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { START_CONVERSATION } from '../actions'
|
|||
import {
|
||||
rules,
|
||||
findRuleByName,
|
||||
findRule,
|
||||
findRuleByDottedName,
|
||||
decodeRuleName
|
||||
} from 'Engine/rules'
|
||||
|
@ -57,7 +58,7 @@ export default class extends Component {
|
|||
this.targetNames = targetNames
|
||||
|
||||
this.targetRules = reject(isNil)(
|
||||
targetNames.map(name => findRuleByName(rules, name))
|
||||
targetNames.map(name => findRule(rules, name))
|
||||
)
|
||||
|
||||
this.targetRules.map(({ dottedName }) => resetFormField(dottedName))
|
||||
|
|
|
@ -29,13 +29,17 @@ export class SearchBar extends React.Component {
|
|||
componentWillMount() {
|
||||
var options = {
|
||||
keys: [
|
||||
{
|
||||
name: 'name',
|
||||
weight: 0.3
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
weight: 0.5
|
||||
weight: 0.3
|
||||
},
|
||||
{
|
||||
name: 'espace',
|
||||
weight: 0.3
|
||||
weight: 0.2
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
|
@ -55,11 +59,19 @@ export class SearchBar extends React.Component {
|
|||
handleChange = selectedOption => {
|
||||
this.setState({ selectedOption })
|
||||
}
|
||||
renderOption = option => (
|
||||
<Highlighter
|
||||
searchWords={[this.state.inputValue]}
|
||||
textToHighlight={option.title}
|
||||
/>
|
||||
renderOption = ({ title, dottedName }) => (
|
||||
<span>
|
||||
<Highlighter
|
||||
searchWords={[this.state.inputValue]}
|
||||
textToHighlight={title}
|
||||
/>
|
||||
<span style={{ opacity: 0.6, fontSize: '75%', marginLeft: '.6em' }}>
|
||||
<Highlighter
|
||||
searchWords={[this.state.inputValue]}
|
||||
textToHighlight={dottedName}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
filterOptions = (options, filter) => this.fuse.search(filter)
|
||||
render() {
|
||||
|
|
|
@ -6,6 +6,9 @@ import knownMecanisms from 'Engine/known-mecanisms.yaml'
|
|||
import { makeJsx } from 'Engine/evaluation'
|
||||
import './Algorithm.css'
|
||||
import { humanFigure } from '../../utils'
|
||||
import { head } from 'ramda'
|
||||
import { analyse } from 'Engine/traverse'
|
||||
import { exampleSituationGateWithDefaults } from './Examples'
|
||||
|
||||
let RuleWithoutFormula = () => (
|
||||
<p>
|
||||
|
@ -16,14 +19,21 @@ let RuleWithoutFormula = () => (
|
|||
|
||||
@AttachDictionary(knownMecanisms)
|
||||
export default class Algorithm extends React.Component {
|
||||
state = {
|
||||
showValues: true
|
||||
}
|
||||
render() {
|
||||
let { rule, showValues } = this.props,
|
||||
let { rule: displayedRule, showValues, currentExample, rules } = this.props,
|
||||
ruleWithoutFormula =
|
||||
!rule['formule'] ||
|
||||
path(['formule', 'explanation', 'une possibilité'], rule)
|
||||
!displayedRule['formule'] ||
|
||||
path(['formule', 'explanation', 'une possibilité'], displayedRule)
|
||||
|
||||
let rule = currentExample
|
||||
? head(
|
||||
analyse(rules, displayedRule.dottedName)(
|
||||
exampleSituationGateWithDefaults(currentExample.situation, rules)
|
||||
).targets
|
||||
)
|
||||
: displayedRule
|
||||
|
||||
console.log('didcomp')
|
||||
|
||||
return (
|
||||
<div id="algorithm">
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import React, { Component } from 'react'
|
||||
import { evolve, path, isEmpty } from 'ramda'
|
||||
import { evolve, path, isEmpty, compose } from 'ramda'
|
||||
import classNames from 'classnames'
|
||||
import { connect } from 'react-redux'
|
||||
import { disambiguateExampleSituation, collectDefaults } from 'Engine/rules.js'
|
||||
import { analyse } from 'Engine/traverse'
|
||||
import './Examples.css'
|
||||
import { assume } from '../../reducers'
|
||||
import { setExample } from '../../actions'
|
||||
|
||||
export let exampleSituationGateWithDefaults = (situationObject, rules) =>
|
||||
assume(() => name => situationObject[name], collectDefaults(rules))()
|
||||
|
||||
// By luck this works as expected for both null and undefined, * but with different branches failing :O *
|
||||
export let isFloat = n => Number(n) === n && n % 1 !== 0
|
||||
|
@ -16,10 +20,8 @@ export let runExamples = (examples, rule, parsedRules) =>
|
|||
examples
|
||||
.map(evolve({ situation: disambiguateExampleSituation(parsedRules, rule) }))
|
||||
.map(ex => {
|
||||
let exampleSituationGate = () => name => ex.situation[name]
|
||||
|
||||
let runExample = analyse(parsedRules, rule.name)(
|
||||
assume(exampleSituationGate, collectDefaults(parsedRules))()
|
||||
let runExample = analyse(parsedRules, rule.dottedName)(
|
||||
exampleSituationGateWithDefaults(ex.situation, parsedRules)
|
||||
),
|
||||
exampleValue = runExample.targets[0].nodeValue,
|
||||
goal = ex['valeur attendue'],
|
||||
|
@ -37,21 +39,25 @@ export let runExamples = (examples, rule, parsedRules) =>
|
|||
}
|
||||
})
|
||||
|
||||
@connect(state => ({
|
||||
situationGate: state.situationGate,
|
||||
parsedRules: state.parsedRules,
|
||||
colour: state.themeColours.colour
|
||||
}))
|
||||
@connect(
|
||||
state => ({
|
||||
situationGate: state.situationGate,
|
||||
parsedRules: state.parsedRules,
|
||||
colour: state.themeColours.colour
|
||||
}),
|
||||
dispatch => ({
|
||||
setExample: compose(dispatch, setExample)
|
||||
})
|
||||
)
|
||||
export default class Examples extends Component {
|
||||
render() {
|
||||
let focusedExample = path(['focusedExample', 'nom'])(this.props),
|
||||
{
|
||||
inject,
|
||||
let {
|
||||
situationExists,
|
||||
showValues,
|
||||
rule,
|
||||
parsedRules,
|
||||
colour
|
||||
colour,
|
||||
setExample,
|
||||
currentExample
|
||||
} = this.props,
|
||||
{ exemples = [] } = rule,
|
||||
examples = runExamples(exemples, rule, parsedRules)
|
||||
|
@ -70,46 +76,51 @@ export default class Examples extends Component {
|
|||
</p>
|
||||
) : (
|
||||
<ul>
|
||||
{examples.map(({ nom, ok, rule, 'valeur attendue': expected }) => (
|
||||
<li
|
||||
key={nom}
|
||||
className={classNames('example', {
|
||||
ok,
|
||||
selected: focusedExample == nom
|
||||
})}
|
||||
onClick={() => inject({ nom, ok, rule })}
|
||||
>
|
||||
<span>
|
||||
{' '}
|
||||
{ok ? (
|
||||
<i className="fa fa-check-circle" aria-hidden="true" />
|
||||
) : (
|
||||
<i className="fa fa-times" aria-hidden="true" />
|
||||
)}
|
||||
</span>
|
||||
<span className="name">{nom}</span>
|
||||
{!ok &&
|
||||
focusedExample == nom && (
|
||||
<div className="ko">
|
||||
Ce test ne passe pas
|
||||
{showValues && (
|
||||
{examples.map(
|
||||
({ nom, ok, rule, 'valeur attendue': expected, situation }) => (
|
||||
<li
|
||||
key={nom}
|
||||
className={classNames('example', {
|
||||
ok,
|
||||
selected: currentExample && currentExample.name == nom
|
||||
})}
|
||||
onClick={() =>
|
||||
currentExample
|
||||
? setExample(null)
|
||||
: setExample(nom, situation)
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{' '}
|
||||
{ok ? (
|
||||
<i className="fa fa-check-circle" aria-hidden="true" />
|
||||
) : (
|
||||
<i className="fa fa-times" aria-hidden="true" />
|
||||
)}
|
||||
</span>
|
||||
<span className="name">{nom}</span>
|
||||
{!ok &&
|
||||
currentExample &&
|
||||
currentExample.name == nom && (
|
||||
<div className="ko">
|
||||
Ce test ne passe pas
|
||||
<span>
|
||||
: le résultat attendu était{' '}
|
||||
<span className="expected">{expected}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
{situationExists &&
|
||||
focusedExample && (
|
||||
currentExample && (
|
||||
<div>
|
||||
<button
|
||||
id="injectSituation"
|
||||
onClick={() => inject()}
|
||||
onClick={() => setExample(null)}
|
||||
style={{ background: colour }}
|
||||
>
|
||||
Revenir à votre situation
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
}
|
||||
|
||||
#meta-content {
|
||||
border-top: 1px solid rgba(51, 51, 80, 0.2);
|
||||
background: rgba(51, 51, 80, 0.03);
|
||||
border: 1px solid rgba(41, 117, 209, 0.3);
|
||||
background: rgba(41, 117, 209, 0.08);
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
@ -78,12 +78,6 @@
|
|||
font-size: 95%;
|
||||
}
|
||||
|
||||
#rule h3 {
|
||||
font-size: 100%;
|
||||
font-weight: 400;
|
||||
margin: 0.4em 0;
|
||||
}
|
||||
|
||||
#destinataire {
|
||||
text-align: center;
|
||||
max-width: 25%;
|
||||
|
@ -127,3 +121,7 @@
|
|||
margin: 3em auto 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#notes {
|
||||
color: #666;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Component } from 'react'
|
||||
import { isEmpty, path } from 'ramda'
|
||||
import { isEmpty, path, last } from 'ramda'
|
||||
import { connect } from 'react-redux'
|
||||
import './Rule.css'
|
||||
import { capitalise0 } from '../../utils'
|
||||
|
@ -17,21 +17,16 @@ import SearchButton from 'Components/SearchButton'
|
|||
|
||||
@connect(state => ({
|
||||
form: state.form,
|
||||
rules: state.parsedRules
|
||||
rules: state.parsedRules,
|
||||
currentExample: state.currentExample
|
||||
}))
|
||||
export default class Rule extends Component {
|
||||
state = {
|
||||
example: null,
|
||||
showValues: true
|
||||
}
|
||||
render() {
|
||||
let { form, rule } = this.props,
|
||||
conversationStarted = !isEmpty(form),
|
||||
situationExists = conversationStarted || this.state.example != null
|
||||
let { form, rule, currentExample, rules } = this.props,
|
||||
conversationStarted = !isEmpty(form)
|
||||
|
||||
let { type, name, title, description, question, ns } = rule,
|
||||
situationOrExampleRule = path(['example', 'rule'])(this.state) || rule,
|
||||
namespaceRules = findRuleByNamespace(this.props.rules, rule.dottedName)
|
||||
namespaceRules = findRuleByNamespace(rules, rule.dottedName)
|
||||
|
||||
return (
|
||||
<div id="rule">
|
||||
|
@ -53,22 +48,24 @@ export default class Rule extends Component {
|
|||
|
||||
<section id="rule-content">
|
||||
<Algorithm
|
||||
rule={situationOrExampleRule}
|
||||
showValues={situationExists}
|
||||
rules={rules}
|
||||
currentExample={currentExample}
|
||||
rule={rule}
|
||||
showValues={conversationStarted || currentExample}
|
||||
/>
|
||||
{rule.note && (
|
||||
<section id="notes">
|
||||
<h3>Note: </h3>
|
||||
{createMarkdownDiv(rule.note)}
|
||||
</section>
|
||||
)}
|
||||
<Examples
|
||||
currentExample={currentExample}
|
||||
situationExists={conversationStarted}
|
||||
rule={rule}
|
||||
focusedExample={this.state.example}
|
||||
showValues={this.state.showValues}
|
||||
inject={example =>
|
||||
this.state.example != null
|
||||
? this.setState({ example: null })
|
||||
: this.setState({ example, showValues: true })
|
||||
}
|
||||
/>
|
||||
{!isEmpty(namespaceRules) && (
|
||||
<NamespaceRules {...{ rule, namespaceRules }} />
|
||||
<NamespaceRulesList {...{ rule, namespaceRules }} />
|
||||
)}
|
||||
{this.renderReferences(rule)}
|
||||
</section>
|
||||
|
@ -86,7 +83,7 @@ export default class Rule extends Component {
|
|||
) : null
|
||||
}
|
||||
|
||||
let NamespaceRules = withColours(({ namespaceRules, rule, colours }) => (
|
||||
let NamespaceRulesList = withColours(({ namespaceRules, rule, colours }) => (
|
||||
<section>
|
||||
<h2>
|
||||
Règles attachées<small>
|
||||
|
@ -101,7 +98,7 @@ let NamespaceRules = withColours(({ namespaceRules, rule, colours }) => (
|
|||
color: colours.textColourOnWhite,
|
||||
textDecoration: 'underline'
|
||||
}}
|
||||
to={'/règle/' + encodeRuleName(r.name)}
|
||||
to={'/règle/' + encodeRuleName(r.dottedName)}
|
||||
>
|
||||
{r.name}
|
||||
</Link>
|
||||
|
@ -133,24 +130,33 @@ let RuleMeta = ({ ns, type, description, question, rule, name }) => (
|
|||
|
||||
export let Namespace = withColours(({ ns, colours }) => (
|
||||
<ul id="namespace">
|
||||
{ns.split(' . ').map(fragment => (
|
||||
<li key={fragment}>
|
||||
<Link
|
||||
style={{
|
||||
color: colours.textColourOnWhite,
|
||||
textDecoration: 'underline'
|
||||
}}
|
||||
to={'/règle/' + encodeRuleName(fragment)}
|
||||
>
|
||||
{capitalise0(fragment)}
|
||||
</Link>
|
||||
<i
|
||||
style={{ margin: '0 .6em', fontSize: '85%' }}
|
||||
className="fa fa-chevron-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
{ns
|
||||
.split(' . ')
|
||||
.reduce(
|
||||
(memo, next) => [
|
||||
...memo,
|
||||
[...(memo.length ? memo.reverse()[0] : []), next]
|
||||
],
|
||||
[]
|
||||
)
|
||||
.map(fragments => (
|
||||
<li key={fragments.join()}>
|
||||
<Link
|
||||
style={{
|
||||
color: colours.textColourOnWhite,
|
||||
textDecoration: 'underline'
|
||||
}}
|
||||
to={'/règle/' + encodeRuleName(fragments.join(' . '))}
|
||||
>
|
||||
{capitalise0(last(fragments))}
|
||||
</Link>
|
||||
<i
|
||||
style={{ margin: '0 .6em', fontSize: '85%' }}
|
||||
className="fa fa-chevron-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
))
|
||||
|
||||
|
|
|
@ -141,3 +141,10 @@ composantes:
|
|||
Il est même possible, pour les mécanismes `barème` et `multiplication` de garder en commun un paramètre comme l'assiette, puis de déclarer des composantes pour le taux.
|
||||
|
||||
> L'example le plus courant de composantes, c'est la distinction part employeur, part salarié (ex. retraite AGIRC).
|
||||
|
||||
allègement:
|
||||
type: numeric
|
||||
description: |
|
||||
Permet de réduire le montant d'une variable.
|
||||
Très utilisé dans le contexte des impôts.
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import React from 'react'
|
||||
import { Node } from './common'
|
||||
import { makeJsx } from '../evaluation'
|
||||
import { mapObjIndexed, values } from 'ramda'
|
||||
|
||||
export default (nodeValue, explanation) => (
|
||||
<div>
|
||||
<Node
|
||||
classes="mecanism allègement"
|
||||
name="allègement"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul className="properties">
|
||||
<li key="assiette">
|
||||
<span className="key">assiette: </span>
|
||||
<span className="value">{makeJsx(explanation.assiette)}</span>
|
||||
</li>
|
||||
{explanation.franchise && (
|
||||
<li key="franchise">
|
||||
<span className="key">franchise: </span>
|
||||
<span className="value">{makeJsx(explanation.franchise)}</span>
|
||||
</li>
|
||||
)}
|
||||
{explanation.décote && (
|
||||
<li key="décote">
|
||||
<span className="key">décote: </span>
|
||||
<span className="value">
|
||||
<ObjectView data={explanation.décote} />
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
{explanation.abattement && (
|
||||
<li key="abattement">
|
||||
<span className="key">abattement: </span>
|
||||
<span className="value">{makeJsx(explanation.abattement)}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
let ObjectView = ({ data }) =>
|
||||
console.log('data', data) || (
|
||||
<ul className="properties">
|
||||
{values(
|
||||
mapObjIndexed(
|
||||
(v, k) => (
|
||||
<li key={k}>
|
||||
{' '}
|
||||
<span className="key">{k}: </span>
|
||||
<span className="value">{makeJsx(v)}</span>
|
||||
</li>
|
||||
),
|
||||
data
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)
|
|
@ -50,6 +50,7 @@ import {
|
|||
|
||||
import 'react-virtualized/styles.css'
|
||||
import Somme from './mecanismViews/Somme'
|
||||
import Allègement from './mecanismViews/Allègement'
|
||||
import buildSelectionView from './mecanismViews/Selection'
|
||||
import uniroot from './uniroot'
|
||||
|
||||
|
@ -194,7 +195,7 @@ let devariate = (recurse, k, v) => {
|
|||
}
|
||||
|
||||
export let mecanismOneOf = (recurse, k, v) => {
|
||||
if (!is(Array, v)) throw 'should be array'
|
||||
if (!is(Array, v)) throw new Error('should be array')
|
||||
|
||||
let explanation = map(recurse, v)
|
||||
|
||||
|
@ -238,7 +239,7 @@ export let mecanismOneOf = (recurse, k, v) => {
|
|||
}
|
||||
|
||||
export let mecanismAllOf = (recurse, k, v) => {
|
||||
if (!is(Array, v)) throw 'should be array'
|
||||
if (!is(Array, v)) throw new Error('should be array')
|
||||
|
||||
let explanation = map(recurse, v)
|
||||
|
||||
|
@ -287,7 +288,9 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
if (is(String, v)) return recurse(v)
|
||||
|
||||
if (!is(Object, v) || keys(v).length == 0) {
|
||||
throw 'Le mécanisme "aiguillage numérique" et ses sous-logiques doivent contenir au moins une proposition'
|
||||
throw new Error(
|
||||
'Le mécanisme "aiguillage numérique" et ses sous-logiques doivent contenir au moins une proposition'
|
||||
)
|
||||
}
|
||||
|
||||
// les termes sont les couples (condition, conséquence) de l'aiguillage numérique
|
||||
|
@ -399,7 +402,9 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
export let findInversion = (situationGate, rules, v, dottedName) => {
|
||||
let inversions = v.avec
|
||||
if (!inversions)
|
||||
throw "Une formule d'inversion doit préciser _avec_ quoi on peut inverser la variable"
|
||||
throw new Error(
|
||||
"Une formule d'inversion doit préciser _avec_ quoi on peut inverser la variable"
|
||||
)
|
||||
/*
|
||||
Quelle variable d'inversion possible a sa valeur renseignée dans la situation courante ?
|
||||
Ex. s'il nous est demandé de calculer le salaire de base, est-ce qu'un candidat à l'inversion, comme
|
||||
|
@ -535,6 +540,58 @@ export let mecanismSum = (recurse, k, v) => {
|
|||
}
|
||||
}
|
||||
|
||||
export let mecanismReduction = (recurse, k, v) => {
|
||||
let objectShape = {
|
||||
assiette: false,
|
||||
abattement: constantNode(0),
|
||||
franchise: constantNode(0)
|
||||
}
|
||||
|
||||
let effect = ({ assiette, abattement, franchise, décote }) => {
|
||||
let v_assiette = val(assiette)
|
||||
|
||||
if (v_assiette == null) return null
|
||||
|
||||
let montantFranchiséDécoté =
|
||||
val(franchise) && v_assiette < val(franchise)
|
||||
? 0
|
||||
: décote
|
||||
? do {
|
||||
let plafond = val(décote.plafond),
|
||||
taux = val(décote.taux)
|
||||
|
||||
v_assiette > plafond
|
||||
? v_assiette
|
||||
: max(0, (1 + taux) * v_assiette - taux * plafond)
|
||||
}
|
||||
: v_assiette
|
||||
|
||||
return abattement
|
||||
? val(abattement) == null
|
||||
? montantFranchiséDécoté === 0 ? 0 : null
|
||||
: max(0, montantFranchiséDécoté - val(abattement))
|
||||
: montantFranchiséDécoté
|
||||
}
|
||||
|
||||
let base = parseObject(recurse, objectShape, v),
|
||||
explanation = v.décote
|
||||
? {
|
||||
...base,
|
||||
décote: map(recurse, v.décote)
|
||||
}
|
||||
: base,
|
||||
evaluate = evaluateObject(objectShape, effect)
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
jsx: Allègement,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'allègement',
|
||||
type: 'numeric'
|
||||
}
|
||||
}
|
||||
|
||||
export let mecanismProduct = (recurse, k, v) => {
|
||||
if (v.composantes) {
|
||||
//mécanisme de composantes. Voir known-mecanisms.md/composantes
|
||||
|
@ -921,5 +978,5 @@ export let mecanismSelection = (recurse, k, v) => {
|
|||
}
|
||||
|
||||
export let mecanismError = (recurse, k, v) => {
|
||||
throw "Le mécanisme '" + k + "' est inconnu !" + v
|
||||
throw new Error("Le mécanisme '" + k + "' est inconnu !" + v)
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
- aide
|
||||
- indemnité
|
||||
- salaire
|
||||
- taxe
|
||||
|
|
|
@ -81,13 +81,12 @@ export let decodeRuleName = name =>
|
|||
|
||||
/* Les variables peuvent être exprimées dans la formule d'une règle relativement à son propre espace de nom, pour une plus grande lisibilité. Cette fonction résoud cette ambiguité.
|
||||
*/
|
||||
|
||||
export let disambiguateRuleReference = (
|
||||
allRules,
|
||||
{ ns, name },
|
||||
partialName
|
||||
) => {
|
||||
let fragments = ns ? ns.split(' . ') : [], // ex. [CDD . événements . rupture]
|
||||
let fragments = ns ? [...ns.split(' . '), name] : [], // ex. [CDD . événements . rupture]
|
||||
pathPossibilities = range(0, fragments.length + 1) // -> [ [CDD . événements . rupture], [CDD . événements], [CDD] ]
|
||||
.map(nbEl => take(nbEl)(fragments))
|
||||
.reverse(),
|
||||
|
@ -103,7 +102,7 @@ export let disambiguateRuleReference = (
|
|||
return (
|
||||
(found && found.dottedName) ||
|
||||
do {
|
||||
throw `OUUUUPS la référence '${partialName}' dans la règle '${name}' est introuvable dans la base`
|
||||
throw new `OUUUUPS la référence '${partialName}' dans la règle '${name}' est introuvable dans la base`()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -144,6 +143,11 @@ export let findRuleByDottedName = (allRules, dottedName) => {
|
|||
return allRules.find(rule => rule.dottedName == dottedName)
|
||||
}
|
||||
|
||||
export let findRule = (rules, nameOrDottedName) =>
|
||||
nameOrDottedName.includes(' . ')
|
||||
? findRuleByDottedName(rules, nameOrDottedName)
|
||||
: findRuleByName(rules, nameOrDottedName)
|
||||
|
||||
export let findRuleByNamespace = (allRules, ns) =>
|
||||
allRules.filter(propEq('ns', ns))
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ import React from 'react'
|
|||
import {
|
||||
findRuleByDottedName,
|
||||
disambiguateRuleReference,
|
||||
findRuleByName
|
||||
findRuleByName,
|
||||
findRule
|
||||
} from './rules'
|
||||
import { evaluateVariable } from './variables'
|
||||
import {
|
||||
|
@ -48,7 +49,8 @@ import {
|
|||
mecanismError,
|
||||
mecanismComplement,
|
||||
mecanismSelection,
|
||||
mecanismInversion
|
||||
mecanismInversion,
|
||||
mecanismReduction
|
||||
} from './mecanisms'
|
||||
import {
|
||||
evaluateNode,
|
||||
|
@ -390,9 +392,9 @@ let treat = (rules, rule) => rawNode => {
|
|||
},
|
||||
treatOther = rawNode => {
|
||||
console.log() // eslint-disable-line no-console
|
||||
throw 'Cette donnée : ' +
|
||||
rawNode +
|
||||
' doit être un Number, String ou Object'
|
||||
throw new Error(
|
||||
'Cette donnée : ' + rawNode + ' doit être un Number, String ou Object'
|
||||
)
|
||||
},
|
||||
treatObject = rawNode => {
|
||||
let mecanisms = intersection(keys(rawNode), keys(knownMecanisms))
|
||||
|
@ -404,7 +406,7 @@ let treat = (rules, rule) => rawNode => {
|
|||
mecanisms,
|
||||
rawNode
|
||||
)
|
||||
throw 'OUPS !'
|
||||
throw new Error('OUPS !')
|
||||
}
|
||||
|
||||
let k = head(mecanisms),
|
||||
|
@ -425,7 +427,8 @@ let treat = (rules, rule) => rawNode => {
|
|||
'une possibilité': 'oui',
|
||||
collectMissing: () => [rule.dottedName]
|
||||
}),
|
||||
inversion: mecanismInversion(rule.dottedName)
|
||||
inversion: mecanismInversion(rule.dottedName),
|
||||
allègement: mecanismReduction
|
||||
},
|
||||
action = propOr(mecanismError, k, dispatch)
|
||||
|
||||
|
@ -621,12 +624,7 @@ export let analyseMany = (parsedRules, targetNames) => situationGate => {
|
|||
// setRule in Rule.js needs to get smarter and pass dottedName
|
||||
let cache = {}
|
||||
|
||||
let parsedTargets = targetNames.map(
|
||||
t =>
|
||||
t.includes(' . ')
|
||||
? findRuleByDottedName(parsedRules, t)
|
||||
: findRuleByName(parsedRules, t)
|
||||
),
|
||||
let parsedTargets = targetNames.map(t => findRule(parsedRules, t)),
|
||||
targets = chain(pt => getTargets(pt, parsedRules), parsedTargets).map(t =>
|
||||
evaluateNode(cache, situationGate, parsedRules, t)
|
||||
)
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import { head, isEmpty, pathOr, reject, contains, without, concat, length } from 'ramda'
|
||||
import {
|
||||
head,
|
||||
isEmpty,
|
||||
pathOr,
|
||||
reject,
|
||||
contains,
|
||||
without,
|
||||
concat,
|
||||
length
|
||||
} from 'ramda'
|
||||
import { combineReducers } from 'redux'
|
||||
import reduceReducers from 'reduce-reducers'
|
||||
import { reducer as formReducer, formValueSelector } from 'redux-form'
|
||||
|
@ -116,7 +125,7 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (
|
|||
if (action.type == STEP_ACTION && action.name == 'fold') {
|
||||
tracker.push([
|
||||
'trackEvent',
|
||||
'answer:'+action.source,
|
||||
'answer:' + action.source,
|
||||
action.step + ': ' + situationWithDefaults(state)(action.step)
|
||||
])
|
||||
|
||||
|
@ -124,7 +133,7 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (
|
|||
tracker.push([
|
||||
'trackEvent',
|
||||
'done',
|
||||
'after'+length(newState.foldedSteps)+'questions'
|
||||
'after' + length(newState.foldedSteps) + 'questions'
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -166,6 +175,15 @@ function explainedVariable(state = null, { type, variableName = null }) {
|
|||
}
|
||||
}
|
||||
|
||||
function currentExample(state = null, { type, situation, name }) {
|
||||
switch (type) {
|
||||
case 'SET_EXAMPLE':
|
||||
return name != null ? { name, situation } : null
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default reduceReducers(
|
||||
combineReducers({
|
||||
sessionId: (id = Math.floor(Math.random() * 1000000000000) + '') => id,
|
||||
|
@ -191,7 +209,9 @@ export default reduceReducers(
|
|||
|
||||
themeColours,
|
||||
|
||||
explainedVariable
|
||||
explainedVariable,
|
||||
|
||||
currentExample
|
||||
}),
|
||||
// cross-cutting concerns because here `state` is the whole state tree
|
||||
reduceSteps(ReactPiwik, rules, formatInputs(rules, formValueSelector))
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
valeur attendue: 55.21
|
||||
|
||||
|
||||
notes: |
|
||||
note: |
|
||||
|
||||
À noter, la loi El Khomri modifie l'article L3141-12:
|
||||
- avant : Les congés peuvent être pris dès l'ouverture des droits [...]
|
||||
|
@ -157,7 +157,7 @@
|
|||
|
||||
alias: prime de précarité
|
||||
description: Somme versée en fin de CDD comme compensation de précarité.
|
||||
notes: |
|
||||
note: |
|
||||
Attention, les exceptions sont légion. Conventions collectives...
|
||||
|
||||
- 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)
|
||||
|
@ -273,10 +273,8 @@
|
|||
La majoration 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
|
||||
Circulaire Unédic: http://www.unedic.org/sites/default/files/ci201317_1.pdf
|
||||
|
||||
notes: |
|
||||
note: |
|
||||
- L'URSSAF liste à la fois des conditions pour "Les CDD concernés par l’application de la majoration" et pour "Les contrats de travail exclus de la majoration". Un doute persiste : difficile de savoir si les premières suffisent au calcul (donc que les deuxièmes sont là pour enlever les doutes) ou si les deuxièmes peuvent faire exception...
|
||||
- Depuis le 1er juillet 2013
|
||||
- l'URSSAF explique longuement la notion de durée du CDD : "Comment déterminer la durée du CDD ?"
|
||||
|
||||
|
||||
- espace: contrat salarié
|
||||
|
@ -564,7 +562,7 @@
|
|||
titre: Contrat jeune vacances
|
||||
question: Est-ce un contrat jeune vacances ?
|
||||
description: Aussi appelé CDD vendanges. Contrat conclu avec un jeune pendant ses vacances scolaires ou universitaires.
|
||||
notes: Ce n'est pas un motif de CDD.
|
||||
note: Ce n'est pas un motif de CDD.
|
||||
par défaut: non
|
||||
|
||||
- espace: contrat salarié . CDD
|
||||
|
@ -992,8 +990,6 @@
|
|||
|
||||
- espace: contrat salarié
|
||||
nom: plafond cice
|
||||
# TODO: calcul du smic proratisé
|
||||
# TODO: smic mensuel défini dans la réduction générale, à déplacer pour mutualiser
|
||||
formule: multiplicateur cice * smic mensuel
|
||||
|
||||
- espace: contrat salarié
|
||||
|
@ -1001,6 +997,25 @@
|
|||
formule: 2.5
|
||||
|
||||
|
||||
- espace: contrat salarié . CITS
|
||||
nom: non abattu
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: assiette cotisations sociales
|
||||
taux: 4%
|
||||
|
||||
- espace: contrat salarié . CITS
|
||||
nom: abattement mensuel par salarié
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: entreprise . taxe sur les salaires . abattement associations
|
||||
taux: 1 / entreprise . effectif
|
||||
facteur: 1 / 12
|
||||
note: |
|
||||
Cette variable révèle deux lacunes de notre modélisation :
|
||||
- on ne peut pas spécifier plusieurs salariés à l'entreprise, et donc calculer correctement le CITS. On fait donc comme si l'unique salarié simulé était le salarié type, multiplié.
|
||||
- on ne gère pas la conversion entre les périodes temporelles.
|
||||
On les résoud en exploitant à tort les capacités du mécanisme multiplication.
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: CITS
|
||||
|
@ -1013,36 +1028,48 @@
|
|||
La loi de finances pour 2017 instaure, au bénéfice des associations et organismes sans but lucratif (OSBL),
|
||||
un dispositif de crédit d'impôt de taxe sur les salaires (CITS).
|
||||
références:
|
||||
fiche: https://www.service-public.fr/associations/actualites/A11012
|
||||
fiche: https://www.service-public.fr/associations/vosdroits/F34066
|
||||
|
||||
non applicable si:
|
||||
une de ces conditions:
|
||||
- assiette cotisations sociales > plafond CITS
|
||||
- ≠ entreprise . association non lucrative
|
||||
applicable si: entreprise . association non lucrative
|
||||
non applicable si: assiette cotisations sociales > plafond
|
||||
|
||||
note: Le CITS est un crédit sur la taxe sur les salaires. Celle-ci est abattue de ~20k. Le CITS est lui-même abattu de ~20k ! Quel intérêt, pourquoi ne pas simplement supprimer l'abattement initial ? Parce que dans certains cas, une entreprise d'un salarié au SMIC, 4% des rémunérations annuelles < abattement de 20k. Donc le crédit est nul. Donc la taxe sur les salaires reste abattue comme initialement prévu.
|
||||
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: assiette cotisations sociales
|
||||
taux: 4%
|
||||
allègement:
|
||||
assiette: non abattu
|
||||
abattement: abattement mensuel par salarié
|
||||
|
||||
exemples:
|
||||
- nom: SMIC
|
||||
- nom: Petite association, non applicable car taxe sur les salaires nulle (car abattue)
|
||||
situation:
|
||||
assiette cotisations sociales: 2300
|
||||
entreprise . association non lucrative: oui
|
||||
valeur attendue: 92
|
||||
valeur attendue: 0
|
||||
- nom: Applicable
|
||||
situation:
|
||||
entreprise . association non lucrative: oui
|
||||
assiette cotisations sociales: 3000
|
||||
entreprise . effectif: 50
|
||||
valeur attendue: 85.82
|
||||
- nom: Non applicable car rémunération trop forte
|
||||
situation:
|
||||
entreprise . association non lucrative: oui
|
||||
assiette cotisations sociales: 4000
|
||||
entreprise . effectif: 50
|
||||
valeur attendue: 0
|
||||
- nom: Non applicable si organisme lucratif
|
||||
situation:
|
||||
assiette cotisations sociales: 2300
|
||||
entreprise . association non lucrative: non
|
||||
assiette cotisations sociales: 2300
|
||||
valeur attendue: 0
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: plafond CITS
|
||||
formule: multiplicateur CITS * smic mensuel
|
||||
- espace: contrat salarié . CITS
|
||||
nom: plafond
|
||||
formule: multiplicateur * smic mensuel
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: multiplicateur CITS
|
||||
- espace: contrat salarié . CITS
|
||||
nom: multiplicateur
|
||||
formule: 2.5
|
||||
|
||||
|
||||
|
@ -1171,8 +1198,6 @@
|
|||
Cotisation de retraite complémentaire
|
||||
(Cotisation pour l'Association pour la Gestion du Fonds de Financement de l’AGIRC et de l’ARRCO)
|
||||
référence: http://www.agirc-arrco.fr/entreprises/gerer-les-salaries/calcul-des-cotisations/
|
||||
notes: |
|
||||
Attention: les tranches du barème sont différentes pour les cadres et non-cadres, en valeur et en nombres.
|
||||
|
||||
formule:
|
||||
barème:
|
||||
|
@ -1322,7 +1347,7 @@
|
|||
- au-dessus de: 8
|
||||
taux: 0%
|
||||
|
||||
notes: |
|
||||
note: |
|
||||
Il existe une tranche C, de 4 à 8 fois la base, sur laquelle la répartition des cotisations est décidée au sein de l’entreprise jusqu’à 20 %. De 20 % à 20,30 %, la répartition est la suivante : 66,67 % à la charge du salarié et 33,33 % pour l’employeur.
|
||||
|
||||
références:
|
||||
|
@ -1336,12 +1361,6 @@
|
|||
branche: chômage
|
||||
references:
|
||||
calcul: https://www.service-public.fr/professionnels-entreprises/vosdroits/F31409
|
||||
notes: |
|
||||
- taux différent pour le personnel intérimaire des entreprises de travail temporaire
|
||||
- Ne sont pas assujetties :
|
||||
- les personnes morales de droit public,
|
||||
- les syndicats de copropriété,
|
||||
- les particuliers employeurs.
|
||||
|
||||
# non applicable si: assimilé salarié
|
||||
|
||||
|
@ -1396,8 +1415,6 @@
|
|||
(Association Pour l’Emploi des Cadres)
|
||||
références:
|
||||
chiffres clés: http://www.agirc-arrco.fr/l-agirc-et-larrco/chiffres-cles
|
||||
notes: |
|
||||
Avant 2011, il y avait une cotisation forfaitaire au lieu de la tranche A
|
||||
|
||||
#TODO double négation en attendant d'ajouter 'applicable si'
|
||||
non applicable si: ≠ statut cadre
|
||||
|
@ -1558,12 +1575,14 @@
|
|||
cotisation:
|
||||
dû par: employeur
|
||||
collecteur: URSSAF
|
||||
description: Contribution patronale destinée à abonder un fonds paritaire dédié au financement des organisations syndicales et des organisations professionnelles d’employeurs.
|
||||
description: |
|
||||
Contribution patronale destinée à abonder un fonds paritaire dédié au financement des organisations syndicales et des organisations professionnelles d’employeurs.
|
||||
|
||||
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
|
||||
notes: |
|
||||
Anciennement 'contribution patronale au financement des organisations syndicales'
|
||||
|
||||
formule:
|
||||
multiplication:
|
||||
|
@ -1595,8 +1614,9 @@
|
|||
- attributs:
|
||||
dû par: salarié
|
||||
taux: 0.13%
|
||||
|
||||
- espace: contrat salarié
|
||||
note: Cette assiette est complexe, cette version n'est qu'une simplification. #TODO
|
||||
note: Cette assiette est complexe, cette version n'est qu'une simplification.
|
||||
nom: assiette CSG
|
||||
références:
|
||||
calcul: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-csg-crds/les-revenus-salariaux-soumis-a-l.html
|
||||
|
@ -1608,7 +1628,6 @@
|
|||
- complémentaire santé (employeur)
|
||||
|
||||
- espace: contrat salarié
|
||||
note: Cette assiette est complexe, cette version n'est qu'une simplification. #TODO
|
||||
nom: assiette CSG abattue
|
||||
formule:
|
||||
barème:
|
||||
|
@ -1673,9 +1692,6 @@
|
|||
branche: logement
|
||||
références:
|
||||
calcul: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-contribution-au-fonds-nationa.html
|
||||
note: Il y avait une cotisation supplémentaire pour les entreprises de >= 20 employés avant 2015, mais le résultat était le même.
|
||||
explication: |
|
||||
Si l'entreprise a un effectif supérieur ou égal à 20 salariés, elle doit verser 0,50 % sur la totalité des salaires. Pour les entreprises de moins de 20 salariés et pour les employeurs occupés aux activités mentionnées aux 1° à 4° de l'article L. 722-1 du code rural et de la pêche maritime et les coopératives mentionnées à l'article L. 521-1 du même code, la cotisation est de 0,10 % des salaires limités au plafond de sécurité sociale (tranche A). Intégrée aux cotisations de sécurité sociale, elle est recouvrée par les Urssaf pour le financement des allocations logement versées par les caisses d'allocations familiales. Les employeurs qui ont atteint ou dépassé pour la première fois, au titre des années 2008 à 2012, le seuil de 20 salariés ont été dispensés de l’ancien Fnal supplémentaire pendant 3 ans. La contribution était ensuite progressivement appelée sur les 3 années suivantes. Un dispositif est mis en place pour 2016, 2017 et 2018. Les employeurs qui atteignent ou dépassent au titre de ces années l'effectif de 20 salariés continuent d'appliquer le taux de 0,10 % pendant trois ans (suite au franchissement de seuil). Cette modalité n'implique pas d'adaptation du calcul du coefficient de la réduction générale. Le seuil de 20 salariés s'apprécie au 31 décembre et la modification de la cotisation est effective au 1er avril suivant.
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: assiette cotisations sociales
|
||||
|
@ -1708,13 +1724,14 @@
|
|||
taux: 1%
|
||||
- si: entreprise . effectif < 11
|
||||
taux: 0.55%
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: maladie
|
||||
cotisation:
|
||||
branche: santé
|
||||
dû par: employeur
|
||||
description: Cotisations de la branche maladie
|
||||
références:
|
||||
références:
|
||||
fiche: https://www.urssaf.fr/portail/home/employeur/calculer-les-cotisations/les-taux-de-cotisations/la-cotisation-maladie---maternit.html
|
||||
Décret n° 2017-1891 relatif au taux des cotisations d'assurance maladie: https://www.legifrance.gouv.fr/eli/decret/2017/12/30/CPAS1732212D/jo/texte
|
||||
formule:
|
||||
|
@ -1759,10 +1776,9 @@
|
|||
impôt: oui
|
||||
références:
|
||||
fiche: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22583
|
||||
notes: |
|
||||
note: |
|
||||
L'employeur a le choix entre verser cet impôt à un "organisme du 1 % patronal" agréé, investir la somme dans le logement de ses salariés, ou accorder à eux et leur famille des prêts de construction à taux réduit.
|
||||
|
||||
|
||||
applicable si: entreprise . effectif >= 20
|
||||
formule:
|
||||
multiplication:
|
||||
|
@ -1798,7 +1814,7 @@
|
|||
description: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22574
|
||||
csa: http://www.opcalia.com/employeurs/financer-la-formation-et-lapprentissage/taxe-dapprentissage/contribution-supplementaire-a-lapprentissage-csa/
|
||||
|
||||
notes: Taxe complexe, comportant notamment des exonérations non prises en compte ici.
|
||||
note: Taxe complexe, comportant notamment des exonérations non prises en compte ici.
|
||||
|
||||
non applicable si: entreprise . association non lucrative
|
||||
# L'association a but non lucratif ne paie pas d'IS de droit commun article 206 du Code général des impôts
|
||||
|
@ -1858,28 +1874,28 @@
|
|||
- entreprise . effectif >= 2000
|
||||
- entreprise . ratio alternants < 1%
|
||||
|
||||
# TODO chantier droits: introduire la répartition entre part régionale, quota d'app., hors quota
|
||||
# https://www.service-public.fr/professionnels-entreprises/vosdroits/F22574
|
||||
- espace: contrat salarié
|
||||
nom: assujettie à la taxe sur les salaires
|
||||
titre: Entreprise assujettie à la taxe sur les salaires
|
||||
description: |
|
||||
Sont assujetties les associations à but non lucratif et les entreprises non soumises à la TVA ou payant la TVA sur moins de 10% de leur chiffre. Les particuliers employeurs, les employeurs agricoles, les établissements d'enseignement supérieur, les auto-entrepreneurs ne sont pas concernés.
|
||||
question: L'entreprise est-elle assujettie à la taxe sur les salaires ?
|
||||
# variable non utilisée pour l'instant, comme dans le simulateur v1
|
||||
# à ajouter quand nous aurons des mécanismes logiques plus évolués (notamment 'applicable si')
|
||||
par défaut: non
|
||||
- espace: contrat salarié . taxe sur les salaires
|
||||
nom: assiette
|
||||
formule:
|
||||
somme:
|
||||
- assiette cotisations sociales
|
||||
- prévoyance obligatoire cadre
|
||||
- complémentaire santé (employeur)
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: taxe sur les salaires annuelle
|
||||
références:
|
||||
assiette: http://bofip.impots.gouv.fr/bofip/6690-PGP.html
|
||||
|
||||
|
||||
- espace: contrat salarié . taxe sur les salaires
|
||||
nom: barème annuel
|
||||
références:
|
||||
description: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22576
|
||||
barème: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22576
|
||||
|
||||
formule:
|
||||
barème:
|
||||
# TODO - les barèmes étant exprimés en base annuelle, et également à cause des limitations
|
||||
# de la grammaire, on a recours à cette formulation trop compliquée; à simplifier
|
||||
assiette: assiette taxe sur les salaires * 12
|
||||
assiette: assiette * 12
|
||||
tranches:
|
||||
- en-dessous de: 7721
|
||||
taux: 4.25%
|
||||
|
@ -1890,40 +1906,79 @@
|
|||
à: 152279
|
||||
taux: 13.6%
|
||||
- au-dessus de: 152279
|
||||
taux: 20%
|
||||
taux: 0%
|
||||
exemples:
|
||||
- nom: salaire médian
|
||||
situation:
|
||||
assiette taxe sur les salaires: 2300
|
||||
assiette: 2300
|
||||
valeur attendue: 2639.16 # calcul annuel : 7721×4.25%+(15417−7721)×8.5%+(27600−15417)×13.6%
|
||||
|
||||
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: assiette taxe sur les salaires
|
||||
- espace: entreprise . taxe sur les salaires
|
||||
nom: barème annuel
|
||||
formule:
|
||||
somme:
|
||||
- assiette cotisations sociales
|
||||
- prévoyance obligatoire cadre
|
||||
- complémentaire santé (employeur)
|
||||
contrat salarié . taxe sur les salaires . barème annuel * effectif
|
||||
|
||||
- espace: entreprise . taxe sur les salaires
|
||||
nom: abattement associations
|
||||
formule: 20507
|
||||
|
||||
|
||||
- espace: entreprise
|
||||
nom: taxe sur les salaires
|
||||
|
||||
formule:
|
||||
allègement:
|
||||
assiette: barème annuel
|
||||
franchise: 1200
|
||||
décote:
|
||||
plafond: 2040
|
||||
taux: 75%
|
||||
abattement: abattement associations
|
||||
|
||||
note: |
|
||||
Attention : l'abattement n'est valable que pour les organismes à but non lucratif.
|
||||
Il n'est pas conditionné ici car on réserve pour l'instant la taxe sur les salaires aux associations 1901
|
||||
|
||||
|
||||
- espace: contrat salarié . taxe sur les salaires
|
||||
nom: montant annuel
|
||||
formule: entreprise . taxe sur les salaires / entreprise . effectif
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: taxe sur les salaires
|
||||
description: La taxe sur les salaires en France est un impôt progressif créé en 1948 que certains employeurs doivent acquitter sur les salaires qu'ils distribuent.
|
||||
non applicable si: ≠ entreprise . association non lucrative
|
||||
taxe:
|
||||
dû par: employeur
|
||||
|
||||
|
||||
description: La taxe sur les salaires en France est un impôt progressif créé en 1948 que certains employeurs doivent acquitter sur les salaires qu'ils distribuent.
|
||||
applicable si: entreprise . association non lucrative
|
||||
|
||||
formule: montant annuel / 12
|
||||
|
||||
note: Cette implémentation de la taxe sur les salaires est spécifique aux associations à but non lucratif, elle est donc largement simplifiée. Plein d'autres organisations sont concernées, en fonction de la TVA qu'elles paient. Les associations y sont assujetties automatiquement.
|
||||
|
||||
formule: taxe sur les salaires annuelle / 12
|
||||
exemples:
|
||||
- nom: non applicable par défaut
|
||||
situation:
|
||||
salaire brut: 2300
|
||||
salaire de base: 2300
|
||||
valeur attendue: 0
|
||||
- nom: association non lucrative
|
||||
# Ce test ne sert qu'à tester la condition "association non lucrative", tant que nous faisons face à la limitation des calculs temporels
|
||||
- nom: association non lucrative unipersonnelle
|
||||
situation:
|
||||
entreprise . association non lucrative: oui
|
||||
taxe sur les salaires annuelle: 2639.16
|
||||
valeur attendue: 219.93
|
||||
salaire de base: 2300
|
||||
entreprise . effectif: 1
|
||||
valeur attendue: 0
|
||||
- nom: association non lucrative
|
||||
situation:
|
||||
entreprise . association non lucrative: oui
|
||||
salaire de base: 2300
|
||||
entreprise . effectif: 10
|
||||
forfait complémentaire santé: 0
|
||||
valeur attendue: 49
|
||||
|
||||
références:
|
||||
fiche: https://www.service-public.fr/professionnels-entreprises/vosdroits/F22576
|
||||
|
||||
- espace: contrat salarié
|
||||
nom: versement transport
|
||||
description: Contribution sur le travail consacrée au financement des transports publics.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var webpack = require('webpack'),
|
||||
path = require('path'),
|
||||
prodEnv = process.env.NODE_ENV == 'production' // eslint-disable-line no-undef
|
||||
prodEnv = process.env.NODE_ENV == 'production', // eslint-disable-line no-undef
|
||||
HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
devtool: 'cheap-module-source-map',
|
||||
|
@ -13,10 +14,8 @@ module.exports = {
|
|||
'@babel/polyfill',
|
||||
'react-hot-loader/patch',
|
||||
'./source/entry.js'
|
||||
],
|
||||
]
|
||||
// le nom "simulateur" est là pour des raisons historiques
|
||||
simulateur: './source/iframe-script.js',
|
||||
'colour-chooser': ['@babel/polyfill', './source/entry-colour-chooser.js']
|
||||
},
|
||||
output: {
|
||||
path: path.resolve('./dist/'),
|
||||
|
@ -92,6 +91,13 @@ module.exports = {
|
|||
new webpack.EnvironmentPlugin({ NODE_ENV: 'development' }),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
]
|
||||
.concat(!prodEnv ? [new webpack.HotModuleReplacementPlugin()] : [])
|
||||
.concat(
|
||||
!prodEnv
|
||||
? [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new HardSourceWebpackPlugin()
|
||||
]
|
||||
: []
|
||||
)
|
||||
.concat(prodEnv ? [new webpack.optimize.UglifyJsPlugin()] : [])
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
- nom: montant
|
||||
format: €
|
||||
|
||||
- test: montant franchisé
|
||||
format: €
|
||||
formule:
|
||||
allègement:
|
||||
assiette: montant
|
||||
franchise: 1200
|
||||
|
||||
exemples:
|
||||
- situation:
|
||||
montant: 1000
|
||||
valeur attendue: 0
|
||||
- situation:
|
||||
valeur attendue: null
|
||||
variables manquantes:
|
||||
- montant
|
||||
|
||||
|
||||
- test: montant décoté
|
||||
format: €
|
||||
formule:
|
||||
allègement:
|
||||
assiette: montant
|
||||
décote:
|
||||
plafond: 2040
|
||||
taux: 100%
|
||||
exemples:
|
||||
- situation:
|
||||
montant: 1000
|
||||
valeur attendue: 0
|
||||
|
||||
- test: montant franchisé et décoté
|
||||
format: €
|
||||
formule:
|
||||
allègement:
|
||||
assiette: montant
|
||||
franchise: 1200
|
||||
décote:
|
||||
plafond: 2040
|
||||
taux: 75%
|
||||
exemples:
|
||||
- situation:
|
||||
montant: 100
|
||||
valeur attendue: 0
|
||||
- situation:
|
||||
montant: 1200
|
||||
valeur attendue: 570
|
||||
- situation:
|
||||
montant: 1620
|
||||
valeur attendue: 1305
|
||||
- situation:
|
||||
montant: 2040
|
||||
valeur attendue: 2040
|
||||
|
||||
|
||||
- test: montant abattu
|
||||
format: €
|
||||
formule:
|
||||
allègement:
|
||||
assiette: montant
|
||||
abattement: 20507
|
||||
exemples:
|
||||
- situation:
|
||||
montant: 10000
|
||||
valeur attendue: 0
|
||||
- situation:
|
||||
montant: 80000
|
||||
valeur attendue: 59493
|
||||
|
||||
|
||||
- test: montant franchisé, décote, abattu
|
||||
format: €
|
||||
formule:
|
||||
allègement:
|
||||
assiette: montant
|
||||
franchise: 1200
|
||||
décote:
|
||||
plafond: 2040
|
||||
taux: 75%
|
||||
abattement: 20507
|
||||
exemples:
|
||||
- situation:
|
||||
montant: 100
|
||||
valeur attendue: 0
|
||||
- situation:
|
||||
montant: 1620
|
||||
valeur attendue: 0
|
||||
- situation:
|
||||
montant: 3000
|
||||
valeur attendue: 0
|
||||
- situation:
|
||||
montant: 21000
|
||||
valeur attendue: 493
|
||||
|
||||
|
|
@ -13,7 +13,6 @@ import R from 'ramda'
|
|||
import { runExamples, isFloat } from '../source/components/rule/Examples'
|
||||
|
||||
let parsedRules = parseAll(rules)
|
||||
|
||||
describe('Tests des règles de notre base de règles', () =>
|
||||
parsedRules.map(rule => {
|
||||
if (!rule.exemples) return null
|
||||
|
|
|
@ -173,4 +173,46 @@ describe('results grid', function() {
|
|||
subCell(maladie, 'contrat salarié . ATMP', 'employeur')
|
||||
).to.be.closeTo(54, 1)
|
||||
})
|
||||
|
||||
it('should access taxe sur les salaires', function() {
|
||||
let fakeState = {}
|
||||
let stateSelector = state => name => fakeState[name]
|
||||
|
||||
let rules = realRules.map(enrichRule),
|
||||
reducer = reduceSteps(tracker, rules, stateSelector)
|
||||
|
||||
var step1 = reducer(
|
||||
{ foldedSteps: [] },
|
||||
{
|
||||
type: 'START_CONVERSATION',
|
||||
targetNames: ['salaire net', 'salaire total']
|
||||
}
|
||||
)
|
||||
fakeState['contrat salarié . salaire de base'] = 2300
|
||||
var step2 = reducer(step1, {
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: 'contrat salarié . salaire de base'
|
||||
})
|
||||
fakeState['entreprise . association non lucrative'] = 'oui'
|
||||
var step3 = reducer(step2, {
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: 'entreprise . association non lucrative'
|
||||
})
|
||||
fakeState['entreprise . effectif'] = 10
|
||||
var step4 = reducer(step3, {
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: 'entreprise . effectif'
|
||||
})
|
||||
|
||||
let analysis = step4.analysis,
|
||||
result = byBranch(analysis),
|
||||
autre = byName(result['autre'])
|
||||
|
||||
expect(
|
||||
subCell(autre, 'contrat salarié . taxe sur les salaires', 'employeur')
|
||||
).to.be.closeTo(51, 1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -52,7 +52,13 @@ describe('rule checks', function() {
|
|||
r.defaultValue == null
|
||||
)
|
||||
|
||||
rulesNeedingDefault.map(r => console.log('yo', r.dottedName))
|
||||
rulesNeedingDefault.map(r =>
|
||||
console.log(
|
||||
'cette règle, ',
|
||||
r.dottedName,
|
||||
'devrait avoir une valeur par défaut'
|
||||
)
|
||||
)
|
||||
expect(rulesNeedingDefault).to.be.empty
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue