Revoie les parcours avec entreprise existante

- Met à jour le style des notifications dans la conversation
- Améliore la landing page
- Améliore la vue de la situation des simulateurs
- Les données de l'entreprise courante sont stockées en publicodes
- Ajoute la possibilité de réinitialisé la simulation sans les données entreprises
pull/2059/head
Johan Girod 2022-03-08 13:40:23 +01:00
parent 53f66769e1
commit 3aa11ace73
87 changed files with 2043 additions and 1588 deletions

View File

@ -27,7 +27,7 @@
"build": "node ../scripts/build-rules.js",
"start": "onchange 'règles/**/*.yaml' -- yarn run build",
"clean": "rimraf dist node_modules",
"postinstall": "yarn run build",
"prepack": "yarn run build",
"up": "yarn version --minor && echo \" N'oubliez pas de poussez le tag git\"",
"test": "node ../scripts/check-changelog.js"
}

View File

@ -28,7 +28,7 @@
"build": "node ../scripts/build-rules.js",
"start": "onchange 'règles/**/*.yaml' -- yarn run build",
"clean": "rimraf dist node_modules",
"postinstall": "yarn run build",
"prepack": "yarn run build",
"up": "yarn version --minor && echo \" N'oubliez pas de poussez le tag git\"",
"test": "node ../scripts/check-changelog.js"
}

View File

@ -1,5 +1,5 @@
bénéficiaire:
valeur: non
applicable si: entreprise . imposition . IS
description: |
Un bénéficiaire est un actionnaire dans une SAS ou un associé dans une
SARL/EURL.

View File

@ -1,13 +1,37 @@
dirigeant:
question: Quel est le régime social du dirigeant ?
dirigeant: oui
dirigeant . gérant minoritaire:
titre: Gérant minoritaire ou égalitaire
question: Êtes-vous gérant minoritaire ou égalitaire de votre entreprise ?
non applicable si:
une de ces conditions:
- entreprise . catégorie juridique . EI
- entreprise . catégorie juridique . SARL . unipersonnelle
- entreprise . catégorie juridique . SAS . unipersonnelle
par défaut: non
formule:
une possibilité:
choix obligatoire: oui
possibilités:
- auto-entrepreneur
- assimilé salarié
- indépendant
dirigeant . régime social:
non applicable si:
une de ces conditions:
- entreprise . catégorie juridique . SELARL #TODO NON IMPLEMENTE
- entreprise . catégorie juridique . SELAS #TODO NON IMPLEMENTE
- entreprise . catégorie juridique . autre
par défaut: non
variations:
- si: entreprise . catégorie juridique . EI . auto-entrepreneur
alors: "'auto-entrepreneur'"
- si:
une de ces conditions:
- entreprise . catégorie juridique . SAS
- toutes ces conditions:
- entreprise . catégorie juridique . SARL
- gérant minoritaire
alors: "'assimilé salarié'"
- si:
non applicable si: gérant minoritaire
une de ces conditions:
- entreprise . catégorie juridique . EI
- entreprise . catégorie juridique . SARL
alors: "'indépendant'"
dirigeant . rémunération: oui
dirigeant . rémunération . totale:
@ -81,14 +105,13 @@ dirigeant . rémunération . nette après impôt:
dirigeant . assimilé salarié:
description: |
Certains dirigeants d'entreprise (c'est notamment le cas pour les SASU) sont considérés par la sécurité sociale comme assimilés aux salariés. Ils sont alors au régime général de la sécurité sociale, avec quelques contraintes cependant. Par exemple, ils ne cotisent pas au chômage, et n'y ont donc pas droit.
formule: dirigeant = 'assimilé salarié'
applicable si: régime social = 'assimilé salarié'
valeur: oui
remplace:
- règle: contrat salarié
par: "'CDI'"
- règle: contrat salarié . statut cadre
par: oui
- règle: entreprise . imposition
par: "'IS'"
rend non applicable:
- contrat salarié . convention collective
- contrat salarié . activité partielle
@ -152,14 +175,9 @@ dirigeant . assimilé salarié . réduction ACRE . notification taux annuel:
simulateur ne prends pas encore en compte le calcul de l'ACRE mois par mois.
dirigeant . auto-entrepreneur:
applicable si: régime social = 'auto-entrepreneur'
valeur: oui
rend non applicable: contrat salarié
remplace:
- règle: entreprise . imposition
par: "'IR'"
- règle: entreprise . imposition . IR . micro-fiscal
par: oui
formule: dirigeant = 'auto-entrepreneur'
icônes: 🚶
description: |
L'auto-entreprise est une entreprise individuelle simplifiée. À l'origine connu sous l'appellation « auto-entrepreneur », le régime de « micro-entrepreneur » est un régime de travailleur indépendant créé pour simplifier la gestion administrative, notamment en remplaçant toutes les cotisations sociales par un prélèvement unique mensuel.
@ -337,40 +355,38 @@ dirigeant . auto-entrepreneur . cotisations et contributions . cotisations . tau
remplace:
règle: taux vente restauration hébergement
par: taux ACRE * taux vente restauration hébergement
description: |
Ce taux correspond à la réduction de cotisations qui s'applique pour
l'auto-entrepreneur bénéficiant de l'Acre. Un taux de 75% signifie que
l'auto-entrepreneur doit s'acquitter de 75% du montant d'origine des
cotisations.
unité: '%'
formule:
variations:
- si: entreprise . date de création < 01/04/2019
alors:
grille:
assiette: entreprise . durée d'activité
tranches:
- montant: 25%
plafond: 1 an
- montant: 50%
plafond: 2 ans
- montant: 90%
plafond: 3 ans
- si: entreprise . date de création < 01/04/2020
alors:
grille:
assiette: entreprise . durée d'activité
tranches:
- montant: 25%
plafond: 1 an
- montant: 75%
plafond: 2 ans
- montant: 90%
plafond: 3 ans
- sinon:
applicable si: entreprise . durée d'activité < 1 an
valeur: 50%
variations:
- si: entreprise . date de création < 01/04/2019
alors:
grille:
assiette: entreprise . durée d'activité
tranches:
- montant: 25%
plafond: 1 an
- montant: 50%
plafond: 2 ans
- montant: 90%
plafond: 3 ans
- si: entreprise . date de création < 01/04/2020
alors:
grille:
assiette: entreprise . durée d'activité
tranches:
- montant: 25%
plafond: 1 an
- montant: 75%
plafond: 2 ans
- montant: 90%
plafond: 3 ans
- si: entreprise . durée d'activité < 1 an
alors: 50%
- sinon: 0%
références:
FAQ Urssaf depuis 04/2020: https://www.autoentrepreneur.urssaf.fr/portail/accueil/une-question/questions-frequentes.html#jai-cree-mon-auto-entreprise-en
@ -496,8 +512,9 @@ dirigeant . auto-entrepreneur . chiffre d'affaires:
- net de cotisations
dirigeant . indépendant:
applicable si: régime social = 'indépendant'
valeur: oui
rend non applicable: contrat salarié
formule: dirigeant = 'indépendant'
dirigeant . indépendant . revenu professionnel:
description: rémunération du dirigeant au régime des indépendant
@ -970,15 +987,15 @@ dirigeant . indépendant . cotisations facultatives . plafond retraite compléme
dirigeant . indépendant . cotisations et contributions . début activité:
titre: Cotisations forfaitaires de début d'activité
description: |
Lorsque vous commencez votre activité, vos **revenus professionnels
Lorsque vous commencez votre activité, vos **revenus professionnels**
nétant pas connus**, les cotisations et contributions des deux premières
années sont calculées sur une **base forfaitaire**.
Ces cotisations seront ajustées et régularisées en fonction de vos revenus réels de
lannée dexercice. Si votre revenu est supérieur à la base forfaitaire prise en compte
pour le calcul des cotisations provisionnelles alors vous serez redevable dun **complément
de cotisations**.
pour le calcul des cotisations provisionnelles alors vous serez redevable dun
**complément de cotisations**.
Ce simulateur calcule les cotisations dites définitives sur la base des revenus réels de votre

View File

@ -1,7 +1,16 @@
entreprise:
valeur: oui
description: |
Le contrat lie une entreprise, identifiée par un code SIREN, et un employé.
entreprise . SIREN:
description: |
Le numéro Siren est un numéro de 9 chiffres unique pour chaque entreprise. Ex : 401237780
type: texte
entreprise . nom:
type: texte
entreprise . date de création:
question: Quelle est votre date de début d'activité ?
par défaut: 01/01/2021
@ -199,7 +208,7 @@ entreprise . chiffre d'affaires . franchise de TVA . seuil service:
entreprise . chiffre d'affaires . franchise de TVA . dépassement:
type: notification
valeur:
formule:
une de ces conditions:
- chiffre d'affaires > seuil vente + seuil service
- vente restauration hébergement > seuil vente
@ -226,120 +235,6 @@ entreprise . résultat fiscal:
- (- charges)
- (- charges . dirigeant)
entreprise . imposition:
question: Comment l'entreprise est-elle imposée ?
description: |
Indiquez si le régime dimposition des revenus liés à lactivité indépendante relèvent :
- de limpôt sur le revenu : les bénéfices de lentreprise sont imposés directement auprès du travailleur indépendant, au barème progressif de limpôt sur le revenu.
- de limpôt sur les sociétés : les bénéfices de lentreprise sont imposés au nom de la société, au taux de limpôt sur les sociétés.
formule:
une possibilité:
choix obligatoire: oui
possibilités:
- IR
- IS
par défaut: "'IR'"
entreprise . imposition . IR:
valeur: imposition = 'IR'
titre: Impôt sur le revenu
entreprise . imposition . IR . micro-fiscal:
rend non applicable: dirigeant . indépendant . cotisations facultatives
question: Avez-vous opté pour le régime micro-fiscal ?
description: |
Avec le régime micro fiscal, les charges déductibles sont estimées forfaitairement,en fonction dun pourcentage du chiffre daffaires. Ce pourcentage dépend du type dactivité : 71% pour les activités de vente, restauration et hébergement (location de meublé de tourisme classé et chambre dhôte), 50% pour les prestations de service commerciales ou artisanales, 34% pour les activités libérales.
Cette option permet de simplifier votre comptabilité et peut être avantageuse en termes de revenu imposable et soumis à cotisations et contributions sociales dans le cas où vos charges de fonctionnement sont faibles.
par défaut: non
entreprise . imposition . IR . micro-fiscal . revenu abattu:
remplace: résultat fiscal
résoudre la référence circulaire: oui
titre: abattement forfaitaire micro-fiscal
description: |
Le micro-entrepreneur est dispensé d'établir une déclaration professionnelle de bénéfices au titre des BNC ou BIC.
Il lui suffit de porter dans la déclaration complémentaire de revenu (n°2042-C Pro) le montant annuel du chiffre d'affaires brut (BIC) ou des recettes (BNC).
somme:
- entreprise . chiffre d'affaires . vente restauration hébergement
- entreprise . chiffre d'affaires . service BIC
- entreprise . chiffre d'affaires . service BNC
abattement:
produit:
composantes:
- assiette: entreprise . chiffre d'affaires . vente restauration hébergement
taux: 71%
- assiette: entreprise . chiffre d'affaires . service BIC
taux: 50%
- assiette: entreprise . chiffre d'affaires . service BNC
taux: 34%
plancher:
variations:
- si: entreprise . activité . mixte
alors: 610 €/an
- sinon: 305 €/an
entreprise . imposition . IR . micro-fiscal . alerte seuil dépassés:
type: notification
sévérité: avertissement
formule: chiffre d'affaires . seuil micro dépassé
description: Le seuil annuel de chiffre d'affaires pour le régime micro-fiscal est dépassé. [En savoir plus](/documentation/entreprise/chiffre-d'affaires/seuil-micro-dépassé)
entreprise . chiffre d'affaires . seuil micro dépassé:
applicable si: imposition . IR
description: |
Le statut de micro-entreprise s'applique tant que le chiffre d'affaires annuel (effectivement encaissé au cours de l'année civile) ne dépasse pas les seuils du régime fiscal de la micro-entreprise.
En cas de dépassement **sur deux années consécutives**, l'entreprise bascule automatiquement dans le régime de [l'entreprise individuelle](/simulateurs/indépendant).
À la fin de la première année d'activité, le CA est proratisé par rapport à la durée d'activité.
Exemple :
> Un contribuable crée une entreprise le 1er août et encaisse des recettes HT de `50 000 €` au cours des cinq mois d'activité de sa première année civile d'exploitation.
> Les recettes de cette première année civile sont ajustées *prorata temporis* pour les comparer au plafond :
>
> `50 000€ x (365/153) = 119 280 €`
Les charges ne sont pas déductibles pour le calcul du plafond (comme pour le calcul des cotisations)
### Multi-activité
Lorsqu'un entrepreneur exerce 2 activités au sein de sa micro-entreprise, le
seuil de chiffre daffaires à respecter nest pas pour autant doublé. En
effet l'exercice de plusieurs activités avec la même micro-entreprise
naugmente pas les seuils.
références:
Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F32353
Article 50-0 du Code général des impôts: https://www.legifrance.gouv.fr/affichCode.do?idSectionTA=LEGISCTA000006199553&cidTexte=LEGITEXT000006069577
Bofip (dépassement micro-bnc): https://bofip.impots.gouv.fr/bofip/4807-PGP.html
Bofip (dépassement micro-bic): https://bofip.impots.gouv.fr/bofip/1802-PGP.html
autoentrepreneur.urssaf.fr: https://www.autoentrepreneur.urssaf.fr/portail/accueil/une-question/questions-frequentes.html
unité: €/an
# TODO: les seuils micro sont dupliqués à plusieurs endroits (artiste-auteur .
# revenus . BNC . contrôle micro-bnc, tableau de la comparaison de régime,
# économie collaborative). Il faudrait référencer la même valeur partout où
# elle est utilisée.
une de ces conditions:
- entreprise . chiffre d'affaires > 176200 €/an
- entreprise . chiffre d'affaires . service > 72600 €/an
entreprise . imposition . IR . information sur le report de déficit:
non applicable si: micro-fiscal
type: notification
formule: résultat fiscal < 0 €/an
description: |
Lorsque votre résultat fiscal est négatif, ce dernier vient réduire le revenu imposables du foyer fiscal.
Un déficit peut être imputé jusqu'à 6 ans après sa réalisation.
[Voir les règles fiscales détaillées](https://bofip.impots.gouv.fr/bofip/2003-PGP.html/identifiant%3DBOI-BIC-DEF-20-10-20170301)
références:
bofip: https://bofip.impots.gouv.fr/bofip/2003-PGP.html/identifiant%3DBOI-BIC-DEF-20-10-20170301
entreprise . exercice: oui
entreprise . exercice . début:
type: date
@ -380,123 +275,6 @@ entreprise . exercice . durée maximale:
formule: durée >= 24 mois
description: La durée maximale d'un exercice comptable est de 24 mois.
entreprise . imposition . IS:
valeur: imposition = 'IS'
titre: Impôt sur les sociétés
entreprise . imposition . IS . résultat imposable:
titre: Résultat de l'exercice
résumé: Imposable à l'impôt sur les sociétés
valeur: résultat fiscal
entreprise . imposition . IS . information sur le report de déficit:
type: notification
formule: résultat imposable < 0 €/an
# TODO: Support des références dans les notifications
description: |
Les déficits subits au cours d'un exercice peuvent être reportés sur les exercices suivants (report en avant), ou sur le seul exercice précédent (report en arrière).
entreprise . imposition . IS . résultat net:
résumé: Après déduction des charges et de l'impôt sur les société
somme:
- chiffre d'affaires
- (- charges)
- (- dirigeant . rémunération . totale)
- (- impôt sur les sociétés)
par défaut: 0
entreprise . imposition . IS . impôt sur les sociétés:
unité: €/an
formule:
barème:
assiette: résultat imposable
multiplicateur: prorata temporis
variations:
- si: exercice . début >= 01/01/2022
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 25%
- si: exercice . début >= 01/01/2021
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 26.5%
- si: exercice . début >= 01/01/2020
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 28%
- si: exercice . début >= 01/01/2019
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 28%
plafond: plafond taux réduit 2
- taux: 31%
- si: exercice . début >= 01/01/2018
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 28%
plafond: plafond taux réduit 2
- taux: 33.3333%
arrondi: oui
références:
Fiche impots.gouv.fr: https://www.impots.gouv.fr/portail/international-professionnel/impot-sur-les-societes
Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23575
entreprise . imposition . IS . impôt sur les sociétés . plafond taux réduit 1:
applicable si: éligible taux réduit
valeur: 38120 €/an
entreprise . imposition . IS . impôt sur les sociétés . plafond taux réduit 2:
applicable si: éligible taux réduit
valeur: 500000 €/an
entreprise . imposition . IS . impôt sur les sociétés . éligible taux réduit:
formule:
toutes ces conditions:
- chiffre d'affaires <= 7630 k€/an * prorata temporis
- nom: capital détenu au moins à 75 pourcents par des personnes physiques
valeur: oui
entreprise . imposition . IS . impôt sur les sociétés . prorata temporis:
description: |
Lorsque la durée de lexercice n'est pas égale à un an, on pro-ratise les
plafonds utilisés dans le barème de l'impôt sur les sociétés.
unité: '%'
formule: exercice . durée / 1 an
# TODO: c'est un peu plus subtil que cela : « En cas dexercice ouvert ou
# arrêté en cours de mois calendaire, le nombre de jours résiduels concourt à
# la détermination du rapport pour un montant égal au rapport existant entre
# ce nombre et 30. »
références:
Bofip: https://bofip.impots.gouv.fr/bofip/2065-PGP.html/identifiant%3DBOI-IS-LIQ-20-20-20180801
entreprise . imposition . IS . impôt sur les sociétés . contribution sociale:
# description: |
# La contribution sociale sur les bénéfices est un impôt distinct de l'impôt sur les sociétés. Son montant n'est pas déductible des résultats.
# L'assiette bénéficie d'un abattement important, et seules les entreprises réalisant plus de 2,3 millions d'euros de bénéficie sont concernées par cette contribution.
description: |
La contribution sociale sur les bénéfices est un impôt distinct de limpôt sur les sociétés. Son montant nest pas déductible des résultats.
Lassiette bénéficie dun abattement important, et seules les entreprises réalisant plus de 2,3 millions deuros de bénéfices sont concernées par cette contribution.
formule:
produit:
taux: 3.3%
assiette:
valeur: impôt sur les sociétés
abattement: 763000 €/an * prorata temporis
références:
Bofip: https://bofip.impots.gouv.fr/bofip/3492-PGP.html/identifiant%3DBOI-IS-AUT-10-20-20130318
entreprise . charges:
synonymes:
- charges d'exploitation
@ -865,6 +643,13 @@ entreprise . activité . débit de tabac:
par défaut: non
établissement:
formule: oui
description: |
Le salarié travaille dans un établissement de l'entreprise, identifié par un code SIRET.
établissement . SIRET:
type: texte
applicable si: entreprise . SIREN
description: |
Le salarié travaille dans un établissement de l'entreprise, identifié par un code SIRET.
@ -931,3 +716,9 @@ entreprise . activité . débit de tabac:
applicable si: entreprise . date de création < 01/2015
question: Votre établissement bénéficie-t-il du dispositif zone franche urbaine (ZFU) ?
par défaut: non
établissement . ZFU . durée d'implantation en fin d'année:
formule:
durée:
depuis: entreprise . date de création
jusqu'à: 31/12/2019

View File

@ -0,0 +1,235 @@
entreprise . imposition:
question: Comment l'entreprise est-elle imposée ?
description: |
Indiquez si le régime dimposition des revenus liés à lactivité indépendante relèvent :
- de limpôt sur le revenu : les bénéfices de lentreprise sont imposés directement auprès du travailleur indépendant, au barème progressif de limpôt sur le revenu.
- de limpôt sur les sociétés : les bénéfices de lentreprise sont imposés au nom de la société, au taux de limpôt sur les sociétés.
une possibilité:
choix obligatoire: oui
possibilités:
- IR
- IS
par défaut:
variations:
- si: catégorie juridique . EI
alors: "'IR'"
- sinon: "'IS'"
entreprise . imposition . IR:
applicable si: imposition = 'IR'
titre: Impôt sur le revenu
valeur: oui
entreprise . imposition . IR . micro-fiscal:
rend non applicable: dirigeant . indépendant . cotisations facultatives
question: Avez-vous opté pour le régime micro-fiscal ?
description: |
Avec le régime micro fiscal, les charges déductibles sont estimées forfaitairement,en fonction dun pourcentage du chiffre daffaires. Ce pourcentage dépend du type dactivité : 71% pour les activités de vente, restauration et hébergement (location de meublé de tourisme classé et chambre dhôte), 50% pour les prestations de service commerciales ou artisanales, 34% pour les activités libérales.
Cette option permet de simplifier votre comptabilité et peut être avantageuse en termes de revenu imposable et soumis à cotisations et contributions sociales dans le cas où vos charges de fonctionnement sont faibles.
par défaut: non
entreprise . imposition . IR . micro-fiscal . revenu abattu:
remplace: résultat fiscal
résoudre la référence circulaire: oui
titre: abattement forfaitaire micro-fiscal
description: |
Le micro-entrepreneur est dispensé d'établir une déclaration professionnelle de bénéfices au titre des BNC ou BIC.
Il lui suffit de porter dans la déclaration complémentaire de revenu (n°2042-C Pro) le montant annuel du chiffre d'affaires brut (BIC) ou des recettes (BNC).
somme:
- entreprise . chiffre d'affaires . vente restauration hébergement
- entreprise . chiffre d'affaires . service BIC
- entreprise . chiffre d'affaires . service BNC
abattement:
produit:
composantes:
- assiette: entreprise . chiffre d'affaires . vente restauration hébergement
taux: 71%
- assiette: entreprise . chiffre d'affaires . service BIC
taux: 50%
- assiette: entreprise . chiffre d'affaires . service BNC
taux: 34%
plancher:
variations:
- si: entreprise . activité . mixte
alors: 610 €/an
- sinon: 305 €/an
entreprise . imposition . IR . micro-fiscal . alerte seuil dépassés:
type: notification
sévérité: avertissement
formule: chiffre d'affaires . seuil micro dépassé
description: Le seuil annuel de chiffre d'affaires pour le régime micro-fiscal est dépassé. [En savoir plus](/documentation/entreprise/chiffre-d'affaires/seuil-micro-dépassé)
entreprise . chiffre d'affaires . seuil micro dépassé:
applicable si: imposition . IR
description: |
Le statut de micro-entreprise s'applique tant que le chiffre d'affaires annuel (effectivement encaissé au cours de l'année civile) ne dépasse pas les seuils du régime fiscal de la micro-entreprise.
En cas de dépassement **sur deux années consécutives**, l'entreprise bascule automatiquement dans le régime de [l'entreprise individuelle](/simulateurs/indépendant).
À la fin de la première année d'activité, le CA est proratisé par rapport à la durée d'activité.
Exemple :
> Un contribuable crée une entreprise le 1er août et encaisse des recettes HT de `50 000 €` au cours des cinq mois d'activité de sa première année civile d'exploitation.
> Les recettes de cette première année civile sont ajustées *prorata temporis* pour les comparer au plafond :
>
> `50 000€ x (365/153) = 119 280 €`
Les charges ne sont pas déductibles pour le calcul du plafond (comme pour le calcul des cotisations)
### Multi-activité
Lorsqu'un entrepreneur exerce 2 activités au sein de sa micro-entreprise, le
seuil de chiffre daffaires à respecter nest pas pour autant doublé. En
effet l'exercice de plusieurs activités avec la même micro-entreprise
naugmente pas les seuils.
références:
Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F32353
Article 50-0 du Code général des impôts: https://www.legifrance.gouv.fr/affichCode.do?idSectionTA=LEGISCTA000006199553&cidTexte=LEGITEXT000006069577
Bofip (dépassement micro-bnc): https://bofip.impots.gouv.fr/bofip/4807-PGP.html
Bofip (dépassement micro-bic): https://bofip.impots.gouv.fr/bofip/1802-PGP.html
autoentrepreneur.urssaf.fr: https://www.autoentrepreneur.urssaf.fr/portail/accueil/une-question/questions-frequentes.html
unité: €/an
# TODO: les seuils micro sont dupliqués à plusieurs endroits (artiste-auteur .
# revenus . BNC . contrôle micro-bnc, tableau de la comparaison de régime,
# économie collaborative). Il faudrait référencer la même valeur partout où
# elle est utilisée.
une de ces conditions:
- entreprise . chiffre d'affaires > 176200 €/an
- entreprise . chiffre d'affaires . service > 72600 €/an
entreprise . imposition . IR . information sur le report de déficit:
non applicable si: micro-fiscal
type: notification
formule: résultat fiscal < 0 €/an
description: |
Lorsque votre résultat fiscal est négatif, ce dernier vient réduire le revenu imposables du foyer fiscal.
Un déficit peut être imputé jusqu'à 6 ans après sa réalisation.
[Voir les règles fiscales détaillées](https://bofip.impots.gouv.fr/bofip/2003-PGP.html/identifiant%3DBOI-BIC-DEF-20-10-20170301)
références:
bofip: https://bofip.impots.gouv.fr/bofip/2003-PGP.html/identifiant%3DBOI-BIC-DEF-20-10-20170301
entreprise . imposition . IS:
applicable si: imposition = 'IS'
valeur: oui
titre: Impôt sur les sociétés
entreprise . imposition . IS . résultat imposable:
titre: Résultat de l'exercice
résumé: Imposable à l'impôt sur les sociétés
valeur: résultat fiscal
entreprise . imposition . IS . information sur le report de déficit:
type: notification
formule: résultat imposable < 0 €/an
# TODO: Support des références dans les notifications
description: |
Les déficits subits au cours d'un exercice peuvent être reportés sur les exercices suivants (report en avant), ou sur le seul exercice précédent (report en arrière).
entreprise . imposition . IS . résultat net:
résumé: Après déduction des charges et de l'impôt sur les société
somme:
- chiffre d'affaires
- (- charges)
- (- dirigeant . rémunération . totale)
- (- impôt sur les sociétés)
par défaut: 0
entreprise . imposition . IS . impôt sur les sociétés:
unité: €/an
formule:
barème:
assiette: résultat imposable
multiplicateur: prorata temporis
variations:
- si: exercice . début >= 01/01/2022
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 25%
- si: exercice . début >= 01/01/2021
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 26.5%
- si: exercice . début >= 01/01/2020
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 28%
- si: exercice . début >= 01/01/2019
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 28%
plafond: plafond taux réduit 2
- taux: 31%
- si: exercice . début >= 01/01/2018
alors:
tranches:
- taux: 15%
plafond: plafond taux réduit 1
- taux: 28%
plafond: plafond taux réduit 2
- taux: 33.3333%
arrondi: oui
références:
Fiche impots.gouv.fr: https://www.impots.gouv.fr/portail/international-professionnel/impot-sur-les-societes
Fiche service-public.fr: https://www.service-public.fr/professionnels-entreprises/vosdroits/F23575
entreprise . imposition . IS . impôt sur les sociétés . plafond taux réduit 1:
applicable si: éligible taux réduit
valeur: 38120 €/an
entreprise . imposition . IS . impôt sur les sociétés . plafond taux réduit 2:
applicable si: éligible taux réduit
valeur: 500000 €/an
entreprise . imposition . IS . impôt sur les sociétés . éligible taux réduit:
formule:
toutes ces conditions:
- chiffre d'affaires <= 7630 k€/an * prorata temporis
- nom: capital détenu au moins à 75 pourcents par des personnes physiques
valeur: oui
entreprise . imposition . IS . impôt sur les sociétés . prorata temporis:
description: |
Lorsque la durée de lexercice n'est pas égale à un an, on pro-ratise les
plafonds utilisés dans le barème de l'impôt sur les sociétés.
unité: '%'
formule: exercice . durée / 1 an
# TODO: c'est un peu plus subtil que cela : « En cas dexercice ouvert ou
# arrêté en cours de mois calendaire, le nombre de jours résiduels concourt à
# la détermination du rapport pour un montant égal au rapport existant entre
# ce nombre et 30. »
références:
Bofip: https://bofip.impots.gouv.fr/bofip/2065-PGP.html/identifiant%3DBOI-IS-LIQ-20-20-20180801
entreprise . imposition . IS . impôt sur les sociétés . contribution sociale:
# description: |
# La contribution sociale sur les bénéfices est un impôt distinct de l'impôt sur les sociétés. Son montant n'est pas déductible des résultats.
# L'assiette bénéficie d'un abattement important, et seules les entreprises réalisant plus de 2,3 millions d'euros de bénéficie sont concernées par cette contribution.
description: |
La contribution sociale sur les bénéfices est un impôt distinct de limpôt sur les sociétés. Son montant nest pas déductible des résultats.
Lassiette bénéficie dun abattement important, et seules les entreprises réalisant plus de 2,3 millions deuros de bénéfices sont concernées par cette contribution.
formule:
produit:
taux: 3.3%
assiette:
valeur: impôt sur les sociétés
abattement: 763000 €/an * prorata temporis
références:
Bofip: https://bofip.impots.gouv.fr/bofip/3492-PGP.html/identifiant%3DBOI-IS-AUT-10-20-20130318

View File

@ -0,0 +1,79 @@
entreprise . catégorie juridique:
description: |
Les catégories juridiques accessibles via l'API SIRENE
par défaut: non
une possibilité:
choix obligatoire: oui
possibilités:
- EI
- SARL
- SAS
- autre
références:
liste des catégories juridique de l'INSEE: https://www.insee.fr/fr/information/2028129
note: On se base ici sur les catégories juridiques définies par l'INSEE
entreprise . catégorie juridique . EI:
titre: 'EI ou EIRL'
valeur: catégorie juridique = 'EI'
entreprise . catégorie juridique . EI . auto-entrepreneur:
question: Êtes-vous auto-entrepreneur ?
remplace:
règle: imposition . IR . micro-fiscal
par: oui
par défaut: oui
entreprise . catégorie juridique . EI . responsabilité limité:
non applicable si: auto-entrepreneur # pour simplifier
titre: 'EIRL'
question: Votre entreprise est-elle une EIRL ?
par défaut: non
entreprise . catégorie juridique . EI . imposition entreprise:
non applicable si: responsabilité limité
remplace: entreprise . imposition
valeur: "'IR'"
entreprise . catégorie juridique . SARL:
titre: 'EURL ou SARL'
valeur: catégorie juridique = 'SARL'
entreprise . catégorie juridique . SARL . unipersonnelle:
titre: EURL
question: Votre entreprise est-elle une EURL ?
par défaut: oui
entreprise . catégorie juridique . SELARL:
titre: 'SELARL'
valeur: catégorie juridique = 'SELARL'
remplace:
- règle: entreprise . activité
par: "'libérale'"
- règle: entreprise . activité . libérale réglementée
par: oui
entreprise . catégorie juridique . SELAS:
titre: 'SELARL'
valeur: catégorie juridique = 'SELAS'
remplace:
- règle: entreprise . activité
par: "'libérale'"
- règle: entreprise . activité . libérale réglementée
par: oui
entreprise . catégorie juridique . SAS:
remplace: #TODO nous ne gérons pas encore les SASU à l'IR
règle: entreprise . imposition
par: "'IS'"
titre: 'SASU ou SAS'
valeur: catégorie juridique = 'SAS'
entreprise . catégorie juridique . SAS . unipersonnelle:
titre: 'SASU'
question: Votre entreprise est-elle une SASU ?
par défaut: oui
entreprise . catégorie juridique . autre:
valeur: catégorie juridique = 'autre'

View File

@ -356,6 +356,7 @@ dirigeant . indépendant . PL . CNAVPL:
l'organisme qui fédère les différentes caisses existantes (CIPAV, CARPIMKO,
CARCDSF, CAVEC etc..)
non applicable si: régime général
valeur: oui
dirigeant . indépendant . PL . CNAVPL . retraite:
titre: retraite de base (CNAVPL)

