diff --git a/package.json b/package.json
index 52654a0b4..bcaf0e6ff 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,9 @@
"react-json-tree": "^0.10.0",
"react-redux": "^4.4.5",
"react-router": "^2.6.1",
+ "reduce-reducers": "^0.1.2",
"redux": "^3.5.2",
+ "redux-form": "^6.4.3",
"redux-saga": "^0.10.5",
"reselect": "^2.5.2",
"whatwg-fetch": "^1.0.0"
diff --git a/règles/rémunération-travail/cdd/simples/CIF.yaml b/règles/rémunération-travail/cdd/simples/CIF.yaml
index 6d75c3c7e..5003b2c85 100644
--- a/règles/rémunération-travail/cdd/simples/CIF.yaml
+++ b/règles/rémunération-travail/cdd/simples/CIF.yaml
@@ -10,9 +10,10 @@
- CDD poursuivi en CDI
# Types de CDD
- CDD type saisonnier
- - contrat jeune vacances
- - contrat aidé # voir la définition précise dans indemnité de fin de contrat
- - apprentissage
+ # TODO Commentés pour le développement de la démo CDD seulement
+ # - contrat jeune vacances
+ # - contrat aidé # voir la définition précise dans indemnité de fin de contrat
+ # - apprentissage
formule:
linéaire:
diff --git a/règles/rémunération-travail/rémunérations-salarié.brouillon.yaml b/règles/rémunération-travail/rémunérations-salarié.brouillon.yaml
index 1486cd50d..b3530648d 100644
--- a/règles/rémunération-travail/rémunérations-salarié.brouillon.yaml
+++ b/règles/rémunération-travail/rémunérations-salarié.brouillon.yaml
@@ -10,6 +10,6 @@
- Variable: assiette cotisations sociales
description: L'assiette de la plupart des cotisations sociales pour le calcul des cotisations sociales sur le travail salarié.
formule:
- somme: ? # donc type numérique
+ somme: ? # juste pour le type numérique
# - salaire brut
# - primes etc.
diff --git a/source/actions.js b/source/actions.js
index e69de29bb..c13d97e0e 100644
--- a/source/actions.js
+++ b/source/actions.js
@@ -0,0 +1,29 @@
+// The input "conversation" is composed of "steps"
+// The state keeps track of which of them have been submitted
+// The user can also come back to one of his answers and edit it
+export const STEP_ACTION = 'STEP_ACTION'
+export function stepAction(name, newState) {
+ return {type: STEP_ACTION, name, newState}
+}
+
+export const START_CONVERSATION = 'START_CONVERSATION'
+
+// Reset the form
+export const UNSUBMIT_ALL = 'UNSUBMIT_ALL'
+
+
+// Collect the input information from the forms, send them to the simulation engine API
+// then update the results in the UI
+export const SIMULATION_UPDATE_REQUEST = 'SIMULATION_UPDATE_REQUEST'
+export const SIMULATION_UPDATE_SUCCESS = 'SIMULATION_UPDATE_SUCCESS'
+export const SIMULATION_UPDATE_FAILURE = 'SIMULATION_UPDATE_FAIL'
+
+// Modify the UI parts displayed to the user
+export const TOGGLE_TOP_SECTION = 'TOGGLE_TOP_SECTION'
+export const TOGGLE_ADVANCED_SECTION = 'TOGGLE_ADVANCED_SECTION'
+
+// The initial request triggers the display of results based on default input information (not filled by the user)
+export const INITIAL_REQUEST = 'INITIAL_REQUEST'
+
+export const CHANGE_THEME_COLOUR = 'CHANGE_THEME_COLOUR'
+export function changeThemeColour(colour) {return {type: CHANGE_THEME_COLOUR, colour}}
diff --git a/source/components/CDD.css b/source/components/CDD.css
index 7dce93847..a0b137309 100644
--- a/source/components/CDD.css
+++ b/source/components/CDD.css
@@ -2,20 +2,22 @@
padding: 2em;
}
-
#sim section {
padding: 2em;
}
#conversation {
+ margin: 3em auto;
+ font-size: 120%;
+ line-height: normal;
display: flex;
justify-content: space-around;
min-height: 10em;
margin: 3em 0;
+ max-width: 80%;
}
#questions-answers {
- background: blue;
min-width: 50%;
}
@@ -25,6 +27,16 @@
}
#results {
- width: 100%;
+ width: 90%;
background: purple;
}
+
+#results ul {
+ list-style: none;
+}
+
+#results li {
+ display: inline-block;
+ border: 1px solid;
+ padding: .6em 2em;
+}
diff --git a/source/components/CDD.js b/source/components/CDD.js
index 63cf3cb03..a9f377a63 100644
--- a/source/components/CDD.js
+++ b/source/components/CDD.js
@@ -1,47 +1,55 @@
import React, { Component } from 'react'
-import {analyseSituation, variableType} from '../traverse'
import './CDD.css'
+import IntroCDD from './IntroCDD'
+import Results from './Results'
+import {reduxForm, formValueSelector} from 'redux-form'
+import {connect} from 'react-redux'
+import './conversation/conversation.css'
+import {START_CONVERSATION} from '../actions'
+@connect(({form: {conversation}}) => ({conversationState: conversation && conversation.values}))
+class Aide extends Component {
+ render() {
+ return
+ {JSON.stringify(this.props.conversationState)}
+
+ }
+}
+let situationSelector = formValueSelector('conversation')
+
+@reduxForm(
+ {form: 'conversation'}
+)
+@connect(state => ({
+ situation: variableName => situationSelector(state, variableName),
+ steps: state.steps,
+ themeColours: state.themeColours,
+ analysedSituation: state.analysedSituation
+}), dispatch => ({
+ startConversation: () => dispatch({type: START_CONVERSATION})
+}))
export default class CDD extends Component {
- state = {
- situation: {}
+ componentDidMount() {
+ this.props.startConversation()
}
render() {
- let [missingVariable] = analyseSituation(this.state.situation)
- let type = variableType(missingVariable)
+ let {steps} = this.props
+
+ let conversation = steps.map(step =>
+
+ )
return (
-
-
- Le CDD en France est un contrat d'exception au CDI. On y a donc recours sous certaines conditions seulement. Cet outil vous aidera à respecter ces conditions et à calculer le prix mensuel de l'embauche, qui en dépend, en vous proposant une suite de questions.
-
- Ici, vous avez le droit de ne pas savoir : certaines questions sont complexes, elles seront toujours accompagnées d'une aide contextuelle. Si ce n'est pas le cas, engueulez-nous* !
-
-
- *: écrivez à contact@contact.contact (on fera mieux après). La loi française est complexe, souvent à raison. Nous ne la changerons pas, mais pouvons la rendre plus transparente.
-
+ Le CDD en France est un contrat d'exception au CDI. On y a donc recours sous certaines conditions seulement. Cet outil vous aidera à respecter ces conditions et à calculer le prix mensuel de l'embauche, qui en dépend, en vous proposant une suite de questions.
+
+ Ici, vous avez le droit de ne pas savoir : certaines questions sont complexes, elles seront toujours accompagnées d'une aide contextuelle. Si ce n'est pas le cas, engueulez-nous* !
+
+
+ *: écrivez à contact@contact.contact (on fera mieux après). La loi française est complexe, souvent à raison. Nous ne la changerons pas, mais pouvons la rendre plus transparente.
+
+
diff --git a/source/components/Results.js b/source/components/Results.js
new file mode 100644
index 000000000..d664777ca
--- /dev/null
+++ b/source/components/Results.js
@@ -0,0 +1,27 @@
+import React, { Component } from 'react'
+
+export default class Results extends Component {
+ render() {
+ let {analysedSituation} = this.props
+ return (
+
+
)
- }
-}
-
diff --git a/source/components/Variables.js b/source/components/Variables.js
deleted file mode 100644
index 72c49e02c..000000000
--- a/source/components/Variables.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react'
-
-import SelectedVariable from './SelectedVariable'
-import colors from './variable-colors.yaml'
-console.log(colors.length);
-import R from 'ramda'
-
-function convertHex(hex,opacity){
- let r = parseInt(hex.substring(0,2), 16),
- g = parseInt(hex.substring(2,4), 16),
- b = parseInt(hex.substring(4,6), 16),
- result =`rgba(${r},${g},${b},${opacity})`
-
- return result
-}
-
-const Variable = ({color, name, selectVariable}) =>
-
selectVariable(name)} >
-
{name}
-
-
-export default class Variables extends React.Component {
- render(){
- let {variables, selectedTags, selectedVariable, selectVariable} = this.props
- console.log('variables prop in set.add(variable), new Set()), // get unique variable names
- // variableColors = [...variableSet].reduce((correspondance, v, i) => Object.assign(correspondance, {[v]: colors[i]}), {})
-
- console.log('selectedVariable',selectedVariable)
- if (selectedVariable != null)
- return
-
- return
- {variables.map((v, i) =>
-
- )}
-
- }
-}
diff --git a/source/components/conversation/Conversation.js b/source/components/conversation/Conversation.js
new file mode 100644
index 000000000..1df51e339
--- /dev/null
+++ b/source/components/conversation/Conversation.js
@@ -0,0 +1,124 @@
+import React, {Component} from 'react'
+import { connect } from 'react-redux'
+import Question from '../components/Forms/Question'
+import Input from '../components/Forms/Input'
+import SelectCommune from '../components/Forms/SelectCommune'
+import SelectTauxRisque from '../components/Forms/SelectTauxRisque'
+import RhetoricalQuestion from '../components/Forms/RhetoricalQuestion'
+import TextArea from '../components/Forms/TextArea'
+import Group from '../components/Group'
+import ResultATMP from '../components/ResultATMP'
+import {reduxForm, formValueSelector} from 'redux-form'
+import { percentage } from '../formValueTypes.js'
+import validate from '../conversation-validate'
+
+let advancedInputSelector = formValueSelector('advancedQuestions'),
+ basicInputSelector = formValueSelector('basicInput')
+
+@reduxForm({
+ form: 'advancedQuestions',
+ validate,
+})
+@connect(state => ({
+ formValue: (field, simple) => simple ? basicInputSelector(state, field): advancedInputSelector(state, field),
+ steps: state.steps,
+ themeColours: state.themeColours
+}))
+class Conversation extends Component {
+ render() {
+ let { formValue, steps, themeColours: {colour, textColour}} = this.props
+ let effectifEntreprise = formValue('effectifEntreprise', 'basicInput')
+
+ /* C'est ici qu'est définie la suite de questions à poser. */
+ return (
+
+ = 10}
+ title="Commune"
+ question="Quelle est la commune de l'embauche ?"
+ name="codeINSEE" />
+
+
+
+
+
+
+
+
+
+
+ = 249 && steps.get('tauxRisque')}
+ name="pourcentage_alternants" />
+
+
+
+
+
+
+
+
+
+ Merci. N'hésitez pas à partager le simulateur !
+
+ } />
+
+
)
+ }
+}
+
+export default Conversation
diff --git a/source/components/conversation/FormDecorator.js b/source/components/conversation/FormDecorator.js
new file mode 100644
index 000000000..ec9eabf16
--- /dev/null
+++ b/source/components/conversation/FormDecorator.js
@@ -0,0 +1,172 @@
+import React, { Component } from 'react'
+import classNames from 'classnames'
+import { connect } from 'react-redux'
+import {Field, change} from 'redux-form'
+import {stepAction} from '../../actions'
+import IgnoreStepButton from './IgnoreStepButton'
+import StepAnswer from './StepAnswer'
+
+/*
+This higher order component wraps "Form" components (e.g. Question.js), that represent user inputs,
+with a header, click actions and more goodies.
+
+Read https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
+to understand those precious higher order components.
+*/
+
+export var FormDecorator = formType => RenderField =>
+ @connect( //... this helper directly to the redux state to avoid passing more props
+ state => ({
+ steps: state.steps,
+ answers: state.form.conversation && state.form.conversation.values,
+ themeColours: state.themeColours
+ }),
+ dispatch => ({
+ stepAction: (name, newState) => dispatch(stepAction(name, newState)),
+ setFormValue: (field, value) => dispatch(change('conversation', field, value))
+ })
+ )
+ class extends Component {
+ state = {
+ helpVisible: false
+ }
+ render() {
+ let {
+ name,
+ visible,
+ steps,
+ stepAction,
+ possibleChoice, // should be found in the question set theoritically, but it is used for a single choice question -> the question itself is dynamic and cannot be input as code,
+ themeColours,
+ // formerly in conversation-steps
+ valueType,
+ defaultValue,
+ attributes,
+ choices,
+ optionsURL,
+ human,
+ helpText
+ } = this.props
+
+ this.step = steps.find(s => s.name == name)
+
+ let ignoreStep = () => {
+ // Renseigne automatiquement la valeur de la saisie (en se plongeant dans les entrailles de redux-form)
+ this.props.setFormValue(name, defaultValue)
+ stepAction(name, 'ignored')
+ }
+
+
+ /* La saisie peut être cachée car ce n'est pas encore son tour,
+ ou parce qu'elle a déjà été remplie. Dans ce dernier cas, un résumé
+ de la réponse est affiché */
+ let stepState = this.step.state,
+ completed = stepState && stepState != 'editing',
+ unfolded = !completed
+
+ if (!visible) return null
+
+ /* Nos propriétés personnalisées à envoyer au RenderField.
+ Elles sont regroupées dans un objet précis pour pouvoir être enlevées des
+ props passées à ce dernier, car React 15.2 n'aime pas les attributes inconnus
+ des balises html, dans notre cas.
+ */
+ let stepProps = {
+ attributes, /* Input component's html attributes */
+ choices, /* Question component's radio choices */
+ optionsURL, /* Select component's data source */
+ possibleChoice, /* RhetoricalQuestion component's only choice :'-( */
+ //TODO hack, enables redux-form/CHANGE to update the form state before the traverse functions are run
+ submit: () => setTimeout(() => stepAction(name, 'filled'), 1),
+ valueType
+ }
+
+ /* There won't be any answer zone here, widen the question zone */
+ let wideQuestion = formType == 'rhetorical-question' && !possibleChoice
+
+ return (
+
+ }
+ }
diff --git a/source/components/conversation/Group.js b/source/components/conversation/Group.js
new file mode 100644
index 000000000..1f1c5e4a8
--- /dev/null
+++ b/source/components/conversation/Group.js
@@ -0,0 +1,84 @@
+import React, {Component} from 'react'
+import GroupTitle from './GroupTitle'
+import classnames from 'classnames'
+import { connect } from 'react-redux'
+import { change} from 'redux-form'
+import {submitStep, editStep} from '../actions'
+import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
+import IgnoreStepButton from './Forms/IgnoreStepButton'
+import conversationData from '../conversation-steps'
+import {formValueSelector} from 'redux-form'
+import StepAnswer from './Forms/StepAnswer'
+
+
+/* Groups can be used only to avoid repeating conditions for all its children,
+or to gather a set of questions that will be eventually collapsed to a final @value,
+marked with the 'explicit' class */
+@connect(state => ({
+ steps: state.steps,
+ formValue: field => formValueSelector('advancedQuestions')(state, field),
+ themeColours: state.themeColours
+}), dispatch => ({
+ editStep: name => dispatch(editStep(name)),
+ submitStep: (name, ignored) => dispatch(submitStep(name, ignored)),
+ setFormValue: (field, value) => dispatch(change('advancedQuestions', field, value)),
+}))
+export default class Group extends Component {
+
+ render() {
+ let {visible, steps, foldTrigger, children, text, themeColours: {colour}} = this.props,
+ folded = foldTrigger ? steps.get(foldTrigger) && steps.get(foldTrigger) != 'editing' : false
+
+ if (!visible) return null
+
+ return (
+
+ )
+
+ }
+
+}
diff --git a/source/components/conversation/GroupTitle.js b/source/components/conversation/GroupTitle.js
new file mode 100644
index 000000000..4f4957d30
--- /dev/null
+++ b/source/components/conversation/GroupTitle.js
@@ -0,0 +1,29 @@
+import React from 'react'
+
+/* Simple way for a visual stack : using two h1,
+hinting at the fact that it is a group result */
+export default ({text, onClick, folded, themeColours: {colour, textColourOnWhite}}) =>
+
+ {folded &&
+
+ {text}
+
+ }
+
+ {text}
+
+
diff --git a/source/components/conversation/IgnoreStepButton.js b/source/components/conversation/IgnoreStepButton.js
new file mode 100644
index 000000000..7637794cb
--- /dev/null
+++ b/source/components/conversation/IgnoreStepButton.js
@@ -0,0 +1,22 @@
+import React, { Component } from 'react'
+
+export default class IgnoreStepButton extends Component {
+ componentDidMount() {
+ // removeEventListener will need the exact same function instance
+ this.boundHandleKeyDown = this.handleKeyDown.bind(this)
+
+ window.addEventListener('keydown', this.boundHandleKeyDown)
+ }
+ handleKeyDown({key}) {
+ if (key !== 'Escape') return
+ this.props.action()
+ }
+ componentWillUnmount() {
+ window.removeEventListener('keydown', this.boundHandleKeyDown)
+ }
+ render() {
+ return
+ passer
+
+ }
+}
diff --git a/source/components/conversation/Input.js b/source/components/conversation/Input.js
new file mode 100644
index 000000000..5ac2f575a
--- /dev/null
+++ b/source/components/conversation/Input.js
@@ -0,0 +1,53 @@
+import React, {Component} from 'react'
+import {FormDecorator} from './FormDecorator'
+import classnames from 'classnames'
+
+@FormDecorator('input')
+export default class Input extends Component {
+ render() {
+ let {
+ name,
+ input,
+ stepProps: {attributes, submit, valueType},
+ meta: {
+ touched, error, active,
+ },
+ themeColours
+ } = this.props,
+ answerSuffix = valueType.suffix,
+ suffixed = answerSuffix != null,
+ inputError = touched && error,
+ sendButtonDisabled = !input.value || inputError
+
+ return (
+
+
+
+ key == 'Enter' && input.value && (
+ !error ?
+ submit() :
+ input.onBlur() // blur will trigger the error
+ )}
+ />
+ { suffixed &&
+
+ }
+
+
+ {inputError && {error}}
+
+ )
+ }
+}
diff --git a/source/components/conversation/Question.js b/source/components/conversation/Question.js
new file mode 100644
index 000000000..000a516cf
--- /dev/null
+++ b/source/components/conversation/Question.js
@@ -0,0 +1,49 @@
+import React, {Component} from 'react'
+import {FormDecorator} from './FormDecorator'
+import {answer, answered} from './userAnswerButtonStyle'
+import HoverDecorator from '../HoverDecorator'
+
+@HoverDecorator
+class RadioLabel extends Component {
+
+ render() {
+ let {choice, input, submit, hover, themeColours} = this.props,
+ labelStyle =
+ Object.assign(
+ (choice === input.value || hover) ? answered(themeColours) : answer(themeColours),
+ )
+
+ return (
+
+ )
+ }
+}
+
+/* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans une liste.
+FormDecorator permet de factoriser du code partagé par les différents types de saisie,
+dont Question est un example */
+@FormDecorator('question')
+export default class Question extends Component {
+ render() {
+ let {
+ input,
+ stepProps: {submit, choices},
+ themeColours
+ } = this.props
+
+ return (
+
+ { choices.map((choice) =>
+
+ )}
+
+ )
+ }
+}
diff --git a/source/components/conversation/RhetoricalQuestion.js b/source/components/conversation/RhetoricalQuestion.js
new file mode 100644
index 000000000..acf57f4db
--- /dev/null
+++ b/source/components/conversation/RhetoricalQuestion.js
@@ -0,0 +1,30 @@
+import React, {Component} from 'react'
+import {FormDecorator} from './FormDecorator'
+import {answer} from './userAnswerButtonStyle'
+
+@FormDecorator('rhetorical-question')
+export default class RhetoricalQuestion extends Component {
+ render() {
+ let {
+ input,
+ stepProps: {submit, possibleChoice},
+ themeColours
+ } = this.props
+
+ if (!possibleChoice) return null // No action possible, don't render an answer
+
+ let {text, value} = possibleChoice
+
+ return (
+
+
+
+ )
+ }
+
+}
diff --git a/source/components/conversation/StepAnswer.js b/source/components/conversation/StepAnswer.js
new file mode 100644
index 000000000..326382172
--- /dev/null
+++ b/source/components/conversation/StepAnswer.js
@@ -0,0 +1,20 @@
+import React, { Component } from 'react'
+import {answered} from './userAnswerButtonStyle'
+
+
+export default class StepAnswer extends Component {
+ render() {
+ let {
+ value, human, valueType, ignored, themeColours
+ } = this.props,
+ // Show a beautiful answer to the user, rather than the technical form value
+ humanFunc = human || valueType && valueType.human || (v => v)
+
+ return (
+
+ {humanFunc(value)}
+ {ignored && (défaut)}
+
+ )
+ }
+}
diff --git a/source/components/conversation/TextArea.js b/source/components/conversation/TextArea.js
new file mode 100644
index 000000000..8018d2e13
--- /dev/null
+++ b/source/components/conversation/TextArea.js
@@ -0,0 +1,44 @@
+import React, {Component} from 'react'
+import {FormDecorator} from './FormDecorator'
+
+@FormDecorator('text-area')
+export default class Input extends Component {
+ render() {
+ let {
+ name,
+ input,
+ stepProps: {submit, attributes},
+ meta: {
+ touched, error,
+ },
+ themeColours
+ } = this.props,
+ inputError = touched && error,
+ sendButtonDisabled = !input.value || inputError
+
+ return (
+
+
+
+ {inputError && {error}}
+
+ )
+ }
+}
diff --git a/source/components/conversation/conversation-steps.js b/source/components/conversation/conversation-steps.js
new file mode 100644
index 000000000..dbef84473
--- /dev/null
+++ b/source/components/conversation/conversation-steps.js
@@ -0,0 +1,208 @@
+import React from 'react'
+import { percentage, euro } from './formValueTypes.js'
+import {simulationDate} from './openfisca.js'
+
+export default {
+
+ // DEFAULTS : These inputs do not exist, but the API needs them
+ 'defaults': {
+ adapt: () => ({
+ allegement_fillon_mode_recouvrement: 'anticipe_regularisation_fin_de_periode',
+ allegement_cotisation_allocations_familiales_mode_recouvrement: 'anticipe_regularisation_fin_de_periode',
+ contrat_de_travail_debut: simulationDate(),
+ }),
+ },
+
+ /*****************************
+ BASIC INPUT FORM FIELDS */
+
+ /* Le type d'entreprise association 190X n'est pas défini comme une catégorie dans OpenFisca,
+ mais comme un booléen */
+ 'typeEntreprise': {
+ initial: 'entreprise',
+ adapt: raw => raw === 'entreprise_est_association_non_lucrative' && {
+ 'entreprise_est_association_non_lucrative': true,
+ },
+ },
+
+ /* Nous simulons une embauche, donc nous incrémentons l'effectif */
+ 'effectifEntreprise': {
+ initial: 0,
+ adapt: raw => ({'effectif_entreprise': +raw + 1}),
+ },
+
+ /* Nous voulons un ratio : on multiplie donc le nombre d'heures par semaine capté par
+ (la durée légale mensuelle divisée par la durée légale hebdomadaire) */
+ 'heuresParSemaine': {
+ initial: 30,
+ adapt: raw => ({ 'heures_remunerees_volume': raw * (151.66 / 35)}),
+ },
+
+ 'typeSalaireEntré': {
+ initial: 'brut',
+ adapt: () => ({}),
+ },
+
+ 'salaire': {
+ initial: 2300,
+ adapt: (raw, value, values) => ({
+ // Use other values to determine the name of this key
+ [values['typeSalaireEntré'] == 'brut' ?
+ 'salaire_de_base' :
+ 'salaire_net_a_payer'
+ ]: value }),
+ },
+
+ 'tempsDeTravail': {
+ initial: 'temps_plein',
+ adapt: raw => ({'contrat_de_travail': raw}),
+ },
+
+ 'categorieSalarié': {
+ initial: 'prive_non_cadre',
+ adapt: raw => ({'categorie_salarie': raw}),
+ },
+
+
+
+
+
+ /****************************
+ ADVANCED VIEW STEPS
+
+ One step is a Question, and an Answer field for the user.
+ The steps, called in Conversation.js, use these data.
+ The value stored in the state is the raw user input.
+ The value is validated (with a "pre" normalisation step) to be able to submit the step.
+ It is then 'adapted' into an object fragment. Object fragments are merged to be sent to the API.
+ */
+
+ 'mutuelle': { // the name of the form field. Data is stored in state.form.advancedQuestions.mutuelle
+ // The attributes of the HTML form field
+ attributes: {
+ /* We use 'text' inputs : browser behaviour with input=number
+ doesn't quite work with our "update simulation on input change"... */
+ inputMode: 'numeric',
+ placeholder: 'votre réponse', // help for the first input
+ },
+ valueType: euro, /* Will give the input a suffix (€), a human representation
+ that will be used in the form resume, and a validation function */
+ defaultValue: '40', // The user can pass steps in the advanced view, this value is set
+ helpText: // What will be displayed in the help box
+
+ L'employeur a l'obligation en 2016 de proposer et financer à 50% une offre
+ de complémentaire santé. Son montant est libre, tant qu'elle couvre un panier légal de soins.
+
+
+ Voir les détails (service-public.fr)
+
+
+ Cette affiliation est obligatoire si l'activité est exercée dans les départements du Bas-Rhin, du Haut-Rhin et de la Moselle. Elle l'est aussi dans certains autres cas, expliqués sur cette page.
+
+
,
+ adapt: raw => ({salarie_regime_alsace_moselle: raw === 'Oui' ? 1 : 0}),
+ },
+
+ 'codeINSEE': {
+ defaultValue: {codeInsee: '29019', nomCommune: 'Ville de 100 000 habitants'},
+ human: v => v.nomCommune,
+ helpText:
Quelle est la commune du lieu de travail effectif du salarié ?
Ce pourcentage de l'ensemble de vos salariés nous permet de calculer le montant de la Contribution Supplémentaire à l'Apprentissage, destinée à encourager cette forme d'emploi.
+ C'est le taux de la cotisation accidents du travail (AT) et maladies professionnelles (MP). Il est accessible sur net-entreprises.fr ou reçu par courrier.
+
+ Les employeurs qui exposent un salarié à un facteur de pénibilité au-delà des seuils prévus est redevable d'une cotisation de pénibilité additionnelle. Elle est doublée si les facteurs sont multiples.
+
+
+ Comprendre la cotisation pénibilité (service-public.fr)
+
+