Merge pull request #1038 from betagouv/dnrti

Intègre les retours DNRTI
pull/1040/head
Maxime Quandalle 2020-05-15 17:16:25 +02:00 committed by GitHub
commit dcf2962f83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 285 additions and 190 deletions

View File

@ -49,8 +49,8 @@
"react-router-hash-link": "^1.2.2",
"react-spring": "=8.0.27",
"react-syntax-highlighter": "^10.1.1",
"react-to-print": "^2.5.1",
"react-transition-group": "^2.2.1",
"react-useportal": "^1.0.13",
"recharts": "^1.8.5",
"reduce-reducers": "^1.0.4",
"redux": "^4.0.4",

View File

@ -1,18 +1,18 @@
import { explainVariable } from 'Actions/actions'
import React, { useContext } from 'react'
import Overlay from 'Components/Overlay'
import { EngineContext } from 'Components/utils/EngineContext'
import React, { useContext, useState } from 'react'
import emoji from 'react-easy-emoji'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
import { useDispatch } from 'react-redux'
import { DottedName } from 'Rules'
import { TrackerContext } from '../utils/withTracker'
import './Explicable.css'
import { EngineContext } from 'Components/utils/EngineContext'
import usePortal from 'react-useportal'
export default function Explicable({ dottedName }: { dottedName: DottedName }) {
export function ExplicableRule({ dottedName }: { dottedName: DottedName }) {
const rules = useContext(EngineContext).getParsedRules()
const tracker = useContext(TrackerContext)
const dispatch = useDispatch()
const explained = useSelector((state: RootState) => state.explainedVariable)
// Rien à expliquer ici, ce n'est pas une règle
if (dottedName == null) return null
@ -42,3 +42,28 @@ export default function Explicable({ dottedName }: { dottedName: DottedName }) {
</button>
)
}
export function Explicable({ children }: { children: React.ReactNode }) {
const { Portal } = usePortal()
const [isOpen, setIsOpen] = useState(false)
return (
<>
{isOpen && (
<Portal>
<Overlay onClose={() => setIsOpen(false)}>{children}</Overlay>
</Portal>
)}
<button
className="ui__ link-button"
onClick={() => setIsOpen(true)}
css={`
margin-left: 0.3rem !important;
vertical-align: middle;
font-size: 110% !important;
`}
>
{emoji('')}
</button>
</>
)
}

View File

@ -1,5 +1,5 @@
import { updateSituation, goToQuestion } from 'Actions/actions'
import Explicable from 'Components/conversation/Explicable'
import { ExplicableRule } from 'Components/conversation/Explicable'
import React, { useContext } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { situationSelector } from 'Selectors/simulationSelectors'
@ -34,7 +34,8 @@ export default function FormDecorator(RenderField) {
return (
<div className="step">
<h3>
{rules[dottedName].question} <Explicable dottedName={dottedName} />
{rules[dottedName].question}{' '}
<ExplicableRule dottedName={dottedName} />
</h3>
<fieldset>

View File

@ -4,7 +4,7 @@ import { is } from 'ramda'
import React, { useCallback, useContext } from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import Explicable from './Explicable'
import { ExplicableRule } from './Explicable'
import SendButton from './SendButton'
/* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans
@ -123,7 +123,7 @@ export default function Question({
export const RadioLabel = props => (
<>
<RadioLabelContent {...props} />
<Explicable dottedName={props.dottedName} />
<ExplicableRule dottedName={props.dottedName} />
</>
)

View File

@ -68,13 +68,13 @@
z-index: -1;
}
.step.input {
.step .input {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: flex-end;
}
.step.input > :first-child {
.step .input > :first-child {
text-align: right;
}

View File

@ -163,18 +163,23 @@ Votre entreprise: Your company
Votre forme juridique: Your legal status
aide: aid or subsidy
aide-déclaration-indépendant:
description: <0>Help with your 2019 income tax return </0><1>This tool is a tax
(income) and social security (ISD) declaration aid for self-employed
workers. It allows you to find out the amount of social security charges
deductible from your net fiscal result.</1><2><0>This tool is for you if you
are in any of the following cases :</0><1><0>you contribute to the general
scheme for self-employed persons</0><1>your business is in the actual tax
system and in accrual accounting</1></1><2>It does not concern you if you
are in one of the following cases:</2><3><0>you are a regulated liberal
profession</0><1>you are a liberal profession contributing to the
CIPAV</1><2>your company is domiciled in the DOMs</2></3></2><3>What is your
professional income in 2019?</3><4>Indicate your net fiscal result before
deduction of social security charges and tax exemptions.</4>
description: <0>Help with your 2019 income tax return</0><1>This tool is a tax
(income) and social security<1> (ISD</1>) declaration aid for self-employed
workers. It enables you to find out the amount of social security charges
deductible from your net tax result.</1><2>You remain fully responsible for
any omissions or inaccuracies in your statements.</2><3><0><0>This tool
concerns you if you are in any of the following cases :</0><1><0>you
contribute to the general scheme for self-employed persons</0><1>your
company is in the actual tax system and in accrual accounting</1></1><2>It
does not concern you if you are in one of the following cases:</2><3><0>you
are self-employed under a pension scheme for the liberal
professions</0><1>you are managers of companies subject to corporate income
tax</1><2>you have opted for the micro-fiscal regime</2><3>your company is
domiciled in the DOM (French Overseas Departments and
Territories)</3></3></0></3><4>What is your tax result in
2019?<1></1><2>Social security charges and tax exemptions not
included<2></2></2> </4><5>The tax result corresponds to income less
expenses. It can be positive (profit) or negative (deficit).</5>
entreprise:
description: You can fill in your company to pre-fill in the form
titre: <0>Company and activity</0>

View File

@ -1,88 +0,0 @@
import RuleLink from 'Components/RuleLink'
import { useEvaluation } from 'Components/utils/EngineContext'
import { formatValue } from 'publicodes'
import React from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import Skeleton from 'react-loading-skeleton'
import ReactToPrint from 'react-to-print'
import Animate from 'Components/ui/animate'
import simulationConfig from './config.yaml'
import { DottedName } from 'Rules'
type ResultsProp = {
componentRef?: any
}
export function Results({ componentRef }: ResultsProp) {
const results = useEvaluation(simulationConfig.objectifs as Array<DottedName>)
const onGoingComputation = !results.filter(node => node.nodeValue != null)
.length
return (
<div
className="ui__ card lighter-bg"
css="margin-top: 3rem; padding: 1rem 0"
>
<h1 css="text-align: center; margin-bottom: 2rem">
<Trans i18nKey="aide-déclaration-indépendant.results.title">
Aide à la déclaration
</Trans>
{emoji('📄')}
</h1>
{onGoingComputation && (
<h2>
<small>
<Trans i18nKey="aide-déclaration-indépendant.results.ongoing">
Calcul en cours...
</Trans>
</small>
</h2>
)}
<>
<Animate.fromTop>
{results.map(r => (
<React.Fragment key={r.title}>
<h4>
{r.title} <small>{r.summary}</small>
</h4>
{r.description && <p className="ui__ notice">{r.description}</p>}
<p className="ui__ lead" css="margin-bottom: 1rem;">
<RuleLink dottedName={r.dottedName}>
{r.nodeValue != null ? (
formatValue({
nodeValue: r.nodeValue || 0,
language: 'fr',
unit: '€',
precision: 0
})
) : (
<Skeleton width={80} />
)}
</RuleLink>
</p>
</React.Fragment>
))}
<p className="ui__ notice">
Résultats calculés le {new Date().toLocaleDateString()}
</p>
{!onGoingComputation && (
<div css="text-align: center">
<style>
{
'@media print {.button.print{display: none;} body {margin: 40px;}}'
}
</style>
<ReactToPrint
trigger={() => (
<button className="ui__ simple button print">
{emoji('🖨')} Imprimer
</button>
)}
content={() => componentRef.current}
/>
</div>
)}
</Animate.fromTop>
</>
</div>
)
}

View File

@ -1,6 +1,6 @@
import { setSimulationConfig, updateSituation } from 'Actions/actions'
import Aide from 'Components/conversation/Aide'
import Explicable from 'Components/conversation/Explicable'
import { Explicable, ExplicableRule } from 'Components/conversation/Explicable'
import 'Components/TargetSelection.css'
import Warning from 'Components/ui/WarningBlock'
import { useEvaluation, EngineContext } from 'Components/utils/EngineContext'
@ -8,13 +8,7 @@ import { ScrollToTop } from 'Components/utils/Scroll'
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
import RuleInput from 'Components/conversation/RuleInput'
import { ParsedRule } from 'publicodes'
import React, {
useCallback,
useEffect,
useRef,
useState,
useContext
} from 'react'
import React, { useCallback, useEffect, useState, useContext } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
@ -24,8 +18,11 @@ import styled from 'styled-components'
import Animate from 'Components/ui/animate'
import { CompanySection } from '../Home'
import simulationConfig from './config.yaml'
import { Results } from './Result'
import { useNextQuestions } from 'Components/utils/useNextQuestion'
import emoji from 'react-easy-emoji'
import RuleLink from 'Components/RuleLink'
import { formatValue } from 'publicodes'
import Skeleton from 'react-loading-skeleton'
export default function() {
const dispatch = useDispatch()
@ -35,55 +32,41 @@ export default function() {
)
useEffect(() => {
dispatch(setSimulationConfig(simulationConfig, true))
}, [])
}, [dispatch])
const { resultsRef, displayForm, updateIncome, currentIncome } = (() => {
const dottedName = 'dirigeant . rémunération totale'
const [resultsRef, resultsInViewPort] = useDisplayOnIntersecting({
threshold: 0.5,
unobserve: false
})
const value = useSelector(situationSelector)[dottedName]
const [currentIncome, setCurrentIncome] = useState(value)
const [displayForm, setDisplayForm] = useState(currentIncome != null)
const updateIncome = useCallback(
income => {
setDisplayForm(income != null)
setCurrentIncome(income)
},
[setDisplayForm, setCurrentIncome]
)
const dispatch = useDispatch()
useEffect(() => {
if (resultsInViewPort && displayForm) {
dispatch(updateSituation(dottedName, currentIncome))
} else {
dispatch(updateSituation(dottedName, null))
}
}, [resultsInViewPort, displayForm, currentIncome])
const [resultsRef, resultsInViewPort] = useDisplayOnIntersecting({
threshold: 0.5,
unobserve: false
})
const dottedName = 'dirigeant . rémunération totale'
const value = useSelector(situationSelector)[dottedName]
const [currentIncome, setCurrentIncome] = useState(value)
const displayForm = currentIncome != null
useEffect(() => {
if (resultsInViewPort && displayForm) {
dispatch(updateSituation(dottedName, currentIncome))
} else {
dispatch(updateSituation(dottedName, null))
}
}, [dispatch, resultsInViewPort, displayForm, currentIncome])
return { updateIncome, resultsRef, displayForm, currentIncome }
})()
const printComponentRef = useRef<HTMLDivElement>(null)
return (
<div ref={printComponentRef}>
<div>
<ScrollToTop />
<Trans i18nKey="aide-déclaration-indépendant.description">
<h1>Aide à la déclaration de revenus au titre de l'année 2019</h1>
<p>
Cet outil est une aide aux déclarations fiscale (revenu) et sociale
(DSI) à destination des travailleurs indépendants. Il vous permet de
connaître le montant des charges sociales déductibles à partir de
votre résultat net fiscal.
Cet outil est une aide aux déclarations fiscale (revenu) et sociale (
<abbr title="Déclaration Sociale des Indépendants">DSI</abbr>) à
destination des travailleurs indépendants. Il vous permet de connaître
le montant des charges sociales déductibles à partir de votre résultat
net fiscal.
</p>
<div
css={`
@media print {
display: none;
}
`}
>
<p>
Vous restez entièrement responsable d'éventuelles omissions ou
inexactitudes dans vos déclarations.
</p>
<div>
<Warning localStorageKey="aide-déclaration-indépendant.warning">
<h3>
Cet outil vous concerne si vous êtes dans tous les cas suivants :
@ -116,18 +99,21 @@ export default function() {
</div>
<h2>
Quel est votre résultat fiscal en 2019 ?<br />
<small>Charges sociales et exonérations fiscales non incluses</small>
<small>
Charges sociales et exonérations fiscales non incluses{' '}
<ExplicationsResultatFiscal />
</small>
</h2>
<p className="ui__ notice">
Le résultat fiscal correspond aux produits moins les charges. Il peut
être positif (bénéfice) ou négatif (pertes).
être positif (bénéfice) ou négatif (déficit).
</p>
</Trans>
<BigInput>
<RuleInput
rules={rules}
dottedName="dirigeant . rémunération totale"
onChange={updateIncome}
onChange={setCurrentIncome}
value={currentIncome}
autoFocus
/>
@ -139,13 +125,7 @@ export default function() {
<Trans i18nKey="aide-déclaration-indépendant.entreprise.titre">
<h2>Entreprise et activité</h2>
</Trans>
<div
css={`
@media print {
display: none;
}
`}
>
<div>
{!company && (
<p className="ui__ notice">
<Trans i18nKey="aide-déclaration-indépendant.entreprise.description">
@ -197,7 +177,7 @@ export default function() {
</Animate.fromTop>
<div ref={resultsRef}>
<Results componentRef={printComponentRef} />
<Results />
</div>
<Aide />
</>
@ -210,6 +190,123 @@ type SubSectionProp = {
dottedName: DottedName
hideTitle?: boolean
}
function ExplicationsResultatFiscal() {
return (
<Explicable>
<>
<h3>Quelles exonérations inclure ?</h3>
<p>
calculer le montant du résultat fiscal avant déduction d ations et des
charges sociales à indiquer dans ce simula s pouvez utiliser votre
liasse fiscale, en reprenant l ts indiqués dans les lignes fiscales du
tableau ci-desso nction de votre situation (imposition au réel normal
ou au réel simplifié).
</p>
<p>Lopération à effectuer est la suivante :</p>
<ul>
<li>
minez le résultat fiscal dans votre liasse, sans montant de vos
cotisations et contributions socia gimes obligatoires de sécurité
sociale. Prenez le résultat fiscal correspondant{' '}
<strong>(1)</strong>
</li>
<li>
Ajoutez les exonérations <strong>(2)</strong>
</li>
</ul>
<table
css={`
font-size: 0.85em;
text-align: center;
tr:nth-child(2n) {
background: #e5effa;
}
td {
padding: 0.5rem;
}
`}
>
<tr>
<td></td>
<td>
Résultat fiscal <strong>(1)</strong>
</td>
<td colSpan={4}>
Exonérations <strong>(2)</strong>
</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Exonérations liées aux zones / activités</td>
<td>Exonérations Madelin et plan dépargne retraite</td>
<td>Exonérations de plus-values à court terme</td>
<td>Suramortissement productif</td>
</tr>
<tr>
<td>BIC réel normal</td>
<td>
<strong>2058-A-SD</strong>
<br />
Ligne XN (bénéfice) Ligne XO (déficit)
</td>
<td>
<strong>2058-A-SD</strong>
<br />
Lignes K9 / L6 / ØV / PP / L2 / 1F / L5 / PA / XC / PB
</td>
<td>
<strong>2053-SD</strong>
<br />
Lignes A7 et A8
</td>
<td>
<strong>2058-A-SD</strong>
<br />
Ligne XG (montant inclus)
</td>
<td>
<strong>2058-A-SD</strong>
<br />
Lignes X9 et YA
</td>
</tr>
<tr>
<td>BIC réel simplifié</td>
<td>
<strong>2033-B-SD</strong>
<br />
Ligne 370 (bénéfice) Ligne 372 déficit)
</td>
<td>
<strong>2033 B-SD</strong>
<br />
Lignes 986 / 127 / 991 / 345 / 992 / 987 / 989 / 990 / 993
</td>
<td>
<strong>2033-SD</strong>
<br />
Lignes 325 et 327
</td>
<td>
<strong>2033 B-SD</strong>
<br />
Ligne 350 (montant inclus)
</td>
<td>
<strong>2033 B-SD</strong>
<br />
Lignes 655 et 643
</td>
</tr>
</table>
</>
</Explicable>
)
}
function SubSection({
dottedName: sectionDottedName,
hideTitle = false
@ -285,14 +382,11 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
css={`
border-left: 3px solid var(--lightColor);
padding-left: 0.6rem;
@media print {
padding-left: 0 !important;
}
`}
>
<p>
{question ?? evaluatedRule.question}
<Explicable dottedName={dottedName} />
<ExplicableRule dottedName={dottedName} />
</p>
<p className="ui__ notice">{summary ?? evaluatedRule.summary}</p>
</div>
@ -308,6 +402,60 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
)
}
function Results() {
const results = useEvaluation(simulationConfig.objectifs as Array<DottedName>)
const onGoingComputation = !results.filter(node => node.nodeValue != null)
.length
return (
<div
className="ui__ card lighter-bg"
css="margin-top: 3rem; padding: 1rem 0"
>
<h1 css="text-align: center; margin-bottom: 2rem">
<Trans i18nKey="aide-déclaration-indépendant.results.title">
Aide à la déclaration
</Trans>
{emoji('📄')}
</h1>
{onGoingComputation && (
<h2>
<small>
<Trans i18nKey="aide-déclaration-indépendant.results.ongoing">
Calcul en cours...
</Trans>
</small>
</h2>
)}
<>
<Animate.fromTop>
{results.map(r => (
<React.Fragment key={r.title}>
<h4>
{r.title} <small>{r.summary}</small>
</h4>
{r.description && <p className="ui__ notice">{r.description}</p>}
<p className="ui__ lead" css="margin-bottom: 1rem;">
<RuleLink dottedName={r.dottedName}>
{r.nodeValue != null ? (
formatValue({
nodeValue: r.nodeValue || 0,
language: 'fr',
unit: '€',
precision: 0
})
) : (
<Skeleton width={80} />
)}
</RuleLink>
</p>
</React.Fragment>
))}
</Animate.fromTop>
</>
</div>
)
}
const FormBlock = styled.section`
max-width: 500px;
padding: 0;

View File

@ -17,7 +17,6 @@ import { Link } from 'react-router-dom'
import { Company } from 'Reducers/inFranceAppReducer'
import { RootState } from 'Reducers/rootReducer'
import * as Animate from 'Components/ui/animate'
import { productionMode } from '../../../../utils'
import AideOrganismeLocal from './AideOrganismeLocal'
import businessPlan from './businessPlan.svg'
@ -93,8 +92,7 @@ export default function SocialSecurity() {
</h2>
<div className="ui__ center-flex">
{company?.statutJuridique === 'EI' &&
!company.isAutoEntrepreneur &&
!productionMode && (
!company.isAutoEntrepreneur && (
<Link
className="ui__ interactive card box"
to={{
@ -317,7 +315,7 @@ export const CompanySection = ({ company }: CompanySectionProps) => {
<ScrollToTop />
<Overlay>
<Trans i18nKey="gérer.entreprise.dirigeant">
<h2> Êtes-vous dirigeant majoritaire ? </h2>
<h2>Êtes-vous dirigeant majoritaire ?</h2>
<p>
Si vous êtes administrateur majoritaire ou si vous faites partie
d'un conseil d'administration majoritaire, vous n'aurez pas le

View File

@ -33,7 +33,8 @@ export function inIframe(): boolean {
// This is different from the process.env.NODE_ENV in that a feature branch may
// be build in production mode (with the NODE_ENV) but we may still want to show
// or hide some features.
export const productionMode = ['master', 'next'].includes(process.env.HEAD)
export const productionMode =
process.env.HEAD && ['master', 'next'].includes(process.env.HEAD)
export function softCatch<ArgType, ReturnType>(
fn: (arg: ArgType) => ReturnType

View File

@ -9673,13 +9673,6 @@ react-test-renderer@^16.0.0-0:
react-is "^16.8.6"
scheduler "^0.19.1"
react-to-print@^2.5.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/react-to-print/-/react-to-print-2.7.0.tgz#3afca47ac857c72d9b76f9944b0793d27714bcc5"
integrity sha512-1GdEskOHtQs7EMj9t7OYSiMcAjzGCZyasb1H6kJQf5GaRpoM3Qr4OLbxGQSHK659Sp1PRZMVSD9+DWu4atVOIw==
dependencies:
prop-types "^15.7.2"
react-transition-group@^2.2.1, react-transition-group@^2.5.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@ -9690,6 +9683,13 @@ react-transition-group@^2.2.1, react-transition-group@^2.5.0:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
react-useportal@^1.0.13:
version "1.0.13"
resolved "https://registry.yarnpkg.com/react-useportal/-/react-useportal-1.0.13.tgz#abfc29f8128756cd7382bff7c81a4f446b792199"
integrity sha512-83KpNTXUIHnRVTLeMberIblCtssvRSKCPnG/xT9NW60gDYfU13pQBNQKCVUF8MBK+7LnCQ/ZrOuXl8Mp+iXdXA==
dependencies:
use-ssr "^1.0.19"
react@^16.13.1, react@^16.3.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
@ -11698,6 +11698,11 @@ url@0.11.0, url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-ssr@^1.0.19:
version "1.0.23"
resolved "https://registry.yarnpkg.com/use-ssr/-/use-ssr-1.0.23.tgz#3bde1e10cd01b3b61ab6386d7cddb72e74828bf8"
integrity sha512-5bvlssgROgPgIrnILJe2mJch4e2Id0/bVm1SQzqvPvEAXmlsinCCVHWK3a2iHcPat7PkdJHBo0gmSmODIz6tNA==
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"