View File

@ -1255,6 +1255,7 @@ contrat salarié . stage . avertissement:
description: >-
Une convention de stage **n'est pas un contrat de travail**, et ne peut pas être conclue pour réaliser une tâche régulière correspondant à un poste de travail permanent, ou à un accroissement temporaire de l'activité de l'entreprise. [Code de l'éducation - Article L124-7](https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000029234119&cidTexte=LEGITEXT000006071191)
Par ailleurs, une entreprise de moins de 20 salariés ne peut pas accueillir plus de **3&nbsp;stagiaires**, et pas plus de **15% de leffectif** pour les entreprises de plus de 20 salariés.
contrat salarié . stage . contrôle gratification minimale:
@ -3419,7 +3420,7 @@ contrat salarié . régime des impatriés:
contrat salarié . régime des impatriés . information:
type: notification
formule: oui
description: >-
description: |-
Pour bénéficier de l'exonération de cotisations vieillesse, il faut remplir les conditions suivantes :
- Pouvoir justifier d'une contribution minimale versée ailleurs pour une assurance vieillesse
- Ne pas avoir été affilié, au cours des cinq années civiles précédant celle de la prise de fonctions, à un régime français obligatoire d'assurance vieillesse, sauf pour des activités accessoires, de caractère saisonnier ou pour les études.

View File

