Ajout de l'option "allowJs" dans tsconfig.js

Cela permet l'inférence de type à partir des fichiers js qui ne sont pas
encore convertis en TypeScript.
Par ailleurs suppression des dernières traces de Flow.
Ajout d'options plus strictes pour dans la config tsconfig.js
pull/810/head
Maxime Quandalle 2019-12-13 17:22:18 +01:00
parent 34085d3b31
commit 1b963b8bbe
35 changed files with 539 additions and 610 deletions

View File

@ -25,7 +25,6 @@ env:
settings:
react:
version: 'detect'
flowVersion: '0.92'
overrides:
- files: ['*.test.js', 'cypress/integration/**/*.js']
@ -39,7 +38,6 @@ extends:
- eslint:recommended
- plugin:react/recommended
- prettier
- prettier/flowtype
- prettier/react
parserOptions:
ecmaFeatures:

1
.gitignore vendored
View File

@ -5,6 +5,5 @@ dist/
.DS_Store
package-lock.json
yarn-error.log
flow-typed/
cypress/videos
cypress/screenshots

View File

@ -9,8 +9,7 @@ module.exports = {
}
],
'@babel/react',
'@babel/preset-typescript',
'@babel/flow'
'@babel/preset-typescript'
],
plugins: [
'@babel/plugin-proposal-class-properties',

View File

@ -101,7 +101,6 @@
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/preset-env": "^7.6.3",
"@babel/preset-flow": "^7.0.0-beta.51",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.6.0",
"@types/classnames": "^2.2.9",
@ -113,6 +112,7 @@
"@types/react-color": "^3.0.1",
"@types/react-dom": "^16.9.3",
"@types/react-helmet": "^5.0.13",
"@types/react-highlight-words": "^0.16.0",
"@types/react-redux": "^7.1.5",
"@types/react-router": "^5.1.2",
"@types/react-router-dom": "^5.1.0",

View File

@ -20,7 +20,7 @@ export type Action =
| UpdateDefaultUnit
| SetActiveTargetAction
type ThunkResult<R> = ThunkAction<
export type ThunkResult<R> = ThunkAction<
R,
RootState,
{ history: History; sitePaths: SitePaths },
@ -74,7 +74,7 @@ export const goToQuestion = (question: string) =>
export const validateStepWithValue = (
dottedName: DottedName,
value: any
value: unknown
): ThunkResult<void> => dispatch => {
dispatch(updateSituation(dottedName, value))
dispatch({
@ -119,7 +119,7 @@ export const deletePreviousSimulation = (): ThunkResult<void> => dispatch => {
deletePersistedSimulation()
}
export const updateSituation = (fieldName: DottedName, value: any) =>
export const updateSituation = (fieldName: DottedName, value: unknown) =>
({
type: 'UPDATE_SITUATION',
fieldName,

View File

@ -1,88 +0,0 @@
import { dropWhile, last } from 'ramda'
import { nextQuestionUrlSelector } from 'Selectors/companyStatusSelectors'
import type {
IsSoleProprietorshipAction,
CompanyHasMultipleAssociatesAction,
DirectorStatus,
IsAutoentrepreneurAction,
ResetCompanyStatusAction,
DirectorIsInAMinorityAction,
DefineDirectorStatusAction
} from 'Types/companyTypes'
import type { Thunk } from 'Types/ActionsTypes'
// Bug : last et dropline sont automatiquement enlevé par le formatOnSave de visual studio code sinon
// eslint-disable-next-line
let x = [dropWhile, last]
const thenGoToNextQuestion = actionCreator => (...args: any) =>
((dispatch, getState, { history, sitePaths }) => {
dispatch(actionCreator(...args))
history.push(nextQuestionUrlSelector(getState(), { sitePaths }))
}: Thunk<any>)
export const isSoleProprietorship = thenGoToNextQuestion(
(isSoleProprietorship: ?boolean): IsSoleProprietorshipAction => ({
type: 'COMPANY_IS_SOLE_PROPRIETORSHIP',
isSoleProprietorship
})
)
export const defineDirectorStatus = thenGoToNextQuestion(
(status: ?DirectorStatus): DefineDirectorStatusAction => ({
type: 'DEFINE_DIRECTOR_STATUS',
status
})
)
export const companyHasMultipleAssociates = thenGoToNextQuestion(
(multipleAssociates: ?boolean): CompanyHasMultipleAssociatesAction => ({
type: 'COMPANY_HAS_MULTIPLE_ASSOCIATES',
multipleAssociates
})
)
export const isAutoentrepreneur = thenGoToNextQuestion(
(autoEntrepreneur: ?boolean): IsAutoentrepreneurAction => ({
type: 'COMPANY_IS_MICROENTERPRISE',
autoEntrepreneur
})
)
export const directorIsInAMinority = thenGoToNextQuestion(
(minorityDirector: ?boolean): DirectorIsInAMinorityAction => ({
type: 'SPECIFY_DIRECTORS_SHARE',
minorityDirector
})
)
export const goToCompanyStatusChoice = (): Thunk<ResetCompanyStatusAction> => (
dispatch,
_,
{history, sitePaths}
) => {
dispatch(
({
type: 'RESET_COMPANY_STATUS_CHOICE'
}: ResetCompanyStatusAction)
)
history.push(sitePaths.créer.index)
}
export const resetCompanyStatusChoice = (
from: string
): Thunk<ResetCompanyStatusAction> => (dispatch, getState) => {
const answeredQuestion = Object.keys(
getState().inFranceApp.companyLegalStatus
)
const answersToReset = dropWhile(a => a !== from, answeredQuestion)
if (!answersToReset.length) {
return
}
dispatch({
type: 'RESET_COMPANY_STATUS_CHOICE',
answersToReset
})
}

View File

@ -0,0 +1,81 @@
import { dropWhile } from 'ramda'
import { nextQuestionUrlSelector } from 'Selectors/companyStatusSelectors'
const thenGoToNextQuestion = actionCreator => (...args: unknown[]) => (
dispatch,
getState,
{ history, sitePaths }
) => {
dispatch(actionCreator(...args))
history.push(nextQuestionUrlSelector(getState(), { sitePaths }))
}
export const isSoleProprietorship = thenGoToNextQuestion(
(isSoleProprietorship?: boolean) =>
({
type: 'COMPANY_IS_SOLE_PROPRIETORSHIP',
isSoleProprietorship
} as const)
)
type DirectorStatus = 'SALARIED' | 'SELF_EMPLOYED'
export const defineDirectorStatus = thenGoToNextQuestion(
(status: DirectorStatus) =>
({
type: 'DEFINE_DIRECTOR_STATUS',
status
} as const)
)
export const companyHasMultipleAssociates = thenGoToNextQuestion(
(multipleAssociates?: boolean) =>
({
type: 'COMPANY_HAS_MULTIPLE_ASSOCIATES',
multipleAssociates
} as const)
)
export const isAutoentrepreneur = thenGoToNextQuestion(
(autoEntrepreneur?: boolean) =>
({
type: 'COMPANY_IS_MICROENTERPRISE',
autoEntrepreneur
} as const)
)
export const directorIsInAMinority = thenGoToNextQuestion(
(minorityDirector?: boolean) =>
({
type: 'SPECIFY_DIRECTORS_SHARE',
minorityDirector
} as const)
)
export const goToCompanyStatusChoice = () => (
dispatch,
_,
{ history, sitePaths }
) => {
dispatch({
type: 'RESET_COMPANY_STATUS_CHOICE'
} as const)
history.push(sitePaths.créer.index)
}
export const resetCompanyStatusChoice = (from: string) => (
dispatch,
getState
) => {
const answeredQuestion = Object.keys(
getState().inFranceApp.companyLegalStatus
)
const answersToReset = dropWhile(a => a !== from, answeredQuestion)
if (!answersToReset.length) {
return
}
dispatch({
type: 'RESET_COMPANY_STATUS_CHOICE',
answersToReset
})
}

View File

@ -1,138 +0,0 @@
import type { FicheDePaie } from 'Types/ResultViewTypes'
import withColours from 'Components/utils/withColours'
import Value from 'Components/Value'
import { findRuleByDottedName, getRuleFromAnalysis } from 'Engine/rules'
import { compose } from 'ramda'
import React, { Fragment } from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import {
analysisWithDefaultsSelector,
parsedRulesSelector
} from 'Selectors/analyseSelectors'
import { analysisToCotisationsSelector } from 'Selectors/ficheDePaieSelectors'
import './PaySlip.css'
import { Line, SalaireBrutSection, SalaireNetSection } from './PaySlipSections'
import RuleLink from './RuleLink'
type ConnectedPropTypes = ?FicheDePaie & {
colours: { lightestColour: string }
}
export default compose(
withColours,
connect(state => ({
cotisations: analysisToCotisationsSelector(state),
analysis: analysisWithDefaultsSelector(state),
parsedRules: parsedRulesSelector(state)
}))
)(
({
colours: { lightestColour },
cotisations,
analysis,
parsedRules
}: ConnectedPropTypes) => {
let getRule = getRuleFromAnalysis(analysis)
const heuresSupplémentaires = getRule(
'contrat salarié . temps de travail . heures supplémentaires'
)
return (
<div
className="payslip__container"
css={`
.value {
display: flex;
align-items: flex-end;
justify-content: flex-end;
padding-right: 0.2em;
}
`}
>
<div className="payslip__salarySection">
<Line
rule={getRule('contrat salarié . temps de travail')}
unit="heures/mois"
maximumFractionDigits={1}
/>
{heuresSupplémentaires?.nodeValue > 0 && (
<Line
rule={heuresSupplémentaires}
unit="heures/mois"
maximumFractionDigits={1}
/>
)}
</div>
<SalaireBrutSection getRule={getRule} />
{/* Section cotisations */}
<div className="payslip__cotisationsSection">
<h4>
<Trans>Cotisations sociales</Trans>
</h4>
<h4>
<Trans>Part employeur</Trans>
</h4>
<h4>
<Trans>Part salarié</Trans>
</h4>
{cotisations.map(([brancheDottedName, cotisationList]) => {
let branche = findRuleByDottedName(parsedRules, brancheDottedName)
return (
<Fragment key={branche.dottedName}>
<h5 className="payslip__cotisationTitle">
<RuleLink {...branche} />
</h5>
{cotisationList.map(cotisation => (
<Fragment key={cotisation.dottedName}>
<RuleLink
style={{ backgroundColor: lightestColour }}
{...cotisation}
/>
<Value
nilValueSymbol="—"
unit="€"
customCSS="background-color: var(--lightestColour)"
>
{cotisation.montant.partPatronale}
</Value>
<Value
nilValueSymbol="—"
unit="€"
customCSS="background-color: var(--lightestColour)"
>
{cotisation.montant.partSalariale}
</Value>
</Fragment>
))}
</Fragment>
)
})}
{/* Total cotisation */}
<div className="payslip__total">
<Trans>Total des retenues</Trans>
</div>
<Value
nilValueSymbol="—"
{...getRule('contrat salarié . cotisations . patronales')}
unit="€"
className="payslip__total"
/>
<Value
nilValueSymbol="—"
{...getRule('contrat salarié . cotisations . salariales')}
unit="€"
className="payslip__total"
/>
{/* Salaire chargé */}
<Line rule={getRule('contrat salarié . rémunération . total')} />
<span />
</div>
{/* Section salaire net */}
<SalaireNetSection getRule={getRule} />
</div>
)
}
)

View File

@ -0,0 +1,122 @@
import { ThemeColoursContext } from 'Components/utils/withColours'
import Value from 'Components/Value'
import { findRuleByDottedName, getRuleFromAnalysis } from 'Engine/rules'
import React, { Fragment, useContext } from 'react'
import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
import {
analysisWithDefaultsSelector,
parsedRulesSelector
} from 'Selectors/analyseSelectors'
import { analysisToCotisationsSelector } from 'Selectors/ficheDePaieSelectors'
import './PaySlip.css'
import { Line, SalaireBrutSection, SalaireNetSection } from './PaySlipSections'
import RuleLink from './RuleLink'
export default function PaySlip() {
const { lightestColour } = useContext(ThemeColoursContext)
const cotisations = useSelector(analysisToCotisationsSelector)
const analysis = useSelector(analysisWithDefaultsSelector)
const parsedRules = useSelector(parsedRulesSelector)
let getRule = getRuleFromAnalysis(analysis)
const heuresSupplémentaires = getRule(
'contrat salarié . temps de travail . heures supplémentaires'
)
return (
<div
className="payslip__container"
css={`
.value {
display: flex;
align-items: flex-end;
justify-content: flex-end;
padding-right: 0.2em;
}
`}
>
<div className="payslip__salarySection">
<Line
rule={getRule('contrat salarié . temps de travail')}
unit="heures/mois"
maximumFractionDigits={1}
/>
{heuresSupplémentaires?.nodeValue > 0 && (
<Line
rule={heuresSupplémentaires}
unit="heures/mois"
maximumFractionDigits={1}
/>
)}
</div>
<SalaireBrutSection getRule={getRule} />
{/* Section cotisations */}
<div className="payslip__cotisationsSection">
<h4>
<Trans>Cotisations sociales</Trans>
</h4>
<h4>
<Trans>Part employeur</Trans>
</h4>
<h4>
<Trans>Part salarié</Trans>
</h4>
{cotisations.map(([brancheDottedName, cotisationList]) => {
let branche = findRuleByDottedName(parsedRules, brancheDottedName)
return (
<Fragment key={branche.dottedName}>
<h5 className="payslip__cotisationTitle">
<RuleLink {...branche} />
</h5>
{cotisationList.map(cotisation => (
<Fragment key={cotisation.dottedName}>
<RuleLink
style={{ backgroundColor: lightestColour }}
{...cotisation}
/>
<Value
nilValueSymbol="—"
unit="€"
customCSS="background-color: var(--lightestColour)"
>
{cotisation.montant.partPatronale}
</Value>
<Value
nilValueSymbol="—"
unit="€"
customCSS="background-color: var(--lightestColour)"
>
{cotisation.montant.partSalariale}
</Value>
</Fragment>
))}
</Fragment>
)
})}
{/* Total cotisation */}
<div className="payslip__total">
<Trans>Total des retenues</Trans>
</div>
<Value
nilValueSymbol="—"
{...getRule('contrat salarié . cotisations . patronales')}
unit="€"
className="payslip__total"
/>
<Value
nilValueSymbol="—"
{...getRule('contrat salarié . cotisations . salariales')}
unit="€"
className="payslip__total"
/>
{/* Salaire chargé */}
<Line rule={getRule('contrat salarié . rémunération . total')} />
<span />
</div>
{/* Section salaire net */}
<SalaireNetSection getRule={getRule} />
</div>
)
}

View File

@ -1,6 +1,6 @@
import { goToQuestion } from 'Actions/actions'
import { T } from 'Components'
import { compose, contains, filter, reject, toPairs } from 'ramda'
import { contains, filter, pipe, reject, toPairs } from 'ramda'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
@ -8,6 +8,7 @@ import {
currentQuestionSelector,
nextStepsSelector
} from 'Selectors/analyseSelectors'
import { DottedName } from 'Types/rule'
export default function QuickLinks() {
const currentQuestion = useSelector(currentQuestionSelector)
@ -23,11 +24,11 @@ export default function QuickLinks() {
if (!quickLinks) {
return null
}
const links = compose(
toPairs,
filter(dottedName => contains(dottedName, nextSteps)) as any,
reject(dottedName => contains(dottedName, quickLinksToHide))
)(quickLinks) as any
const links = pipe(
reject((dottedName: DottedName) => contains(dottedName, quickLinksToHide)),
filter((dottedName: DottedName) => contains(dottedName, nextSteps)),
toPairs
)(quickLinks)
return (
!!links.length && (

View File

@ -10,7 +10,7 @@ type RuleLinkProps = {
dottedName: Rule['dottedName']
title?: Rule['title']
style?: React.CSSProperties
children: React.ReactNode
children?: React.ReactNode
}
export default function RuleLink({

View File

@ -39,7 +39,7 @@ export default function RulePage({ match }) {
if (!findRuleByDottedName(flatRules, decodedRuleName))
return <Redirect to="/404" />
return renderRule(decodedRuleName)
return renderRule(decodedRuleName as DottedName)
}
const BackToSimulation = connect(null, { goBackToSimulation })(

View File

@ -5,16 +5,23 @@ import React, { useContext, useEffect, useState } from 'react'
import Highlighter from 'react-highlight-words'
import { useTranslation } from 'react-i18next'
import { Link, Redirect } from 'react-router-dom'
import { Rule } from 'Types/rule'
import Worker from 'worker-loader!./SearchBar.worker.js'
import { capitalise0 } from '../utils'
const worker = new Worker()
type SearchBarProps = {
rules: Array<Rule>
showDefaultList: boolean
finally?: () => void
}
export default function SearchBar({
rules,
showDefaultList,
finally: finallyCallback
}) {
}: SearchBarProps) {
const sitePaths = useContext(SitePathsContext)
const [input, setInput] = useState('')
const [selectedOption, setSelectedOption] = useState(null)
@ -31,7 +38,7 @@ export default function SearchBar({
worker.onmessage = ({ data: results }) => setResults(results)
}, [rules])
let renderOptions = rules => {
let renderOptions = (rules?: Array<Rule>) => {
let options =
(rules && sortBy(rule => rule.dottedName, rules)) || take(5)(results)
return <ul>{options.map(option => renderOption(option))}</ul>
@ -57,7 +64,7 @@ export default function SearchBar({
>
<div
style={{
fontWeight: '300',
fontWeight: 300,
fontSize: '85%',
lineHeight: '.9em'
}}

View File

@ -1,118 +0,0 @@
import { goToQuestion, resetSimulation } from 'Actions/actions'
import Overlay from 'Components/Overlay'
import RuleLink from 'Components/RuleLink'
import Value from 'Components/Value'
import { getRuleFromAnalysis } from 'Engine/rules'
import { compose } from 'ramda'
import React from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import {
analysisWithDefaultsSelector,
nextStepsSelector
} from 'Selectors/analyseSelectors'
import { softCatch } from '../../utils'
import './AnswerList.css'
const AnswerList = ({
folded,
next,
onClose,
goToQuestion,
resetSimulation
}) => (
<Overlay onClose={onClose} className="answer-list">
<h2>
{emoji('📋 ')}
<Trans>Mes réponses</Trans>
<small css="margin-left: 2em; img {font-size: .8em}">
{emoji('🗑')}{' '}
<button
className="ui__ simple small button"
onClick={() => {
resetSimulation()
onClose()
}}>
<Trans>Tout effacer</Trans>
</button>
</small>
</h2>
<StepsTable {...{ rules: folded, onClose, goToQuestion }} />
<h2>
{emoji('🔮 ')}
<Trans>Prochaines questions</Trans>
</h2>
<StepsTable {...{ rules: next, onClose, goToQuestion }} />
</Overlay>
)
let StepsTable = ({ rules, onClose, goToQuestion }) => (
<table>
<tbody>
{rules.map(rule => (
<tr
key={rule.dottedName}
css={`
background: var(--lightestColour);
`}>
<td>
<RuleLink {...rule} />
</td>
<td>
<button
className="answer"
css={`
display: inline-block;
padding: 0.6rem;
color: inherit;
font-size: inherit;
width: 100%;
text-align: start;
font-weight: 500;
> span {
border-bottom: 1px dashed blue;
border-bottom-color: var(--textColourOnWhite);
padding: 0.05em 0em;
display: inline-block;
}
`}
onClick={() => {
goToQuestion(rule.dottedName)
onClose()
}}>
<span className="answerContent">
<Value {...rule} />
</span>
</button>{' '}
</td>
</tr>
))}
</tbody>
</table>
)
const stepsToRules = createSelector(
state => state.conversationSteps.foldedSteps,
nextStepsSelector,
analysisWithDefaultsSelector,
(folded, nextSteps, analysis) => ({
folded: folded
.map(softCatch(getRuleFromAnalysis(analysis)))
.filter(Boolean),
next: nextSteps
.map(softCatch(getRuleFromAnalysis(analysis)))
.filter(Boolean)
})
)
export default compose(
connect(
state => stepsToRules(state),
{
resetSimulation,
goToQuestion
}
)
)(AnswerList)

View File

@ -0,0 +1,112 @@
import { goToQuestion, resetSimulation } from 'Actions/actions'
import Overlay from 'Components/Overlay'
import RuleLink from 'Components/RuleLink'
import Value from 'Components/Value'
import { getRuleFromAnalysis } from 'Engine/rules'
import React from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
import { createSelector } from 'reselect'
import {
analysisWithDefaultsSelector,
nextStepsSelector
} from 'Selectors/analyseSelectors'
import { softCatch } from '../../utils'
import './AnswerList.css'
export default function AnswerList({ onClose }) {
const dispatch = useDispatch()
const { folded, next } = useSelector(stepsToRules)
return (
<Overlay onClose={onClose} className="answer-list">
<h2>
{emoji('📋 ')}
<Trans>Mes réponses</Trans>
<small css="margin-left: 2em; img {font-size: .8em}">
{emoji('🗑')}{' '}
<button
className="ui__ simple small button"
onClick={() => {
dispatch(resetSimulation())
onClose()
}}
>
<Trans>Tout effacer</Trans>
</button>
</small>
</h2>
<StepsTable {...{ rules: folded, onClose }} />
<h2>
{emoji('🔮 ')}
<Trans>Prochaines questions</Trans>
</h2>
<StepsTable {...{ rules: next, onClose }} />
</Overlay>
)
}
function StepsTable({ rules, onClose }) {
const dispatch = useDispatch()
return (
<table>
<tbody>
{rules.map(rule => (
<tr
key={rule.dottedName}
css={`
background: var(--lightestColour);
`}
>
<td>
<RuleLink {...rule} />
</td>
<td>
<button
className="answer"
css={`
display: inline-block;
padding: 0.6rem;
color: inherit;
font-size: inherit;
width: 100%;
text-align: start;
font-weight: 500;
> span {
border-bottom: 1px dashed blue;
border-bottom-color: var(--textColourOnWhite);
padding: 0.05em 0em;
display: inline-block;
}
`}
onClick={() => {
dispatch(goToQuestion(rule.dottedName))
onClose()
}}
>
<span className="answerContent">
<Value {...rule} />
</span>
</button>{' '}
</td>
</tr>
))}
</tbody>
</table>
)
}
const stepsToRules = createSelector(
(state: RootState) => state.conversationSteps.foldedSteps,
nextStepsSelector,
analysisWithDefaultsSelector,
(folded, nextSteps, analysis) => ({
folded: folded
.map(softCatch(getRuleFromAnalysis(analysis)))
.filter(Boolean),
next: nextSteps
.map(softCatch(getRuleFromAnalysis(analysis)))
.filter(Boolean)
})
)

View File

@ -3,14 +3,21 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { defaultUnitsSelector } from 'Selectors/analyseSelectors'
import { convertUnit, parseUnit } from '../../engine/units'
import { convertUnit, parseUnit, Unit } from '../../engine/units'
type InputSuggestionsProps = {
suggestions: Record<string, number>
onFirstClick: (val: number) => void
onSecondClick?: (val: number) => void
unit: Unit
}
export default function InputSuggestions({
suggestions,
onSecondClick = x => x,
onFirstClick,
unit
}) {
}: InputSuggestionsProps) {
const [suggestion, setSuggestion] = useState(null)
const { t } = useTranslation()
const defaultUnit = parseUnit(useSelector(defaultUnitsSelector)[0])

View File

@ -53,7 +53,7 @@ export let getNextSteps = missingVariablesByTarget => {
missingByTotalScore
),
pairs = toPairs(missingByCompound),
sortedPairs = sortWith([descend(byCount), descend(byScore)], pairs)
sortedPairs = sortWith([descend(byCount), descend(byScore) as any], pairs)
return map(head, sortedPairs)
}

View File

@ -15,11 +15,16 @@ export default (rules, rule, parsedRules) => {
parsedRules[rule.dottedName] = 'being parsed'
/*
The parseRule function will traverse the tree of the `rule` and produce an AST, an object containing other objects containing other objects...
Some of the attributes of the rule are dynamic, they need to be parsed. It is the case of `non applicable si`, `applicable si`, `formule`.
These attributes' values themselves may have mechanism properties (e. g. `barème`) or inline expressions (e. g. `maVariable + 3`).
These mechanisms or variables are in turn traversed by `parse()`. During this processing, 'evaluate' and'jsx' functions are attached to the objects of the AST. They will be evaluated during the evaluation phase, called "analyse".
*/
The parseRule function will traverse the tree of the `rule` and produce an
AST, an object containing other objects containing other objects... Some of
the attributes of the rule are dynamic, they need to be parsed. It is the
case of `non applicable si`, `applicable si`, `formule`. These attributes'
values themselves may have mechanism properties (e. g. `barème`) or inline
expressions (e. g. `maVariable + 3`). These mechanisms or variables are in
turn traversed by `parse()`. During this processing, 'evaluate' and'jsx'
functions are attached to the objects of the AST. They will be evaluated
during the evaluation phase, called "analyse".
*/
let parentDependencies = findParentDependencies(rules, rule)
@ -84,7 +89,7 @@ export default (rules, rule, parsedRules) => {
let child = parse(rules, rule, parsedRules)(value)
let jsx = (nodeValue, explanation) => makeJsx(explanation)
let jsx = (_nodeValue, explanation) => makeJsx(explanation)
return {
evaluate,
@ -96,7 +101,7 @@ export default (rules, rule, parsedRules) => {
explanation: child
}
},
contrôles: map(control => {
contrôles: map((control: any) => {
let testExpression = parse(rules, rule, parsedRules)(control.si)
if (
!testExpression.explanation &&
@ -118,7 +123,9 @@ export default (rules, rule, parsedRules) => {
})(root)
parsedRules[rule.dottedName] = {
// Pas de propriété explanation et jsx ici car on est parti du (mauvais) principe que 'non applicable si' et 'formule' sont particuliers, alors qu'ils pourraient être rangé avec les autres mécanismes
// Pas de propriété explanation et jsx ici car on est parti du (mauvais)
// principe que 'non applicable si' et 'formule' sont particuliers, alors
// qu'ils pourraient être rangé avec les autres mécanismes
...parsedRoot,
evaluate,
parsed: true,
@ -135,7 +142,7 @@ export default (rules, rule, parsedRules) => {
const explanation = { ...node.explanation, isDisabledBy }
return { ...node, explanation, nodeValue }
},
jsx: (nodeValue, { isDisabledBy }) => {
jsx: (_nodeValue, { isDisabledBy }) => {
return (
isDisabledBy.length > 0 && (
<>
@ -183,6 +190,7 @@ let evolveCond = (name, rule, rules, parsedRules) => value => {
classes="ruleProp mecanism cond"
name={name}
value={nodeValue}
unit={undefined}
child={
explanation.category === 'variable' ? (
<div className="node">{makeJsx(explanation)}</div>

View File

@ -46,7 +46,7 @@ export let enrichRule = rule => {
if (defaultUnit && unit) {
warning(
dottedName,
"Le paramètre `unité` n'est plus contraignant que `unité par défaut`.",
'Le paramètre `unité` est plus contraignant que `unité par défaut`.',
'Si vous souhaitez que la valeur de votre variable soit toujours la même unité, gardez `unité`'
)
}

View File

@ -1,6 +1,7 @@
import { evaluateControls } from 'Engine/controls'
import parseRule from 'Engine/parseRule'
import { chain, path } from 'ramda'
import { DottedName } from 'Types/rule'
import { evaluateNode } from './evaluation'
import { parseReference } from './parseReference'
import {
@ -8,7 +9,7 @@ import {
findRule,
findRuleByDottedName
} from './rules'
import { parseUnit } from './units'
import { parseUnit, Unit } from './units'
/*
Dans ce fichier, les règles YAML sont parsées.
@ -47,14 +48,17 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre
*/
export let parseAll = flatRules => {
/* First we parse each rule one by one. When a mechanism is encountered, it is recursively parsed. When a reference to a variable is encountered, a 'variable' node is created, we don't parse variables recursively. */
/* First we parse each rule one by one. When a mechanism is encountered, it is
recursively parsed. When a reference to a variable is encountered, a
'variable' node is created, we don't parse variables recursively. */
let parsedRules = {}
/* A rule `A` can disable a rule `B` using the rule `rend non applicable: B` in the definition of `A`.
We need to map these exonerations to be able to retreive them from `B` */
let nonApplicableMapping = {}
let replacedByMapping = {}
/* A rule `A` can disable a rule `B` using the rule `rend non applicable: B`
in the definition of `A`. We need to map these exonerations to be able to
retreive them from `B` */
let nonApplicableMapping: Record<string, any> = {}
let replacedByMapping: Record<string, any> = {}
flatRules.forEach(rule => {
const parsed = parseRule(flatRules, rule, parsedRules)
if (parsed['rend non applicable']) {
@ -107,7 +111,7 @@ export let parseAll = flatRules => {
export let getTargets = (target, rules) => {
let multiSimulation = path(['simulateur', 'objectifs'])(target)
let targets = multiSimulation
let targets = Array.isArray(multiSimulation)
? // On a un simulateur qui définit une liste d'objectifs
multiSimulation
.map(n => disambiguateRuleReference(rules, target, n))
@ -118,16 +122,23 @@ export let getTargets = (target, rules) => {
return targets
}
export let analyseMany = (
parsedRules,
targetNames,
defaultUnits = []
) => situationGate => {
type CacheMeta = {
contextRule: Array<string>
defaultUnits: Array<Unit>
inversionFail?: {
given: string
estimated: string
}
}
export let analyseMany = (parsedRules, targetNames, defaultUnits = []) => (
situationGate: (name: DottedName) => any
) => {
// TODO: we should really make use of namespaces at this level, in particular
// setRule in Rule.js needs to get smarter and pass dottedName
defaultUnits = defaultUnits.map(parseUnit)
let cache = {
_meta: { contextRule: [], defaultUnits }
_meta: { contextRule: [], defaultUnits } as CacheMeta
}
let parsedTargets = targetNames.map(t => {
@ -148,6 +159,8 @@ export let analyseMany = (
return { targets, cache, controls }
}
export type Analysis = ReturnType<ReturnType<typeof analyse>>
export let analyse = (parsedRules, target, defaultUnits = []) => {
return analyseMany(parsedRules, [target], defaultUnits)
}

View File

@ -1,13 +1,14 @@
import { Action } from 'Actions/actions'
import { Analysis } from 'Engine/traverse'
import { areUnitConvertible, convertUnit, parseUnit } from 'Engine/units'
import {
compose,
defaultTo,
dissoc,
identity,
lensPath,
omit,
over,
pipe,
set,
uniq,
without
@ -18,7 +19,6 @@ import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
import { SavedSimulation } from 'Selectors/storageSelectors'
import { DottedName, Rule } from 'Types/rule'
import i18n, { AvailableLangs } from '../i18n'
import { Unit } from './../engine/units'
import inFranceAppReducer from './inFranceAppReducer'
import storageRootReducer from './storageReducer'
@ -180,7 +180,7 @@ export type Simulation = {
function simulation(
state: Simulation = null,
action: Action,
analysis: Record<DottedName, { nodeValue: any; unit: Unit | undefined }>
analysis: Analysis | Array<Analysis>
): Simulation | null {
if (action.type === 'SET_SIMULATION') {
const { config, url } = action
@ -226,23 +226,27 @@ function simulation(
return state
}
const addAnswerToSituation = (dottedName: DottedName, value: any, state) => {
return (compose(
set(lensPath(['simulation', 'situation', dottedName]), value),
const addAnswerToSituation = (
dottedName: DottedName,
value: unknown,
state: RootState
) => {
return pipe(
over(lensPath(['conversationSteps', 'foldedSteps']), (steps = []) =>
uniq([...steps, dottedName])
) as any
) as any)(state)
),
set(lensPath(['simulation', 'situation', dottedName]), value)
)(state)
}
const removeAnswerFromSituation = (dottedName: DottedName, state) => {
return (compose(
over(lensPath(['simulation', 'situation']), dissoc(dottedName)),
over(
lensPath(['conversationSteps', 'foldedSteps']),
without([dottedName])
) as any
) as any)(state)
const removeAnswerFromSituation = (
dottedName: DottedName,
state: RootState
) => {
return pipe(
over(lensPath(['conversationSteps', 'foldedSteps']), without([dottedName])),
over(lensPath(['simulation', 'situation']), dissoc(dottedName))
)(state)
}
const existingCompanyRootReducer = (state: RootState, action) => {

View File

@ -86,7 +86,7 @@ export const useTarget = (dottedName: DottedName) => {
return targets && targets.find(t => t.dottedName === dottedName)
}
export let noUserInputSelector = state =>
export let noUserInputSelector = (state: RootState) =>
!Object.keys(situationSelector(state)).length
export let firstStepCompletedSelector = createSelector(
@ -221,7 +221,7 @@ export let exampleAnalysisSelector = createSelector(
analyseRule(
rules,
dottedName,
dottedName => situation[dottedName],
(dottedName: DottedName) => situation[dottedName],
example.defaultUnits
)
)
@ -234,18 +234,19 @@ let makeAnalysisSelector = (situationSelector: SituationSelectorType) =>
situationSelector,
defaultUnitsSelector
],
(parsedRules, targetNames, situations, defaultUnits) =>
mapOrApply(
(parsedRules, targetNames, situations, defaultUnits) => {
return mapOrApply(
situation =>
analyseMany(
parsedRules,
targetNames,
defaultUnits
)(dottedName => {
)((dottedName: DottedName) => {
return situation[dottedName]
}),
situations
)
}
)
export let analysisWithDefaultsSelector = makeAnalysisSelector(

View File

@ -1,4 +1,4 @@
/* @flow */
import { Analysis } from 'Engine/traverse'
import {
add,
concat,
@ -16,10 +16,20 @@ import {
} from 'ramda'
import { createSelector } from 'reselect'
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
import { Branch, Cotisation } from './repartitionSelectors'
// Used for type consistency
export const BLANK_COTISATION: Cotisation = {
montant: {
partPatronale: 0,
partSalariale: 0
},
unit: 'ERROR_SHOULD_BE_INSTANCIATED',
dottedName: 'ERROR_SHOULD_BE_INSTANCIATED' as any,
title: 'ERROR_SHOULD_BE_INSTANCIATED',
branche: 'protection sociale . autres'
}
// These functions help build the payslip. They take the cotisations from the cache, braving all the particularities of the current engine's implementation, handles the part patronale and part salariale, and gives a map by branch.
export const COTISATION_BRANCHE_ORDER: Array<Branche> = [
export const COTISATION_BRANCHE_ORDER: Array<Branch> = [
'protection sociale . santé',
'protection sociale . accidents du travail et maladies professionnelles',
'protection sociale . retraite',
@ -30,50 +40,34 @@ export const COTISATION_BRANCHE_ORDER: Array<Branche> = [
'protection sociale . autres'
]
// Used for type consistency
export const BLANK_COTISATION: Cotisation = {
montant: {
partPatronale: 0,
partSalariale: 0
},
dottedName: 'ERROR_SHOULD_BE_INSTANCIATED',
title: 'ERROR_SHOULD_BE_INSTANCIATED',
branche: 'protection sociale . autres'
}
function duParSelector(
variable: VariableWithCotisation
): ?('employeur' | 'employé') {
function duParSelector(variable): 'employeur' | 'salarié' {
const dusPar = [
['cotisation', 'dû par'],
['taxe', 'dû par'],
['explanation', 'cotisation', 'dû par'],
['explanation', 'taxe', 'dû par']
].map(p => path(p, variable))
return dusPar.filter(Boolean)[0]
return dusPar.filter(Boolean)[0] as any
}
function brancheSelector(variable: VariableWithCotisation): Branche {
function brancheSelector(variable): Branch {
const branches = [
['cotisation', 'branche'],
['taxe', 'branche'],
['explanation', 'cotisation', 'branche'],
['explanation', 'taxe', 'branche']
].map(p => path(p, variable))
return (
// $FlowFixMe
'protection sociale . ' + (branches.filter(Boolean)[0] || 'autres')
)
return ('protection sociale . ' +
(branches.filter(Boolean)[0] || 'autres')) as any
}
// $FlowFixMe
export const mergeCotisations: (
Cotisation,
Cotisation
a: Cotisation,
b: Cotisation
) => Cotisation = mergeWithKey((key, a, b) =>
key === 'montant' ? mergeWith(add, a, b) : b
)
const variableToCotisation = (variable: VariableWithCotisation): Cotisation => {
const variableToCotisation = (variable): Cotisation => {
return mergeCotisations(BLANK_COTISATION, {
...variable.explanation,
branche: brancheSelector(variable),
@ -84,7 +78,7 @@ const variableToCotisation = (variable: VariableWithCotisation): Cotisation => {
}
})
}
const groupByBranche = (cotisations: Array<Cotisation>): Cotisations => {
const groupByBranche = (cotisations: Array<Cotisation>) => {
const cotisationsMap = cotisations.reduce(
(acc, cotisation) => ({
...acc,
@ -94,11 +88,10 @@ const groupByBranche = (cotisations: Array<Cotisation>): Cotisations => {
)
return COTISATION_BRANCHE_ORDER.map(branche => [
branche,
// $FlowFixMe
cotisationsMap[branche]
])
}
export let analysisToCotisations = analysis => {
export let analysisToCotisations = (analysis: Analysis) => {
const variables = [
'contrat salarié . cotisations . salariales',
'contrat salarié . cotisations . patronales'
@ -108,7 +101,7 @@ export let analysisToCotisations = analysis => {
.reduce(concat, [])
const cotisations = pipe(
map(rule =>
map((rule: any) =>
// Following : weird logic to automatically handle negative negated value in sum
rule.operationType === 'calculation' &&

View File

@ -1,5 +1,3 @@
/* @flow */
import { getRuleFromAnalysis } from 'Engine/rules'
import {
add,
@ -20,25 +18,39 @@ import {
} from 'ramda'
import { createSelector } from 'reselect'
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
import { Rule } from 'Types/rule'
import {
analysisToCotisations,
BLANK_COTISATION,
mergeCotisations
} from './ficheDePaieSelectors'
import type {
Cotisation,
MontantPartagé,
Branche,
Répartition
} from 'Types/ResultViewTypes'
export type Cotisation = Rule & {
branche: Branch
montant: MontantPartagé
}
type MontantPartagé = {
partSalariale: number
partPatronale: number
}
export type Branch =
| 'protection sociale . santé'
| 'protection sociale . accidents du travail et maladies professionnelles'
| 'protection sociale . retraite'
| 'protection sociale . famille'
| 'protection sociale . assurance chômage'
| 'protection sociale . formation'
| 'protection sociale . transport'
| 'protection sociale . autres'
const totalCotisations = (cotisations: Array<Cotisation>): MontantPartagé =>
cotisations.reduce(mergeCotisations, BLANK_COTISATION).montant
const byMontantTotal = (
a: [Branche, MontantPartagé],
b: [Branche, MontantPartagé]
a: [Branch, MontantPartagé],
b: [Branch, MontantPartagé]
): number => {
return (
b[1].partPatronale +
@ -48,7 +60,7 @@ const byMontantTotal = (
)
}
const REPARTITION_CSG: { [Branche]: number } = {
const REPARTITION_CSG: Partial<Record<Branch, number>> = {
'protection sociale . famille': 0.85,
'protection sociale . santé': 7.75,
// TODO: cette part correspond à l'amortissement de la dette de la sécurité sociale.
@ -57,10 +69,9 @@ const REPARTITION_CSG: { [Branche]: number } = {
}
function applyCSGInPlace(
CSG: Cotisation,
rawRépartition: { [Branche]: MontantPartagé }
rawRépartition: Record<Branch, MontantPartagé>
): void {
// $FlowFixMe
for (const branche: Branche in REPARTITION_CSG) {
for (const branche in REPARTITION_CSG) {
rawRépartition[branche] = {
partPatronale:
rawRépartition[branche].partPatronale +
@ -82,15 +93,14 @@ const brancheConcernéeParLaRéduction = [
].map(branche => 'protection sociale . ' + branche)
function applyReduction(
réduction,
répartitionMap: { [Branche]: MontantPartagé }
): { [Branche]: MontantPartagé } {
const totalPatronal = pipe(
répartitionMap: Record<Branch, MontantPartagé>
): Record<Branch, MontantPartagé> {
const totalPatronal = (pipe(
pick(brancheConcernéeParLaRéduction),
Object.values,
reduce(mergeWith(add), {})
// $FlowFixMe
)(répartitionMap).partPatronale
)(répartitionMap) as any).partPatronale
return mapObjIndexed(
({ partPatronale, partSalariale }, branche) => ({
partPatronale: brancheConcernéeParLaRéduction.find(equals(branche))
@ -98,16 +108,12 @@ function applyReduction(
: partPatronale,
partSalariale
}),
// $FlowFixMe
répartitionMap
)
}
const répartition = (analysis): ?Répartition => {
// $FlowFixMe
let cotisations: { [Branche]: Array<Cotisation> } = fromPairs(
analysisToCotisations(analysis)
)
const répartition = analysis => {
let cotisations = fromPairs(analysisToCotisations(analysis) as any)
const getRule = getRuleFromAnalysis(analysis),
salaireNet = getRule('contrat salarié . rémunération . net'),
@ -117,7 +123,7 @@ const répartition = (analysis): ?Répartition => {
'contrat salarié . cotisations . patronales . réductions de cotisations'
)
let CSG
const autresCotisations = cotisations['protection sociale . autres']
const autresCotisations = cotisations['protection sociale . autres'] as any
if (autresCotisations) {
CSG = autresCotisations.find(propEq('dottedName', 'contrat salarié . CSG'))
cotisations['protection sociale . autres'] = without(
@ -126,39 +132,34 @@ const répartition = (analysis): ?Répartition => {
)
}
// $FlowFixMe
let répartitionMap: { [Branche]: MontantPartagé } = map(
totalCotisations,
cotisations
)
let répartitionMap = map(totalCotisations, cotisations) as Record<
Branch,
MontantPartagé
>
if (CSG) {
applyCSGInPlace(CSG, répartitionMap)
}
répartitionMap = applyReduction(réductionsDeCotisations, répartitionMap)
return {
// $FlowFixMe
répartition: compose(
sort(byMontantTotal),
Object.entries,
Object.entries as any,
filter(
({ partPatronale, partSalariale }) =>
Math.round(partPatronale + partSalariale) !== 0
)
)(répartitionMap),
// $FlowFixMe
total: cotisationsRule.nodeValue,
cotisations: cotisationsRule,
maximum: compose(
reduce(max, 0),
map(montant => montant.partPatronale + montant.partSalariale),
Object.values
// $FlowFixMe
)(répartitionMap),
salaireNet,
salaireChargé
}
}
// $FlowFixMe
export default createSelector([analysisWithDefaultsSelector], répartition)

View File

@ -1,10 +1,9 @@
import type { Store } from 'redux'
import { Action } from 'Actions/actions'
import { omit } from 'ramda'
import { RootState } from 'Reducers/rootReducer'
import { Store } from 'redux'
import { debounce } from '../utils'
import safeLocalStorage from './safeLocalStorage'
import type { State } from 'Types/State'
import type { Action } from 'Types/ActionsTypes'
const VERSION = 3
@ -13,8 +12,8 @@ const LOCAL_STORAGE_KEY = 'mycompanyinfrance::persisted-everything:v' + VERSION
type OptionsType = {
except?: Array<string>
}
export const persistEverything = (options?: OptionsType = {}) => (
store: Store<State, Action>
export const persistEverything = (options: OptionsType = {}) => (
store: Store<RootState, Action>
): void => {
const listener = () => {
const state = store.getState()
@ -26,7 +25,7 @@ export const persistEverything = (options?: OptionsType = {}) => (
store.subscribe(debounce(1000, listener))
}
export function retrievePersistedState(): ?State {
export function retrievePersistedState(): RootState {
const serializedState = safeLocalStorage.getItem(LOCAL_STORAGE_KEY)
return serializedState ? JSON.parse(serializedState) : null
}

View File

@ -1,16 +1,16 @@
import type { Store } from 'redux'
import { Action } from 'Actions/actions'
import { RootState } from 'Reducers/rootReducer'
import { Store } from 'redux'
import { SavedSimulation } from 'Selectors/storageSelectors'
import { debounce } from '../utils'
import safeLocalStorage from './safeLocalStorage'
import { deserialize, serialize } from './serializeSimulation'
import type { State, SavedSimulation } from '../types/State'
import type { Action } from 'Types/ActionsTypes'
const VERSION = 3
const LOCAL_STORAGE_KEY = 'embauche.gouv.fr::persisted-simulation::v' + VERSION
export function persistSimulation(store: Store<State, Action>) {
export function persistSimulation(store: Store<RootState, Action>) {
const listener = () => {
const state = store.getState()
if (!state.conversationSteps.foldedSteps.length) {
@ -21,7 +21,7 @@ export function persistSimulation(store: Store<State, Action>) {
store.subscribe(debounce(1000, listener))
}
export function retrievePersistedSimulation(): ?SavedSimulation {
export function retrievePersistedSimulation(): SavedSimulation {
const serializedState = safeLocalStorage.getItem(LOCAL_STORAGE_KEY)
return serializedState ? deserialize(serializedState) : null
}

View File

@ -1,9 +1,10 @@
export default {
removeItem: function (...args) {
removeItem: function (key: string) {
try {
return window.localStorage.removeItem(...args)
return window.localStorage.removeItem(key)
} catch (error) {
if (error.name === 'SecurityError') {
// eslint-disable-next-line no-console
console.warn(
'[localStorage] Unable to remove item due to security settings'
)
@ -11,11 +12,12 @@ export default {
return null
}
},
getItem: function (...args) {
getItem: function (key: string) {
try {
return window.localStorage.getItem(...args)
return window.localStorage.getItem(key)
} catch (error) {
if (error.name === 'SecurityError') {
// eslint-disable-next-line no-console
console.warn(
'[localStorage] Unable to get item due to security settings'
)
@ -23,11 +25,12 @@ export default {
return null
}
},
setItem: function (...args) {
setItem: function (key: string, value: string) {
try {
return window.localStorage.setItem(...args)
return window.localStorage.setItem(key, value)
} catch (error) {
if (error.name === 'SecurityError') {
// eslint-disable-next-line no-console
console.warn(
'[localStorage] Unable to set item due to security settings'
)

View File

@ -1,10 +0,0 @@
import type { State, SavedSimulation } from '../types/State.js'
import { pipe } from 'ramda'
import { currentSimulationSelector } from 'Selectors/storageSelectors'
export const serialize: State => string = pipe(
currentSimulationSelector,
JSON.stringify
)
export const deserialize: string => SavedSimulation = JSON.parse

View File

@ -0,0 +1,9 @@
import { pipe } from 'ramda'
import { currentSimulationSelector } from 'Selectors/storageSelectors'
export const serialize = pipe(
currentSimulationSelector,
JSON.stringify
)
export const deserialize = JSON.parse

View File

@ -1,68 +0,0 @@
import type { RègleAvecMontant, Règle } from './RegleTypes'
export type Cotisation = Règle & {
branche: Branche,
montant: MontantPartagé
}
export type Branche =
| 'protection sociale . santé'
| 'protection sociale . accidents du travail et maladies professionnelles'
| 'protection sociale . retraite'
| 'protection sociale . famille'
| 'protection sociale . assurance chômage'
| 'protection sociale . formation'
| 'protection sociale . transport'
| 'protection sociale . autres'
export type MontantPartagé = {
partSalariale: number,
partPatronale: number
}
export type Cotisations = Array<[Règle, Array<Cotisation>]>
export type VariableWithCotisation = {
category: 'variable',
name: string,
title: string,
cotisation: {|
'dû par'?: 'salarié' | 'employeur',
branche?: Branche
|},
dottedName: string,
nodeValue: number,
explanation: {
cotisation: {
'dû par'?: 'salarié' | 'employeur',
branche?: Branche
},
taxe: {
'dû par'?: 'salarié' | 'employeur',
branche?: Branche
}
}
}
export type FicheDePaie = {
salaireBrut: RègleAvecMontant,
avantagesEnNature: RègleAvecMontant,
indemnitésSalarié: RègleAvecMontant,
salaireDeBase: RègleAvecMontant,
// TODO supprimer (cf https://github.com/betagouv/syso/issues/242)
réductionsDeCotisations: RègleAvecMontant,
cotisations: Cotisations,
totalCotisations: MontantPartagé,
salaireChargé: RègleAvecMontant,
salaireNetDeCotisations: RègleAvecMontant,
rémunérationNetteImposable: RègleAvecMontant,
salaireNet: RègleAvecMontant,
nombreHeuresTravaillées: number
}
export type Répartition = {
répartition: Array<[Règle, MontantPartagé]>,
total: MontantPartagé,
salaireNet: RègleAvecMontant,
salaireChargé: RègleAvecMontant,
cotisationMaximum: number
}

7
source/types/worker-loader.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
declare module "worker-loader*" {
class WebpackWorker extends Worker {
constructor();
}
export = WebpackWorker;
}

View File

@ -3,8 +3,8 @@ export let capitalise0 = (name: string): string =>
export function debounce<ArgType>(
timeout: number,
fn: (arg: ArgType) => void
): (arg: ArgType) => void {
fn: (arg?: ArgType) => void
): (arg?: ArgType) => void {
let timeoutId: ReturnType<typeof setTimeout>
return (...args) => {
clearTimeout(timeoutId)
@ -43,10 +43,10 @@ export function softCatch<ArgType, ReturnType>(
}
}
}
export function mapOrApply<A, B>(
fn: (a: A) => B,
x: Array<A> | A
): Array<B> | B {
export function mapOrApply<A, B>(fn: (a: A) => B, x: A): B
export function mapOrApply<A, B>(fn: (a: A) => B, x: Array<A>): Array<B>
export function mapOrApply(fn, x) {
return Array.isArray(x) ? x.map(fn) : fn(x)
}

View File

@ -1,5 +1,4 @@
import { expect } from 'chai'
// $FlowFixMe
import salariéConfig from 'Components/simulationConfigs/salarié.yaml'
import { getRuleFromAnalysis, rules } from 'Engine/rules'
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
@ -27,20 +26,17 @@ let cotisations = null,
describe('pay slip selector', function() {
beforeEach(() => {
// $FlowFixMe
cotisations = analysisToCotisationsSelector(state)
analysis = analysisWithDefaultsSelector(state)
expect(cotisations).not.to.eq(null)
})
it('should have cotisations grouped by branches in the proper ordering', function() {
// $FlowFixMe
let branches = cotisations.map(([branche]) => branche)
expect(branches).to.eql(COTISATION_BRANCHE_ORDER)
})
it('should collect all cotisations in a branche', function() {
// $FlowFixMe
let cotisationsSanté = (cotisations.find(([branche]) =>
branche.includes('santé')
) || [])[1].map(cotisation => cotisation.name)
@ -61,7 +57,6 @@ describe('pay slip selector', function() {
})
it('should have value for "salarié" and "employeur" for a cotisation', function() {
// $FlowFixMe
let cotisationATMP = (cotisations.find(([branche]) =>
branche.includes('accidents du travail et maladies professionnelles')
) || [])[1][0]

View File

@ -10,6 +10,14 @@
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowJs": true,
// The end goal is to enable `"strict": true` which correspond to the
// following settings: noImplicitAny, noImplicitThis, alwaysStrict,
// strictBindCallApply, strictNullChecks, strictFunctionTypes, and
// strictPropertyInitialization. During the transition we enable these
// settings one by one.
"noImplicitThis": true,
"strictBindCallApply": true,
"paths": {
"Actions/*": ["actions/*"],
"Components": ["components"],

View File

@ -390,13 +390,6 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-flow@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz#a765f061f803bc48f240c26f8747faf97c26bf7c"
integrity sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-json-strings@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470"
@ -529,14 +522,6 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-flow-strip-types@^7.0.0":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.4.tgz#d267a081f49a8705fc9146de0768c6b58dccd8f7"
integrity sha512-WyVedfeEIILYEaWGAUWzVNyqG4sfsNooMhXWsu/YzOvVGcsnPb5PguysjJqI3t3qiaYj0BR8T2f5njdjTGe44Q==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-flow" "^7.2.0"
"@babel/plugin-transform-for-of@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556"
@ -805,14 +790,6 @@
js-levenshtein "^1.1.3"
semver "^5.5.0"
"@babel/preset-flow@^7.0.0-beta.51":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2"
integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-flow-strip-types" "^7.0.0"
"@babel/preset-react@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0"
@ -1383,6 +1360,13 @@
dependencies:
"@types/react" "*"
"@types/react-highlight-words@^0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@types/react-highlight-words/-/react-highlight-words-0.16.0.tgz#bcd67e9724fb5f070c955732f604068de5cbe30b"
integrity sha512-bSVlhM5OXLO67UZD/orsoT1lS5p7w8ffoDis3TtU1mqmXW7epHHt4kB7QmmZzwXjgzRm++yOo6Xpp7PhRFBpXA==
dependencies:
"@types/react" "*"
"@types/react-native@*":
version "0.60.21"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.60.21.tgz#81a41cae7b232f52ab3983d854f4a0b0df79531e"