📦 Déplace les règles dans un paquet NPM systeme-social

pull/1299/head
Maxime Quandalle 2020-11-18 18:53:37 +01:00
parent 800ade5a0a
commit c9a783a09c
67 changed files with 144 additions and 147 deletions

View File

@ -1 +1,2 @@
.eslintrc.js
dist

View File

@ -99,6 +99,7 @@
"reselect": "^4.0.0",
"styled-components": "^5.1.0",
"swr": "^0.1.16",
"systeme-social": "^1.0.0",
"whatwg-fetch": "^3.0.0",
"yaml": "^1.9.2"
},

View File

@ -1,25 +0,0 @@
// It is currently not possible to automatically type yaml import with
// Typescript types, so we manually watch the yaml file containing the rules,
// convert it to json and persit it on the file system so that we can access the
// list of dotted names in the Typescript types.
//
// A fututre version of typescript may support "plugin" to type files such as
// yaml.
const fs = require('fs')
const path = require('path')
const { readRules } = require('./rules')
const sourceDirPath = path.resolve(__dirname, '../source/rules')
// Note: we can't put the output file in the fs.watched directory
const outPath = path.resolve(__dirname, '../source/types/dottednames.json')
function persistJsonFileFromYaml() {
const rules = readRules()
const jsonString = JSON.stringify(rules, null, 2)
fs.writeFileSync(outPath, jsonString)
}
persistJsonFileFromYaml()
exports.watchDottedNames = () =>
fs.watch(sourceDirPath, persistJsonFileFromYaml)

View File