@ -32,7 +32,9 @@
},
"resolutions": {
"prettier": "^2.5.1",
"@mui/styled-engine": "npm:@mui/styled-engine-sc@latest"
"@mui/styled-engine": "npm:@mui/styled-engine-sc@latest",
"publicodes": "portal:/home/johan/Projets/publicodes/packages/core",
"publicodes-react": "portal:/home/johan/Projets/publicodes/packages/react-ui"
},
"packageManager": "yarn@3.2.0",
"devDependencies": {

View File

@ -55,7 +55,7 @@ describe(`Navigation to income simulator using company name (${
})
it('should allow to retrieve company and show link corresponding to the legal status', function () {
cy.contains(
fr ? 'Rechercher une entreprise ' : 'Search for a company '
fr ? 'Rechercher votre entreprise ' : 'Search for a company '
).click()
cy.get('input').first().type('menoz')
cy.contains('834364291').click()
@ -66,7 +66,7 @@ describe(`Navigation to income simulator using company name (${
})
it('should allow auto entrepreneur to access the corresponding income simulator', function () {
cy.contains(
fr ? 'Rechercher une entreprise ' : 'Search for a company '
fr ? 'Rechercher votre entreprise ' : 'Search for a company '
).click()
cy.get('input').first().type('johan girod')
cy.contains('834825614').click()

View File

@ -17,7 +17,7 @@ describe('Champs localisation (simulateur salarié)', function () {
.type('Steenvoorde')
cy.contains('Steenvoorde (59114)').click({ force: true })
cy.contains('Suivant').click({ force: true })
cy.contains('Voir mes paramètres').click({ force: true })
cy.contains('Voir ma situation').click({ force: true })
cy.contains('Steenvoorde')
})
})

View File

@ -21,7 +21,7 @@ describe('Partage (simulateur salarié)', function () {
.invoke('val')
.should('match', /1[\s]539[\s]€/)
cy.contains('Voir mes paramètres').click()
cy.contains('Voir ma situation').click()
cy.contains('CDD')
})
it('should set URL from input value', function () {

View File

@ -11,7 +11,7 @@ describe('Simulateur salarié', function () {
describe('part time contract', function () {
before(function () {
cy.get('button').contains('SMIC').click()
cy.contains('Voir mes paramètres').click()
cy.contains('Voir ma situation').click()
cy.get('div[role="dialog"]').contains('Temps partiel').click()
cy.contains('Oui').click()
cy.wait(100)

View File

@ -1,10 +1,3 @@
import rules from 'modele-social'
import { StrictMode, useContext, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { Redirect, Route, Switch } from 'react-router-dom'
import styled, { css } from 'styled-components'
import Footer from '@/components/layout/Footer/Footer'
import Header from '@/components/layout/Header'
import Route404 from '@/components/Route404'
@ -18,9 +11,17 @@ import {
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Container, Spacing } from '@/design-system/layout'
import {
companySituationSelector,
configSituationSelector,
situationSelector,
} from '@/selectors/simulationSelectors'
import rules from 'modele-social'
import { StrictMode, useContext, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { Redirect, Route, Switch } from 'react-router-dom'
import styled, { css } from 'styled-components'
import Accessibilité from './pages/Accessibilité'
import Budget from './pages/Budget/Budget'
import Créer from './pages/Creer'
@ -39,9 +40,13 @@ import Provider, { ProviderProps } from './Provider'
import redirects from './redirects'
import { constructLocalizedSitePath } from './sitePaths'
import {
retrievePersistedInFranceApp,
setupInFranceAppPersistence,
} from './storage/persistInFranceApp'
retrievePersistedChoixStatutJuridique,
setupChoixStatutJuridiquePersistence,
} from './storage/persistChoixStatutJuridique'
import {
retrievePersistedCompanySituation,
setupCompanySituationPersistence,
} from './storage/persistCompanySituation'
import { setupSimulationPersistence } from './storage/persistSimulation'
type RootProps = {
@ -71,11 +76,13 @@ export default function Root({
basename={basename}
sitePaths={paths}
onStoreCreated={(store) => {
setupInFranceAppPersistence(store)
setupChoixStatutJuridiquePersistence(store)
setupCompanySituationPersistence(store)
setupSimulationPersistence(store)
}}
initialStore={{
inFranceApp: retrievePersistedInFranceApp(),
choixStatutJuridique: retrievePersistedChoixStatutJuridique(),
companySituation: retrievePersistedCompanySituation(),
}}
>
<EngineProvider value={engine}>
@ -87,14 +94,16 @@ export default function Root({
}
const Router = () => {
const userSituation = useSelector(situationSelector)
const simulatorSituation = useSelector(situationSelector)
const configSituation = useSelector(configSituationSelector)
const companySituation = useSelector(companySituationSelector)
const situation = useMemo(
() => ({
...companySituation,
...configSituation,
...userSituation,
...simulatorSituation,
}),
[configSituation, userSituation]
[configSituation, simulatorSituation, companySituation]
)
return (
<SituationProvider situation={situation}>

View File

@ -1,9 +1,9 @@
import { DottedName } from 'modele-social'
import Engine from 'publicodes'
import { SimulationConfig, Situation } from '@/reducers/rootReducer'
import { SimulationConfig } from '@/reducers/rootReducer'
import { CompanyCreationAction } from './companyCreationChecklistActions'
import { CompanyStatusAction } from './companyStatusActions'
import { ActionExistingCompany } from './existingCompanyActions'
import { CompanyActions } from './companyActions'
import { HiringChecklistAction } from './hiringChecklistAction'
export type Action =
@ -23,7 +23,7 @@ export type Action =
>
| CompanyCreationAction
| CompanyStatusAction
| ActionExistingCompany
| CompanyActions
| HiringChecklistAction
export const resetSimulation = () =>
@ -46,16 +46,11 @@ export const stepAction = (step: DottedName, source?: string) =>
source,
} as const)
export const setSimulationConfig = (
config: SimulationConfig,
url: string,
initialSituation?: Situation
) =>
export const setSimulationConfig = (config: SimulationConfig, url: string) =>
({
type: 'SET_SIMULATION',
url,
config,
initialSituation,
} as const)
export const setActiveTarget = (targetName: DottedName) =>
@ -72,7 +67,7 @@ export const updateSituation = (fieldName: DottedName, value: unknown) =>
} as const)
export const batchUpdateSituation = (
situation: Parameters<Engine<DottedName>['setSituation']>[0]
situation: NonNullable<Parameters<Engine<DottedName>['setSituation']>[0]>
) =>
({
type: 'BATCH_UPDATE_SITUATION',

View File

@ -0,0 +1,24 @@
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { ApiCommuneJson } from '@/components/conversation/select/SelectCommune'
export type CompanyActions = ReturnType<
typeof resetCompany | typeof setCompany | typeof addCommuneDetails
>
export const resetCompany = () =>
({
type: 'COMPANY::RESET',
} as const)
export const addCommuneDetails = (details: ApiCommuneJson) =>
({
type: 'COMPANY::ADD_COMMUNE_DETAILS',
details,
} as const)
export const setCompany = (entreprise: FabriqueSocialEntreprise) => {
return {
type: 'COMPANY::SET_EXISTING_COMPANY',
entreprise,
} as const
}

View File

@ -6,7 +6,7 @@ import { useHistory } from 'react-router'
import { useNextQuestionUrl } from '@/selectors/companyStatusSelectors'
import { LegalStatusRequirements } from '@/types/companyTypes'
import { Action } from './actions'
import { addCommuneDetails, setCompany } from './existingCompanyActions'
import { addCommuneDetails, setCompany } from './companyActions'
export type CompanyStatusAction = ReturnType<
| typeof isSoleProprietorship
@ -78,21 +78,18 @@ const fetchCommuneDetails = async function (codeCommune: string) {
const response = await fetch(
`https://geo.api.gouv.fr/communes/${codeCommune}?fields=departement,region`
)
return await response.json()
return (await response.json()) as ApiCommuneJson
}
export const useSetEntreprise = () => {
const dispatch = useDispatch()
return async (entreprise: FabriqueSocialEntreprise) => {
return (entreprise: FabriqueSocialEntreprise) => {
if (entreprise === null) {
return
}
dispatch(setCompany(entreprise))
if (entreprise.firstMatchingEtablissement.is_siege) {
const communeDetails: ApiCommuneJson = await fetchCommuneDetails(
entreprise.firstMatchingEtablissement.codeCommuneEtablissement
)
dispatch(addCommuneDetails(communeDetails))
}
void fetchCommuneDetails(
entreprise.firstMatchingEtablissement.codeCommuneEtablissement
).then((communeDetails) => dispatch(addCommuneDetails(communeDetails)))
}
}

View File

@ -1,42 +0,0 @@
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { ApiCommuneJson } from '@/components/conversation/select/SelectCommune'
export type ActionExistingCompany = ReturnType<
| typeof specifyIfAutoEntrepreneur
| typeof specifyIfDirigeantMajoritaire
| typeof resetEntreprise
| typeof setCompany
| typeof addCommuneDetails
>
export const specifyIfAutoEntrepreneur = (isAutoEntrepreneur: boolean) =>
({
type: 'EXISTING_COMPANY::SPECIFY_AUTO_ENTREPRENEUR',
isAutoEntrepreneur,
} as const)
export const specifyIfDirigeantMajoritaire = (
isDirigeantMajoritaire: boolean
) =>
({
type: 'EXISTING_COMPANY::SPECIFY_DIRIGEANT_MAJORITAIRE',
isDirigeantMajoritaire,
} as const)
export const resetEntreprise = () =>
({
type: 'EXISTING_COMPANY::RESET',
} as const)
export const addCommuneDetails = (details: ApiCommuneJson) =>
({
type: 'EXISTING_COMPANY::ADD_COMMUNE_DETAILS',
details,
} as const)
export const setCompany = (entreprise: FabriqueSocialEntreprise) => {
return {
type: 'EXISTING_COMPANY::SET_COMPANY',
entreprise,
} as const
}

View File

@ -108,7 +108,6 @@ function ActivitéMixte() {
const rule = useEngine().getRule('entreprise . activité . mixte')
const defaultChecked =
useEngine().evaluate('entreprise . activité . mixte').nodeValue === true
const onMixteChecked = useCallback(
(checked: boolean) => {
dispatch(
@ -124,21 +123,23 @@ function ActivitéMixte() {
)
return (
<StyledActivitéMixteContainer>
<Trans>
<Switch
size="XS"
defaultSelected={defaultChecked}
onChange={onMixteChecked}
light
>
Activité mixte
</Switch>
</Trans>
<ButtonHelp type="aide" title={rule.title} light>
<Markdown>{rule.rawNode.description ?? ''}</Markdown>
</ButtonHelp>
</StyledActivitéMixteContainer>
<div key={defaultChecked}>
<StyledActivitéMixteContainer>
<Trans>
<Switch
size="XS"
defaultSelected={defaultChecked}
onChange={onMixteChecked}
light
>
Activité mixte
</Switch>
</Trans>
<ButtonHelp type="aide" title={rule.title} light>
<Markdown>{rule.rawNode.description ?? ''}</Markdown>
</ButtonHelp>
</StyledActivitéMixteContainer>
</div>
)
}

View File

@ -5,7 +5,6 @@ import Engine, {
isNotApplicable,
isNotYetDefined,
PublicodesExpression,
UNSAFE_isNotApplicable,
} from 'publicodes'
import React from 'react'
import { useTranslation } from 'react-i18next'
@ -94,7 +93,9 @@ export function WhenApplicable({
children: React.ReactNode
}) {
const engine = useEngine()
if (UNSAFE_isNotApplicable(engine, dottedName)) return null
if (engine.evaluate(dottedName).nodeValue == null) {
return null
}
return <>{children}</>
}
export function WhenNotApplicable({
@ -105,7 +106,9 @@ export function WhenNotApplicable({
children: React.ReactNode
}) {
const engine = useEngine()
if (!UNSAFE_isNotApplicable(engine, dottedName)) return null
if (engine.evaluate(dottedName).nodeValue !== null) {
return null
}
return <>{children}</>
}
@ -122,3 +125,17 @@ export function WhenAlreadyDefined({
}
return <>{children}</>
}
export function WhenNotAlreadyDefined({
dottedName: dottedName,
children,
}: {
dottedName: DottedName
children: React.ReactNode
}) {
const engine = useEngine()
if (!isNotYetDefined(engine.evaluate(dottedName).nodeValue)) {
return null
}
return <>{children}</>
}

View File

@ -11,6 +11,7 @@ import styled from 'styled-components'
import RuleLink from './RuleLink'
import Emoji from './utils/Emoji'
import { Markdown } from './utils/markdown'
import { Message } from '@/design-system'
// To add a new notification to a simulator, you should create a publicodes rule
// with the "type: notification" attribute. The display can be customized with
@ -62,18 +63,23 @@ export default function Notifications() {
).filter(({ dottedName }) => !hiddenNotifications?.includes(dottedName))
return (
<NotificationsContainer id="notificationsBlock">
<div
css={`
margin-top: 1rem;
`}
>
{messages.map(({ sévérité, dottedName, résumé, description }) => (
<Notification className="notification" key={dottedName}>
<Emoji emoji={sévérité == 'avertissement' ? '⚠️' : '💁🏻'} />
<NotificationContent className="notificationText">
<Markdown>{résumé ?? description ?? ''}</Markdown>{' '}
{résumé && (
<RuleLink dottedName={dottedName as DottedName}>
<Trans>En savoir plus</Trans>
</RuleLink>
)}
</NotificationContent>
<Message
icon
type={sévérité == 'avertissement' ? 'info' : 'primary'}
key={dottedName}
>
<Markdown>{résumé ?? description ?? ''}</Markdown>{' '}
{résumé && (
<RuleLink dottedName={dottedName as DottedName}>
<Trans>En savoir plus</Trans>
</RuleLink>
)}
<HideButton
className="hide"
aria-label="close"
@ -81,47 +87,12 @@ export default function Notifications() {
>
×
</HideButton>
</Notification>
</Message>
))}
</NotificationsContainer>
</div>
)
}
const NotificationsContainer = styled.ul`
list-style-type: none;
padding: 0;
`
const Notification = styled.li`
display: flex;
position: relative;
flex-direction: row;
align-items: center;
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.colors.bases.primary[100]};
border: 2px solid;
border-color: ${({ theme }) => theme.colors.bases.primary[500]};
border-radius: 0.375rem;
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
& img {
height: ${({ theme }) => theme.spacings.xl} !important;
width: ${({ theme }) => theme.spacings.xl} !important;
margin-right: ${({ theme }) => theme.spacings.sm} !important;
}
`
const NotificationContent = styled.div`
flex-grow: 1;
margin-right: 2rem;
margin-left: 0.5rem;
`
const HideButton = styled(Button)<GenericButtonOrLinkProps>`
&& {
display: flex;

View File

@ -65,7 +65,7 @@ export const PlacesDesEntreprisesButton = ({
siret,
}: {
pathname: string
siret?: string
siret?: string | null
}) => {
const { t } = useTranslation()
const baseURL =

View File

@ -55,7 +55,7 @@ export default function SchemeComparaison({
() =>
engine.shallowCopy().setSituation({
...situation,
dirigeant: "'assimilé salarié'",
'dirigeant . régime social': "'assimilé salarié'",
}),
[situation]
)
@ -63,7 +63,7 @@ export default function SchemeComparaison({
() =>
engine.shallowCopy().setSituation({
...situation,
dirigeant: "'auto-entrepreneur'",
'dirigeant . régime social': "'auto-entrepreneur'",
}),
[situation]
)
@ -71,7 +71,7 @@ export default function SchemeComparaison({
() =>
engine.shallowCopy().setSituation({
...situation,
dirigeant: "'indépendant'",
'dirigeant . régime social': "'indépendant'",
}),
[situation]
)

View File

@ -7,7 +7,10 @@ import { CurrentSimulatorDataContext } from '../../pages/Simulateurs/metadata'
import { useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { situationSelector } from '@/selectors/simulationSelectors'
import {
companySituationSelector,
situationSelector,
} from '@/selectors/simulationSelectors'
import styled from 'styled-components'
import { TrackingContext } from '../../ATInternetTracking'
import { useParamsFromSituation } from '../utils/useSearchParamsSimulationSharing'
@ -16,7 +19,11 @@ import { PlacesDesEntreprisesButton } from '../PlaceDesEntreprises'
export function useUrl() {
const language = useTranslation().i18n.language
const situation = useSelector(situationSelector)
const situation = {
...useSelector(situationSelector),
...useSelector(companySituationSelector),
}
delete situation['entreprise . SIREN']
const searchParams = useParamsFromSituation(situation)
const currentSimulatorData = useContext(CurrentSimulatorDataContext)

View File

@ -1,13 +1,20 @@
import { Grid, styled } from '@mui/material'
import { ConversationProps } from '@/components/conversation/Conversation'
import PageFeedback from '@/components/Feedback'
import ShareOrSaveSimulationBanner from '@/components/ShareSimulationBanner'
import { PopoverWithTrigger } from '@/design-system'
import { Spacing } from '@/design-system/layout'
import { Link } from '@/design-system/typography/link'
import {
companySituationSelector,
firstStepCompletedSelector,
} from '@/selectors/simulationSelectors'
import { Grid, styled } from '@mui/material'
import React from 'react'
import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
import { firstStepCompletedSelector } from '@/selectors/simulationSelectors'
import { TrackPage } from '../../ATInternetTracking'
import Banner from '../Banner'
import AnswerList from '../conversation/AnswerList'
import PreviousSimulationBanner from './../PreviousSimulationBanner'
import ExportRecover from './../simulationExplanation/ExportRecover'
import { FadeIn, FromTop } from './../ui/animate'
@ -42,6 +49,9 @@ export default function Simulation({
customEndMessages,
}: SimulationProps) {
const firstStepCompleted = useSelector(firstStepCompletedSelector)
const existingCompany = !!useSelector(companySituationSelector)[
'entreprise . SIREN'
]
return (
<>
{!firstStepCompleted && <TrackPage name="accueil" />}
@ -53,6 +63,7 @@ export default function Simulation({
<div className="print-hidden">
{!firstStepCompleted && (
<>
<Spacing sm />
<PreviousSimulationBanner />
{afterQuestionsSlot}
</>
@ -62,10 +73,30 @@ export default function Simulation({
<FromTop>
{results}
<Questions customEndMessages={customEndMessages} />
{afterQuestionsSlot || <Spacing sm />}
<Spacing sm />
{afterQuestionsSlot}
</FromTop>
)}
{existingCompany && (
<Banner icon="✏">
Ce simulateur a été prérempli avec les données de votre
entreprise.{' '}
<PopoverWithTrigger
trigger={(buttonProps) => (
<Link {...buttonProps}>
<Trans>Voir ma situation</Trans>
</Link>
)}
>
{(close) => <AnswerList onClose={close} />}
</PopoverWithTrigger>
</Banner>
)}
{firstStepCompleted && (
<>
<ShareOrSaveSimulationBanner />
<Spacing lg />
</FromTop>
</>
)}
</div>
</StyledGrid>

View File

@ -1,35 +0,0 @@
import { Helmet } from 'react-helmet-async'
let createQueryParams = (params) =>
Object.keys(params)
.map((k) => `${k}=${encodeURI(params[k])}`)
.join('&')
let url = (hiddenVariables) =>
'https://embauchegouv.typeform.com/to/dvbINf?' +
createQueryParams(hiddenVariables)
let TypeFormEmbed = ({ hiddenVariables }) => (
<div>
<a
className="typeform-share button"
href={url(hiddenVariables)}
data-mode="popup"
data-auto-open
data-hide-headers
data-hide-footer
target="_blank"
/>
<Helmet
script={[
{
type: 'text/javascript',
innerHTML:
'(function() { var qs,js,q,s,d=document, gi=d.getElementById, ce=d.createElement, gt=d.getElementsByTagName, id="typef_orm_share", b="https://embed.typeform.com/"; if(!gi.call(d,id)){ js=ce.call(d,"script"); js.id=id; js.src=b+"embed.js"; q=gt.call(d,"script")[0]; q.parentNode.insertBefore(js,q) } })() ',
},
]}
/>
</div>
)
export default TypeFormEmbed

View File

@ -0,0 +1,34 @@
import { Message } from '@/design-system'
import { CardContainer } from '@/design-system/card/Card'
import { Strong } from '@/design-system/typography'
import { H4 } from '@/design-system/typography/heading'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import styled from 'styled-components'
import Value from '../EngineValue'
export function CompanyDetails() {
return (
<StyledCompanyContainer>
<H4>
{' '}
<Value expression="entreprise . nom" linkToRule={false} />{' '}
<Value expression="entreprise . SIREN" linkToRule={false} />
</H4>
<Body>
Crée le{' '}
<Strong>
<Value
expression="entreprise . date de création"
linkToRule={false}
/>
</Strong>{' '}
et domiciliée à{' '}
<Strong>
<Value expression="établissement . localisation" linkToRule={false} />
</Strong>
</Body>
</StyledCompanyContainer>
)
}
const StyledCompanyContainer = styled(Message).attrs({ border: false })``

View File

@ -1,26 +1,20 @@
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { Spacing } from '@/design-system/layout'
import { Strong } from '@/design-system/typography'
import { H3 } from '@/design-system/typography/heading'
import { Body } from '@/design-system/typography/paragraphs'
import { H4 } from '@/design-system/typography/heading'
import { useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Company } from '@/reducers/inFranceAppReducer'
import styled from 'styled-components'
export default function CompanyDetails({
export default function CompanySearchDetails({
entreprise,
}: {
entreprise: FabriqueSocialEntreprise | Company
entreprise: FabriqueSocialEntreprise
}) {
const { i18n } = useTranslation()
const {
siren,
label,
dateCreationUniteLegale,
firstMatchingEtablissement,
allMatchingEtablissements,
} = entreprise
const { siren, label, dateCreationUniteLegale, firstMatchingEtablissement } =
entreprise
const DateFormatter = useMemo(
() =>
@ -40,12 +34,12 @@ export default function CompanyDetails({
// </SmallBody>
// )
// }
const siege = allMatchingEtablissements.find((e) => e.is_siege)
return (
<CompanyContainer>
<H3
<H4
as="div"
css={`
margin-top: 0;
margin: 0;
`}
>
<>
@ -53,33 +47,20 @@ export default function CompanyDetails({
? highlightLabelToJSX(entreprise.highlightLabel)
: label}{' '}
<small>({siren})</small>
</>{' '}
</H3>
<InfoContainer as="div">
{dateCreationUniteLegale && (
<div>
<Trans>Crée le</Trans>{' '}
<Strong>
{DateFormatter.format(new Date(dateCreationUniteLegale))}
</Strong>
</div>
)}
<div>{firstMatchingEtablissement.address}</div>
{siege &&
allMatchingEtablissements.length > 1 &&
siege.address !== firstMatchingEtablissement.address && (
<div>
<Trans>Siège :</Trans> {siege.address}
</div>
)}
</InfoContainer>
</>
</H4>
<Spacing sm />
<Trans>Crée le :</Trans>{' '}
<Strong>{DateFormatter.format(new Date(dateCreationUniteLegale))}</Strong>
<br />
<Trans>Domiciliée à l'adresse :</Trans>{' '}
<Strong>{firstMatchingEtablissement.address}</Strong>
</CompanyContainer>
)
}
function highlightLabelToJSX(highlightLabel: string) {
const highlightRE = /(.*?)<b><u>(\w+)<\/u><\/b>/gm
const highlightRE = /(.*?)<b><u>(.+?)<\/u><\/b>/gm
let parsedLength = 0
const result = []
let matches
@ -97,16 +78,9 @@ function highlightLabelToJSX(highlightLabel: string) {
}
const Highlight = styled.strong`
text-decoration: underline;
background-color: ${({ theme }) => theme.colors.bases.secondary[100]};
`
const CompanyContainer = styled.div`
margin-top: 0.5rem;
margin-bottom: 0.5rem;
text-align: left;
`
const InfoContainer = styled(Body)`
display: flex;
flex-direction: column;
`

View File

@ -11,8 +11,8 @@ import useSearchCompany from '@/hooks/useSearchCompany'
import { ReactNode, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import CompanyDetails from './CompanyDetails'
import { FromTop } from './ui/animate'
import CompanySearchDetails from './SearchDetails'
import { FromTop } from '../ui/animate'
const StyledCard = styled(Card)`
flex-direction: row; // for Safari <= 13
@ -100,9 +100,9 @@ function Results({
<FromTop>
<Grid container spacing={2} data-testid="company-search-results">
{results.map((etablissement) => (
<Grid key={etablissement.siren} item xs={12} xl={6}>
<Grid key={etablissement.siren} item xs={12}>
<StyledCard onPress={() => onSubmit?.(etablissement)} compact>
<CompanyDetails entreprise={etablissement} />
<CompanySearchDetails entreprise={etablissement} />
</StyledCard>
</Grid>
))}

View File

@ -1,40 +1,36 @@
import { goToQuestion, resetSimulation } from '@/actions/actions'
import { resetCompany } from '@/actions/companyActions'
import Emoji from '@/components/utils/Emoji'
import { useEngine } from '@/components/utils/EngineContext'
import { useNextQuestions } from '@/components/utils/useNextQuestion'
import { Button } from '@/design-system/buttons'
import { H2 } from '@/design-system/typography/heading'
import { Spacing } from '@/design-system/layout'
import { H2, H3 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { DottedName } from 'modele-social'
import { EvaluatedNode, formatValue } from 'publicodes'
import { useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import {
answeredQuestionsSelector,
companySituationSelector,
situationSelector,
} from '@/selectors/simulationSelectors'
import { Grid } from '@mui/material'
import { DottedName } from 'modele-social'
import { EvaluatedNode } from 'publicodes'
import { useMemo } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import styled from 'styled-components'
import Value from '../EngineValue'
import './AnswerList.css'
type AnswerListProps = {
onClose: () => void
}
const Header = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`
const Title = styled(H2)`
flex-grow: 1;
`
export default function AnswerList({ onClose }: AnswerListProps) {
const dispatch = useDispatch()
const engine = useEngine()
const situation = useSelector(situationSelector)
const companySituation = useSelector(companySituationSelector)
const passedQuestions = useSelector(answeredQuestionsSelector)
const answeredAndPassedQuestions = useMemo(
() =>
@ -43,37 +39,79 @@ export default function AnswerList({ onClose }: AnswerListProps) {
(answered) => !passedQuestions.some((passed) => answered === passed)
)
.concat(passedQuestions)
.filter((answered) => !(answered in companySituation))
.map((dottedName) => engine.evaluate(engine.getRule(dottedName))),
[engine, passedQuestions, situation]
[engine, passedQuestions, situation, companySituation]
)
const nextSteps = useNextQuestions().map((dottedName) =>
engine.evaluate(engine.getRule(dottedName))
)
const companyQuestions = useMemo(
() =>
(Object.keys(companySituation) as DottedName[]).map((dottedName) =>
engine.evaluate(engine.getRule(dottedName))
),
[engine, companySituation]
)
return (
<div className="answer-list">
<H2>
<Emoji emoji="📋 " />
<Trans>Ma situation</Trans>
</H2>
{!!answeredAndPassedQuestions.length && (
<>
<Header>
<Title>
<Emoji emoji="📋 " />
<Trans>Mes réponses</Trans>
</Title>
<H3>
<Trans>Données de simulation</Trans>
</H3>
<StepsTable {...{ rules: answeredAndPassedQuestions, onClose }} />
<Spacing sm />
<div
css={`
text-align: center;
`}
>
<Button
size="XS"
light
onPress={() => {
dispatch(resetSimulation())
onClose()
}}
>
<Emoji emoji="🗑" /> <Trans>Tout effacer</Trans>
<Emoji emoji="🗑" /> <Trans>Recommencer la simulation</Trans>
</Button>
</Header>
<StepsTable {...{ rules: answeredAndPassedQuestions, onClose }} />
</div>
</>
)}
{companyQuestions.length > 0 && (
<>
<H3>
<Trans>Données de l'entreprise</Trans>
</H3>
<StepsTable {...{ rules: companyQuestions, onClose }} />
<Spacing sm />
<div
css={`
text-align: center;
`}
>
<Button
light
size="XS"
onClick={() => {
dispatch(resetSimulation())
dispatch(resetCompany())
}}
>
<Emoji emoji="🗑" /> <Trans>Effacer toutes mes données</Trans>
</Button>
</div>
</>
)}
{!!nextSteps.length && (
<>
<H2>
@ -87,16 +125,6 @@ export default function AnswerList({ onClose }: AnswerListProps) {
)
}
const TBody = styled.tbody`
font-family: ${({ theme }) => theme.fonts.main};
& > tr > td {
padding: 0.5rem 0.75rem;
}
& > tr:nth-child(2n) {
background-color: ${({ theme }) => theme.colors.bases.primary[100]};
}
`
function StepsTable({
rules,
onClose,
@ -105,28 +133,45 @@ function StepsTable({
onClose: () => void
}) {
const dispatch = useDispatch()
const language = useTranslation().i18n.language
return (
<table>
<TBody>
{rules.map((rule) => (
<tr key={rule.dottedName}>
<td>
<>
{rules
.filter((rule) => rule.nodeValue !== null)
.map((rule) => (
<StyledAnswerList
container
alignItems={'baseline'}
key={rule.dottedName}
>
<Grid item md={8}>
{rule.title}
</Grid>
<StyledAnswer item lg={4}>
<Link
onPress={() => {
dispatch(goToQuestion(rule.dottedName))
onClose()
}}
title="Modifier"
>
{rule.title}
<Value expression={rule.dottedName} linkToRule={false} />{' '}
<Emoji emoji="✏" alt="Modifier" />
</Link>
</td>
<td>
<span className="">{formatValue(rule, { language })}</span>
</td>
</tr>
</StyledAnswer>
</StyledAnswerList>
))}
</TBody>
</table>
</>
)
}
const StyledAnswer = styled(Grid)`
text-align: right;
`
const StyledAnswerList = styled(Grid)`
padding: ${({ theme }) => theme.spacings.xs};
margin: 0 -${({ theme }) => theme.spacings.xs};
font-family: ${({ theme }) => theme.fonts.main};
:nth-child(2n) {
background-color: ${({ theme }) => theme.colors.bases.primary[100]};
}
`

View File

@ -24,10 +24,14 @@ import { ExplicableRule } from './Explicable'
import SeeAnswersButton from './SeeAnswersButton'
export type ConversationProps = {
displayNotification: boolean
customEndMessages?: React.ReactNode
}
export default function Conversation({ customEndMessages }: ConversationProps) {
export default function Conversation({
customEndMessages,
displayNotification = true,
}: ConversationProps) {
const dispatch = useDispatch()
const engine = useContext(EngineContext)
const currentQuestion = useNextQuestions()[0]
@ -107,7 +111,7 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
<SeeAnswersButton />
</Grid>
</Grid>
<Notifications />
{displayNotification && <Notifications />}
</form>
<QuickLinks />
</>

View File

@ -9,10 +9,9 @@ export default function SeeAnswersButton() {
<PopoverWithTrigger
trigger={(buttonProps) => (
<Button {...buttonProps} light size="XS">
<Trans>Voir mes paramètres</Trans>
<Trans>Voir ma situation</Trans>
</Button>
)}
small
>
{(close) => <Answers onClose={close} />}
</PopoverWithTrigger>

View File

@ -2,6 +2,7 @@ import Value from '@/components/EngineValue'
import { FromBottom } from '@/components/ui/animate'
import { useEngine } from '@/components/utils/EngineContext'
import { Markdown } from '@/components/utils/markdown'
import { Message } from '@/design-system'
import { Button } from '@/design-system/buttons'
import { Spacing } from '@/design-system/layout'
import { H3 } from '@/design-system/typography/heading'
@ -14,7 +15,7 @@ export default function CotisationsForfaitaires() {
)
return (
<FromBottom>
<div>
<Message>
<H3 as="h2">{rule.title}</H3>
<Intro>
<Trans i18nKey="pages.simulateurs.indépendant.cotisations-forfaitaires">
@ -26,7 +27,7 @@ export default function CotisationsForfaitaires() {
<Markdown>{rule.rawNode.description ?? ''}</Markdown>
{rule.rawNode.références && (
<>
<Spacing lg />
<Spacing md />
<Button
href={Object.values(rule.rawNode.références)[0]}
size="XS"
@ -34,9 +35,10 @@ export default function CotisationsForfaitaires() {
>
<Trans>Voir la fiche Urssaf</Trans>
</Button>
<Spacing lg />
</>
)}
</div>
</Message>
</FromBottom>
)
}

View File

@ -19,6 +19,7 @@ import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
import { targetUnitSelector } from '@/selectors/simulationSelectors'
import styled from 'styled-components'
import { Message } from '@/design-system'
export default function InstitutionsPartenaires() {
const unit = useSelector(targetUnitSelector)
@ -30,44 +31,49 @@ export default function InstitutionsPartenaires() {
Vos institutions partenaires
</Trans>
</H3>
<InstitutionsTable>
<WhenApplicable dottedName="dirigeant . indépendant . PL . CNAVPL">
<CotisationsUrssaf rule="dirigeant . indépendant . PL . cotisations Urssaf" />
<CaisseRetraite />
</WhenApplicable>
<WhenNotApplicable dottedName="dirigeant . indépendant . PL . CNAVPL">
<CotisationsUrssaf rule="dirigeant . indépendant . cotisations et contributions" />
</WhenNotApplicable>
<ImpôtsDGFIP />
<Condition expression="dirigeant . indépendant . PL . PAMC . participation CPAM > 0">
<InstitutionLine>
<InstitutionLogo
href="https://www.ameli.fr/assure/droits-demarches/salaries-travailleurs-independants-et-personnes-sans-emploi/emploi-independant-non-salarie/praticien-auxiliaire-medical"
target="_blank"
rel="noreferrer"
>
<img src={assuranceMaladieSrc} title="Logo CPAM" />
</InstitutionLogo>
<Body>
<Trans i18nKey="simulateurs.explanation.institutions.cpam">
En tant que professionnel de santé conventionné, vous
bénéficiez d'une prise en charge d'une partie de vos
cotisations par l'Assurance Maladie.
</Trans>
</Body>
<Body className="ui__ lead">
<Emoji emoji="🎁" />{' '}
<RuleLink dottedName="dirigeant . indépendant . PL . PAMC . participation CPAM">
<Value
unit={unit}
displayedUnit="€"
expression="- dirigeant . indépendant . PL . PAMC . participation CPAM"
/>
</RuleLink>
</Body>
</InstitutionLine>
</Condition>
</InstitutionsTable>
<Grid container>
<Grid item lg={12} xl={10}>
<Message border={false}>
<WhenApplicable dottedName="dirigeant . indépendant . PL . CNAVPL">
<CotisationsUrssaf rule="dirigeant . indépendant . PL . cotisations Urssaf" />
<CaisseRetraite />
</WhenApplicable>
<WhenNotApplicable dottedName="dirigeant . indépendant . PL . CNAVPL">
<CotisationsUrssaf rule="dirigeant . indépendant . cotisations et contributions" />
</WhenNotApplicable>
<ImpôtsDGFIP />
<Condition expression="dirigeant . indépendant . PL . PAMC . participation CPAM > 0">
<InstitutionLine>
<InstitutionLogo
href="https://www.ameli.fr/assure/droits-demarches/salaries-travailleurs-independants-et-personnes-sans-emploi/emploi-independant-non-salarie/praticien-auxiliaire-medical"
target="_blank"
rel="noreferrer"
>
<img src={assuranceMaladieSrc} title="Logo CPAM" />
</InstitutionLogo>
<Body>
<Trans i18nKey="simulateurs.explanation.institutions.cpam">
En tant que professionnel de santé conventionné, vous
bénéficiez d'une prise en charge d'une partie de vos
cotisations par l'Assurance Maladie.
</Trans>
</Body>
<Body className="ui__ lead">
<Emoji emoji="🎁" />{' '}
<RuleLink dottedName="dirigeant . indépendant . PL . PAMC . participation CPAM">
<Value
unit={unit}
displayedUnit="€"
expression="- dirigeant . indépendant . PL . PAMC . participation CPAM"
/>
</RuleLink>
</Body>
</InstitutionLine>
</Condition>
</Message>
</Grid>
</Grid>
<Condition expression="dirigeant . indépendant . cotisations et contributions . exonérations . ACRE > 0">
<SmallBody>
<Trans i18nKey="simulateurs.explanation.institutions.notice acre">
@ -202,37 +208,41 @@ export function InstitutionsPartenairesArtisteAuteur() {
return (
<section>
<H3>Vos cotisations</H3>
<InstitutionsTable>
<CotisationsUrssaf
rule="artiste-auteur . cotisations"
extraNotice={
<Condition expression="artiste-auteur . revenus . traitements et salaires > 0">
<Trans i18nKey="simulateurs.explanation.institutions.précompte-artiste-auteur">
Pour vos revenus en traitement et salaires, ces cotisations sont
« précomptées », c'est à dire payées à la source par le
diffuseur.
</Trans>
</Condition>
}
/>
<Condition expression="artiste-auteur . cotisations . IRCEC > 0">
<InstitutionLine>
<InstitutionLogo
href="http://www.ircec.fr/"
target="_blank"
rel="noreferrer"
>
<img src={logosSrc['IRCEC']} title="logo IRCEC" />
</InstitutionLogo>
<Body>{descriptionIRCEC}</Body>
<Value
displayedUnit="€"
unit={unit}
expression="artiste-auteur . cotisations . IRCEC"
<Grid container>
<Grid item lg={12} xl={10}>
<Message border={false}>
<CotisationsUrssaf
rule="artiste-auteur . cotisations"
extraNotice={
<Condition expression="artiste-auteur . revenus . traitements et salaires > 0">
<Trans i18nKey="simulateurs.explanation.institutions.précompte-artiste-auteur">
Pour vos revenus en traitement et salaires, ces cotisations
sont « précomptées », c'est à dire payées à la source par le
diffuseur.
</Trans>
</Condition>
}
/>
</InstitutionLine>
</Condition>
</InstitutionsTable>
<Condition expression="artiste-auteur . cotisations . IRCEC > 0">
<InstitutionLine>
<InstitutionLogo
href="http://www.ircec.fr/"
target="_blank"
rel="noreferrer"
>
<img src={logosSrc['IRCEC']} title="logo IRCEC" />
</InstitutionLogo>
<Body>{descriptionIRCEC}</Body>
<Value
displayedUnit="€"
unit={unit}
expression="artiste-auteur . cotisations . IRCEC"
/>
</InstitutionLine>
</Condition>
</Message>
</Grid>
</Grid>
</section>
)
}
@ -246,22 +256,19 @@ export function InstitutionsPartenairesAutoEntrepreneur() {
Vos institutions partenaires
</Trans>
</H2>
<InstitutionsTable>
<CotisationsUrssaf rule="dirigeant . auto-entrepreneur . cotisations et contributions" />
<ImpôtsDGFIP />
</InstitutionsTable>
<Grid container>
<Grid item lg={12} xl={10}>
<Message border={false}>
<CotisationsUrssaf rule="dirigeant . auto-entrepreneur . cotisations et contributions" />
<ImpôtsDGFIP />
</Message>
</Grid>
</Grid>
</FromBottom>
</section>
)
}
const InstitutionsTable = styled(Grid).attrs({ item: true, xl: 10 })`
border-radius: ${({ theme }) => theme.box.borderRadius};
box-shadow: ${({ theme }) => theme.elevations[2]};
padding: ${({ theme }) => theme.spacings.xs}
${({ theme }) => theme.spacings.md};
`
const InstitutionLogo = styled.a`
img {
max-width: 100%;
@ -273,13 +280,9 @@ const InstitutionLine = styled.div`
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
padding: ${({ theme }) => theme.spacings.md};
flex-wrap: wrap;
&:not(:first-child) {
border-top: 1px solid var(--lighterColor);
}
> ${InstitutionLogo} {
display: block;
width: 13ch;

View File

@ -2,13 +2,15 @@ import emojiFn from 'react-easy-emoji'
import { useTranslation } from 'react-i18next'
type PropType = {
emoji: string | undefined
alt?: string
title?: string
}
// This custom component has several advantages over the direct use of the
// `emojiFn` provided by `react-easy-emoji` :
// - allow to configure the URL to self host twemoji images in production
// - using a real React component works better with the translation scripts
export default function Emoji({ emoji }: PropType) {
export default function Emoji({ emoji, alt, title }: PropType) {
const language = useTranslation().i18n.language
const siteUrl =
@ -24,7 +26,11 @@ export default function Emoji({ emoji }: PropType) {
? {
baseUrl: siteUrl + '/twemoji/2/',
ext: '.png',
props: {
alt,
title,
},
}
: undefined
: { props: { alt, title }, ext: '.png' }
)
}

View File

@ -14,8 +14,8 @@ const engineOptions = {
return key || unit
},
}
export function engineFactory(rules: Rules) {
return new Engine(rules, engineOptions)
export function engineFactory(rules: Rules, options = {}) {
return new Engine(rules, { ...engineOptions, ...options })
}
export const EngineContext = createContext<Engine>(new Engine())

View File

@ -129,21 +129,24 @@ export const useNextQuestions = function (): Array<DottedName> {
(node) => engine.evaluate(node).missingVariables ?? {}
)
const nextQuestions = useMemo(() => {
const next = getNextQuestions(
let next = getNextQuestions(
missingVariables,
questionsConfig ?? {},
answeredQuestions,
situation
)
if (currentQuestion && currentQuestion !== next[0]) {
return [currentQuestion, ...next.filter((val) => val !== currentQuestion)]
next = [currentQuestion, ...next.filter((val) => val !== currentQuestion)]
}
return next
return next.filter(
(question) => engine.evaluate(question).nodeValue !== null
)
}, [
missingVariables,
questionsConfig,
answeredQuestions,
situation,
engine,
currentQuestion,
])

View File

@ -7,7 +7,11 @@ import { useEngine } from '@/components/utils/EngineContext'
import { configSelector } from '@/selectors/simulationSelectors'
import Engine, { ParsedRules, serializeEvaluation } from 'publicodes'
import { DottedName } from 'modele-social'
import { updateSituation, setActiveTarget } from '@/actions/actions'
import {
updateSituation,
setActiveTarget,
batchUpdateSituation,
} from '@/actions/actions'
type Objectifs = (string | { objectifs: string[] })[]
type ShortName = string
@ -28,12 +32,13 @@ export default function useSearchParamsSimulationSharing() {
)
useEffect(() => {
const hasConfig = Object.keys(config).length > 0
// TODO: this check is specific to `useSimulationConfig` and
// `setSimulationConfig`, so we'd prefer not doing it here. Other ideas
// include having the config in a provider rather than in state.
const configLoadedInState = simulationUrl === currentUrl
if (!hasConfig || !configLoadedInState) return
// const hasConfig = Object.keys(config).length > 0
// // TODO: this check is specific to `useSimulationConfig` and
// // `setSimulationConfig`, so we'd prefer not doing it here. Other ideas
// // include having the config in a provider rather than in state.
// const configLoadedInState = simulationUrl === currentUrl
// if (!hasConfig || !configLoadedInState) return
// On load:
if (!urlSituationIsExtracted) {
@ -43,9 +48,8 @@ export default function useSearchParamsSimulationSharing() {
dottedNameParamName
)
Object.entries(newSituation).forEach(([dottedName, value]) => {
dispatch(updateSituation(dottedName as DottedName, value))
})
dispatch(batchUpdateSituation(newSituation as Situation))
const newActiveTarget = Object.keys(newSituation).filter((dottedName) =>
objectifs.includes(dottedName)
)[0]

View File

@ -1,14 +1,12 @@
import { setSimulationConfig } from '@/actions/actions'
import { SimulationConfig } from '@/reducers/rootReducer'
import { configSelector } from '@/selectors/simulationSelectors'
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useHistory } from 'react-router'
import { Company } from '@/reducers/inFranceAppReducer'
import { RootState, SimulationConfig, Situation } from '@/reducers/rootReducer'
import { configSelector } from '@/selectors/simulationSelectors'
export default function useSimulationConfig(
config: SimulationConfig | undefined,
{ useExistingCompanyFromSituation = false } = {}
config: SimulationConfig | undefined
) {
const dispatch = useDispatch()
// TODO : Reading the URL here is buggy because when we do SPA navigation the
@ -17,31 +15,10 @@ export default function useSimulationConfig(
// accessible from the situation config but is defined in the metadata file.
const url = useHistory().location.pathname.split('?')[0]
const existingCompany = useSelector(
(state: RootState) => state.inFranceApp.existingCompany
)
const initialSituation = useExistingCompanyFromSituation
? getCompanySituation(existingCompany)
: undefined
const lastConfig = useSelector(configSelector)
useEffect(() => {
if (config && lastConfig !== config) {
dispatch(setSimulationConfig(config ?? {}, url, initialSituation))
dispatch(setSimulationConfig(config ?? {}, url))
}
}, [config, dispatch, lastConfig, initialSituation, url])
}
export function getCompanySituation(company: Company | null): Situation {
return {
...(company?.localisation && {
'établissement . localisation': { objet: company.localisation },
}),
...(company?.dateCreationUniteLegale && {
'entreprise . date de création': company.dateCreationUniteLegale.replace(
/(.*)-(.*)-(.*)/,
'$3/$2/$1'
),
}),
}
}, [config, dispatch, lastConfig, url])
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import styled, { css } from 'styled-components'
import styled, { css, ThemeProvider } from 'styled-components'
import baseIcon from './baseIcon.svg'
import infoIcon from './infoIcon.svg'
import errorIcon from './errorIcon.svg'
@ -11,7 +11,7 @@ type MessageProps = {
children: React.ReactNode
icon?: boolean
border?: boolean
type: MessageType
type?: MessageType
light?: boolean
}
export function Message({
@ -25,42 +25,47 @@ export function Message({
children = <Body>{children}</Body>
}
return (
<StyledMessage type={type} border={border} light={light}>
{icon &&
(type === 'success' ? (
<StyledIcon
src={successIcon}
title="succès"
alt="icône signalant une alerte sur un succès"
/>
) : type === 'error' ? (
<StyledIcon
src={errorIcon}
title="error"
alt="icône signalant une alerte sur une erreur"
/>
) : type === 'info' ? (
<StyledIcon
src={infoIcon}
title="info"
alt="icône signalant une alerte sur une information"
/>
) : (
<StyledIcon
src={baseIcon}
title="paragraph"
alt="icône signalant un texte informatif"
/>
))}
<div>{children}</div>
</StyledMessage>
<ThemeProvider theme={(theme) => ({ ...theme, darkMode: false })}>
<StyledMessage type={type} border={border} light={light}>
{icon &&
(type === 'success' ? (
<StyledIcon
src={successIcon}
title="succès"
alt="icône signalant une alerte sur un succès"
/>
) : type === 'error' ? (
<StyledIcon
src={errorIcon}
title="error"
alt="icône signalant une alerte sur une erreur"
/>
) : type === 'info' ? (
<StyledIcon
src={infoIcon}
title="info"
alt="icône signalant une alerte sur une information"
/>
) : (
<StyledIcon
src={baseIcon}
title="paragraph"
alt="icône signalant un texte informatif"
/>
))}
<div>{children}</div>
</StyledMessage>
</ThemeProvider>
)
}
const StyledMessage = styled.div<
Pick<MessageProps, 'border' | 'type' | 'light'>
Pick<MessageProps, 'border' | 'light'> & {
type: NonNullable<MessageProps['type']>
}
>`
display: flex;
position: relative;
align-items: flex-start;
${({ theme, type, border, light }) => {
const colorSpace =

View File

@ -0,0 +1,27 @@
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { Li, Ul } from '@/design-system/typography/list'
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
component: Ul,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
} as ComponentMeta<typeof Ul>
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Ul> = (args) => (
<Ul {...args}>
<Li>Élément 1</Li>
<Li>Élément 2</Li>
<Li>Élément 3</Li>
</Ul>
)
export const Basic = Template.bind({})
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Basic.args = {}
export const XL = Template.bind({})
// More on args: https://storybook.js.org/docs/react/writing-stories/args
XL.args = {
size: 'XL',
}

View File

@ -0,0 +1,34 @@
import { stepAction, updateSituation } from '@/actions/actions'
import { useEngine } from '@/components/utils/EngineContext'
import { useNextQuestions } from '@/components/utils/useNextQuestion'
import { answeredQuestionsSelector } from '@/selectors/simulationSelectors'
import { DottedName } from 'modele-social'
import { PublicodesExpression, RuleNode } from 'publicodes'
import { useDispatch, useSelector } from 'react-redux'
export function useQuestionList(): [
questions: Array<RuleNode & { dottedName: DottedName }>,
onQuestionAnswered: (
dottedName: DottedName
) => (value?: PublicodesExpression) => void
] {
const answeredQuestions = useSelector(answeredQuestionsSelector)
const nextQuestions = useNextQuestions()
const engine = useEngine()
const questions = [...answeredQuestions, ...nextQuestions]
.filter((dottedName) => engine.evaluate(dottedName).nodeValue !== null)
.map((dottedName) => engine.getRule(dottedName))
const dispatch = useDispatch()
const onQuestionAnswered =
(dottedName: DottedName) => (value?: PublicodesExpression) => {
if (!answeredQuestions.includes(dottedName)) {
dispatch(stepAction(dottedName))
}
dispatch(updateSituation(dottedName, value))
}
return [questions, onQuestionAnswered]
}

View File

@ -2982,21 +2982,23 @@ contrat salarié . régime des impatriés:
titre.fr: régime des impatriés
contrat salarié . régime des impatriés . information:
description.en: >-
[automatic] The following conditions must be met in order to benefit from
the old-age contribution exemption: - Be able to prove a minimum
contribution paid elsewhere for old-age insurance - Not have been
affiliated, during the five calendar years preceding that of taking up the
post, to a compulsory French old-age insurance scheme, except for ancillary
activities of a seasonal nature or for studies.
[automatic] In order to benefit from the exemption from old-age
contributions, the following conditions must be met:
- Be able to justify a minimum contribution paid elsewhere for old age insurance
- Not to have been affiliated, during the five calendar years preceding that of the taking up of duties, to a compulsory French old age insurance scheme, except for accessory activities, of a seasonal nature or for studies.
[Lire le texte de loi](https://www.legifrance.gouv.fr/affichCode.do;jsessionid=F5CFB7C90D1D1F529A2CDC9FFD20BD6E.tplgfr34s_3?idSectionTA=LEGISCTA000038510929&amp;cidTexte=LEGITEXT000006073189&amp;dateTexte=20190626)
description.fr: >-
Pour bénéficier de l'exonération de cotisations vieillesse, il faut remplir
les conditions suivantes : - Pouvoir justifier d'une contribution minimale
versée ailleurs pour une assurance vieillesse - Ne pas avoir été affilié, au
cours des cinq années civiles précédant celle de la prise de fonctions, à un
régime français obligatoire d'assurance vieillesse, sauf pour des activités
accessoires, de caractère saisonnier ou pour les études.
les conditions suivantes :
- Pouvoir justifier d'une contribution minimale versée ailleurs pour une assurance vieillesse
- Ne pas avoir été affilié, au cours des cinq années civiles précédant celle de la prise de fonctions, à un régime français obligatoire d'assurance vieillesse, sauf pour des activités accessoires, de caractère saisonnier ou pour les études.
[Lire le texte de loi](https://www.legifrance.gouv.fr/affichCode.do;jsessionid=F5CFB7C90D1D1F529A2CDC9FFD20BD6E.tplgfr34s_3?idSectionTA=LEGISCTA000038510929&cidTexte=LEGITEXT000006073189&dateTexte=20190626)
titre.en: '[automatic] information'
@ -3487,12 +3489,13 @@ contrat salarié . stage:
contrat salarié . stage . avertissement:
description.en: >-
[automatic] An internship agreement **is not an employment contract**, and
cannot be concluded to carry out a regular task corresponding to a permanent
job, or a temporary increase in the company's activity. [Education Code -
Article
cannot be concluded to perform a regular task corresponding to a permanent
job, or to a temporary increase in the company's activity. [Code de
l'éducation - Article
L124-7](https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000029234119&amp;cidTexte=LEGITEXT000006071191)
Furthermore, a company with less than 20 employees may not take on more than **3 trainees**, and no more than **15% of the workforce** for companies with more than 20 employees.
In addition, a company with less than 20 employees may not host more than **3 trainees**, and no more than **15% of the workforce** for companies with more than 20 employees.
description.fr: >-
Une convention de stage **n'est pas un contrat de travail**, et ne peut pas
être conclue pour réaliser une tâche régulière correspondant à un poste de
@ -3500,6 +3503,7 @@ contrat salarié . stage . avertissement:
l'entreprise. [Code de l'éducation - Article
L124-7](https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000029234119&cidTexte=LEGITEXT000006071191)
Par ailleurs, une entreprise de moins de 20 salariés ne peut pas accueillir plus de **3&nbsp;stagiaires**, et pas plus de **15% de leffectif** pour les entreprises de plus de 20 salariés.
titre.en: '[automatic] warning'
titre.fr: avertissement
@ -3891,8 +3895,6 @@ contrat salarié . vieillesse:
titre.en: Basic pension contribution
titre.fr: vieillesse
dirigeant:
question.en: '[automatic] What is the social regime of the manager?'
question.fr: Quel est le régime social du dirigeant ?
titre.en: '[automatic] manager'
titre.fr: dirigeant
dirigeant . assimilé salarié:
@ -4163,6 +4165,11 @@ dirigeant . auto-entrepreneur . notification calcul ACRE annuel:
d'acre sur une meme année.
titre.en: '[automatic] notification annual ACRE calculation'
titre.fr: notification calcul ACRE annuel
dirigeant . gérant minoritaire:
question.en: '[automatic] Are you a minority or equal shareholder of your company?'
question.fr: Êtes-vous gérant minoritaire ou égalitaire de votre entreprise ?
titre.en: '[automatic] Minority or equal shareholder manager'
titre.fr: Gérant minoritaire ou égalitaire
dirigeant . indépendant:
titre.en: '[automatic] independent'
titre.fr: indépendant
@ -6181,6 +6188,9 @@ dirigeant . indépendant . revenus étrangers . montant:
question.fr: Quel est leur montant ?
titre.en: '[automatic] income received abroad'
titre.fr: revenus perçu à l'étranger
dirigeant . régime social:
titre.en: '[automatic] social regime'
titre.fr: régime social
dirigeant . rémunération:
titre.en: '[automatic] compensation'
titre.fr: rémunération
@ -7171,6 +7181,15 @@ entreprise . ACRE:
entreprise . ACRE par défaut:
titre.en: '[automatic] Default ACRE'
titre.fr: ACRE par défaut
entreprise . SIREN:
description.en: >
[automatic] The Siren number is a unique 9 digit number for each company. Ex
: 401237780
description.fr: >
Le numéro Siren est un numéro de 9 chiffres unique pour chaque entreprise.
Ex : 401237780
titre.en: '[automatic] SIREN'
titre.fr: SIREN
entreprise . activité:
description.en: '[automatic] Your type of activity will determine a large part
of the contribution, contribution and tax calculations.'
@ -7397,6 +7416,56 @@ entreprise . capital social:
question.fr: Quele est le capital social de la société ?
titre.en: '[automatic] Share capital'
titre.fr: Capital social
entreprise . catégorie juridique:
description.en: |
[automatic] Legal categories accessible via the SIRENE API
description.fr: |
Les catégories juridiques accessibles via l'API SIRENE
note.en: '[automatic] We base ourselves here on the legal categories defined by INSEE'
note.fr: On se base ici sur les catégories juridiques définies par l'INSEE
titre.en: '[automatic] legal category'
titre.fr: catégorie juridique
entreprise . catégorie juridique . EI:
titre.en: '[automatic] EI or EIRL'
titre.fr: EI ou EIRL
entreprise . catégorie juridique . EI . auto-entrepreneur:
question.en: '[automatic] Are you an auto-entrepreneur?'
question.fr: Êtes-vous auto-entrepreneur ?
titre.en: '[automatic] auto-entrepreneur'
titre.fr: auto-entrepreneur
entreprise . catégorie juridique . EI . imposition entreprise:
titre.en: '[automatic] taxation company'
titre.fr: imposition entreprise
entreprise . catégorie juridique . EI . responsabilité limité:
question.en: '[automatic] Is your business an EIRL?'
question.fr: Votre entreprise est-elle une EIRL ?
titre.en: '[automatic] EIRL'
titre.fr: EIRL
entreprise . catégorie juridique . SARL:
titre.en: '[automatic] EURL or SARL'
titre.fr: EURL ou SARL
entreprise . catégorie juridique . SARL . unipersonnelle:
question.en: '[automatic] Is your company an EURL?'
question.fr: Votre entreprise est-elle une EURL ?
titre.en: '[automatic] EURL'
titre.fr: EURL
entreprise . catégorie juridique . SAS:
titre.en: '[automatic] SASU or SAS'
titre.fr: SASU ou SAS
entreprise . catégorie juridique . SAS . unipersonnelle:
question.en: '[automatic] Is your company a SASU?'
question.fr: Votre entreprise est-elle une SASU ?
titre.en: '[automatic] SASU'
titre.fr: SASU
entreprise . catégorie juridique . SELARL:
titre.en: '[automatic] SELARL'
titre.fr: SELARL
entreprise . catégorie juridique . SELAS:
titre.en: '[automatic] SELARL'
titre.fr: SELARL
entreprise . catégorie juridique . autre:
titre.en: '[automatic] other'
titre.fr: autre
entreprise . charges:
description.en: >
[automatic]
@ -8028,6 +8097,9 @@ entreprise . imposition . IS . résultat net:
résumé.fr: Après déduction des charges et de l'impôt sur les société
titre.en: '[automatic] net result'
titre.fr: résultat net
entreprise . nom:
titre.en: '[automatic] name'
titre.fr: nom
entreprise . ratio alternants:
description.en: >
This fraction determines the additional contribution for learning for the
@ -8991,6 +9063,9 @@ situation personnelle . domiciliation fiscale à l'étranger:
question.fr: Votre établissement bénéficie-t-il du dispositif zone franche urbaine (ZFU) ?
titre.en: ZFU
titre.fr: ZFU
établissement . ZFU . durée d'implantation en fin d'année:
titre.en: '[automatic] duration of implementation at the end of the year'
titre.fr: durée d'implantation en fin d'année
établissement . localisation:
description.en: |-
When a company has more than one establishment, certain contributions are

View File

@ -5,6 +5,11 @@
"<0></0> Pour en savoir plus, rendez-vous sur le site <3>aquoiserventlescotisations":
urssaf:
fr</3>: <0></0> To find out more, go to <3>aquoiserventlescotisations.urssaf.fr</3>
"<0><0>Il n'existe pas encore de simulateur de revenu pour votre type d'entreprise sur ce site.</0><1>Si vous souhaitez que nous développions un nouveau simulateur, laissez-nous message en cliquant sur le bouton \"Faire une suggestion\" en bas de cette page.</1></0>":
<0><0>There is not yet an income simulator for your type of business on this
site.</0><1>If you would like us to develop a new simulator, leave us a
message by clicking on the "Make a suggestion" button at the bottom of this
page.</1></0>
<0>Activité mixte</0>: <0>Mixed activity</0>
"<0>Covid-19 et chômage partiel </0>: <3>Calculez votre indemnité</3>": "<0>Covid-19 and Short-Time </0>Work: <3>Calculate Your Benefit</3>"
<0>Oui</0>: <0>Yes</0>
@ -56,11 +61,15 @@ Covid 19: Covid 19
"Covid-19 : Découvrir les mesures de soutien aux entreprises": "Covid-19: Discovering Business Support Measures"
Coût pour l'entreprise: Cost to the company
Crée le: Created on
"Crée le :": "Created on :"
Créer une: Create a
De: From
Demande de mobilité: Demand for mobility
Destinataire: Levied by
Devenir: Become
"Domiciliée à l'adresse :": "Domiciled at the address :"
Données de l'entreprise: Company data
Données de simulation: Simulation data
Déclenchement: Applicability
Découvrir: Discover
"Décrivez votre projet ou votre problème en donnant quelques éléments de contexte. Nous identifions, parmi lensemble des partenaires publics et parapublics, le conseiller compétent pour votre demande. Celui-ci vous contacte par téléphone sous 5 jours et vous accompagne en fonction de votre situation.":
@ -72,6 +81,7 @@ Démarches de création: Creation process checklist
Désactivée: Inactive
Détail annuel des cotisations: Annual detail of my contributions
Effacer: Reset
Effacer toutes mes données: Delete all my data
Embauche: Hiring process
Employeur: Employer
En incluant l'indemnité de chômage partiel: Including short-time working allowance
@ -113,6 +123,7 @@ Jusquau: Until
La somme de: This rule is the sum of
Liste des intégrations: List of integrations
Liste des statuts juridiques: List of legal statutes
Ma situation: My situation
Mensuel: Monthly
Mes réponses: My answers
Modifier: Modify
@ -160,8 +171,9 @@ Quelques exemples de salaires: Some salary exemples
Quelques intégrations: Some integrations
Recherche en cours...: Searching...
Rechercher: Search
Rechercher une entreprise: Search for a company
Rechercher votre entreprise: Search for a company
Recommencer: Start again
Recommencer la simulation: Start the simulation again
Rend non applicable les règles suivantes: Makes the following rules not applicable
Renseigner mon entreprise: Find my company
Responsabilité limitée: Limited liability
@ -218,7 +230,7 @@ Voir la fiche de paie: See the pay slip
Voir la répartition des cotisations: View contribution breakdown
Voir le code source: See the source code
Voir les autres simulateurs: See the other simulators
Voir mes paramètres: See my situation
Voir ma situation: See my situation
Votre adresse e-mail: Your email address
Votre entreprise: Your company
Votre forme juridique: Your legal status
@ -283,6 +295,12 @@ après:
arrondi-to-decimals: Rounding to {explanation.decimals.nodeValue} decimal
arrondi-to-decimals_plural: Rounding to {explanation.decimals.nodeValue} decimals
assiette: base
assistant-DRI:
description: <0><0>This tool will help you to fill in your tax <2>return</2> on
impot.gouv.fr. You will have at the end :</0><1><0>The list of boxes that
concern you with the amount to fill in</0><1>An estimate of the social
security contributions to be paid to the Urssaf in 2022</1></1></0><1>My
company</1>
associés:
choix1: Alone
choix2: Several partners
@ -879,7 +897,7 @@ landing:
create:
body: Assistance in choosing a legal status and a complete list of the steps
involved in setting up a business
title: Starting a business
title: I don't have a business yet
manage:
body: Personalized tools to anticipate the amount of social contributions to be
paid and better manage your cash flow.
@ -1628,8 +1646,8 @@ pages:
remuneration. To do this, simply enter the total amount allocated in the
\"total charged\" box. The simulation can then be refined by answering
the various questions.</4>"
shortname: SASU
title: SASU Simulator
shortname: SAS(U)
title: SAS(U) Simulator
titre: Revenue simulator for SAS(U) executive
select-year:
access: Access the {{year}} simulator
@ -1863,7 +1881,7 @@ une de ces conditions: one of these applies
économieCollaborative:
WIP: <0>This assistant is under development.</0> Do not hesitate to send us all
your remarks, ideas, questions by clicking on the "Make a suggestion" button
above.
at the bottom of the page.
accueil:
contenu: <0>Do you have income from <2>online platforms</2> (Airbnb, Abritel,
Drivy, Blablacar, Leboncoin, etc.)? You must declare them in most cases.

View File

@ -6,6 +6,11 @@
urssaf:
fr</3>: <0></0> Pour en savoir plus, rendez-vous sur le site
<3>aquoiserventlescotisations.urssaf.fr</3>
"<0><0>Il n'existe pas encore de simulateur de revenu pour votre type d'entreprise sur ce site.</0><1>Si vous souhaitez que nous développions un nouveau simulateur, laissez-nous message en cliquant sur le bouton \"Faire une suggestion\" en bas de cette page.</1></0>":
<0><0>Il n'existe pas encore de simulateur de revenu pour votre type
d'entreprise sur ce site.</0><1>Si vous souhaitez que nous développions un
nouveau simulateur, laissez-nous message en cliquant sur le bouton "Faire une
suggestion" en bas de cette page.</1></0>
<0>Activité mixte</0>: <0>Activité mixte</0>
<0>Oui</0>: <0>Oui</0>
Assimilé salarié: Assimilé salarié
@ -26,15 +31,23 @@ CompanySearchField:
Continuer: Continuer
Cotisations sociales: Cotisations sociales
Crée le: Crée le
"Crée le :": "Crée le :"
Créer une: Créer une
Devenir: Devenir
"Domiciliée à l'adresse :": "Domiciliée à l'adresse :"
Données de l'entreprise: Données de l'entreprise
Données de simulation: Données de simulation
Découvrir: Découvrir
<<<<<<< HEAD
"Décrivez votre projet ou votre problème en donnant quelques éléments de contexte. Nous identifions, parmi lensemble des partenaires publics et parapublics, le conseiller compétent pour votre demande. Celui-ci vous contacte par téléphone sous 5 jours et vous accompagne en fonction de votre situation.":
Décrivez votre projet ou votre problème en donnant quelques éléments de
contexte. Nous identifions, parmi lensemble des partenaires publics et
parapublics, le conseiller compétent pour votre demande. Celui-ci vous
contacte par téléphone sous 5 jours et vous accompagne en fonction de votre
situation.
=======
Effacer toutes mes données: Effacer toutes mes données
>>>>>>> 4a2520a52 (:sparkles: Revoie les parcours avec entreprise existante)
En savoir plus: En savoir plus
Entreprise Individuelle: Entreprise Individuelle
Exonérations: Exonérations
@ -55,6 +68,7 @@ J'ai compris: J'ai compris
Jusquau: Jusquau
Liste des intégrations: Liste des intégrations
Liste des statuts juridiques: Liste des statuts juridiques
Ma situation: Ma situation
Mes réponses: Mes réponses
Modifier: Modifier
Montant de l'impôt sur les sociétés: Montant de l'impôt sur les sociétés
@ -80,7 +94,8 @@ Prévisualisation: Prévisualisation
Que cherchez-vous ?: Que cherchez-vous ?
Quelques intégrations: Quelques intégrations
Rechercher: Rechercher
Rechercher une entreprise: Rechercher une entreprise
Rechercher votre entreprise: Rechercher votre entreprise
Recommencer la simulation: Recommencer la simulation
Ressources utiles: Ressources utiles
Retour: Retour
Retour à la création: Retour à la création
@ -108,7 +123,7 @@ Une idée&nbsp;?<1></1>Contactez-nous&nbsp;!: Une idée&nbsp;?<1></1>Contactez-n
Voir la fiche Urssaf: Voir la fiche Urssaf
Voir la fiche de paie: Voir la fiche de paie
Voir les autres simulateurs: Voir les autres simulateurs
Voir mes paramètres: Voir mes paramètres
Voir ma situation: Voir ma situation
Votre adresse e-mail: Votre adresse e-mail
Votre forme juridique: Votre forme juridique
Vous êtes dirigeant d'une SAS(U) ? <2>Accéder au simulateur de revenu dédié</2>: Vous êtes dirigeant d'une SAS(U) ? <2>Accéder au simulateur de revenu dédié</2>
@ -162,6 +177,12 @@ après:
(NIC).
titre: Le numéro SIRET
titre: Après la création
assistant-DRI:
description: <0><0>Cet outil vous aidera à remplir votre <2>déclaration de
revenu</2> sur impot.gouv.fr. Vous aurez à la fin :</0><1><0>La liste des
cases qui vous concernent avec le montant à remplir</0><1>Une estimation des
cotisations sociales à payer à l'Urssaf en 2022</1></1></0><1>Mon
entreprise</1>
associés:
choix1: Seul
choix2: Plusieurs personnes
@ -632,7 +653,7 @@ landing:
create:
body: Un accompagnement au choix du statut juridique et la liste complète des
démarches de création
title: Créer une entreprise
title: Je n'ai pas encore d'entreprise
manage:
body: Des outils personnalisés pour anticiper le montant des cotisations
sociales à payer et mieux gérer votre trésorerie.
@ -1285,8 +1306,8 @@ pages:
alloué à la rémunération du dirigeant. Il vous suffit pour cela saisir
le montant total alloué dans la case \"total chargé\". La simulation
peut ensuite être affinée en répondant aux différentes questions.</4>"
shortname: SASU
title: Simulateur de SASU
shortname: SAS(U)
title: Simulateur de SAS(U)
select-year:
access: Accéder au simulateur {{year}}
back: Retourner au simulateur {{year}}
@ -1468,7 +1489,7 @@ trouver:
économieCollaborative:
WIP: <0>Cet assistant est en cours de développement.</0> N'hésitez pas à nous
faire part de toute vos remarques, idées, questions en cliquant sur le
bouton "Faire une suggestion" ci dessus.
bouton "Faire une suggestion" en bas de la page.
accueil:
contenu: <0>Vous avez des revenus issus des <2>plateformes en ligne</2> (Airbnb,
Abritel, Drivy, Blablacar, Leboncoin, etc.) ? Vous devez les déclarer dans

View File

@ -14,7 +14,7 @@ import siret from './siret.jpg'
export default function AfterRegistration() {
const sitePaths = useContext(SitePathsContext)
const statutChoisi = useSelector(
(state: RootState) => state.inFranceApp.companyStatusChoice
(state: RootState) => state.choixStatutJuridique.companyStatusChoice
)
const { t } = useTranslation()
const isAutoentrepreneur = statutChoisi?.match('auto-entrepreneur')

View File

@ -33,7 +33,7 @@ export default function CreateCompany({ statut }: CreateCompanyProps) {
const { t, i18n } = useTranslation()
const sitePaths = useContext(SitePathsContext)
const companyCreationChecklist = useSelector(
(state: RootState) => state.inFranceApp.companyCreationChecklist
(state: RootState) => state.choixStatutJuridique.companyCreationChecklist
)
const dispatch = useDispatch()

View File

@ -67,7 +67,7 @@ const PreviousAnswersItem = styled.li`
export default function PreviousAnswers() {
const sitePaths = useContext(SitePathsContext)
const legalStatus = useSelector(
(state: RootState) => state.inFranceApp.companyLegalStatus
(state: RootState) => state.choixStatutJuridique.companyLegalStatus
)
if (Object.values(legalStatus).length < 1) {
return null

View File

@ -25,8 +25,8 @@ const useResetFollowingAnswers = () => {
const answeredQuestion = useSelector(
(state: RootState) =>
Object.keys(
state.inFranceApp.companyLegalStatus
) as (keyof typeof state.inFranceApp.companyLegalStatus)[]
state.choixStatutJuridique.companyLegalStatus
) as (keyof typeof state.choixStatutJuridique.companyLegalStatus)[]
)
useEffect(() => {
const companyStatusCurrentQuestionName = (toPairs(

View File

@ -22,7 +22,7 @@ export default function Créer() {
const nextQuestionUrl = useNextQuestionUrl()
const guideAlreadyStarted = useSelector(
(state: RootState) =>
!!Object.keys(state.inFranceApp.companyLegalStatus).length
!!Object.keys(state.choixStatutJuridique.companyLegalStatus).length
)
return (
<FromBottom>

View File

@ -11,7 +11,7 @@ import { RuleNode } from 'publicodes'
import { useCallback, useContext } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { situationSelector } from '@/selectors/simulationSelectors'
import { Question } from './index'
import { Question } from './PreviousVersion'
type SubSectionProp = {
dottedName: DottedName

View File

@ -0,0 +1,306 @@
import { Grid } from '@mui/material'
import { updateSituation } from '@/actions/actions'
import RuleInput from '@/components/conversation/RuleInput'
import { Condition, WhenAlreadyDefined } from '@/components/EngineValue'
import PageHeader from '@/components/PageHeader'
import PreviousSimulationBanner from '@/components/PreviousSimulationBanner'
import { FromTop } from '@/components/ui/animate'
import Warning from '@/components/ui/WarningBlock'
import Emoji from '@/components/utils/Emoji'
import useSimulationConfig from '@/components/utils/useSimulationConfig'
import { Strong } from '@/design-system/typography'
import { H2, H3 } from '@/design-system/typography/heading'
import { Li, Ul } from '@/design-system/typography/list'
import { Body, Intro, SmallBody } from '@/design-system/typography/paragraphs'
import { useCallback } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { situationSelector } from '@/selectors/simulationSelectors'
import styled from 'styled-components'
import { TrackPage } from '../../../ATInternetTracking'
import simulationConfig from './config.yaml'
import { ExplicationsResultatFiscal } from './ExplicationResultatFiscal'
import { SimpleField, SubSection } from './Fields'
import ResultatsSimples from './RésultatSimple'
import ResultatsParFormulaire from './RésultatsParFormulaire'
import illustration from './undraw_fill_in_mie5.svg'
/**
* Nous avons proposé une nouvelle vision des résultat plus complète, avec une proposition d'aide pour
* l'ensemble des cases liées aux cotisations sociales.
*
* Hors de propos pour 2021, étant donné que cela prendrait beaucoup de temps à valider par la DGFiP
* En attendant, on propose la version "simple" (mais moins utile).
*
* Le but est de faire valider la version plus complète pour la déclaration de revenu 2021.
*/
const FEATURE_FLAG_RESULTATS_COMPLETS =
!import.meta.env.SSR && document.location.search.includes('next')
export default function AideDéclarationIndépendant() {
useSimulationConfig(simulationConfig)
const situation = useSelector(situationSelector)
return (
<>
<Trans i18nKey="aide-déclaration-indépendant.description">
<PageHeader picture={illustration}>
<Intro>
Cet outil est une aide à la déclaration de revenus à destination des{' '}
<Strong>travailleurs indépendants</Strong>. Il vous permet de
connaître le montant des charges sociales déductibles.
</Intro>
<SmallBody>
Vous restez entièrement responsable d'éventuelles omissions ou
inexactitudes dans votre déclaration.
</SmallBody>
</PageHeader>
<Warning localStorageKey="aide-déclaration-indépendant.warning">
<Body>
<Strong>
Cet outil vous concerne si vous êtes dans le cas suivant :
</Strong>
</Body>
<Ul>
<Li>
Vous cotisez au régime général des travailleurs indépendants
</Li>
</Ul>
<Body>
<Strong>
Il ne vous concerne pas si vous êtes dans un des cas suivants :
</Strong>
</Body>
<Ul>
<Li>
Vous exercez une activité libérale relevant dun régime de
retraite des professions libérales en comptabilité d'engagement
</Li>
<Li>Votre entreprise est domiciliée dans les DOM</Li>
</Ul>
</Warning>
<PreviousSimulationBanner />
<H2>Imposition</H2>
<Body>
Ces quelques questions permettent de déterminer le type de déclaration
à remplir, ainsi que les modalités de calcul des cotisations sociales.
</Body>
</Trans>
{Object.keys(situation).length ? (
<TrackPage name="commence" />
) : (
<TrackPage name="accueil" />
)}
<ImpositionSection />
<FromTop>
<Grid container>
<Grid item xs={12} sm={10} md={9} lg={8}>
<Condition expression="déclaration indépendants . comptabilité . engagement">
<Trans i18nKey="aide-déclaration-indépendant.entreprise.titre">
<H2>Entreprise et activité</H2>
</Trans>
<SimpleField
dottedName="entreprise . date de création"
showSuggestions={false}
/>
<Condition expression="entreprise . date de création > 31/12/2021">
<SmallBody
css={`
color: #ff2d96;
`}
>
Cette aide à la déclaration concerne uniquement les
entreprises déjà en activité en 2021
</SmallBody>
</Condition>
<SubSection dottedName="déclaration indépendants . nature de l'activité" />
{/* PLNR */}
<SimpleField dottedName="entreprise . activité . débit de tabac" />
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . déduction tabac" />
<SimpleField dottedName="dirigeant . indépendant . PL . régime général . taux spécifique retraite complémentaire" />
<H2>
<Trans>Situation personnelle</Trans>
</H2>
<SimpleField dottedName="situation personnelle . RSA" />
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<SubSection dottedName="dirigeant . indépendant . IJSS" />
</Condition>
<SubSection dottedName="dirigeant . indépendant . conjoint collaborateur" />
<H2>
<Trans>Exonérations</Trans>
</H2>
<Body>
<Emoji emoji="🏗️" /> Les calculs de l'exonération COVID 2021
sont en cours d'implémentation
</Body>
<SimpleField dottedName="déclaration indépendants . ACRE" />
<SimpleField dottedName="établissement . ZFU" />
<SubSection
hideTitle
dottedName="entreprise . effectif . seuil"
/>
<SubSection
dottedName="dirigeant . indépendant . cotisations et contributions . exonérations"
hideTitle
/>
{FEATURE_FLAG_RESULTATS_COMPLETS && (
<SubSection dottedName="dirigeant . indépendant . cotisations facultatives" />
)}
<H2>
<Trans>International</Trans>
</H2>
<SimpleField dottedName="situation personnelle . domiciliation fiscale à l'étranger" />
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<SubSection
dottedName="dirigeant . indépendant . revenus étrangers"
hideTitle
/>
</Condition>
</Condition>
<Condition expression="déclaration indépendants . cotisations payées">
<SubSection dottedName="déclaration indépendants . cotisations payées" />
<SimpleField dottedName="déclaration indépendants . nature de l'activité" />
<SimpleField dottedName="dirigeant . indépendant . conjoint collaborateur" />
<SubSection dottedName="dirigeant . indépendant . cotisations facultatives" />
{/* We can't use a subsection here cause revenu étrangers is not missing when CSG is replaced */}
<H3>
<Trans>Revenus étranger</Trans>
</H3>
<SimpleField dottedName="dirigeant . indépendant . revenus étrangers" />
<Condition expression="dirigeant . indépendant . revenus étrangers">
<SimpleField dottedName="dirigeant . indépendant . revenus étrangers . montant" />
</Condition>
</Condition>
<Condition expression="déclaration indépendants . cotisations payées version simple">
<SimpleField dottedName="déclaration indépendants . cotisations payées version simple . cotisations sociales" />
<SimpleField dottedName="déclaration indépendants . cotisations payées version simple . CSG déductible et CFP" />
</Condition>
</Grid>
</Grid>
</FromTop>
{FEATURE_FLAG_RESULTATS_COMPLETS ? (
<>
<SubSection dottedName="déclaration indépendants . régime d'imposition" />
<Condition
expression={{
'une de ces conditions': [
"déclaration indépendants . régime d'imposition . réel",
"déclaration indépendants . régime d'imposition . déclaration contrôlée",
'entreprise . imposition . IR . micro-fiscal',
],
}}
>
<TrackPage name="simulation terminée" />
<ResultatsParFormulaire />
</Condition>
</>
) : (
<WhenAlreadyDefined dottedName="déclaration indépendants . résultat simple . cotisations obligatoires">
<ResultatsSimples />
</WhenAlreadyDefined>
)}
</>
)
}
function ImpositionSection() {
const dispatch = useDispatch()
const situation = useSelector(situationSelector)
const setSituation = useCallback(
(value, dottedName) => {
dispatch(updateSituation(dottedName, value))
},
[dispatch]
)
return (
<>
<SimpleField dottedName="entreprise . imposition" />
{situation['entreprise . imposition'] != null && (
<>
{/* <WhenApplicable dottedName="déclaration indépendants . comptabilité"> */}
<SimpleField dottedName="déclaration indépendants . comptabilité" />
{/* </WhenApplicable> */}
<Condition
expression={
FEATURE_FLAG_RESULTATS_COMPLETS
? 'oui'
: 'déclaration indépendants . cotisations payées version simple = non'
}
>
<FromTop key={situation['entreprise . imposition']}>
<Condition expression="entreprise . imposition . IR">
<SimpleField dottedName="entreprise . imposition . IR . micro-fiscal" />
<Condition expression="entreprise . imposition . IR . micro-fiscal">
<H2>
Quel est votre chiffre d'affaires hors taxes en 2021 ?
</H2>
<SmallBody>
Indiquez le montant hors taxes de votre chiffre daffaires
ou de vos recettes bruts (avant déduction de labattement
forfaitaire pour frais et charges) et avant déduction des
exonérations fiscales dont vous avez bénéficié
</SmallBody>
<SimpleField dottedName="entreprise . chiffre d'affaires . vente restauration hébergement" />
<SimpleField dottedName="entreprise . chiffre d'affaires . service BIC" />
<SimpleField dottedName="entreprise . chiffre d'affaires . service BNC" />
</Condition>
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<H2>
Quel est votre résultat fiscal en 2021 ?<br />
<small>
Charges sociales et exonérations fiscales non incluses{' '}
<ExplicationsResultatFiscal />
</small>
</H2>
<SmallBody>
Le résultat fiscal correspond aux produits moins les
charges. Il peut être positif (bénéfice) ou négatif
(déficit).
</SmallBody>
<BigInput>
<RuleInput
dottedName="dirigeant . rémunération . totale"
onChange={setSituation}
autoFocus
/>
</BigInput>
</Condition>
</Condition>
<Condition expression="entreprise . imposition . IS">
<H2>
Quel est le montant net de votre rémunération en 2021 ?
<br />
<small>Sans tenir compte des charges sociales</small>
</H2>
<BigInput>
<RuleInput
dottedName="dirigeant . rémunération . nette"
onChange={setSituation}
autoFocus
/>
</BigInput>
</Condition>
</FromTop>
</Condition>
</>
)}
</>
)
}
export const Question = styled.div`
margin-top: 1em;
`
const BigInput = styled.div`
font-size: 130%;
`

View File

@ -8,7 +8,7 @@ objectifs:
- déclaration indépendants . formulaire 2035
situation:
dirigeant: "'indépendant'"
dirigeant . régime social: "'indépendant'"
année: 2021
déclaration indépendants: oui
dirigeant . indépendant . PL . CIPAV: non # TODO En attendant la transitivité des remplacements

View File

@ -1,308 +1,93 @@
import { Grid } from '@mui/material'
import { updateSituation } from '@/actions/actions'
import RuleInput from '@/components/conversation/RuleInput'
import { resetCompany } from '@/actions/companyActions'
import { useSetEntreprise } from '@/actions/companyStatusActions'
import { CompanyDetails } from '@/components/company/Details'
import { CompanySearchField } from '@/components/company/SearchField'
import {
Condition,
WhenAlreadyDefined,
WhenApplicable,
WhenNotAlreadyDefined,
} from '@/components/EngineValue'
import PageHeader from '@/components/PageHeader'
import PreviousSimulationBanner from '@/components/PreviousSimulationBanner'
import { FromTop } from '@/components/ui/animate'
import Warning from '@/components/ui/WarningBlock'
import Emoji from '@/components/utils/Emoji'
import useSimulationConfig from '@/components/utils/useSimulationConfig'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Message } from '@/design-system'
import { Button } from '@/design-system/buttons'
import { Spacing } from '@/design-system/layout'
import { Strong } from '@/design-system/typography'
import { H2, H3 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { Li, Ul } from '@/design-system/typography/list'
import { Body, Intro, SmallBody } from '@/design-system/typography/paragraphs'
import { useCallback } from 'react'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import { Grid } from '@mui/material'
import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { situationSelector } from '@/selectors/simulationSelectors'
import styled from 'styled-components'
import { TrackPage } from '../../../ATInternetTracking'
import simulationConfig from './config.yaml'
import { ExplicationsResultatFiscal } from './ExplicationResultatFiscal'
import { SimpleField, SubSection } from './Fields'
import ResultatsSimples from './RésultatSimple'
import ResultatsParFormulaire from './RésultatsParFormulaire'
import { useDispatch } from 'react-redux'
import illustration from './undraw_fill_in_mie5.svg'
/**
* Nous avons proposé une nouvelle vision des résultat plus complète, avec une proposition d'aide pour
* l'ensemble des cases liées aux cotisations sociales.
*
* Hors de propos pour 2021, étant donné que cela prendrait beaucoup de temps à valider par la DGFiP
* En attendant, on propose la version "simple" (mais moins utile).
*
* Le but est de faire valider la version plus complète pour la déclaration de revenu 2021.
*/
const FEATURE_FLAG_RESULTATS_COMPLETS =
!import.meta.env.SSR && document.location.search.includes('next')
export default function AideDéclarationIndépendant() {
useSimulationConfig(simulationConfig)
const situation = useSelector(situationSelector)
const setEntreprise = useSetEntreprise()
const sitePaths = useContext(SitePathsContext)
const dispatch = useDispatch()
return (
<>
<Trans i18nKey="aide-déclaration-indépendant.description">
<Trans i18nKey="assistant-DRI.description">
<PageHeader picture={illustration}>
<Intro>
Cet outil est une aide à la déclaration de revenus à destination des{' '}
<Strong>travailleurs indépendants</Strong>. Il vous permet de
connaître le montant des charges sociales déductibles.
Cet outil vous aidera à remplir votre{' '}
<Strong>déclaration de revenu</Strong> sur impot.gouv.fr. Vous aurez
à la fin :
</Intro>
<SmallBody>
Vous restez entièrement responsable d'éventuelles omissions ou
inexactitudes dans votre déclaration.
</SmallBody>
<Ul size="XL">
<Li>
La liste des cases qui vous concernent avec le montant à remplir
</Li>
<Li>
Une estimation des cotisations sociales à payer à l'Urssaf en 2022
</Li>
</Ul>
</PageHeader>
<Warning localStorageKey="aide-déclaration-indépendant.warning">
<Body>
<Strong>
Cet outil vous concerne si vous êtes dans le cas suivant :
</Strong>
</Body>
<Ul>
<Li>
Vous cotisez au régime général des travailleurs indépendants
</Li>
</Ul>
<Body>
<Strong>
Il ne vous concerne pas si vous êtes dans un des cas suivants :
</Strong>
</Body>
<Ul>
<Li>
Vous exercez une activité libérale relevant dun régime de
retraite des professions libérales en comptabilité d'engagement
</Li>
<Li>Votre entreprise est domiciliée dans les DOM</Li>
</Ul>
</Warning>
<PreviousSimulationBanner />
<H2>Imposition</H2>
<Body>
Ces quelques questions permettent de déterminer le type de déclaration
à remplir, ainsi que les modalités de calcul des cotisations sociales.
</Body>
<H2>Mon entreprise</H2>
</Trans>
{Object.keys(situation).length ? (
<TrackPage name="commence" />
) : (
<TrackPage name="accueil" />
)}
<ImpositionSection />
<FromTop>
<Grid container>
<Grid item xs={12} sm={10} md={9} lg={8}>
<Condition expression="déclaration indépendants . comptabilité . engagement">
<Trans i18nKey="aide-déclaration-indépendant.entreprise.titre">
<H2>Entreprise et activité</H2>
</Trans>
<SimpleField
dottedName="entreprise . date de création"
showSuggestions={false}
/>
<Condition expression="entreprise . date de création > 31/12/2021">
<SmallBody
css={`
color: #ff2d96;
`}
>
Cette aide à la déclaration concerne uniquement les
entreprises déjà en activité en 2021
</SmallBody>
</Condition>
<SubSection dottedName="déclaration indépendants . nature de l'activité" />
{/* PLNR */}
<SimpleField dottedName="entreprise . activité . débit de tabac" />
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . déduction tabac" />
<SimpleField dottedName="dirigeant . indépendant . PL . régime général . taux spécifique retraite complémentaire" />
<H2>
<Trans>Situation personnelle</Trans>
</H2>
<SimpleField dottedName="situation personnelle . RSA" />
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<SubSection dottedName="dirigeant . indépendant . IJSS" />
</Condition>
<SubSection dottedName="dirigeant . indépendant . conjoint collaborateur" />
<H2>
<Trans>Exonérations</Trans>
</H2>
<Body>
<Emoji emoji="🏗️" /> Les calculs de l'exonération COVID 2021
sont en cours d'implémentation
</Body>
<SimpleField dottedName="déclaration indépendants . ACRE" />
<SimpleField dottedName="établissement . ZFU" />
<Condition expression="établissement . ZFU">
<SimpleField dottedName="entreprise . effectif . seuil" />
</Condition>
<SubSection
dottedName="dirigeant . indépendant . cotisations et contributions . exonérations"
hideTitle
/>
{FEATURE_FLAG_RESULTATS_COMPLETS && (
<SubSection dottedName="dirigeant . indépendant . cotisations facultatives" />
)}
<H2>
<Trans>International</Trans>
</H2>
<SimpleField dottedName="situation personnelle . domiciliation fiscale à l'étranger" />
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<SubSection
dottedName="dirigeant . indépendant . revenus étrangers"
hideTitle
/>
</Condition>
</Condition>
<Condition expression="déclaration indépendants . cotisations payées">
<SubSection dottedName="déclaration indépendants . cotisations payées" />
<SimpleField dottedName="déclaration indépendants . nature de l'activité" />
<SimpleField dottedName="dirigeant . indépendant . conjoint collaborateur" />
<SubSection dottedName="dirigeant . indépendant . cotisations facultatives" />
{/* We can't use a subsection here cause revenu étrangers is not missing when CSG is replaced */}
<H3>
<Trans>Revenus étranger</Trans>
</H3>
<SimpleField dottedName="dirigeant . indépendant . revenus étrangers" />
<Condition expression="dirigeant . indépendant . revenus étrangers">
<SimpleField dottedName="dirigeant . indépendant . revenus étrangers . montant" />
</Condition>
</Condition>
<Condition expression="déclaration indépendants . cotisations payées version simple">
<SimpleField dottedName="déclaration indépendants . cotisations payées version simple . cotisations sociales" />
<SimpleField dottedName="déclaration indépendants . cotisations payées version simple . CSG déductible et CFP" />
</Condition>
</Grid>
<Grid container>
<Grid item lg={8}>
<WhenNotAlreadyDefined dottedName="entreprise . SIREN">
<Body>
Cherchez avec votre nom, le nom de votre entreprise, le SIREN ou
le SIRET
</Body>
<CompanySearchField onSubmit={setEntreprise} />
</WhenNotAlreadyDefined>
<WhenAlreadyDefined dottedName="entreprise . SIREN">
<CompanyDetails />
<Button onPress={() => dispatch(resetCompany())}>
<Trans>Modifier</Trans>
</Button>
</WhenAlreadyDefined>
</Grid>
</FromTop>
{FEATURE_FLAG_RESULTATS_COMPLETS ? (
<>
<SubSection dottedName="déclaration indépendants . régime d'imposition" />
<Condition
expression={{
'une de ces conditions': [
"déclaration indépendants . régime d'imposition . réel",
"déclaration indépendants . régime d'imposition . déclaration contrôlée",
'entreprise . imposition . IR . micro-fiscal',
],
}}
>
<TrackPage name="simulation terminée" />
<ResultatsParFormulaire />
</Condition>
</>
) : (
<WhenAlreadyDefined dottedName="déclaration indépendants . résultat simple . cotisations obligatoires">
<ResultatsSimples />
</WhenAlreadyDefined>
)}
</Grid>
<Condition expression="entreprise . catégorie juridique . SAS">
<Spacing md />
<Message type="info">
<H3>Cet assistant ne gère pas le cas des dirigeants de SAS(U)</H3>
<Body>
Nous sommes désolés. Si vous rencontrez des difficultés à remplir
votre déclaration, rapprochez-vous de votre comptable. Si vous êtes
sans comptable, vous pouvez contacter le service des impôts.
</Body>
<Body>
Si vous souhaitez que cet assistant à la déclaration gère votre cas
dans le futur, laissez-nous message en cliquant sur le bouton "Faire
une suggestion" en bas de la page.
</Body>
<Body>
Ce site propose d'autres outils qui pourraient vous intéresser (par
exemple un simulateur de revenu net après impôt).
</Body>
<Link to={sitePaths.gérer.index}>
Découvrir les outils pour mon entreprise
</Link>
<Spacing md />
</Message>
</Condition>
</>
)
}
function ImpositionSection() {
const dispatch = useDispatch()
const situation = useSelector(situationSelector)
const setSituation = useCallback(
(value, dottedName) => {
dispatch(updateSituation(dottedName, value))
},
[dispatch]
)
return (
<>
<SimpleField dottedName="entreprise . imposition" />
{situation['entreprise . imposition'] != null && (
<>
{/* <WhenApplicable dottedName="déclaration indépendants . comptabilité"> */}
<SimpleField dottedName="déclaration indépendants . comptabilité" />
{/* </WhenApplicable> */}
<Condition
expression={
FEATURE_FLAG_RESULTATS_COMPLETS
? 'oui'
: 'déclaration indépendants . cotisations payées version simple = non'
}
>
<FromTop key={situation['entreprise . imposition']}>
<Condition expression="entreprise . imposition . IR">
<SimpleField dottedName="entreprise . imposition . IR . micro-fiscal" />
<Condition expression="entreprise . imposition . IR . micro-fiscal">
<H2>
Quel est votre chiffre d'affaires hors taxes en 2021 ?
</H2>
<SmallBody>
Indiquez le montant hors taxes de votre chiffre daffaires
ou de vos recettes bruts (avant déduction de labattement
forfaitaire pour frais et charges) et avant déduction des
exonérations fiscales dont vous avez bénéficié
</SmallBody>
<SimpleField dottedName="entreprise . chiffre d'affaires . vente restauration hébergement" />
<SimpleField dottedName="entreprise . chiffre d'affaires . service BIC" />
<SimpleField dottedName="entreprise . chiffre d'affaires . service BNC" />
</Condition>
<Condition expression="entreprise . imposition . IR . micro-fiscal = non">
<H2>
Quel est votre résultat fiscal en 2021 ?<br />
<small>
Charges sociales et exonérations fiscales non incluses{' '}
<ExplicationsResultatFiscal />
</small>
</H2>
<SmallBody>
Le résultat fiscal correspond aux produits moins les
charges. Il peut être positif (bénéfice) ou négatif
(déficit).
</SmallBody>
<BigInput>
<RuleInput
dottedName="dirigeant . rémunération . totale"
onChange={setSituation}
autoFocus
/>
</BigInput>
</Condition>
</Condition>
<Condition expression="entreprise . imposition . IS">
<H2>
Quel est le montant net de votre rémunération en 2021 ?
<br />
<small>Sans tenir compte des charges sociales</small>
</H2>
<BigInput>
<RuleInput
dottedName="dirigeant . rémunération . nette"
onChange={setSituation}
autoFocus
/>
</BigInput>
</Condition>
</FromTop>
</Condition>
</>
)}
</>
)
}
export const Question = styled.div`
margin-top: 1em;
`
const BigInput = styled.div`
font-size: 130%;
`

View File

@ -8,7 +8,7 @@ import { RootState } from '@/reducers/rootReducer'
import aideOrganismeSvg from './aideOrganisme.svg'
const aideMidiPyrenéesAutoEntrepreneur = (state: RootState) => {
const company = state.inFranceApp.existingCompany
const company = state.choixStatutJuridique.existingCompany
if (!company) {
return false
}

View File

@ -23,7 +23,7 @@ type EmbaucherProps = {
function Embaucher({ onChecklistInitialization, onItemCheck }: EmbaucherProps) {
const { t } = useTranslation()
const hiringChecklist = useSelector(
(state: RootState) => state.inFranceApp.hiringChecklist
(state: RootState) => state.choixStatutJuridique.hiringChecklist
)
return (
<FromBottom>
@ -247,7 +247,7 @@ function Embaucher({ onChecklistInitialization, onItemCheck }: EmbaucherProps) {
export default connect(
(state: RootState) => ({
hiringChecklist: state.inFranceApp.hiringChecklist,
hiringChecklist: state.choixStatutJuridique.hiringChecklist,
}),
{
onChecklistInitialization: initializeHiringChecklist,

View File

@ -1,23 +1,23 @@
import { Grid } from '@mui/material'
import { specifyIfAutoEntrepreneur } from '@/actions/existingCompanyActions'
import CompanyDetails from '@/components/CompanyDetails'
import { DottedName } from '@/../../modele-social'
import RuleInput from '@/components/conversation/RuleInput'
import { WhenApplicable, WhenNotApplicable } from '@/components/EngineValue'
import PageHeader from '@/components/PageHeader'
import { FromBottom } from '@/components/ui/animate'
import { ScrollToTop } from '@/components/utils/Scroll'
import { PlacesDesEntreprisesButton } from '@/components/PlaceDesEntreprises'
import { FromTop } from '@/components/ui/animate'
import { useEngine } from '@/components/utils/EngineContext'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Button } from '@/design-system/buttons'
import useSimulationConfig from '@/components/utils/useSimulationConfig'
import { Message } from '@/design-system'
import { Container, Spacing } from '@/design-system/layout'
import Popover from '@/design-system/Popover'
import { H2 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { Intro } from '@/design-system/typography/paragraphs'
import { useContext, useEffect, useRef, useState } from 'react'
import { H2, H3 } from '@/design-system/typography/heading'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import { useQuestionList } from '@/hooks/useQuestionList'
import { Grid } from '@mui/material'
import Engine from 'publicodes'
import { useContext } from 'react'
import { Helmet } from 'react-helmet-async'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { Redirect } from 'react-router'
import { Company } from '@/reducers/inFranceAppReducer'
import { RootState } from '@/reducers/rootReducer'
import styled from 'styled-components'
import { TrackPage } from '../../ATInternetTracking'
import { SimulateurCard } from '../Simulateurs/Home'
@ -30,85 +30,82 @@ import { MobiliteCard } from './cards/MobiliteCard'
import { SecuriteSocialeCard } from './cards/SecuriteSocialeCard'
import forms from './forms.svg'
import growth from './growth.svg'
import { PlacesDesEntreprisesButton } from '@/components/PlaceDesEntreprises'
export type DirigeantOrNull = keyof SimulatorData | null
const infereDirigeantSimulateurFromCompanyDetails = (
company: Company | null
): DirigeantOrNull => {
if (!company) {
return null
}
if (company.isAutoEntrepreneur) {
const infereSimulateurRevenuFromSituation = (
engine: Engine<DottedName>
): keyof SimulatorData | null => {
if (
engine.evaluate('entreprise . catégorie juridique . EI . auto-entrepreneur')
.nodeValue
) {
return 'auto-entrepreneur'
}
if (
company.statutJuridique &&
['EIRL', 'EURL', 'EI'].includes(company.statutJuridique) &&
inferPLSimulateurFromCompanyDetails(company)
) {
return inferPLSimulateurFromCompanyDetails(company)
}
if (company.statutJuridique === 'EI') {
return 'entreprise-individuelle'
}
if (
company.statutJuridique &&
['EIRL', 'SASU', 'EURL'].includes(company.statutJuridique)
) {
return company.statutJuridique.toLowerCase() as 'eirl' | 'sasu' | 'eurl'
}
if (company.statutJuridique === 'SARL') {
return 'indépendant'
}
if (company.statutJuridique === 'SAS') {
if (
engine.evaluate('entreprise . catégorie juridique . SARL . unipersonnelle')
.nodeValue
) {
return 'eurl'
}
if (
engine.evaluate('entreprise . catégorie juridique . SAS . unipersonnelle')
.nodeValue
) {
return 'sasu'
}
return null
}
// Profession Libérale
const inferPLSimulateurFromCompanyDetails = (
company: Company | null
): DirigeantOrNull => {
if (!company) {
return null
if (
engine.evaluate(
'entreprise . catégorie juridique . EI . responsabilité limité'
).nodeValue
) {
return 'eirl'
}
const activiteToSimulator = {
'Activités comptables': 'expert-comptable',
'Activité des médecins généralistes': 'médecin',
'Activités de radiodiagnostic et de radiothérapie': 'médecin',
'Activités chirurgicales': 'médecin',
'Activité des médecins spécialistes': 'médecin',
'Activités hospitalières': 'pamc',
'Pratique dentaire': 'chirurgien-dentiste',
'Commerce de détail de produits pharmaceutiques en magasin spécialisé':
'pharmacien',
'Activités des infirmiers et des sages-femmes': 'pamc',
"Activités des professionnels de la rééducation, de l'appareillage et des pédicures-podologues":
'auxiliaire-médical',
"Laboratoires d'analyses médicales": 'pharmacien',
'Arts du spectacle vivant': 'artiste-auteur',
'Création artistique relevant des arts plastiques': 'artiste-auteur',
'Autre création artistique': 'artiste-auteur',
'Activités photographiques': 'artiste-auteur',
} as Record<string, keyof SimulatorData>
return activiteToSimulator[company.activitePrincipale] || null
if (engine.evaluate('entreprise . catégorie juridique . EI').nodeValue) {
const métierProfessionLibéral = engine.evaluate(
'dirigeant . indépendant . PL . métier'
).nodeValue
switch (métierProfessionLibéral) {
case 'avocat':
return 'avocat'
case 'expert-comptable':
return 'expert-comptable'
case 'santé . médecin':
return 'médecin'
case 'santé . chirurgien-dentiste':
return 'chirurgien-dentiste'
case 'santé . sage-femme':
return 'sage-femme'
case 'santé . auxiliaire médical':
return 'auxiliaire-médical'
case 'santé . pharmacien':
return 'pharmacien'
}
if (engine.evaluate('dirigeant . indépendant . PL').nodeValue) {
return 'profession-libérale'
}
return 'entreprise-individuelle'
}
const régimeSocial = engine.evaluate('dirigeant . régime social').nodeValue
if (régimeSocial === 'indépendant') {
return 'indépendant'
}
// TODO : assimilé-salarié
// if (
// régimeSocial === 'assimilé-salarié'
// ) {
// return 'assimilé-salarié'
// }
return null
}
export default function Gérer() {
const { t, i18n } = useTranslation()
const company = useSelector(
(state: RootState) => state.inFranceApp.existingCompany
)
const dirigeantSimulateur =
infereDirigeantSimulateurFromCompanyDetails(company)
const dirigeantSimulateur = infereSimulateurRevenuFromSituation(useEngine())
const simulateurs = useSimulatorsData()
const sitePaths = useContext(SitePathsContext)
if (!company) {
const engine = useEngine()
if (!engine.evaluate('entreprise . SIREN').nodeValue) {
return <Redirect to={sitePaths.index} />
}
return (
@ -118,191 +115,156 @@ export default function Gérer() {
</Helmet>
<TrackPage name="accueil" />
<ScrollToTop />
<FromBottom>
<PageHeader
picture={growth}
titre={<Trans i18nKey="gérer.titre">Gérer mon activité</Trans>}
>
<Intro>
<Trans i18nKey="gérer.description">
Vous souhaitez vous verser un revenu ou embaucher ? Vous aurez à
payer des cotisations et des impôts. Anticipez leurs montants
grâce aux simulateurs adaptés à votre situation.
</Trans>
</Intro>
<CompanySection company={company} />
<Spacing xl />
</PageHeader>
<PageHeader
picture={growth}
titre={<Trans i18nKey="gérer.titre">Gérer mon activité</Trans>}
>
<Intro>
<Trans i18nKey="gérer.description">
Vous souhaitez vous verser un revenu ou embaucher ? Vous aurez à
payer des cotisations et des impôts. Anticipez leurs montants grâce
aux simulateurs adaptés à votre situation.
</Trans>
</Intro>
<AskCompanyMissingDetails />
<Spacing xl />
</PageHeader>
{dirigeantSimulateur && (
<Container
backgroundColor={(theme) => theme.colors.bases.primary[600]}
darkMode
>
<FormsImage src={forms} alt="" />
<Spacing xs />
<Container
backgroundColor={(theme) => theme.colors.bases.primary[600]}
darkMode
>
<FromTop>
<FormsImage src={forms} alt="" />
<Spacing xs />
<H2>Simulateurs pour votre entreprise</H2>
<Grid container spacing={3} position="relative">
{dirigeantSimulateur ? (
<SimulateurCard fromGérer {...simulateurs[dirigeantSimulateur]} />
) : (
<Grid item>
<Trans>
<Message>
<Intro>
Il n'existe pas encore de simulateur de revenu pour votre
type d'entreprise sur ce site.
</Intro>
<Body>
Si vous souhaitez que nous développions un nouveau
simulateur, laissez-nous message en cliquant sur le bouton
"Faire une suggestion" en bas de cette page.
</Body>
</Message>
</Trans>
</Grid>
)}
<H2>Entreprise et revenus</H2>
<Grid container spacing={3} position="relative">
{dirigeantSimulateur !== null && (
<SimulateurCard
fromGérer
{...simulateurs[dirigeantSimulateur]}
/>
)}
{company?.statutJuridique &&
['EIRL', 'EI', 'EURL', 'SARL'].includes(
company.statutJuridique
) &&
!company.isAutoEntrepreneur && (
<WhenApplicable dottedName="dirigeant . indépendant">
<SimulateurCard
fromGérer
{...simulateurs['aide-déclaration-indépendant']}
/>
</WhenApplicable>
<WhenApplicable dottedName="entreprise . imposition . IS">
<Grid item xs={12} md={6} lg={4} alignSelf="flex-end">
<Grid container spacing={3} columns={2}>
<SimulateurCard fromGérer {...simulateurs['is']} small />
<SimulateurCard
fromGérer
{...simulateurs['aide-déclaration-indépendant']}
{...simulateurs['dividendes']}
small
/>
)}
{company?.statutJuridique &&
['SARL', 'SASU', 'SAS'].includes(company.statutJuridique) && (
<Grid item xs={12} md={6} lg={4} alignSelf="flex-end">
<Grid container spacing={3} columns={2}>
<SimulateurCard fromGérer {...simulateurs['is']} small />
<SimulateurCard
fromGérer
{...simulateurs['dividendes']}
small
/>
</Grid>
</Grid>
)}
</Grid>
<Spacing xl />
</Container>
)}
{dirigeantSimulateur !== 'auto-entrepreneur' && (
<>
<H2>
<Trans>Salariés et embauche</Trans>
</H2>
<Grid container spacing={3}>
<SimulateurCard fromGérer {...simulateurs['salarié']} />
<SimulateurCard fromGérer {...simulateurs['chômage-partiel']} />
</Grid>
</>
)}
<AideOrganismeLocal />
<H2>
<Trans>Ressources utiles</Trans>
</H2>
<Grid container spacing={3}>
{dirigeantSimulateur === 'indépendant' && i18n.language === 'fr' && (
<Grid item sm={12} md={4}>
<MobiliteCard />
</Grid>
)}
{!company?.isAutoEntrepreneur && (
<Grid item sm={12} md={4}>
<DemarcheEmbaucheCard />
</Grid>
)}
{company?.isAutoEntrepreneur && (
<Grid item sm={12} md={4}>
<AutoEntrepreneurCard />
</Grid>
)}
<Grid item sm={12} md={4}>
<SecuriteSocialeCard />
</Grid>
</Grid>
</WhenApplicable>
</Grid>
<Grid item sm={12} md={4}>
<KbisCard dirigeant={dirigeantSimulateur} />
</FromTop>
<Spacing xl />
</Container>
{dirigeantSimulateur !== 'auto-entrepreneur' && (
<FromTop>
<H2>
<Trans>Salariés et embauche</Trans>
</H2>
<Grid container spacing={3}>
<SimulateurCard fromGérer {...simulateurs['salarié']} />
<SimulateurCard fromGérer {...simulateurs['chômage-partiel']} />
</Grid>
</FromTop>
)}
<AideOrganismeLocal />
<H2>
<Trans>Ressources utiles</Trans>
</H2>
<Grid container spacing={3}>
{dirigeantSimulateur === 'indépendant' && i18n.language === 'fr' && (
<Grid item sm={12} md={4}>
<MobiliteCard />
</Grid>
)}
<WhenNotApplicable dottedName="entreprise . catégorie juridique . EI . auto-entrepreneur">
<Grid item sm={12} md={4}>
<DemarcheEmbaucheCard />
</Grid>
</WhenNotApplicable>
<WhenApplicable dottedName="entreprise . catégorie juridique . EI . auto-entrepreneur">
<Grid item sm={12} md={4}>
<AutoEntrepreneurCard />
</Grid>
</WhenApplicable>
<Grid item sm={12} md={4}>
<SecuriteSocialeCard />
</Grid>
<Spacing lg />
<PlacesDesEntreprisesButton
pathname="/aide-entreprise/mon-entreprise-urssaf-fr"
siret={company.firstMatchingEtablissement.siret}
/>
</FromBottom>
<Grid item sm={12} md={4}>
<KbisCard dirigeant={dirigeantSimulateur} />
</Grid>
</Grid>
<PlacesDesEntreprisesButton
pathname="/aide-entreprise/mon-entreprise-urssaf-fr"
siret={engine.evaluate('établissement . SIRET').nodeValue}
/>
</>
)
}
type CompanySectionProps = {
company: Company | null
const companyDetailsConfig = {
situation: {
'contrat salarié': 'non',
},
objectifs: [
'dirigeant . régime social',
'entreprise . imposition',
] as DottedName[],
}
export const AskCompanyMissingDetails = () => {
useSimulationConfig(companyDetailsConfig)
export const CompanySection = ({ company }: CompanySectionProps) => {
const [autoEntrepreneurModal, showAutoEntrepreneurModal] = useState(false)
const sitePaths = useContext(SitePathsContext)
const companyRef = useRef<Company | null>(null)
useEffect(() => {
if (companyRef.current !== company) {
companyRef.current = company
if (
company?.statutJuridique === 'EI' &&
company?.isAutoEntrepreneur == null &&
!inferPLSimulateurFromCompanyDetails(company)
) {
showAutoEntrepreneurModal(true)
}
}
}, [company])
const dispatch = useDispatch()
const handleAnswerAutoEntrepreneur = (isAutoEntrepreneur: boolean) => {
dispatch(specifyIfAutoEntrepreneur(isAutoEntrepreneur))
showAutoEntrepreneurModal(false)
const [questions, onQuestionAnswered] = useQuestionList()
if (!questions.length) {
return null
}
const { t } = useTranslation()
return (
<>
{autoEntrepreneurModal && (
<>
<ScrollToTop />
<Popover
title={t('gérer.entreprise.auto', 'Êtes-vous auto-entrepreneur ?')}
small
>
<Grid container spacing={2}>
<Grid item>
<Button
size="XS"
onPress={() => handleAnswerAutoEntrepreneur(true)}
>
<Trans>Oui</Trans>
</Button>
</Grid>
<Grid item>
<Button
size="XS"
onPress={() => handleAnswerAutoEntrepreneur(false)}
>
<Trans>Non</Trans>
</Button>
</Grid>
</Grid>
</Popover>
</>
)}
{company && (
<>
<CompanyDetails entreprise={company} />
<Link to={sitePaths.index}>
<Trans i18nKey="gérer.entreprise.changer">
Changer l'entreprise sélectionnée
</Trans>
</Link>
</>
)}
<Body>
Répondez à ces quelques questions rapides pour selectionner les outils
et assistants qui vous conviennent le mieux.
</Body>
{questions.map((question) => (
<FromTop key={question.dottedName}>
<H3>{question.rawNode.question}</H3>
<RuleInput
dottedName={question.dottedName}
onChange={onQuestionAnswered(question.dottedName)}
/>
</FromTop>
))}
</>
)
}

View File

@ -1,11 +1,11 @@
import { Grid } from '@mui/material'
import CompanyDetails from '@/components/CompanyDetails'
import CompanySearchDetails from '@/components/company/SearchDetails'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Card } from '@/design-system/card'
import { H3 } from '@/design-system/typography/heading'
import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { Company } from '@/reducers/inFranceAppReducer'
import { Company } from '@/reducers/choixStatutJuridiqueReducer'
type ContinueWithCompanyProps = {
company: Company
@ -29,7 +29,7 @@ export const ContinueWithCompany = ({ company }: ContinueWithCompanyProps) => {
data-testid="currently-selected-company"
bodyAs="div"
>
<CompanyDetails entreprise={company} />
<CompanySearchDetails entreprise={company} />
</Card>
</Grid>
</Grid>

View File

@ -24,9 +24,6 @@ import SearchOrCreate from './SearchOrCreate'
export default function Landing() {
const simulators = useSimulatorsData()
const sitePaths = useContext(SitePathsContext)
const company = useSelector(
(state: RootState) => state.inFranceApp.existingCompany
)
return (
<>
@ -59,7 +56,6 @@ export default function Landing() {
darkMode
backgroundColor={(theme) => theme.colors.bases.primary[600]}
>
{company && <ContinueWithCompany company={company} />}
<SearchOrCreate />
<Spacing xl />
</Container>

View File

@ -1,49 +1,70 @@
import { Grid } from '@mui/material'
import { resetCompany } from '@/actions/companyActions'
import { useSetEntreprise } from '@/actions/companyStatusActions'
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { CompanySearchField } from '@/components/CompanySearchField'
import Emoji from '@/components/utils/Emoji'
import { CompanyDetails } from '@/components/company/Details'
import { CompanySearchField } from '@/components/company/SearchField'
import Value from '@/components/EngineValue'
import { useEngine } from '@/components/utils/EngineContext'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Message } from '@/design-system'
import AnswerGroup from '@/design-system/answer-group'
import { Button } from '@/design-system/buttons'
import { H3 } from '@/design-system/typography/heading'
import { Spacing } from '@/design-system/layout'
import { H3, H4 } from '@/design-system/typography/heading'
import { RootState } from '@/reducers/rootReducer'
import { Grid } from '@mui/material'
import { useCallback, useContext } from 'react'
import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { useHistory } from 'react-router-dom'
import { RootState } from '@/reducers/rootReducer'
import styled from 'styled-components'
export default function SearchOrCreate() {
const sitePaths = useContext(SitePathsContext)
const statutChoisi = useSelector(
(state: RootState) => state.inFranceApp.companyStatusChoice
(state: RootState) => state.choixStatutJuridique.companyStatusChoice
)
const companySIREN = useEngine().evaluate('entreprise . SIREN').nodeValue
const handleCompanySubmit = useHandleCompanySubmit()
const dispatch = useDispatch()
return (
<Grid container spacing={3}>
<Grid item lg={8} md={12}>
<H3 as="h2">
<Trans>Rechercher une entreprise</Trans>{' '}
</H3>
<CompanySearchField onSubmit={handleCompanySubmit} />
</Grid>
<Grid item lg md={12}>
<ButtonContainer>
<Button
size="XL"
to={
statutChoisi
? sitePaths.créer[statutChoisi]
: sitePaths.créer.index
}
>
<Trans i18nKey="landing.choice.create.title">
Créer une entreprise
</Trans>{' '}
<Emoji emoji="💡" />
</Button>
</ButtonContainer>
<Grid item xl={8} lg={10} md={12}>
{companySIREN ? (
<>
<H3 as="h2">Votre entreprise</H3>
<CompanyDetails />
<Spacing md />
<AnswerGroup>
<Button to={sitePaths.gérer.index}>
Continuer avec cette entreprise
</Button>
<Button light onPress={() => dispatch(resetCompany())}>
Modifier
</Button>
</AnswerGroup>
</>
) : (
<>
<H3 as="h2">
<Trans>Rechercher votre entreprise</Trans>{' '}
</H3>
<CompanySearchField onSubmit={handleCompanySubmit} />
<Spacing md />
<Button
size="XL"
to={
statutChoisi
? sitePaths.créer[statutChoisi]
: sitePaths.créer.index
}
>
<Trans i18nKey="landing.choice.create.title">
Je n'ai pas encore d'entreprise
</Trans>
</Button>
</>
)}
</Grid>
</Grid>
)
@ -62,12 +83,3 @@ function useHandleCompanySubmit() {
)
return handleCompanySubmit
}
const ButtonContainer = styled.h2`
text-align: center;
margin: 0;
@media (min-width: ${({ theme }) => theme.breakpointsWidth.lg}) {
position: relative;
top: 4.25rem;
}
`

View File

@ -149,7 +149,8 @@ export default function VotreSituation() {
<Trans i18nKey="économieCollaborative.WIP">
<Strong>Cet assistant est en cours de développement.</Strong>{' '}
N'hésitez pas à nous faire part de toute vos remarques, idées,
questions en cliquant sur le bouton "Faire une suggestion" ci dessus.
questions en cliquant sur le bouton "Faire une suggestion" en bas de
la page.
</Trans>
</SmallBody>
</FromBottom>

View File

@ -54,8 +54,7 @@ export default function PageData(props: PageDataProps) {
const year = typeof année === 'number' && année != 2022 ? ` - ${année}` : ''
const inIframe = useIsEmbedded()
const fromGérer = !!useLocation<{ fromGérer?: boolean }>().state?.fromGérer
useSimulationConfig(config, { useExistingCompanyFromSituation: fromGérer })
useSimulationConfig(config)
useSearchParamsSimulationSharing()
// TODO : Move this logic elsewhere.

View File

@ -1,5 +1,8 @@
import Banner from '@/components/Banner'
import Value, { Condition } from '@/components/EngineValue'
import Value, {
Condition,
WhenNotAlreadyDefined,
} from '@/components/EngineValue'
import PeriodSwitch from '@/components/PeriodSwitch'
import RuleLink from '@/components/RuleLink'
import Simulation from '@/components/Simulation'
@ -45,23 +48,27 @@ export default function SalariéSimulation() {
</ButtonContainer>
</Body>
}
afterQuestionsSlot={
<BrowserOnly>
{/** L'équipe Code Du Travail Numérique ne souhaite pas référencer
* le simulateur dirigeant de SASU sur son site. */}
{!import.meta.env.SSR &&
!document.referrer?.includes('code.travail.gouv.fr') && (
<WhenNotAlreadyDefined dottedName="entreprise . catégorie juridique">
<Banner icon={'👨‍✈️'}>
<Trans>
Vous êtes dirigeant d'une SAS(U) ?{' '}
<Link to={sitePaths.simulateurs.sasu}>
Accéder au simulateur de revenu dédié
</Link>
</Trans>
</Banner>
</WhenNotAlreadyDefined>
)}
</BrowserOnly>
}
>
<SalariéSimulationGoals />
<BrowserOnly>
{/** L'équipe Code Du Travail Numérique ne souhaite pas référencer
* le simulateur dirigeant de SASU sur son site. */}
{!import.meta.env.SSR &&
!document.referrer?.includes('code.travail.gouv.fr') && (
<Banner icon={'👨‍✈️'}>
<Trans>
Vous êtes dirigeant d'une SAS(U) ?{' '}
<Link to={sitePaths.simulateurs.sasu}>
Accéder au simulateur de revenu dédié
</Link>
</Trans>
</Banner>
)}
</BrowserOnly>
</Simulation>
</>
)

View File

@ -1,5 +1,6 @@
situation:
dirigeant: "'artiste-auteur'"
dirigeant: non
contrat salarié: non
unité par défaut: €/an
objectifs:
- artiste-auteur . cotisations

View File

@ -22,8 +22,9 @@ questions:
liste noire:
- entreprise . charges
- entreprise . chiffre d'affaires
- entreprise . activité . mixte
unité par défaut: €/an
situation:
entreprise . activité . mixte: non
dirigeant: "'auto-entrepreneur'"
entreprise . catégorie juridique: "'EI'"
entreprise . catégorie juridique . EI . auto-entrepreneur: oui

View File

@ -12,6 +12,7 @@ questions:
- établissement . localisation
unité par défaut: €/mois
situation:
dirigeant: non
contrat salarié . activité partielle: oui

View File

@ -27,7 +27,7 @@ questions:
unité par défaut: €/an
situation:
dirigeant: "'assimilé salarié'"
entreprise . catégorie juridique: "'SAS'"
entreprise . résultat fiscal: 0 €/an
#TODO : en attendant que la transitivité du remplacement soit implémentée (https://github.com/betagouv/publicodes/issues/55)

View File

@ -11,8 +11,7 @@ questions:
unité par défaut: €/an
situation:
dirigeant: "'assimilé salarié'" # [TODO] [dividendes-indep]
bénéficiaire: oui
dirigeant . régime social: "'assimilé salarié'" # [TODO] [dividendes-indep]
entreprise . imposition: "'IS'"
impôt . méthode de calcul: "'PFU'"
dirigeant . rémunération . imposable: 0 €/an

View File

@ -17,6 +17,7 @@ questions:
- entreprise . imposition
- entreprise . exercice . début
- entreprise . exercice . fin
- entreprise . catégorie juridique
liste:
- entreprise
- établissement
@ -30,4 +31,4 @@ questions:
- entreprise . activité . débit de tabac
unité par défaut: €/an
situation:
dirigeant: "'indépendant'"
dirigeant . régime social: "'indépendant'"

View File

@ -26,6 +26,6 @@ questions:
- entreprise . ZFU
unité par défaut: €/an
situation:
dirigeant: "'indépendant'"
dirigeant . régime social: "'indépendant'"
entreprise . activité: "'libérale'"
entreprise . imposition: "'IR'"

View File

@ -17,6 +17,6 @@ questions:
- entreprise . activité
unité par défaut: €/an
situation:
dirigeant: "'auto-entrepreneur'"
dirigeant . régime social: "'auto-entrepreneur'"
entreprise . activité . mixte: non
contrat salarié . ATMP . taux réduit: oui

View File

@ -128,8 +128,8 @@ const metadataSrc = (t: TFunction<'translation', string>) => {
),
},
pathId: 'simulateurs.sasu',
shortName: t('pages.simulateurs.sasu.shortname', 'SASU'),
title: t('pages.simulateurs.sasu.title', 'Simulateur de SASU'),
shortName: t('pages.simulateurs.sasu.shortname', 'SAS(U)'),
title: t('pages.simulateurs.sasu.title', 'Simulateur de SAS(U)'),
nextSteps: ['is', 'comparaison-statuts'],
},
eurl: {

View File

@ -4,12 +4,12 @@ import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { H2 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { Body } from '@/design-system/typography/paragraphs'
import { SimulationConfig } from '@/reducers/rootReducer'
import React, { createContext, useContext, useMemo } from 'react'
import { TFunction, Trans, useTranslation } from 'react-i18next'
import { SimulationConfig } from '@/reducers/rootReducer'
import { constructLocalizedSitePath, SitePathsType } from '../../sitePaths'
import Créer from '../Creer/Home'
import AideDéclarationIndépendant from '../Gerer/AideDéclarationIndépendant'
import AideDéclarationIndépendant from '../Gerer/AideDéclarationIndépendant/PreviousVersion'
import FormulaireMobilitéIndépendant from '../Gerer/DemandeMobilite'
import AidesEmbauche from './AidesEmbauche'
import ArtisteAuteur from './ArtisteAuteur'

View File

@ -1,10 +1,8 @@
import { Action } from '@/actions/actions'
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { ApiCommuneJson } from '@/components/conversation/select/SelectCommune'
import { omit } from '../utils'
import { combineReducers } from 'redux'
import { LegalStatus } from '@/selectors/companyStatusSelectors'
import { LegalStatusRequirements } from '@/types/companyTypes'
import { combineReducers } from 'redux'
import { omit } from '../utils'
function companyLegalStatus(
state: LegalStatusRequirements = {},
@ -86,95 +84,14 @@ function companyStatusChoice(state: LegalStatus | null = null, action: Action) {
return action.statusName
}
type StatutJuridique =
| 'EI'
| 'EURL'
| 'SARL'
| 'SAS'
| 'SA'
| 'SASU'
| 'NON_IMPLÉMENTÉ'
const infereLegalStatusFromCategorieJuridique = (
catégorieJuridique: string
): StatutJuridique => {
/*
Nous utilisons le code entreprise pour connaitre le statut juridique
(voir https://www.insee.fr/fr/information/2028129)
En revanche, impossible de différencier EI et auto-entreprise
https://www.sirene.fr/sirene/public/question.action?idQuestion=2933
*/
if (catégorieJuridique === '1000') {
return 'EI'
}
if (catégorieJuridique === '5498') {
return 'EURL'
}
if (/^54..$/.exec(catégorieJuridique)) {
return 'SARL'
}
if (/^55..$/.exec(catégorieJuridique)) {
return 'SA'
}
if (catégorieJuridique === '5720') {
return 'SASU'
}
if (/^57..$/.exec(catégorieJuridique)) {
return 'SAS'
}
return 'NON_IMPLÉMENTÉ'
}
export type Company = Omit<FabriqueSocialEntreprise, 'highlightLabel'> & {
statutJuridique?: StatutJuridique
isAutoEntrepreneur?: boolean
isDirigeantMajoritaire?: boolean
localisation?: ApiCommuneJson
}
function existingCompany(
state: Company | null = null,
action: Action
): Company | null {
if (!action.type.startsWith('EXISTING_COMPANY::')) {
return state
}
if (action.type === 'EXISTING_COMPANY::RESET') {
return null
}
if (action.type === 'EXISTING_COMPANY::SET_COMPANY') {
const statutJuridique = infereLegalStatusFromCategorieJuridique(
action.entreprise.categorieJuridiqueUniteLegale
)
return {
...omit(action.entreprise, 'highlightLabel'),
statutJuridique,
}
}
if (state && action.type === 'EXISTING_COMPANY::SPECIFY_AUTO_ENTREPRENEUR') {
return { ...state, isAutoEntrepreneur: action.isAutoEntrepreneur }
}
if (
state &&
action.type === 'EXISTING_COMPANY::SPECIFY_DIRIGEANT_MAJORITAIRE'
) {
return { ...state, isDirigeantMajoritaire: action.isDirigeantMajoritaire }
}
if (state && action.type === 'EXISTING_COMPANY::ADD_COMMUNE_DETAILS') {
return { ...state, localisation: action.details }
}
return state
}
const inFranceAppReducer = combineReducers({
const choixStatutJuridiqueReducer = combineReducers({
companyLegalStatus,
companyStatusChoice,
companyCreationChecklist,
existingCompany,
hiringChecklist,
})
export default inFranceAppReducer
export default choixStatutJuridiqueReducer
export type InFranceAppState = ReturnType<typeof inFranceAppReducer>
export type ChoixStatutJuridiqueState = ReturnType<
typeof choixStatutJuridiqueReducer
>

View File

@ -0,0 +1,129 @@
import { DottedName } from '@/../../modele-social'
import { Action } from '@/actions/actions'
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { Situation } from './rootReducer'
const SAVED_NAMESPACES = [
'contrat salarié . ATMP',
'contrat salarié . convention collective',
'dirigeant . gérant minoritaire',
'dirigeant . indépendant . PL . métier',
'entreprise . ACRE',
'entreprise . activité',
'entreprise . catégorie juridique',
'entreprise . date de création',
'entreprise . effectif',
'entreprise . exonérée de TVA',
'entreprise . imposition',
'entreprise . SIREN',
'entreprise . nom',
'établissement . adresse',
'établissement . localisation',
] as Array<DottedName>
export type Company = Omit<FabriqueSocialEntreprise, 'highlightLabel'>
export function companySituation(state: Situation = {}, action: Action) {
switch (action.type) {
case 'UPDATE_SITUATION':
if (
SAVED_NAMESPACES.some((namespace) =>
action.fieldName.startsWith(namespace)
)
) {
return {
[action.fieldName]: action.value,
...state,
}
}
break
case 'COMPANY::SET_EXISTING_COMPANY':
return getCompanySituation(action.entreprise)
case 'COMPANY::RESET':
return {}
case 'COMPANY::ADD_COMMUNE_DETAILS':
return {
...state,
'établissement . localisation': { objet: action.details },
}
case 'SET_SIMULATION':
state['entreprise . SIREN'] ? state : {}
}
return state
}
export function getCompanySituation(company: Company): Situation {
return {
'entreprise . date de création': company.dateCreationUniteLegale.replace(
/(.*)-(.*)-(.*)/,
'$3/$2/$1'
),
'entreprise . catégorie juridique': `'${getCatégorieFromCode(
company.categorieJuridiqueUniteLegale
)}'`,
'entreprise . SIREN': `'${company.siren}'`,
'entreprise . nom': `'${company.label}'`,
'établissement . SIRET': `'${company.firstMatchingEtablissement.siret}'`,
}
}
type CatégorieJuridique = 'EI' | 'SARL' | 'SAS' | 'SELARL' | 'SELAS' | 'AUTRE'
const getCatégorieFromCode = (code: string): CatégorieJuridique => {
/*
Nous utilisons le code entreprise pour connaitre le statut juridique
(voir https://www.insee.fr/fr/information/2028129)
En revanche, impossible de différencier EI et auto-entreprise
https://www.sirene.fr/sirene/public/question.action?idQuestion=2933
*/
if (code === '1000') {
return 'EI'
}
if (code === '5485') {
return 'SELARL'
}
if (code === '5470') {
return 'AUTRE'
}
if (/^54..$/.exec(code)) {
return 'SARL'
}
if (code === '5785') {
return 'SELAS'
}
if (code === '5710') {
return 'SAS'
}
return 'AUTRE'
}
// // Profession Libérale
// const inferPLSimulateurFromCompanyDetails = (
// company: Company | null
// ): DirigeantOrNull => {
// if (!company) {
// return null
// }
// const activiteToSimulator = {
// 'Activités comptables': 'expert-comptable',
// 'Activité des médecins généralistes': 'médecin',
// 'Activités de radiodiagnostic et de radiothérapie': 'médecin',
// 'Activités chirurgicales': 'médecin',
// 'Activité des médecins spécialistes': 'médecin',
// 'Activités hospitalières': 'pamc',
// 'Pratique dentaire': 'chirurgien-dentiste',
// 'Commerce de détail de produits pharmaceutiques en magasin spécialisé':
// 'pharmacien',
// 'Activités des infirmiers et des sages-femmes': 'pamc',
// "Activités des professionnels de la rééducation, de l'appareillage et des pédicures-podologues":
// 'auxiliaire-médical',
// "Laboratoires d'analyses médicales": 'pharmacien',
// 'Arts du spectacle vivant': 'artiste-auteur',
// 'Création artistique relevant des arts plastiques': 'artiste-auteur',
// 'Autre création artistique': 'artiste-auteur',
// 'Activités photographiques': 'artiste-auteur',
// } as Record<string, keyof SimulatorData>
// return activiteToSimulator[company.activitePrincipale] || null
// }

View File

@ -1,15 +1,15 @@
import { Action } from '@/actions/actions'
import { getCompanySituation } from '@/components/utils/useSimulationConfig'
import { ApiCommuneJson } from '@/components/conversation/select/SelectCommune'
import { PreviousSimulation } from '@/selectors/previousSimulationSelectors'
import { DottedName } from 'modele-social'
import { defaultTo, without } from 'ramda'
import { omit } from '../utils'
import reduceReducers from 'reduce-reducers'
import { combineReducers, Reducer } from 'redux'
import { PreviousSimulation } from '@/selectors/previousSimulationSelectors'
import { objectifsSelector } from '../selectors/simulationSelectors'
import inFranceAppReducer from './inFranceAppReducer'
import { omit } from '../utils'
import { companySituation } from './companySituationReducer'
import choixStatutJuridique from './choixStatutJuridiqueReducer'
import previousSimulationRootReducer from './previousSimulationRootReducer'
import { ApiCommuneJson } from '@/components/conversation/select/SelectCommune'
function explainedVariable(
state: DottedName | null = null,
@ -73,7 +73,6 @@ export type Simulation = {
url: string
hiddenNotifications: Array<string>
situation: Situation
initialSituation: Situation
targetUnit: string
foldedSteps: Array<DottedName>
unfoldedStep?: DottedName | null
@ -84,15 +83,14 @@ function simulation(
action: Action
): Simulation | null {
if (action.type === 'SET_SIMULATION') {
const { config, url, initialSituation } = action
const { config, url } = action
return {
config,
url,
hiddenNotifications: [],
situation: initialSituation ?? {},
initialSituation: initialSituation ?? {},
situation: {},
targetUnit: config['unité par défaut'] || '€/mois',
foldedSteps: Object.keys(initialSituation ?? {}) as Array<DottedName>,
foldedSteps: [],
unfoldedStep: null,
}
}
@ -111,23 +109,11 @@ function simulation(
return {
...state,
hiddenNotifications: [],
situation: state.initialSituation,
situation: {},
foldedSteps: [],
unfoldedStep: null,
}
case 'BATCH_UPDATE_SITUATION': {
return (
Object.entries(action.situation as any) as Array<[DottedName, unknown]>
).reduce<Simulation | null>(
(newState, [fieldName, value]) =>
simulation(newState, {
type: 'UPDATE_SITUATION',
fieldName,
value,
}),
state
)
}
case 'UPDATE_SITUATION': {
const objectifs = without(
['entreprise . charges'],
@ -175,32 +161,34 @@ function simulation(
}
return state
}
const existingCompanyReducer = (state: RootState, action: Action) => {
if (action.type.startsWith('EXISTING_COMPANY::') && state.simulation) {
return {
...state,
simulation: {
...state.simulation,
situation: {
...state.simulation.situation,
...getCompanySituation(state.inFranceApp.existingCompany),
},
},
}
function batchUpdateSituationReducer(state: RootState, action: Action) {
if (action.type !== 'BATCH_UPDATE_SITUATION') {
return state
}
return state
return Object.entries(action.situation).reduce<RootState | null>(
(newState, [fieldName, value]) =>
mainReducer(newState ?? undefined, {
type: 'UPDATE_SITUATION',
fieldName,
value,
}),
state
)
}
const mainReducer = combineReducers({
explainedVariable,
simulation,
companySituation,
previousSimulation: defaultTo(null) as Reducer<PreviousSimulation | null>,
activeTargetInput,
inFranceApp: inFranceAppReducer,
choixStatutJuridique,
})
export default reduceReducers<RootState>(
mainReducer as any,
existingCompanyReducer as Reducer<RootState>,
mainReducer as Reducer<RootState>,
batchUpdateSituationReducer as Reducer<RootState>,
previousSimulationRootReducer as Reducer<RootState>
) as Reducer<RootState>

View File

@ -126,12 +126,12 @@ const possibleStatus = (
)
export const possibleStatusSelector = (state: {
inFranceApp: State
choixStatutJuridique: State
}): Record<LegalStatus, boolean> =>
possibleStatus(state.inFranceApp.companyLegalStatus)
possibleStatus(state.choixStatutJuridique.companyLegalStatus)
export const nextQuestionSelector = (state: RootState): Question | null => {
const legalStatusRequirements = state.inFranceApp.companyLegalStatus
const legalStatusRequirements = state.choixStatutJuridique.companyLegalStatus
const questionAnswered = Object.keys(
legalStatusRequirements
) as Array<Question>

View File

@ -1,5 +1,5 @@
import { DottedName } from 'modele-social'
import { RootState, SimulationConfig, Situation } from '@/reducers/rootReducer'
import { DottedName } from 'modele-social'
export const configSelector = (state: RootState): Partial<SimulationConfig> =>
state.simulation?.config ?? {}
@ -13,7 +13,7 @@ export const objectifsSelector = (state: RootState) => {
.flat()
const objectifs = [...primaryObjectifs, ...(config['objectifs cachés'] ?? [])]
return objectifs
return objectifs as Array<DottedName>
}
const emptySituation: Situation = {}
@ -21,23 +21,15 @@ const emptySituation: Situation = {}
export const situationSelector = (state: RootState) =>
state.simulation?.situation ?? emptySituation
export const initialSituationSelector = (state: RootState) =>
state.simulation?.initialSituation ?? emptySituation
export const configSituationSelector = (state: RootState) =>
configSelector(state).situation ?? emptySituation
export const companySituationSelector = (state: RootState) =>
state.companySituation
export const firstStepCompletedSelector = (state: RootState) => {
const situation = situationSelector(state)
const baseSituation = configSituationSelector(state)
const initialSituation = initialSituationSelector(state)
return (
Object.keys(situation).filter(
(dottedName) =>
!Object.keys(baseSituation).includes(dottedName) &&
!Object.keys(initialSituation).includes(dottedName)
).length > 0
)
return Object.keys(situation).length > 0
}
export const targetUnitSelector = (state: RootState) =>

View File

@ -1,5 +1,5 @@
import { Action } from '@/actions/actions'
import { InFranceAppState } from '@/reducers/inFranceAppReducer'
import { ChoixStatutJuridiqueState } from '@/reducers/choixStatutJuridiqueReducer'
import { RootState } from '@/reducers/rootReducer'
import { Store } from 'redux'
import { debounce } from '../utils'
@ -9,18 +9,22 @@ const VERSION = 7
const LOCAL_STORAGE_KEY = 'mon-entreprise::persisted-infranceapp::v' + VERSION
export function setupInFranceAppPersistence(store: Store<RootState, Action>) {
export function setupChoixStatutJuridiquePersistence(
store: Store<RootState, Action>
) {
const listener = () => {
const state = store.getState()
safeLocalStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(state.inFranceApp)
JSON.stringify(state.choixStatutJuridique)
)
}
store.subscribe(debounce(1000, listener))
}
export function retrievePersistedInFranceApp(): InFranceAppState {
export function retrievePersistedChoixStatutJuridique(): ChoixStatutJuridiqueState {
const serializedState = safeLocalStorage.getItem(LOCAL_STORAGE_KEY)
return serializedState ? JSON.parse(serializedState) : undefined
return serializedState && serializedState !== 'undefined'
? JSON.parse(serializedState)
: undefined
}

View File

@ -0,0 +1,30 @@
import { Action } from '@/actions/actions'
import { RootState, Situation } from '@/reducers/rootReducer'
import { Store } from 'redux'
import { debounce } from '../utils'
import * as safeLocalStorage from './safeLocalStorage'
const VERSION = 1
const LOCAL_STORAGE_KEY = `mon-entreprise::companySituation::v${VERSION}`
export function setupCompanySituationPersistence(
store: Store<RootState, Action>
) {
const listener = () => {
const state = store.getState()
safeLocalStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(state.companySituation)
)
}
store.subscribe(debounce(1000, listener))
}
export function retrievePersistedCompanySituation(): Situation | undefined {
const serializedState = safeLocalStorage.getItem(LOCAL_STORAGE_KEY)
return serializedState && serializedState !== 'undefined'
? (JSON.parse(serializedState) as Situation)
: undefined
}

View File

@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest'
import { nextQuestionSelector } from '@/selectors/companyStatusSelectors'
const state = (companyLegalStatus) => ({
inFranceApp: {
choixStatutJuridique: {
companyLegalStatus,
existingCompany: null,
companyStatusChoice: null,

View File

@ -29,7 +29,7 @@ const initialSimulation: Simulation = {
url: '/someurl',
hiddenNotifications: [],
situation: {},
initialSituation: {},
companySituation: {},
targetUnit: '€/mois',
foldedSteps: ['somestep' as DottedName],
unfoldedStep: null,

View File

@ -1,47 +1,47 @@
pfu:
- bénéficiaire . dividendes . bruts: 200 €/an
impôt . méthode de calcul: "'PFU'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
- bénéficiaire . dividendes . bruts: 20000000 €/an
impôt . méthode de calcul: "'PFU'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
barème défauts:
- bénéficiaire . dividendes . bruts: 200 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
- bénéficiaire . dividendes . bruts: 20000000 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
- bénéficiaire . dividendes . bruts: 200 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
impôt . foyer fiscal . revenu imposable . autres revenus imposables: 500000 €/an
- bénéficiaire . dividendes . bruts: 20000 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
impôt . foyer fiscal . revenu imposable . autres revenus imposables: 50000 €/an
barème couple 2 enfants:
- bénéficiaire . dividendes . bruts: 200 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
impôt . foyer fiscal . enfants à charge: 2
impôt . foyer fiscal . situation de famille: "'couple'"
- bénéficiaire . dividendes . bruts: 20000000 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
impôt . foyer fiscal . enfants à charge: 2
impôt . foyer fiscal . situation de famille: "'couple'"
- bénéficiaire . dividendes . bruts: 200 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
impôt . foyer fiscal . revenu imposable . autres revenus imposables: 500000 €/an
impôt . foyer fiscal . enfants à charge: 2
impôt . foyer fiscal . situation de famille: "'couple'"
- bénéficiaire . dividendes . bruts: 20000 €/an
impôt . méthode de calcul: "'barème standard'"
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
impôt . foyer fiscal . revenu imposable . autres revenus imposables: 50000 €/an
impôt . foyer fiscal . enfants à charge: 2
impôt . foyer fiscal . situation de famille: "'couple'"

View File

@ -86,16 +86,6 @@ atmp:
- contrat salarié . rémunération . brut de base: 2000 €/mois
contrat salarié . ATMP . taux collectif ATMP: 5%
assimilé salarié:
- dirigeant: "'assimilé salarié'"
contrat salarié . rémunération . brut de base: 5000 €/mois
- dirigeant: "'assimilé salarié'"
contrat salarié . rémunération . brut de base: 1500 €/mois
entreprise . ACRE: oui
- dirigeant: "'assimilé salarié'"
contrat salarié . rémunération . brut de base: 3000 €/mois
entreprise . ACRE: oui
aides:
- contrat salarié . rémunération . brut de base: 2000 €/mois
contrat salarié . statut JEI: oui
@ -266,7 +256,7 @@ JEI:
- contrat salarié . rémunération . brut de base: 20000 €/mois
contrat salarié . statut JEI: oui
- contrat salarié . rémunération . brut de base: 4000 €/mois
dirigeant: "'assimilé salarié'"
dirigeant . régime social: "'assimilé salarié'"
contrat salarié . statut JEI: oui
frais pro - titres restaurant:

View File

@ -29,7 +29,9 @@ import employeeSituations from './simulations-salarié.yaml'
type SituationsSpecs = Record<string, Simulation['situation'][]>
const roundResult = (arr: number[]) => arr.map((x) => Math.round(x))
const engine = engineFactory(rules)
const engine = engineFactory(rules, {
logger: { warn: () => {}, error: (m) => console.error(m), log: () => {} },
})
const runSimulations = (
situationsSpecs: SituationsSpecs,
objectifs: DottedName[],
@ -108,7 +110,7 @@ it('calculate simulations-rémunération-dirigeant (assimilé salarié)', () =>
remunerationDirigeantConfig.objectifs,
{
...remunerationDirigeantConfig.situation,
dirigeant: "'assimilé salarié'",
'dirigeant . régime social': "'assimilé salarié'",
}
)
})
@ -119,7 +121,8 @@ it('calculate simulations-rémunération-dirigeant (auto-entrepreneur)', () => {
remunerationDirigeantConfig.objectifs,
{
...remunerationDirigeantConfig.situation,
dirigeant: "'auto-entrepreneur'",
'entreprise . catégorie juridique': "'EI'",
'entreprise . catégorie juridique . EI . auto-entrepreneur': 'oui',
}
)
})
@ -130,7 +133,8 @@ it('calculate simulations-rémunération-dirigeant (indépendant)', () => {
remunerationDirigeantConfig.objectifs,
{
...remunerationDirigeantConfig.situation,
dirigeant: "'indépendant'",
'dirigeant . régime social': "'indépendant'",
'entreprise . imposition': "'IR'",
}
)
})

View File

@ -15827,30 +15827,28 @@ __metadata:
languageName: node
linkType: hard
"publicodes-react@npm:^1.0.0-beta.32":
version: 1.0.0-beta.32
resolution: "publicodes-react@npm:1.0.0-beta.32"
"publicodes-react@portal:/home/johan/Projets/publicodes/packages/react-ui::locator=root%40workspace%3A.":
version: 0.0.0-use.local
resolution: "publicodes-react@portal:/home/johan/Projets/publicodes/packages/react-ui::locator=root%40workspace%3A."
dependencies:
styled-components: ^5.1.0
peerDependencies:
publicodes: 1.0.0-beta.32
react: ^17.0.2
checksum: 2cdf91420982e909869cf9ddc1754943d4e5e8bef7545e4d5afdfc202eb1bbdcb33197bb4ad2978f6cfd02aad3229ec53061ff06680c9f2917638fde79b98b28
languageName: node
linkType: hard
linkType: soft
"publicodes@npm:^1.0.0-beta.32":
version: 1.0.0-beta.32
resolution: "publicodes@npm:1.0.0-beta.32"
"publicodes@portal:/home/johan/Projets/publicodes/packages/core::locator=root%40workspace%3A.":
version: 0.0.0-use.local
resolution: "publicodes@portal:/home/johan/Projets/publicodes/packages/core::locator=root%40workspace%3A."
dependencies:
moo: ^0.5.1
nearley: ^2.19.2
yaml: ^1.9.2
peerDependencies:
"@types/mocha": ^9.0.0
checksum: 674a5f1ee9f755cf8f9fd2312523ee70f3963b1c8cb9e0249919ad48269f3dbe831d4fe3e1c13400216bd21b34d2bbd5f71e571b3adc210ebdb1c2dc18ee72d6
languageName: node
linkType: hard
linkType: soft
"pump@npm:^2.0.0":
version: 2.0.1