import { path, propEq, pathEq, groupBy, prop, map, mapObjIndexed, sum, filter, concat, pathOr, toPairs, keys, find } from 'ramda' import React, { Component } from 'react' import { Trans, translate } from 'react-i18next' import { connect } from 'react-redux' import { withRouter } from 'react-router' import { Link } from 'react-router-dom' import withLanguage from './withLanguage' import { formValueSelector } from 'redux-form' import './Results.css' import '../engine/mecanismViews/Somme.css' import { capitalise0 } from '../utils' import { nameLeaf, encodeRuleName, findRuleByDottedName } from 'Engine/rules' // Filtered variables and rules can't be filtered in a uniform way, for now let paidBy = payer => item => pathEq(['explanation', item.explanation.type, 'dû par'], payer, item) let filteredBy = pathEq(['cotisation', 'dû par']) export let byName = groupBy(prop('dottedName')) export let cell = (branch, payer, analysis) => { let row = byBranch(analysis)[branch], items = filter(item => paidBy(payer)(item) || filteredBy(payer)(item), row), values = map(prop('nodeValue'), items) return sum(values) } export let subCell = (row, name, payer) => { let cells = row[name], items = filter( item => paidBy(payer)(item) || filteredBy(payer)(item), cells ), values = map(prop('nodeValue'), items) return sum(values) } export let byBranch = analysis => { let sal = analysis.cache['contrat salarié . cotisations salariales'] let pat = analysis.cache['contrat salarié . cotisations patronales'] let l1 = sal ? sal.explanation.formule.explanation.explanation : [], l2 = pat ? pat.explanation.formule.explanation.explanation : [], explanations = concat(l1, l2), result = groupBy( pathOr('autre', ['explanation', 'cotisation', 'branche']), explanations ) return result } let formatCurrency = language => number => Intl.NumberFormat(language, { style: 'currency', currency: 'EUR' }).format(number) @withRouter @connect(state => ({ analysis: state.analysis, targetNames: state.targetNames, situationGate: state.situationGate, flatRules: state.flatRules, inversions: formValueSelector('conversation')(state, 'inversions') })) @translate() @withLanguage export default class ResultsGrid extends Component { render() { let { analysis, situationGate, targetNames, inversions, flatRules, language } = this.props, rules = flatRules if (!analysis) return null let extract = x => (typeof x == 'string' ? +x : (x && x.nodeValue) || 0), fromEval = name => find(propEq('dottedName', name), analysis.targets), fromDict = name => analysis.cache[name], get = name => extract(situationGate(name) || fromEval(name) || fromDict(name)), title = rule => rule.title || capitalise0(rule.name) let results = byBranch(analysis), brut = get('contrat salarié . salaire brut'), base = get('contrat salarié . salaire de base'), avan = get('contrat salarié . avantages salarié'), net = get('contrat salarié . salaire net'), total = get('contrat salarié . salaire total') let inversion = path(['contrat salarié ', ' salaire brut'], inversions), relevantSalaries = new Set( targetNames .concat(inversion ? [nameLeaf(inversion)] : []) .concat(['salaire brut']) ) let brutR = findRuleByDottedName( flatRules, 'contrat salarié . salaire brut' ) let baseR = findRuleByDottedName( flatRules, 'contrat salarié . salaire de base' ) let avanR = findRuleByDottedName( flatRules, 'contrat salarié . avantages salarié' ) let formatLocalizedCurrency = formatCurrency(language) return ( <div className="somme resultsGrid"> <table> <thead> <tr> <td className="element category"> <Link to={'/règle/' + encodeRuleName(baseR.dottedName)}> <div className="rule-box"> <span className="rule-name">{title(baseR)}</span> </div> </Link> </td> <td colSpan={(relevantSalaries.size - 1) * 2} className="element value" id="sommeBase"> {formatLocalizedCurrency(base)}{' '} </td> </tr> </thead> <thead> <tr> <td className="element category"> <Link to={'/règle/' + encodeRuleName(avanR.dottedName)}> <div className="rule-box"> <span className="rule-name">{title(avanR)}</span> </div> </Link> </td> <td colSpan={(relevantSalaries.size - 1) * 2} className="element value" id="sommeBase"> <span className="operator">+ </span> {formatLocalizedCurrency(avan)}{' '} </td> </tr> </thead> <thead> <tr> <td className="element category"> <Link to={'/règle/' + encodeRuleName(brutR.dottedName)}> <div className="rule-box"> <span className="rule-name">{title(brutR)}</span> </div> </Link> </td> <td colSpan={(relevantSalaries.size - 1) * 2} className="element value" id="sommeBase"> <span className="operator">= </span> {formatLocalizedCurrency(brut)}{' '} </td> </tr> </thead> <tbody> {toPairs(results).map(([branch, values]) => { let props = { branch, values, analysis, rules, relevantSalaries } return <Row key={branch} {...props} /> })} <ReductionRow node={fromDict('contrat salarié . réductions de cotisations')} relevantSalaries={relevantSalaries} /> <tr> <td key="blank" className="element" /> {relevantSalaries.has('salaire net') && ( <> <td key="netOperator" className="operator"> = </td> <td key="net" className="element value"> {formatLocalizedCurrency(net)}{' '} <span className="annotation"> <Trans>Salaire net</Trans> </span> </td> </> )} {relevantSalaries.has('salaire total') && [ <td key="totalOperator" className="operator"> = </td>, <td key="total" className="element value"> {formatLocalizedCurrency(total)}{' '} <span className="annotation"> <Trans>Salaire chargé</Trans> </span> </td> ]} </tr> </tbody> </table> </div> ) } } @translate() @withLanguage class Row extends Component { state = { folded: true } render() { let { rules, branch, values, analysis, relevantSalaries, language, t } = this.props, detail = byName(values), ruleData = mapObjIndexed( (v, k) => findRuleByDottedName(rules, k), detail ), formatLocalizedCurrency = formatCurrency(language) let title = name => { let node = ruleData[name] return node.title || capitalise0(t(node.name)) } let aggregateRow = ( <tr key="aggregateRow" onClick={() => this.setState({ folded: !this.state.folded })}> <td key="category" className="element category name"> {capitalise0(t(branch))} <span className="unfoldIndication"> {this.state.folded ? t('déplier') + ' >' : t('replier')} </span> </td> {this.state.folded ? ( <> {relevantSalaries.has('salaire net') && ( <> <td key="operator1" className="operator"> - </td> <td key="value1" className="element value"> {formatLocalizedCurrency(cell(branch, 'salarié', analysis))} </td> </> )} {relevantSalaries.has('salaire total') && ( <> <td key="operator2" className="operator"> + </td> <td key="value2" className="element value"> {formatLocalizedCurrency(cell(branch, 'employeur', analysis))} </td> </> )} </> ) : ( <td key="blank" colSpan="4" /> )} </tr> ) let detailRows = this.state.folded ? [] : keys(detail).map(subCellName => ( <tr key={'detailsRow' + subCellName} className="detailsRow"> <td className="element name"> <Link to={'/règle/' + encodeRuleName(nameLeaf(subCellName))}> {title(subCellName)} </Link> </td> {relevantSalaries.has('salaire net') && ( <> <td key="operator1" className="operator"> - </td> <td key="value1" className="element value"> {formatLocalizedCurrency( subCell(detail, subCellName, 'salarié') )} </td> </> )} {relevantSalaries.has('salaire total') && ( <> <td key="operator2" className="operator"> + </td> <td key="value2" className="element value"> {formatLocalizedCurrency( subCell(detail, subCellName, 'employeur') )} </td> </> )} </tr> )) // returns an array of <tr> return concat([aggregateRow], detailRows) } } // TODO Ce code est beaucoup trop spécifique // C'est essentiellement une copie de Row @translate() @withLanguage class ReductionRow extends Component { state = { folded: true } render() { let { relevantSalaries, node, language, t } = this.props if (!relevantSalaries.has('salaire total')) return null let value = node && node.nodeValue ? node.nodeValue : 0 let formatLocalizedCurrency = formatCurrency(language) let aggregateRow = ( <tr key="aggregateRowReductions" onClick={() => this.setState({ folded: !this.state.folded })}> <td key="category" className="element category name"> <Trans>Réductions</Trans> <span className="unfoldIndication"> {this.state.folded ? t('déplier') + ' >' : t('replier')} </span> </td> {this.state.folded ? ( <> {relevantSalaries.has('salaire net') && ( <> <td key="operator1" className="operator"> + </td> <td key="value1" className="element value"> {formatLocalizedCurrency(0)} </td> </> )} {relevantSalaries.has('salaire total') && ( <> <td key="operator2" className="operator"> - </td> <td key="value2" className="element value"> {formatLocalizedCurrency(value)} </td> </> )} </> ) : ( <td key="blank" colSpan="4" /> )} </tr> ) let detailRow = this.state.folded ? null : ( <tr key="detailsRowRéductions" className="detailsRow"> <td className="element name"> <Link to={'/règle/' + encodeRuleName('réductions de cotisations')}> Réductions de cotisations </Link> </td> {relevantSalaries.has('salaire net') && ( <> <td key="operator1" className="operator"> + </td> <td key="value1" className="element value"> {formatLocalizedCurrency(0)} </td> </> )} {relevantSalaries.has('salaire total') && ( <> <td key="operator2" className="operator"> - </td> <td key="value2" className="element value"> {formatLocalizedCurrency(value)} </td> </> )} </tr> ) // returns an array of <tr> return [aggregateRow, detailRow] } }