@ -4,10 +4,9 @@ var fs = require('fs')
var path = require('path')
let R = require('ramda')
var querystring = require('querystring')
let { readRules } = require('../rules')
// let { utils } = require('publicodes')
let rules = require('systeme-social')
let { parse } = require('yaml')
let rulesTranslationPath = path.resolve('source/locales/rules-en.yaml')
let UiTranslationPath = path.resolve('source/locales/ui-en.yaml')
@ -21,8 +20,6 @@ let attributesToTranslate = [
]
function getRulesMissingTranslations() {
let rules = readRules()
let currentExternalization = parse(
fs.readFileSync(rulesTranslationPath, 'utf-8')
)

View File

@ -1,3 +1,2 @@
require('./dottednames.js')
require('./fetch-releases.js')
require('./fetch-stats.js')

View File

@ -1,28 +0,0 @@
// In a vanilla NodeJS environment it is not possible to use the "import"
// statement with the Webpack transformer (from yaml to json).
const fs = require('fs')
const path = require('path')
const yaml = require('yaml')
const publicodesDir = path.resolve(__dirname, '../source/rules')
function concatenateFilesInDir(dirPath = publicodesDir) {
return fs
.readdirSync(dirPath)
.map(filename => {
const fullpath = path.join(dirPath, filename)
if (fs.statSync(fullpath).isDirectory()) {
return concatenateFilesInDir(fullpath)
} else {
return filename.endsWith('.yaml') ? fs.readFileSync(fullpath) : ''
}
})
.reduce((acc, cur) => acc + '\n' + cur, '')
}
function readRules() {
return yaml.parse(concatenateFilesInDir())
}
exports.readRules = readRules

View File

@ -2,7 +2,7 @@ import { SitePaths } from 'Components/utils/SitePathsContext'
import { History } from 'history'
import { RootState, SimulationConfig } from 'Reducers/rootReducer'
import { ThunkAction } from 'redux-thunk'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { deletePersistedSimulation } from '../storage/persistSimulation'
import { CompanyStatusAction } from './companyStatusActions'

View File

@ -2,7 +2,7 @@ import { EngineContext, useEngine } from 'Components/utils/EngineContext'
import { max } from 'ramda'
import { useContext } from 'react'
import { useSelector } from 'react-redux'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { targetUnitSelector } from 'Selectors/simulationSelectors'
import BarChartBranch from './BarChart'
import './Distribution.css'

View File

@ -1,7 +1,7 @@
import Engine, { formatValue } from 'publicodes'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { coerceArray } from '../utils'
import RuleLink from './RuleLink'
import { EngineContext, useEngine } from './utils/EngineContext'

View File

@ -10,7 +10,7 @@ import {
} from 'publicodes'
import { Fragment, useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import './PaySlip.css'
import { Line, SalaireBrutSection, SalaireNetSection } from './PaySlipSections'

View File

@ -1,7 +1,7 @@
import Value, { Condition, ValueProps } from 'Components/EngineValue'
import RuleLink from 'Components/RuleLink'
import { Trans } from 'react-i18next'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
export const SalaireBrutSection = () => {
return (

View File

@ -3,7 +3,7 @@ import { contains, filter, pipe, reject, toPairs } from 'ramda'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { useNextQuestions } from './utils/useNextQuestion'
import {

View File

@ -1,7 +1,7 @@
import { RuleLink as EngineRuleLink } from 'publicodes'
import React, { useContext } from 'react'
import { Link } from 'react-router-dom'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { EngineContext } from './utils/EngineContext'
import { SitePathsContext } from './utils/SitePathsContext'

View File

@ -1,6 +1,6 @@
import React, { useContext, useEffect, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import Worker from 'worker-loader!./SearchBar.worker.js'
import RuleLink from './RuleLink'
import './SearchBar.css'

View File

@ -3,7 +3,7 @@ import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
import { EvaluatedNode, EvaluatedRule } from 'publicodes'
import React from 'react'
import { animated, useSpring } from 'react-spring'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import styled from 'styled-components'
const BarStack = styled.div`

View File

@ -26,7 +26,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { useLocation } from 'react-router-dom'
import { RootState } from 'Reducers/rootReducer'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { targetUnitSelector } from 'Selectors/simulationSelectors'
import CurrencyInput from './CurrencyInput/CurrencyInput'
import './TargetSelection.css'

View File

@ -6,7 +6,7 @@ import { EvaluatedNode, formatValue } from 'publicodes'
import emoji from 'react-easy-emoji'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { situationSelector } from 'Selectors/simulationSelectors'
import './AnswerList.css'

View File

@ -4,7 +4,7 @@ import { EngineContext } from 'Components/utils/EngineContext'
import React, { useContext, useState } from 'react'
import emoji from 'react-easy-emoji'
import { useDispatch } from 'react-redux'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { TrackerContext } from '../utils/withTracker'
import './Explicable.css'
import usePortal from 'react-useportal'

View File

@ -17,8 +17,7 @@ import {
import { Evaluation } from 'publicodes/dist/types/AST/types'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { DottedName } from 'Rules'
import { serialize } from 'storage/serializeSimulation'
import { DottedName } from 'systeme-social'
import DateInput from './DateInput'
import ParagrapheInput from './ParagrapheInput'
import SelectEuropeCountry from './select/SelectEuropeCountry'

View File

@ -1,7 +1,7 @@
import Value, { Condition } from 'Components/EngineValue'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
type AidesCovidProps = {
aidesRule?: DottedName

View File

@ -15,7 +15,7 @@ import { max } from 'ramda'
import { useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { targetUnitSelector } from 'Selectors/simulationSelectors'
import styled from 'styled-components'
import AidesCovid from './AidesCovid'

View File

@ -1,6 +1,6 @@
import Engine from 'publicodes'
import React, { createContext, useContext } from 'react'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
export const EngineContext = createContext<Engine>(new Engine({}))
export const EngineProvider = EngineContext.Provider

View File

@ -25,7 +25,7 @@ import {
import { useContext, useMemo } from 'react'
import { useSelector } from 'react-redux'
import { Simulation, SimulationConfig } from 'Reducers/rootReducer'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import {
answeredQuestionsSelector,
configSelector,

View File

@ -3,7 +3,7 @@ import { defaultTo, omit, without } from 'ramda'
import reduceReducers from 'reduce-reducers'
import { combineReducers, Reducer } from 'redux'
import { SavedSimulation } from 'Selectors/storageSelectors'
import { DottedName } from '../rules/index'
import { DottedName } from 'systeme-social'
import { objectifsSelector } from '../selectors/simulationSelectors'
import inFranceAppReducer, { Company } from './inFranceAppReducer'
import storageRootReducer from './storageReducer'

View File

@ -1,47 +0,0 @@
// Currenty we systematically bundle all the rules even if we only need a
// sub-section of them. We might support "code-splitting" the rules in the
// future.
import jsonRules from '../types/dottednames.json'
import artisteAuteur from './artiste-auteur.yaml'
import base from './base.yaml'
import chômagePartiel from './chômage-partiel.yaml'
import CCBatiment from './conventions-collectives/bâtiment.yaml'
import CCCompta from './conventions-collectives/experts-comptables.yaml'
import CCHotels from './conventions-collectives/hôtels-cafés-restaurants.yaml'
import CCOptique from './conventions-collectives/optique.yaml'
import CCSpectacleVivant from './conventions-collectives/spectacle-vivant.yaml'
import CCSport from './conventions-collectives/sport.yaml'
import dirigeant from './dirigeant.yaml'
import déclarationIndépendant from './déclaration-revenu-indépendant.yaml'
import entrepriseEtablissement from './entreprise-établissement.yaml'
import impot from './impôt.yaml'
import professionLibérale from './profession-libérale.yaml'
import protectionSociale from './protection-sociale.yaml'
import salarié from './salarié.yaml'
import situationPersonnelle from './situation-personnelle.yaml'
export type DottedName = keyof typeof jsonRules
const rules = {
...base,
// TODO: rule order shouldn't matter but there is a bug if "impot" is after
// "dirigeant".
...impot,
...déclarationIndépendant,
...artisteAuteur,
...dirigeant,
...professionLibérale,
...entrepriseEtablissement,
...protectionSociale,
...salarié,
...CCBatiment,
...CCHotels,
...CCOptique,
...CCSpectacleVivant,
...CCSport,
...CCCompta,
...situationPersonnelle,
...chômagePartiel,
}
export default rules

View File

@ -1,4 +1,4 @@
import { DottedName } from '../rules/index'
import { DottedName } from 'systeme-social'
import { createSelector } from 'reselect'
import { RootState, SimulationConfig } from 'Reducers/rootReducer'

View File

@ -1,5 +1,5 @@
import { RootState, Simulation } from 'Reducers/rootReducer'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
// Note: it is currently not possible to define SavedSimulation as the return
// type of the currentSimulationSelector function because the type would then

View File

@ -7,7 +7,7 @@ const config = require('../webpack.dev.js')
const compiler = webpack(config)
const history = require('connect-history-api-fallback')
const { watchDottedNames } = require('../scripts/dottednames')
const { watchDottedNames } = require('../../systeme-social/build')
watchDottedNames()
const rewrite = (basename) => ({

View File

@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { Route, Switch } from 'react-router-dom'
import createSentryMiddleware from 'redux-sentry-middleware'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import {
configSituationSelector,
situationSelector,

View File

@ -3,7 +3,7 @@ import { hot } from 'react-hot-loader/root'
import { translateRules } from 'publicodes'
import { render } from 'react-dom'
import 'regenerator-runtime/runtime'
import rules from 'Rules'
import rules from 'systeme-social'
import i18next from '../../i18n'
import translations from '../../locales/ui-en.yaml'
import ruleTranslations from '../../locales/rules-en.yaml'

View File

@ -3,7 +3,7 @@ import 'core-js/stable'
import 'react-hot-loader'
import { render } from 'react-dom'
import 'regenerator-runtime/runtime'
import rules from 'Rules'
import rules from 'systeme-social'
import App from './App'
import i18next from '../../i18n'

View File

@ -1,4 +1,4 @@
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { situationSelector } from 'Selectors/simulationSelectors'
import Tracker from 'Tracker'

View File

@ -17,7 +17,7 @@ import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { situationSelector } from 'Selectors/simulationSelectors'
import styled from 'styled-components'
import { CompanySection } from '../Home'

View File

@ -12,7 +12,7 @@ import { equals } from 'ramda'
import { createContext, useContext, useEffect, useState } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import { situationSelector } from 'Selectors/simulationSelectors'
import styled from 'styled-components'
import config from './configs/artiste-auteur.yaml'

View File

@ -7,7 +7,7 @@ import { EngineContext, useEngine } from 'Components/utils/EngineContext'
import { EvaluatedRule, evaluateRule, formatValue } from 'publicodes'
import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { DottedName } from 'Rules'
import { DottedName } from 'systeme-social'
import styled from 'styled-components'
declare global {

View File

@ -14,7 +14,6 @@ const EXAMPLE_CODE = `
# Pour en savoir plus sur le langage, consultez le tutoriel :
# => https://publi.codes
prix:
prix . carottes: 2/kg
prix . champignons: 5/kg
prix . avocat: 2/avocat

View File

@ -4,7 +4,7 @@ import {
getNextQuestions,
getNextSteps,
} from '../source/components/utils/useNextQuestion'
import rules from '../source/rules'
import rules from 'systeme-social'
describe('conversation', function () {
it('should start with the first missing variable', function () {

View File

@ -1,6 +1,6 @@
import { expect } from 'chai'
import { cyclesLib } from 'publicodes'
import rules from '../source/rules'
import rules from 'systeme-social'
describe('DottedNames graph', () => {
it("shouldn't have cycles", () => {

View File

@ -1,7 +1,7 @@
import { AssertionError } from 'chai'
import Engine, { parsePublicodes } from 'publicodes'
import { disambiguateRuleReference } from '../../publicodes/source/ruleUtils'
import rules from 'Rules'
import rules from 'systeme-social'
// les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle,
// comme dans sa formule
@ -46,7 +46,7 @@ describe('Tests des règles de notre base de règles', () =>
if (!example.ok) {
throw new AssertionError(`
Valeur attendue : ${example['valeur attendue']}
Valeur obtenue : ${example.rule.nodeValue}
Valeur obtenue : ${example.rule.nodeValue}
${
example.rule.nodeValue === null
? 'Variables manquantes : ' +

View File

@ -7,7 +7,7 @@
/* eslint-disable no-undef */
import Engine, { evaluateRule } from 'publicodes'
import rules from '../../source/rules'
import rules from 'systeme-social'
import artisteAuteurConfig from '../../source/sites/mon-entreprise.fr/pages/Simulateurs/configs/artiste-auteur.yaml'
import autoentrepreneurConfig from '../../source/sites/mon-entreprise.fr/pages/Simulateurs/configs/auto-entrepreneur.yaml'
import independantConfig from '../../source/sites/mon-entreprise.fr/pages/Simulateurs/configs/indépendant.yaml'

View File

@ -1,7 +1,7 @@
import { expect } from 'chai'
import { parsePublicodes } from 'publicodes'
import { uniq } from 'ramda'
import rawRules from '../source/rules'
import rawRules from 'systeme-social'
import unitsTranslations from '../../publicodes/source/locales/units.yaml'
it('use unit that exists in publicode', () => {

View File

@ -8,8 +8,6 @@
"Components/*": ["components/*"],
"Images/*": ["images/*"],
"Reducers/*": ["reducers/*"],
"Rules/*": ["rules/*"],
"Rules": ["rules"],
"Selectors/*": ["selectors/*"],
"Types/*": ["types/*"]
},

View File

@ -109,19 +109,20 @@
"lint:eslintrc": "eslint --print-config .eslintrc.js | eslint-config-prettier-check",
"lint:eslint": "export NODE_OPTIONS='--max-old-space-size=4096'; eslint . --ext .js,.jsx,.ts,.tsx",
"lint:eslint:fix": "yarn lint:eslint --fix",
"lint:prettier": "yarn workspaces run prettier --check \"{source,test,cypress}/**/*.{js,jsx,ts,tsx}\"",
"lint:prettier": "yarn run prettier --check \"**/*.{js,jsx,ts,tsx}\"",
"lint:prettier:fix": "yarn lint:prettier --write",
"lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix",
"prepare": "if [ -z \"$NETLIFY\" ]; then yarn workspaces run prepare; fi",
"lint": "yarn lint:eslintrc && yarn lint:eslint && yarn lint:prettier",
"test": "yarn workspaces run test",
"test:type": "yarn workspaces run tsc",
"test:type": "yarn workspace publicodes run tsc && yarn workspace mon-entreprise run tsc",
"test:regressions": "jest",
"clean": "yarn workspaces run clean",
"start": "yarn workspace publicodes build --watch & yarn workspace mon-entreprise start"
},
"workspaces": [
"publicodes",
"systeme-social",
"mon-entreprise"
]
}

View File

6
systeme-social/README.md Normal file
View File

@ -0,0 +1,6 @@
# Système social français en publicodes
Ce paquet contient les règles publicodes utilisées sur https://mon-entreprise.fr
pour le calcul des cotisations sociales, des impôts et des droits sociaux.
Pour voir des exemples d'utilisation rendez-vous sur https://mon-entreprise.fr/int%C3%A9gration/biblioth%C3%A8que-de-calcul

46
systeme-social/build.js Normal file
View File

@ -0,0 +1,46 @@
/* eslint-env node */
const fs = require('fs')
const path = require('path')
const yaml = require('yaml')
const publicodesDir = path.resolve(__dirname, './règles')
const outDir = path.resolve(__dirname, './dist')
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir)
}
function concatenateFilesInDir(dirPath = publicodesDir) {
return fs
.readdirSync(dirPath)
.map((filename) => {
const fullpath = path.join(dirPath, filename)
if (fs.statSync(fullpath).isDirectory()) {
return concatenateFilesInDir(fullpath)
} else {
return filename.endsWith('.yaml') ? fs.readFileSync(fullpath) : ''
}
})
.reduce((acc, cur) => acc + '\n' + cur, '')
}
function readRules() {
return yaml.parse(concatenateFilesInDir())
}
// Note: we can't put the output file in the fs.watched directory
function writeJSFile() {
const rules = readRules()
const names = Object.keys(rules)
const jsString = `module.exports = ${JSON.stringify(rules, null, 2)}`
fs.writeFileSync(path.resolve(outDir, 'index.js'), jsString)
fs.writeFileSync(
path.resolve(outDir, 'names.ts'),
`\nexport type Names = ${names.map((name) => `"${name}"`).join('\n | ')}\n`
)
}
writeJSFile()
exports.watchDottedNames = () => fs.watch(publicodesDir, writeJSFile)

19
systeme-social/index.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
// Currenty we systematically bundle all the rules even if we only need a
// sub-section of them. We might support "code-splitting" the rules in the
// future.
import {
EvaluatedRule as GenericEvaluatedRule,
ParsedRule as GenericParsedRule,
ParsedRules as GenericParsedRules,
Rules as GenericRules
} from 'publicodes'
import { Names } from './dist/names'
export type DottedName = Names
export type Rules = GenericRules<Names>
export type ParsedRules = GenericParsedRules<Names>
export type ParsedRule = GenericParsedRule<Names>
export type EvaluatedRule = GenericEvaluatedRule<Names>
export type Situation = Partial<Record<Names, string>>
declare let rules: Rules
export default rules

View File

@ -0,0 +1,31 @@
{
"name": "systeme-social",
"version": "1.0.0",
"description": "Les règles publicodes du système social français",
"main": "./dist/index.js",
"types": "./index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/betagouv/mon-entreprise.git",
"directory": "systeme-social"
},
"bugs": "https://github.com/betagouv/mon-entreprise/issues?q=is%3Aopen+is%3Aissue+label%3A%22%F0%9F%93%95+l%C3%A9gislation%22",
"homepage": "https://mon-entreprise.fr/int%C3%A9gration/biblioth%C3%A8que-de-calcul",
"license": "MIT",
"files": [
"dist/index.js"
],
"devDependencies": {
"yaml": "^1.10.0",
"publicodes": "^1.0.0-beta.4"
},
"dependencies": {},
"peerDependencies": {
"publicodes": "^1.0.0-beta.4"
},
"scripts": {
"build": "node build.js",
"prepare": "yarn run build",
"test": "echo 1"
}
}

View File

@ -14119,7 +14119,7 @@ yaml-loader@^0.5.0:
dependencies:
js-yaml "^3.5.2"
yaml@^1.9.2:
yaml@^1.10.0, yaml@^1.9.2:
version "1.10.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==