🔨 ⚙️ ajoute un mécanisme durée pour la proratisation de l'ACRE :wip:

pull/789/head
Johan Girod 2019-12-16 18:47:15 +01:00
parent d42070eb16
commit 4749727938
16 changed files with 317 additions and 133 deletions

View File

@ -22,6 +22,7 @@
"@babel/polyfill": "^7.4.0",
"@babel/runtime": "^7.3.4",
"classnames": "^2.2.5",
"cleave.js": "^1.5.3",
"color-convert": "^1.9.2",
"core-js": "^3.2.1",
"focus-trap-react": "^3.1.2",

View File

@ -0,0 +1,63 @@
import FormattedInput from 'cleave.js/react'
import withColours from 'Components/utils/withColours'
import { compose } from 'ramda'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { debounce } from '../../utils'
import { FormDecorator } from './FormDecorator'
import InputSuggestions from './InputSuggestions'
import SendButton from './SendButton'
// TODO: fusionner Input.js et CurrencyInput.js
export default compose(
FormDecorator('input'),
withColours
)(function Input({
suggestions,
setFormValue,
submit,
dottedName,
value,
colours,
unit
}) {
const debouncedSetFormValue = useCallback(debounce(750, setFormValue), [])
const { language } = useTranslation().i18n
return (
<>
<div css="width: 100%">
<InputSuggestions
suggestions={suggestions}
onFirstClick={value => {
setFormValue(value)
}}
onSecondClick={() => submit('suggestion')}
/>
</div>
<div className="answer">
<FormattedInput
autoFocus
id={'step-' + dottedName}
placeholder="JJ/MM/AAAA"
options={{
date: true,
delimiter: '/',
datePattern: ['d', 'm', 'Y']
}}
style={{ border: `1px solid ${colours.textColourOnWhite}` }}
onChange={({ target: { value } }) => {
debouncedSetFormValue(value)
}}
value={value}
autoComplete="off"
/>
<label className="suffix" htmlFor={'step-' + dottedName}>
{unit}
</label>
<SendButton {...{ disabled: value === undefined, submit }} />
</div>
</>
)
})

View File

@ -17,8 +17,9 @@ objectifs:
questions:
à l'affiche:
Type d'activité: entreprise . catégorie d'activité
Impôt sur le revenu: impôt . méthode de calcul
Date de création: entreprise . date de création
ACRE: entreprise . ACRE
Impôt sur le revenu: impôt . méthode de calcul
liste noire:
- entreprise . charges
non prioritaires:

View File

@ -8,7 +8,7 @@ export function convertToDateIfNeeded(...values: string[]) {
dateStrings.forEach(dateString => {
if (!dateString.match(dateRegexp)) {
throw new TypeError(
`L'opérande '${dateString}' n'est pas une date valide`
`'${dateString}' n'est pas une date valide (format attendu: mm/aaaa ou jj/mm/aaaa)`
)
}
})

View File

@ -5,6 +5,7 @@ import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
import { serialiseUnit } from 'Engine/units'
import { is, pick, prop, unless } from 'ramda'
import React from 'react'
import DateInput from '../components/conversation/DateInput'
import { findRuleByDottedName, queryRule } from './rules'
// This function takes the unknown rule and finds which React component should be displayed to get a user input through successive if statements
@ -17,42 +18,37 @@ export default rules => dottedName => {
let commonProps = {
key: dottedName,
fieldName: dottedName,
...pick(['dottedName', 'title', 'question', 'defaultValue'], rule)
...pick(
['dottedName', 'title', 'question', 'defaultValue', 'suggestions'],
rule
)
}
if (getVariant(rule))
return (
<Question
{...{
...commonProps,
choices: buildVariantTree(rules, dottedName)
}}
{...commonProps}
choices={buildVariantTree(rules, dottedName)}
/>
)
if (rule.API && rule.API === 'géo')
return <SelectGéo {...{ ...commonProps }} />
if (rule.API) throw new Error("Le seul API implémenté est l'API géo")
if (rule.suggestions == 'atmp-2017')
return (
<SelectAtmp
{...{
...commonProps,
suggestions: rule.suggestions
}}
/>
)
if (rule.suggestions == 'atmp-2017') return <SelectAtmp {...commonProps} />
if (rule.type === 'date') {
return <DateInput {...commonProps} />
}
if (rule.unit == null && rule.defaultUnit == null)
return (
<Question
{...{
...commonProps,
choices: [
{ value: 'non', label: 'Non' },
{ value: 'oui', label: 'Oui' }
]
}}
{...commonProps}
choices={[
{ value: 'non', label: 'Non' },
{ value: 'oui', label: 'Oui' }
]}
/>
)
@ -60,11 +56,8 @@ export default rules => dottedName => {
return (
<Input
{...{
...commonProps,
unit: serialiseUnit(rule.unit || rule.defaultUnit),
suggestions: rule.suggestions
}}
{...commonProps}
unit={serialiseUnit(rule.unit || rule.defaultUnit)}
/>
)
}

View File

@ -183,6 +183,11 @@ encadrement:
description: |
Permet d'ajouter un plafond et/ou un plancher à une valeur.
durée:
type: numeric
description: |
Permet d'obtenir le nombre de jours entre deux dates
synchronisation:
type: object
description: |

View File

@ -0,0 +1,88 @@
import { convertToDateIfNeeded } from 'Engine/date'
import {
defaultNode,
evaluateNode,
makeJsx,
parseObject
} from 'Engine/evaluation'
import { Node } from 'Engine/mecanismViews/common'
import { parseUnit } from 'Engine/units'
import React from 'react'
function MecanismDurée({ nodeValue, explanation, unit }) {
return (
<Node
classes="mecanism durée"
name="durée"
value={nodeValue}
unit={unit}
child={
<>
<p>
<strong className="key">Depuis : </strong>
<span className="value">{makeJsx(explanation.depuis)}</span>
</p>
<p>
<strong className="key">Jusqu'à : </strong>
<span className="value">{makeJsx(explanation["jusqu'à"])}</span>
</p>
</>
}
/>
)
}
const pad = (n: number) => (n < 10 ? `0{n}` : +n)
const today = new Date()
const todayString = `${pad(today.getDate())}/${pad(
today.getMonth() + 1
)}/${today.getFullYear()}`
const objectShape = {
depuis: defaultNode(todayString),
"jusqu'à": defaultNode(todayString)
}
const evaluate = (cache, situation, parsedRules, node) => {
let evaluateAttribute = evaluateNode.bind(null, cache, situation, parsedRules)
let from = evaluateAttribute(node.explanation.depuis)
let to = evaluateAttribute(node.explanation["jusqu'à"])
let nodeValue = 0
if ([from, to].some(({ nodeValue }) => nodeValue === null)) {
nodeValue = null
} else {
let [fromDate, toDate] = convertToDateIfNeeded(from.nodeValue, to.nodeValue)
nodeValue = Math.max(
0,
Math.round((toDate - fromDate) / (1000 * 60 * 60 * 24))
)
}
return {
...node,
nodeValue,
explanation: {
depuis: from,
"jusqu'à": to
}
}
}
export default (recurse, k, v) => {
const explanation = parseObject(recurse, objectShape, v)
return {
evaluate,
// eslint-disable-next-line
jsx: (nodeValue, explanation, _, unit) => (
<MecanismDurée
nodeValue={nodeValue}
explanation={explanation}
unit={unit}
/>
),
explanation,
category: 'mecanism',
name: 'Durée',
type: 'numeric',
unit: parseUnit('jours')
}
}

View File

@ -43,9 +43,22 @@ export default (k, operatorFunction, symbol) => (recurse, k, v) => {
)
}
}
let nodeValue = operatorFunction(
...convertToDateIfNeeded(node1.nodeValue, node2.nodeValue)
)
let node1Value = node1.nodeValue
let node2Value = node2.nodeValue
try {
;[node1Value, node2Value] = convertToDateIfNeeded(
node1.nodeValue,
node2.nodeValue
)
} catch (e) {
typeWarning(
cache._meta.contextRule,
`Impossible de convertir une des valeur en date`,
e
)
}
let nodeValue = operatorFunction(node1Value, node2Value)
let unit = inferUnit(k, [node1.unit, node2.unit])
return {
...node,

View File

@ -6,6 +6,7 @@ import { formatValue } from 'Engine/format'
import barème from 'Engine/mecanisms/barème'
import barèmeContinu from 'Engine/mecanisms/barème-continu'
import barèmeLinéaire from 'Engine/mecanisms/barème-linéaire'
import durée from 'Engine/mecanisms/durée'
import encadrement from 'Engine/mecanisms/encadrement'
import operation from 'Engine/mecanisms/operation'
import variations from 'Engine/mecanisms/variations'
@ -143,6 +144,7 @@ export let parseObject = (rules, rule, parsedRules) => rawNode => {
'barème linéaire': barèmeLinéaire,
'barème continu': barèmeContinu,
encadrement,
durée,
'le maximum de': mecanismMax,
'le minimum de': mecanismMin,
complément: mecanismComplement,

View File

@ -1,6 +0,0 @@
# Ce fichier n'est que temporaire et remplace une vraie définition de types
- cotisation
- aide
- indemnité
- salaire
- taxe

View File

@ -5,7 +5,6 @@ import {
dropLast,
filter,
fromPairs,
has,
is,
isNil,
join,
@ -30,7 +29,6 @@ import translations from 'Règles/externalized.yaml'
// TODO - should be in UI, not engine
import { capitalise0, coerceArray } from '../utils'
import { syntaxError, warning } from './error'
import possibleVariableTypes from './possibleVariableTypes.yaml'
/***********************************
Functions working on one rule */
@ -55,7 +53,7 @@ export let enrichRule = rule => {
...rule,
dottedName,
name,
type: possibleVariableTypes.find(t => has(t, rule) || rule.type === t),
type: rule.type,
title: capitalise0(rule['titre'] || name),
defaultValue: rule['par défaut'],
examples: rule['exemples'],

View File

@ -258,7 +258,6 @@ export function areUnitConvertible(a: Unit, b: Unit) {
flatten,
uniq
)([numA, denomA, numB, denomB])
return unitClasses.every(
unitClass =>
(numA[unitClass] || 0) - (denomA[unitClass] || 0) ===

View File

@ -3,6 +3,7 @@ fr:
jour_plural: jours
semaine_plural: semaines
trimestre_plural: trimestres
an_plural: ans
employé_plural: employés
point_plural: points
en:
@ -18,3 +19,5 @@ en:
repas_plural: meals
employé: employee
employé_plural: employees
an: year
an_plural: years

View File

@ -1,5 +1,5 @@
année courante:
formule: 2019
début d'année:
formule: 01/01/2019
période:
période . jours ouvrés moyen par mois:
@ -3149,23 +3149,40 @@ impôt . CEHR:
Bofip.impots.gouv.fr: http://bofip.impots.gouv.fr/bofip/7804-PGP
entreprise . date de création:
question: En quelle année avez-vous créé votre entreprise ?
par défaut: 2019
question: Quand avez-vous créé votre entreprise ?
par défaut: 01/01/2019
suggestions:
2019: 2019
2018: 2018
2017: 2017
unité: " "
Décembre 2019: 01/12/2019
Janvier 2019: 01/01/2019
Janvier 2018: 01/01/2018
type: date
contrôles:
- si: date de création > 2020
- si: date de création > 01/2020
niveau: avertissement
message: Nous ne pouvons voir aussi loin dans le futur
- si: date de création < 1900
- si: date de création < 01/1900
niveau: avertissement
message: Il s'agit d'une très vieille entreprise ! Êtes-vous sûr de ne pas vous être trompé dans la saisie ?
entreprise . année d'activité:
formule: année courante - date de création
entreprise . durée d'activité:
formule:
durée:
depuis: date de création
entreprise . durée d'activité . en fin d'année:
titre: durée d'activité à la fin de l'année
formule:
durée:
depuis: date de création
jusqu'à: 31/12/2019
entreprise . durée d'activité . en début d'année:
titre: durée d'activité au début de l'année
formule:
durée:
depuis: date de création
jusqu'à: 01/01/2019
entreprise . chiffre d'affaires:
titre: chiffre d'affaires (H.T.)
@ -3271,18 +3288,42 @@ entreprise . charges:
par défaut: 0
unité par défaut: €/an
dirigeant . indépendant . cotisations et contributions . réduction ACRE:
dirigeant . indépendant . cotisations et contributions . exonérations . ACRE:
applicable si: entreprise . ACRE
formule:
multiplication:
assiette: cotisations - cotisations . retraite complémentaire
taux: taux ACRE
taux: taux
facteur: prorata sur l'année
dirigeant . indépendant . cotisations et contributions . réduction ACRE . taux ACRE:
dirigeant . indépendant . cotisations et contributions . exonérations . ACRE . PSS proratisé:
formule:
multiplication:
assiette: plafond sécurité sociale temps plein
taux:
encadrement:
valeur: entreprise . durée d'activité . en fin d'année / 1 an
plafond: 100%
dirigeant . indépendant . cotisations et contributions . exonérations . ACRE . prorata sur l'année:
description: |
Comme le calcul des cotisations indépendants s'effectue sur l'année entière,
l'exonération est proratisée en fonction de la durée effective de l'ACRE sur l'année courante.
Par exemple, pour une entreprise crée le 1 fevrier 2018, le calcul du prorata pour les
cotisations 2019 sera le suivant :
`31 jours d'acre restant en 2019 / 365 jours = 8,5%`
formule: (1 an - entreprise . durée d'activité . en début d'année) / 1 an
dirigeant . indépendant . cotisations et contributions . exonérations . ACRE . taux:
formule:
barème continu:
assiette: revenu professionnel
multiplicateur: plafond sécurité sociale temps plein
multiplicateur: PSS proratisé
points:
0: 100%
0.75: 100%
@ -3294,7 +3335,7 @@ dirigeant . indépendant . revenu net de cotisations:
somme:
- revenu professionnel
- situation personnelle . IJSS . défiscalisées
- (- cotisations et contributions . CSG et CRDS [non déductible])
- (- cotisations et contributions . CSG et CRDS .non déductible)
résumé: Avant impôt
question: Quel revenu avant impôt voulez-vous toucher ?
description: Il s'agit du revenu net de cotisations et de charges, avant le paiement de l'impôt sur le revenu.
@ -3682,7 +3723,6 @@ dirigeant . indépendant . cotisations et contributions:
- CSG et CRDS
- formation professionnelle
- conjoint collaborateur . cotisations
- (- réduction ACRE)
- (- exonérations)
unité par défaut: €/an
@ -3721,11 +3761,11 @@ dirigeant . rattachement CIPAV:
- entreprise . catégorie d'activité . libérale règlementée
- toutes ces conditions:
- dirigeant = 'indépendant'
- entreprise . date de création < 2019
- entreprise . date de création < 01/2019
- entreprise . catégorie d'activité = 'libérale'
- toutes ces conditions:
- dirigeant = 'auto-entrepreneur'
- entreprise . date de création < 2018
- entreprise . date de création < 01/2018
- entreprise . catégorie d'activité = 'libérale'
rend non applicable:
@ -3739,7 +3779,7 @@ dirigeant . indépendant . PLNR régime général:
toutes ces conditions:
- entreprise . catégorie d'activité = 'libérale'
- entreprise . catégorie d'activité . libérale règlementée = non
- entreprise . date de création < 2019
- entreprise . date de création < 01/2019
question: Avez-vous opté pour le rattachement au régime général des indépendant ?
description: En tant que profession libéral non reglementée, vous pouvez choisir d'être rattaché au régime général plutôt que la CIPAV
rend non applicable: rattachement CIPAV
@ -3953,7 +3993,6 @@ dirigeant . indépendant . cotisations et contributions . cotisations . retraite
taux: 8%
dirigeant . indépendant . cotisations et contributions . cotisations . invalidité et décès:
non applicable si: exonérations . âge
formule:
multiplication:
assiette: assiette
@ -4058,46 +4097,50 @@ dirigeant . indépendant . cotisations et contributions . cotisations . allocati
1.1: 0%
1.4: 3.1%
entreprise . ZFU:
question: Votre entreprise bénéficie-t-elle du dispositif zone franche urbaine (ZFU) ?
établissement . ZFU:
applicable si: entreprise . date de création < 01/2015
question: Votre établissement bénéficie-t-il du dispositif zone franche urbaine (ZFU) ?
par défaut: non
établissement . ZFU . durée d'implantation en fin d'année:
formule:
durée:
depuis: entreprise . date de création
jusqu'à: 31/12/2019
dirigeant . indépendant . cotisations et contributions . exonérations:
période: flexible
formule:
somme:
- ZFU
- ACRE
dirigeant . indépendant . cotisations et contributions . exonérations . ZFU:
applicable si:
toutes ces conditions:
- entreprise . date de création < 2015
- entreprise . ZFU
applicable si: établissement . ZFU
formule:
multiplication:
assiette:
le minimum de:
- cotisations . maladie . assiette [annuel]
- 3042 * SMIC horaire
assiette: cotisations . maladie
# TODO : ceci n'est pas bon (le plafond est sur le revenu exonéré, et est proratisé en début / fin d'éxo)
plafond: 3042 heures/an * SMIC horaire
taux: taux
dirigeant . indépendant . cotisations et contributions . exonérations . âge:
question: Bénéficiez-vous du dispositif d'exonération "âge"
description: Ce dispositif a été arrêté en 2015, mais est toujours actif pour les personnes qui en bénéficiait avant son abbrogation.
applicable si: entreprise . date de création < 2016
par défaut: non
applicable si: entreprise . date de création < 01/2016
rend non applicable: cotisations . invalidité et décès
dirigeant . indépendant . cotisations et contributions . exonérations . invalidité:
question: Êtes-vous titulaire dune pension dinvalidité du régime des travailleurs indépendants ?
description: Les personnes titulaires dune pension dinvalidité versée par un régime des travailleurs non-salariés non agricoles bénéficient dune exonération totale des cotisations maladie et retraite complémentaire.
par défaut: non
rend non applicable:
- exonérations . ZFU
- cotisations . maladie
- cotisations . indemnités journalières maladie
- cotisations . retraite complémentaire
situation personnelle . IJSS:
titre: indemnités journalières de sécurité sociale
description: |
@ -4154,43 +4197,34 @@ situation personnelle . IJSS . total:
somme:
- fiscalisées
- défiscalisées
dirigeant . indépendant . cotisations et contributions . exonérations . ZFU . taux:
titre: taux exonération ZFU
formule:
variations:
- si: entreprise . effectif < 5
alors:
barème linéaire:
assiette: entreprise . année d'activité
retourne seulement le taux: oui
tranches:
- en-dessous de: 6
taux: 100%
- de: 6
à: 10
taux: 60%
- de: 11
à: 12
taux: 40%
- de: 13
à: 14
taux: 20%
- au-dessus de: 14
taux: 0%
- sinon:
variations:
- si: entreprise . année d'activité <= 5
alors: 100%
- si: entreprise . année d'activité = 6
alors: 60%
- si: entreprise . année d'activité = 7
alors: 40%
- si: entreprise . année d'activité = 8
alors: 20%
- sinon: 0%
barème continu:
assiette: établissement . ZFU . durée d'implantation en fin d'année [an]
retourne seulement le taux: oui
variations:
- si: entreprise . effectif
alors:
points:
0: 100%
5: 100%
6: 60%
10: 60%
11: 40%
12: 40%
13: 20%
14: 20%
15: 0%
- sinon:
points:
0: 100%
5: 100%
6: 60%
7: 40%
8: 20%
9: 0%
situation personnelle . domiciliation fiscale à l'étranger:
description: |
@ -4465,46 +4499,30 @@ entreprise . ACRE:
une de ces conditions:
- toutes ces conditions:
- dirigeant = 'auto-entrepreneur'
- entreprise . année d'activité < 4
- entreprise . année d'activité < 2
- entreprise . durée d'activité < 3 ans
- entreprise . date de création < 01/01/2020
- entreprise . durée d'activité . en début d'année < 1 an
par défaut: non
note: Les auto-entreprises crées entre le 1er janvier et le 31 décembre 2019 bénéficient d'un dispositif plus favorable, actif pendant 3 années.
entreprise . ACRE . année:
question: Quel est l'âge de l'entreprise ?
formule:
une possibilité:
choix obligatoire: oui
possibilités:
- moins d'un an
- moins de deux ans
- moins de trois ans
par défaut: moins d'un an
entreprise . ACRE . année . moins d'un an:
entreprise . ACRE . année . moins de deux ans:
entreprise . ACRE . année . moins de trois ans:
dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . taux ACRE:
formule: 100% - réduction ACRE
dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . réduction ACRE:
titre: réduction ACRE
applicable si: entreprise . ACRE
description: Ce taux peut dans certains cas réduire le montant des cotisations sociales de l'auto-entrepreneur pour l'aider dans ses premières année d'activité.
description: Ce taux peut dans certains cas réduire le montant des cotisations sociales de l'auto-entrepreneur pour l'aider dans ses premières années d'activité.
formule:
variations:
# TODO : ce code fonctionne en 2020, mais en 2021 il faudra faire la
# distinction entre les entreprises créées en 2019 (qui ont le droit à
# l'ACRE pendant 3 ans) et celles créées en 2020 et après qui n'ont le
# droit qu'à une année de réduction.
- si: entreprise . ACRE . année = 'moins d'un an'
- si: entreprise . durée d'activité < 1 an
alors: 50%
- si: entreprise . ACRE . année = 'moins de deux ans'
- si: entreprise . durée d'activité < 2 ans
alors: 25%
- si: entreprise . ACRE . année = 'moins de trois ans'
- si: entreprise . durée d'activité < 3 ans
alors: 10%
références:
Fiche URSSAF: https://www.urssaf.fr/portail/home/indépendant/je-beneficie-dexonerations/accre.html

View File

@ -119,6 +119,7 @@ describe('convertUnit', () => {
describe('areUnitConvertible', () => {
it('should be true for temporel unit', () => {
expect(areUnitConvertible(parseUnit('mois'), parseUnit('an'))).to.eq(true)
expect(areUnitConvertible(parseUnit('jours'), parseUnit('ans'))).to.eq(true)
expect(areUnitConvertible(parseUnit('kg/an'), parseUnit('kg/mois'))).to.eq(
true
)

View File

@ -2813,6 +2813,11 @@ clean-css@4.2.x:
dependencies:
source-map "~0.6.0"
cleave.js@^1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/cleave.js/-/cleave.js-1.5.3.tgz#1ef36e1375dea289bffeefe8ebb1e3ef2ee23240"
integrity sha512-tLum0abk+NRUZtMxpqLQS0jE9k2KYzsUlnMAqfMd2LAf8bb5xTHLHXTtPyI+dCk6DThtmWER3EzKfUi4NHdR7A==
cli-boxes@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"