diff --git a/.eslintrc.yml b/.eslintrc.yml index dd1bdad1..1237586a 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -7,7 +7,7 @@ parserOptions: rules: indent: - error - - 4 + - 2 linebreak-style: - error - unix diff --git a/client/js/components/AggregatedInstructionViz.js b/client/js/components/AggregatedInstructionViz.js index 67097e42..f453a972 100644 --- a/client/js/components/AggregatedInstructionViz.js +++ b/client/js/components/AggregatedInstructionViz.js @@ -1,14 +1,16 @@ -import React from 'react' +import React from 'react'; import Sunburst from './Sunburst'; import hierarchicalM52 from '../finance/hierarchicalM52.js'; -export default function(props){ - const hierarchicalData = hierarchicalM52(props.M52Instruction); +export default function(props) { + const hierarchicalData = hierarchicalM52(props.M52Instruction); - return React.createElement('div', {}, - React.createElement('h1', {}, hierarchicalData.name), - React.createElement('h2', {}, hierarchicalData.total.toFixed(2)), - React.createElement(Sunburst, { hierarchicalData }) - ); -} \ No newline at end of file + return React.createElement( + 'div', + {}, + React.createElement('h1', {}, hierarchicalData.name), + React.createElement('h2', {}, hierarchicalData.total.toFixed(2)), + React.createElement(Sunburst, { hierarchicalData }) + ); +} diff --git a/client/js/components/AggregatedViz.js b/client/js/components/AggregatedViz.js index e3158255..4a8849b7 100644 --- a/client/js/components/AggregatedViz.js +++ b/client/js/components/AggregatedViz.js @@ -1,9 +1,9 @@ -import React from 'react' +import React from 'react'; import Sunburst from './Sunburst'; import TextualAggregated from './TextualAggregated'; import hierarchicalAggregated from '../finance/hierarchicalAggregated.js'; -import {PAR_PUBLIC_VIEW, PAR_PRESTATION_VIEW} from '../finance/constants'; +import { PAR_PUBLIC_VIEW, PAR_PRESTATION_VIEW } from '../finance/constants'; /* @@ -13,41 +13,69 @@ interface AggregatedViZProps{ } */ -export default function({ - aggregatedHierarchical, aggregatedHighlightedNodes, selectedNode, - rdfi, dfView, - onSliceOvered, onSliceSelected, onAggregatedDFViewChange - }){ - const rdfiId = rdfi.rd + rdfi.fi; +export default function( + { + aggregatedHierarchical, + aggregatedHighlightedNodes, + selectedNode, + rdfi, + dfView, + onSliceOvered, + onSliceSelected, + onAggregatedDFViewChange + } +) { + const rdfiId = rdfi.rd + rdfi.fi; - return React.createElement('div', {}, - React.createElement('h1', {}, 'Instruction agrégée'), - (rdfiId === 'DF' ? React.createElement( - 'div', - { - className: 'view-selector', - onChange: e => { - onAggregatedDFViewChange(e.currentTarget.querySelector('input[type="radio"][name="view"]:checked').value) - } - }, - React.createElement('label', {}, - 'Actions par public ', - React.createElement('input', - {name: 'view', value: PAR_PUBLIC_VIEW, type: "radio", defaultChecked: dfView === PAR_PUBLIC_VIEW} - ) - ), - React.createElement('label', {}, - 'Actions par prestation ', - React.createElement('input', - {name: 'view', value: PAR_PRESTATION_VIEW, type: "radio", defaultChecked: dfView === PAR_PRESTATION_VIEW} - ) - ) - ) : undefined), - React.createElement(Sunburst, { - hierarchicalData: aggregatedHierarchical, - highlightedNodes: aggregatedHighlightedNodes, selectedNode, - donutWidth: 55, outerRadius: 120, - onSliceOvered, onSliceSelected - }) - ); + return React.createElement( + 'div', + {}, + React.createElement('h1', {}, 'Instruction agrégée'), + rdfiId === 'DF' + ? React.createElement( + 'div', + { + className: 'view-selector', + onChange: e => { + onAggregatedDFViewChange( + e.currentTarget.querySelector( + 'input[type="radio"][name="view"]:checked' + ).value + ); + } + }, + React.createElement( + 'label', + {}, + 'Actions par public ', + React.createElement('input', { + name: 'view', + value: PAR_PUBLIC_VIEW, + type: 'radio', + defaultChecked: dfView === PAR_PUBLIC_VIEW + }) + ), + React.createElement( + 'label', + {}, + 'Actions par prestation ', + React.createElement('input', { + name: 'view', + value: PAR_PRESTATION_VIEW, + type: 'radio', + defaultChecked: dfView === PAR_PRESTATION_VIEW + }) + ) + ) + : undefined, + React.createElement(Sunburst, { + hierarchicalData: aggregatedHierarchical, + highlightedNodes: aggregatedHighlightedNodes, + selectedNode, + donutWidth: 55, + outerRadius: 120, + onSliceOvered, + onSliceSelected + }) + ); } diff --git a/client/js/components/M52Viz.js b/client/js/components/M52Viz.js index a590f489..b76c0cd4 100644 --- a/client/js/components/M52Viz.js +++ b/client/js/components/M52Viz.js @@ -1,4 +1,4 @@ -import React from 'react' +import React from 'react'; import Sunburst from './Sunburst'; /* @@ -9,17 +9,25 @@ interface M52VizProps{ */ -export default function({ - M52Hierarchical, M52HighlightedNodes, selectedNode, - onSliceOvered, onSliceSelected - }){ - - return React.createElement('div', {}, - React.createElement('h1', {}, "Instruction M52"), - React.createElement(Sunburst, { - hierarchicalData: M52Hierarchical, - highlightedNodes: M52HighlightedNodes, selectedNode, - onSliceOvered, onSliceSelected - }) - ); +export default function( + { + M52Hierarchical, + M52HighlightedNodes, + selectedNode, + onSliceOvered, + onSliceSelected + } +) { + return React.createElement( + 'div', + {}, + React.createElement('h1', {}, 'Instruction M52'), + React.createElement(Sunburst, { + hierarchicalData: M52Hierarchical, + highlightedNodes: M52HighlightedNodes, + selectedNode, + onSliceOvered, + onSliceSelected + }) + ); } diff --git a/client/js/components/RDFISelector.js b/client/js/components/RDFISelector.js index c0e95d27..0ccfc2c6 100644 --- a/client/js/components/RDFISelector.js +++ b/client/js/components/RDFISelector.js @@ -1,4 +1,4 @@ -import React from 'react' +import React from 'react'; /* @@ -9,52 +9,80 @@ interface RDFISelector{ */ -export default class RDFISelector extends React.PureComponent{ - render(){ - const { rdfi, onRDFIChange } = this.props; - return React.createElement( - 'div', - { - className: 'rdfi', - style: { - padding: "1em" - }, - onChange: e => { - onRDFIChange({ - rd: e.currentTarget.querySelector('input[type="radio"][name="rd"]:checked').value, - fi: e.currentTarget.querySelector('input[type="radio"][name="fi"]:checked').value - }) - } - }, - React.createElement('span', {}, 'Répartition des'), - React.createElement('div', {className: 'selector'}, - React.createElement('label', {}, - 'dépenses', - React.createElement('input', - {name: 'rd', value: "D", type: "radio", defaultChecked: rdfi['rd'] === 'D'} - ) - ), - React.createElement('label', {}, - 'recettes', - React.createElement('input', - {name: 'rd', value: "R", type: "radio", defaultChecked: rdfi['rd'] === 'R'} - ) - ) - ), - React.createElement('div', {className: 'selector'}, - React.createElement('label', {}, - 'de fonctionnement', - React.createElement('input', - {name: 'fi', value: "F", type:"radio", defaultChecked: rdfi['fi'] === 'F'} - ) - ), - React.createElement('label', {}, - "d'investissement", - React.createElement('input', - {name: 'fi', value: "I", type:"radio", defaultChecked: rdfi['fi'] === 'I'} - ) - ) - ) +export default class RDFISelector extends React.PureComponent { + render() { + const { rdfi, onRDFIChange } = this.props; + return React.createElement( + 'div', + { + className: 'rdfi', + style: { + padding: '1em' + }, + onChange: e => { + onRDFIChange({ + rd: e.currentTarget.querySelector( + 'input[type="radio"][name="rd"]:checked' + ).value, + fi: e.currentTarget.querySelector( + 'input[type="radio"][name="fi"]:checked' + ).value + }); + } + }, + React.createElement('span', {}, 'Répartition des'), + React.createElement( + 'div', + { className: 'selector' }, + React.createElement( + 'label', + {}, + 'dépenses', + React.createElement('input', { + name: 'rd', + value: 'D', + type: 'radio', + defaultChecked: rdfi['rd'] === 'D' + }) + ), + React.createElement( + 'label', + {}, + 'recettes', + React.createElement('input', { + name: 'rd', + value: 'R', + type: 'radio', + defaultChecked: rdfi['rd'] === 'R' + }) ) - } + ), + React.createElement( + 'div', + { className: 'selector' }, + React.createElement( + 'label', + {}, + 'de fonctionnement', + React.createElement('input', { + name: 'fi', + value: 'F', + type: 'radio', + defaultChecked: rdfi['fi'] === 'F' + }) + ), + React.createElement( + 'label', + {}, + "d'investissement", + React.createElement('input', { + name: 'fi', + value: 'I', + type: 'radio', + defaultChecked: rdfi['fi'] === 'I' + }) + ) + ) + ); + } } diff --git a/client/js/components/Sunburst.js b/client/js/components/Sunburst.js index 045c53bf..1da4ac22 100644 --- a/client/js/components/Sunburst.js +++ b/client/js/components/Sunburst.js @@ -1,7 +1,7 @@ -import React from 'react' -import d3Shape from 'd3-shape' +import React from 'react'; +import d3Shape from 'd3-shape'; -import SunburstSlice from './SunburstSlice' +import SunburstSlice from './SunburstSlice'; const DONUT_WIDTH = 45; const RADIUS = 80; @@ -28,72 +28,79 @@ interface HierarchicalData{ } */ -export default function({ - hierarchicalData, width, height, - highlightedNodes, selectedNode, - donutWidth, outerRadius, - onSliceOvered, onSliceSelected - }){ +export default function( + { + hierarchicalData, + width, + height, + highlightedNodes, + selectedNode, + donutWidth, + outerRadius, + onSliceOvered, + onSliceSelected + } +) { + console.log('selectedNode', selectedNode); - console.log('selectedNode', selectedNode) + width = width || 500; + height = height || 500; - width = width || 500; - height = height || 500; + donutWidth = donutWidth || DONUT_WIDTH; + outerRadius = outerRadius || RADIUS; - donutWidth = donutWidth || DONUT_WIDTH; - outerRadius = outerRadius || RADIUS; + const children = Array.from(hierarchicalData.children.values()); - const children = Array.from(hierarchicalData.children.values()); - - const pie = d3Shape.pie(); - const arc = d3Shape.arc() - .innerRadius(outerRadius - donutWidth) - .outerRadius(outerRadius); - - const childrenArcDescs = pie(children.map(c => c.total)); + const pie = d3Shape.pie(); + const arc = d3Shape + .arc() + .innerRadius(outerRadius - donutWidth) + .outerRadius(outerRadius); - return React.createElement( - 'div', + const childrenArcDescs = pie(children.map(c => c.total)); + + return React.createElement( + 'div', + { + className: ['sunburst', highlightedNodes ? 'active-selection' : undefined] + .filter(s => s) + .join(' '), + onMouseOver(e) { + if (!e.target.matches('.slice *')) { + onSliceOvered(undefined); + } + }, + onClick(e) { + if (!e.target.matches('.slice *')) { + onSliceSelected(undefined); + } + } + }, + React.createElement( + 'svg', + { width: width, height: height }, + React.createElement( + 'g', { - className: [ - 'sunburst', - highlightedNodes ? 'active-selection' : undefined - ].filter(s => s).join(' '), - onMouseOver(e){ - if(!e.target.matches('.slice *')){ - onSliceOvered(undefined); - } - }, - onClick(e){ - if(!e.target.matches('.slice *')){ - onSliceSelected(undefined); - } - } + transform: 'translate(' + width / 2 + ',' + height / 2 + ')' }, - React.createElement('svg', {width: width, height: height}, - React.createElement( - 'g', - {transform: 'translate('+width/2+','+height/2+')'}, - children.map((child, i) => { - const arcDesc = childrenArcDescs[i]; + children.map((child, i) => { + const arcDesc = childrenArcDescs[i]; - return React.createElement( - SunburstSlice, - { - key: child.name, - node: child, - radius: outerRadius, - donutWidth, - startAngle: arcDesc.startAngle, - endAngle: arcDesc.endAngle, - highlightedNodes, - selectedNode, - onSliceOvered, - onSliceSelected - } - ) - }) - ) - ) - ); + return React.createElement(SunburstSlice, { + key: child.name, + node: child, + radius: outerRadius, + donutWidth, + startAngle: arcDesc.startAngle, + endAngle: arcDesc.endAngle, + highlightedNodes, + selectedNode, + onSliceOvered, + onSliceSelected + }); + }) + ) + ) + ); } diff --git a/client/js/components/SunburstSlice.js b/client/js/components/SunburstSlice.js index caf9cedd..7b46f797 100644 --- a/client/js/components/SunburstSlice.js +++ b/client/js/components/SunburstSlice.js @@ -1,107 +1,124 @@ -import React from 'react' -import d3Shape from 'd3-shape' +import React from 'react'; +import d3Shape from 'd3-shape'; -import {flattenTree} from '../finance/visitHierarchical.js'; +import { flattenTree } from '../finance/visitHierarchical.js'; -export default class SunburstSlice extends React.Component{ - - shouldComponentUpdate(nextProps){ - if(['radius', 'donutWidth', 'startAngle', 'endAngle', 'selectedNode'] - .some(k => this.props[k] !== nextProps[k])) - return true; +export default class SunburstSlice extends React.Component { + shouldComponentUpdate(nextProps) { + if ( + ['radius', 'donutWidth', 'startAngle', 'endAngle', 'selectedNode'].some( + k => this.props[k] !== nextProps[k] + ) + ) + return true; - if(this.props.node !== nextProps.node) - return true; - // from now on, this.props.node === nextProps.node - const node = nextProps.node; + if (this.props.node !== nextProps.node) return true; + // from now on, this.props.node === nextProps.node + const node = nextProps.node; - if(this.props.highlightedNodes === nextProps.highlightedNodes) - return false; - else{ - if(!this.props.highlightedNodes || !nextProps.highlightedNodes) - return true; - - const nodes = flattenTree(node); - - // update the component if there is a difference in this.props.highlightedNodes VS nextProps.highlightedNodes - // in regard to the node being drawn - return nodes.some(n => this.props.highlightedNodes.has(n) !== nextProps.highlightedNodes.has(n)) - } + if (this.props.highlightedNodes === nextProps.highlightedNodes) + return false; + else { + if (!this.props.highlightedNodes || !nextProps.highlightedNodes) + return true; + const nodes = flattenTree(node); + + // update the component if there is a difference in this.props.highlightedNodes VS nextProps.highlightedNodes + // in regard to the node being drawn + return nodes.some( + n => + this.props.highlightedNodes.has(n) !== + nextProps.highlightedNodes.has(n) + ); } + } + + render() { + const { + node, + radius, + donutWidth, + startAngle, + endAngle, + highlightedNodes, + selectedNode, + onSliceOvered, + onSliceSelected + } = this.props; + const { label } = node; + + const children = node.children ? Array.from(node.children.values()) : []; - render(){ - const { - node, radius, donutWidth, startAngle, endAngle, - highlightedNodes, selectedNode, - onSliceOvered, onSliceSelected - } = this.props; - const {label} = node; + const pie = d3Shape.pie().startAngle(startAngle).endAngle(endAngle); + const arc = d3Shape + .arc() + .innerRadius(radius - donutWidth) + .outerRadius(radius); - const children = node.children ? Array.from(node.children.values()) : []; - - const pie = d3Shape.pie() - .startAngle(startAngle) - .endAngle(endAngle); - const arc = d3Shape.arc() - .innerRadius(radius - donutWidth) - .outerRadius(radius); - - const childrenArcDescs = pie(children.map(c => c.total)); - const parentArcDesc = { startAngle, endAngle }; + const childrenArcDescs = pie(children.map(c => c.total)); + const parentArcDesc = { startAngle, endAngle }; - const highlighted = highlightedNodes && highlightedNodes.has(node); - const selected = selectedNode === node; + const highlighted = highlightedNodes && highlightedNodes.has(node); + const selected = selectedNode === node; - return React.createElement( - 'g', - { - className: [ - 'slice', - highlighted ? 'highlighted' : undefined, - selected ? 'selected' : undefined - ].filter(s => s).join(' ') - }, - React.createElement( - 'g', - { - className: 'piece', - onMouseOver(e){ - onSliceOvered(node); - }, - onClick(e){ - onSliceSelected(node); - } + return React.createElement( + 'g', + { + className: [ + 'slice', + highlighted ? 'highlighted' : undefined, + selected ? 'selected' : undefined + ] + .filter(s => s) + .join(' ') + }, + React.createElement( + 'g', + { + className: 'piece', + onMouseOver(e) { + onSliceOvered(node); + }, + onClick(e) { + onSliceSelected(node); + } + }, + React.createElement('path', { + d: arc(parentArcDesc) + }), + highlighted + ? React.createElement( + 'text', + { + transform: 'translate(' + arc.centroid(parentArcDesc) + ')', + style: { + textAnchor: 'middle', + fill: '#111' }, - React.createElement('path', { - d: arc(parentArcDesc) - }), - highlighted ? React.createElement('text', { - transform: 'translate('+arc.centroid(parentArcDesc)+')', - style: { - textAnchor: 'middle', - fill: '#111' - }, - dy: '.35em' - }, label) : undefined - ), - children.map((child, i) => { - const arcDesc = childrenArcDescs[i]; - - return React.createElement( - SunburstSlice, // yep recursive call - { - key: child.name, - node: child, - radius: radius + donutWidth, - donutWidth, - startAngle: arcDesc.startAngle, - endAngle: arcDesc.endAngle, - highlightedNodes, selectedNode, - onSliceOvered, onSliceSelected - } - ) - }) - ); - } + dy: '.35em' + }, + label + ) + : undefined + ), + children.map((child, i) => { + const arcDesc = childrenArcDescs[i]; + + return React.createElement(SunburstSlice, { + // yep recursive call + key: child.name, + node: child, + radius: radius + donutWidth, + donutWidth, + startAngle: arcDesc.startAngle, + endAngle: arcDesc.endAngle, + highlightedNodes, + selectedNode, + onSliceOvered, + onSliceSelected + }); + }) + ); + } } diff --git a/client/js/components/TextualAggregated.js b/client/js/components/TextualAggregated.js index 6bf27dfc..88033f69 100644 --- a/client/js/components/TextualAggregated.js +++ b/client/js/components/TextualAggregated.js @@ -1,59 +1,61 @@ -import React from 'react' -import {format} from 'currency-formatter' +import React from 'react'; +import { format } from 'currency-formatter'; -import {isOR} from '../finance/m52ToAggregated'; +import { isOR } from '../finance/m52ToAggregated'; -function makeUnusedM52RowsSet(aggregatedInstruction, M52Instruction){ - return M52Instruction.filter(m52row => { - return !aggregatedInstruction.some(aggRow => aggRow['M52Rows'].has(m52row)) && isOR(m52row); - }) +function makeUnusedM52RowsSet(aggregatedInstruction, M52Instruction) { + return M52Instruction.filter(m52row => { + return !aggregatedInstruction.some(aggRow => + aggRow['M52Rows'].has(m52row)) && + isOR(m52row); + }); } -function makeUsedMoreThanOnceM52RowsSet(aggregatedInstruction, M52Instruction){ - const m52RowToAggRows = new Map(); - - const actionsSocialesParPrestationsRows = aggregatedInstruction - .filter( r => r.id.startsWith('DF-1')) - .map( r => r["M52Rows"] ) - .flatten(1); - - const actionsSocialesParPubliqueRows = aggregatedInstruction - .filter( r => r.id.startsWith('DF-2')) - .map( r => r["M52Rows"] ) - .flatten(1); - - M52Instruction.forEach(m52row => { - const usingAggRows = new Set(); - - aggregatedInstruction.forEach(aggRow => { - if(aggRow['M52Rows'].has(m52row)){ - usingAggRows.add(aggRow); - } - }); - - if(usingAggRows.size >= 2){ - if(usingAggRows.size === 2 && - actionsSocialesParPrestationsRows.has(m52row) && - actionsSocialesParPubliqueRows.has(m52row) - ){ - // skip, because it's expected - } - else{ - m52RowToAggRows.set(m52row, usingAggRows) - } - } +function makeUsedMoreThanOnceM52RowsSet(aggregatedInstruction, M52Instruction) { + const m52RowToAggRows = new Map(); + + const actionsSocialesParPrestationsRows = aggregatedInstruction + .filter(r => r.id.startsWith('DF-1')) + .map(r => r['M52Rows']) + .flatten(1); + + const actionsSocialesParPubliqueRows = aggregatedInstruction + .filter(r => r.id.startsWith('DF-2')) + .map(r => r['M52Rows']) + .flatten(1); + + M52Instruction.forEach(m52row => { + const usingAggRows = new Set(); + + aggregatedInstruction.forEach(aggRow => { + if (aggRow['M52Rows'].has(m52row)) { + usingAggRows.add(aggRow); + } }); - return m52RowToAggRows; + if (usingAggRows.size >= 2) { + if ( + usingAggRows.size === 2 && + actionsSocialesParPrestationsRows.has(m52row) && + actionsSocialesParPubliqueRows.has(m52row) + ) { + // skip, because it's expected + } else { + m52RowToAggRows.set(m52row, usingAggRows); + } + } + }); + + return m52RowToAggRows; } -function makeM52RowId(m52Row){ - return [ - m52Row['Dépense/Recette'] + m52Row['Investissement/Fonctionnement'], - m52Row["Chapitre"], - m52Row["Rubrique fonctionnelle"], - m52Row["Article"] - ].join(' '); +function makeM52RowId(m52Row) { + return [ + m52Row['Dépense/Recette'] + m52Row['Investissement/Fonctionnement'], + m52Row['Chapitre'], + m52Row['Rubrique fonctionnelle'], + m52Row['Article'] + ].join(' '); } /* @@ -64,57 +66,106 @@ interface TextualAggregated{ } */ -export default class TextualSelected extends React.PureComponent{ - - render(){ - const {aggregatedInstruction, M52Instruction} = this.props; - - const unusedM52Set = makeUnusedM52RowsSet(aggregatedInstruction, M52Instruction); - const usedMoreThanOnceM52RowsSet = makeUsedMoreThanOnceM52RowsSet(aggregatedInstruction, M52Instruction); - - return React.createElement('div', {}, - React.createElement('div', {}, - React.createElement('h1', {}, "Tableau aggrégé"), - React.createElement('table', {className: 'aggregated'}, aggregatedInstruction.map(aggRow => ( - React.createElement( - 'tr', - { - key: aggRow['id'], - className: [ - aggRow['M52Rows'].size === 0 ? 'zero-m52' : '', - aggRow['Statut'] === 'TEMPORARY' ? 'temporary' : '' - ].filter(c => c).join(' ') - }, - React.createElement('td', {}, aggRow['id']), - React.createElement('td', {}, aggRow['Libellé']), - React.createElement('td', {className: 'money-amount'}, format(aggRow['Montant'], { code: 'EUR' })), - React.createElement('td', {}, aggRow['M52Rows'].size) - ) - ))) - ), - React.createElement('div', {}, - React.createElement('h1', {}, "Lignes M52 utilisées dans aucune formule d'aggrégation ("+unusedM52Set.size+")"), - React.createElement('table', {}, unusedM52Set.map(m52 => { - const m52Id = makeM52RowId(m52); - - return React.createElement('tr', {key: m52Id}, - React.createElement('td', {}, m52Id), - React.createElement('td', {className: 'money-amount'}, format(m52["Montant"], { code: 'EUR' })) - ) - })) +export default class TextualSelected extends React.PureComponent { + render() { + const { aggregatedInstruction, M52Instruction } = this.props; + + const unusedM52Set = makeUnusedM52RowsSet( + aggregatedInstruction, + M52Instruction + ); + const usedMoreThanOnceM52RowsSet = makeUsedMoreThanOnceM52RowsSet( + aggregatedInstruction, + M52Instruction + ); + + return React.createElement( + 'div', + {}, + React.createElement( + 'div', + {}, + React.createElement('h1', {}, 'Tableau aggrégé'), + React.createElement( + 'table', + { className: 'aggregated' }, + aggregatedInstruction.map(aggRow => React.createElement( + 'tr', + { + key: aggRow['id'], + className: [ + aggRow['M52Rows'].size === 0 ? 'zero-m52' : '', + aggRow['Statut'] === 'TEMPORARY' ? 'temporary' : '' + ] + .filter(c => c) + .join(' ') + }, + React.createElement('td', {}, aggRow['id']), + React.createElement('td', {}, aggRow['Libellé']), + React.createElement( + 'td', + { className: 'money-amount' }, + format(aggRow['Montant'], { code: 'EUR' }) ), - React.createElement('div', {}, - React.createElement('h1', {}, "Lignes M52 utilisées dans au moins 2 formules d'aggrégation ("+usedMoreThanOnceM52RowsSet.size+")"), - React.createElement('ul', {}, Array.from(usedMoreThanOnceM52RowsSet).map(([m52Row, aggSet]) => { - const m52Id = makeM52RowId(m52Row); - - return React.createElement('li', {key: m52Id}, - m52Id, - ' utilisé dans ', - [...aggSet].map(aggRow => aggRow.id).join(', ') - ) - })) - ) - ); - } -}; \ No newline at end of file + React.createElement('td', {}, aggRow['M52Rows'].size) + )) + ) + ), + React.createElement( + 'div', + {}, + React.createElement( + 'h1', + {}, + "Lignes M52 utilisées dans aucune formule d'aggrégation (" + + unusedM52Set.size + + ')' + ), + React.createElement( + 'table', + {}, + unusedM52Set.map(m52 => { + const m52Id = makeM52RowId(m52); + + return React.createElement( + 'tr', + { key: m52Id }, + React.createElement('td', {}, m52Id), + React.createElement( + 'td', + { className: 'money-amount' }, + format(m52['Montant'], { code: 'EUR' }) + ) + ); + }) + ) + ), + React.createElement( + 'div', + {}, + React.createElement( + 'h1', + {}, + "Lignes M52 utilisées dans au moins 2 formules d'aggrégation (" + + usedMoreThanOnceM52RowsSet.size + + ')' + ), + React.createElement( + 'ul', + {}, + Array.from(usedMoreThanOnceM52RowsSet).map(([m52Row, aggSet]) => { + const m52Id = makeM52RowId(m52Row); + + return React.createElement( + 'li', + { key: m52Id }, + m52Id, + ' utilisé dans ', + [...aggSet].map(aggRow => aggRow.id).join(', ') + ); + }) + ) + ) + ); + } +} diff --git a/client/js/components/TextualSelected.js b/client/js/components/TextualSelected.js index 23309b55..1f3c71ec 100644 --- a/client/js/components/TextualSelected.js +++ b/client/js/components/TextualSelected.js @@ -1,43 +1,65 @@ import React from 'react'; -import {OrderedSet} from 'immutable'; -import {format} from 'currency-formatter'; +import { OrderedSet } from 'immutable'; +import { format } from 'currency-formatter'; -import {M52_INSTRUCTION, AGGREGATED_INSTRUCTION} from '../finance/constants'; +import { M52_INSTRUCTION, AGGREGATED_INSTRUCTION } from '../finance/constants'; - -function makeM52RowId(m52Row){ - return [ - m52Row['Dépense/Recette'] + m52Row['Investissement/Fonctionnement'], - m52Row['Chapitre'], - m52Row['Rubrique fonctionnelle'], - m52Row['Article'] - ].join(' '); +function makeM52RowId(m52Row) { + return [ + m52Row['Dépense/Recette'] + m52Row['Investissement/Fonctionnement'], + m52Row['Chapitre'], + m52Row['Rubrique fonctionnelle'], + m52Row['Article'] + ].join(' '); } -export default class TextualSelected extends React.PureComponent{ - - render(){ - const {selection} = this.props; - const {type, node} = selection; +export default class TextualSelected extends React.PureComponent { + render() { + const { selection } = this.props; + const { type, node } = selection; - const m52Rows = type === M52_INSTRUCTION ? - Array.from(node.elements) : - type === AGGREGATED_INSTRUCTION ? - new OrderedSet(Array.from(node.elements).map(e => e['M52Rows'])).flatten(1) : - undefined; + const m52Rows = type === M52_INSTRUCTION + ? Array.from(node.elements) + : type === AGGREGATED_INSTRUCTION + ? new OrderedSet( + Array.from(node.elements).map(e => e['M52Rows']) + ).flatten(1) + : undefined; - return React.createElement('div', {}, - React.createElement('h1', {}, type === M52_INSTRUCTION ? 'Morceau de la M52 sélectionnée' : 'Morceau de l\'agrégée selectionné'), - React.createElement('h2', {}, node.id + ' ' + node.label), - React.createElement('h3', {className: 'money-amount'}, format(node.total, { code: 'EUR' })), - React.createElement('table', {}, m52Rows.map(m52 => { - const m52Id = makeM52RowId(m52); + return React.createElement( + 'div', + {}, + React.createElement( + 'h1', + {}, + type === M52_INSTRUCTION + ? 'Morceau de la M52 sélectionnée' + : "Morceau de l'agrégée selectionné" + ), + React.createElement('h2', {}, node.id + ' ' + node.label), + React.createElement( + 'h3', + { className: 'money-amount' }, + format(node.total, { code: 'EUR' }) + ), + React.createElement( + 'table', + {}, + m52Rows.map(m52 => { + const m52Id = makeM52RowId(m52); - return React.createElement('tr', {key: m52Id}, - React.createElement('td', {}, m52Id), - React.createElement('td', {className: 'money-amount'}, format(m52['Montant'], { code: 'EUR' })) - ); - })) - ); - } + return React.createElement( + 'tr', + { key: m52Id }, + React.createElement('td', {}, m52Id), + React.createElement( + 'td', + { className: 'money-amount' }, + format(m52['Montant'], { code: 'EUR' }) + ) + ); + }) + ) + ); + } } diff --git a/client/js/components/TopLevel.js b/client/js/components/TopLevel.js index 6e23ae98..64ccf284 100644 --- a/client/js/components/TopLevel.js +++ b/client/js/components/TopLevel.js @@ -1,4 +1,4 @@ -import React from 'react' +import React from 'react'; import M52Viz from './M52Viz'; import AggregatedViz from './AggregatedViz'; @@ -7,7 +7,7 @@ import TextualSelected from './TextualSelected'; import RDFISelector from './RDFISelector'; import m52ToAggregated from '../finance/m52ToAggregated.js'; -import {M52_INSTRUCTION, AGGREGATED_INSTRUCTION} from '../finance/constants'; +import { M52_INSTRUCTION, AGGREGATED_INSTRUCTION } from '../finance/constants'; /* rdfi, dfView, @@ -18,38 +18,67 @@ import {M52_INSTRUCTION, AGGREGATED_INSTRUCTION} from '../finance/constants'; */ -export default function({ - rdfi, dfView, - M52Instruction, aggregatedInstruction, - M52Hierarchical, M52HighlightedNodes, - aggregatedHierarchical, aggregatedHighlightedNodes, - over, selection, - onM52NodeOvered, onAggregatedNodeOvered, - onM52NodeSelected, onAggregatedNodeSelected, - onRDFIChange, onAggregatedDFViewChange - }){ - - return M52Instruction ? React.createElement('div', {className: 'top-level'}, - React.createElement('div', {}, - React.createElement(M52Viz, { - M52Hierarchical, - M52HighlightedNodes, - selectedNode: selection && selection.type === M52_INSTRUCTION ? selection.node : undefined, - onSliceOvered: onM52NodeOvered, - onSliceSelected: onM52NodeSelected - }), - React.createElement(RDFISelector, { rdfi, onRDFIChange }), - React.createElement(AggregatedViz, { - aggregatedHierarchical, - aggregatedHighlightedNodes, - selectedNode: selection && selection.type === AGGREGATED_INSTRUCTION ? selection.node : undefined, - rdfi, dfView, - onSliceOvered: onAggregatedNodeOvered, - onSliceSelected: onAggregatedNodeSelected, - onAggregatedDFViewChange - }) +export default function( + { + rdfi, + dfView, + M52Instruction, + aggregatedInstruction, + M52Hierarchical, + M52HighlightedNodes, + aggregatedHierarchical, + aggregatedHighlightedNodes, + over, + selection, + onM52NodeOvered, + onAggregatedNodeOvered, + onM52NodeSelected, + onAggregatedNodeSelected, + onRDFIChange, + onAggregatedDFViewChange + } +) { + return M52Instruction + ? React.createElement( + 'div', + { className: 'top-level' }, + React.createElement( + 'div', + {}, + React.createElement(M52Viz, { + M52Hierarchical, + M52HighlightedNodes, + selectedNode: ( + selection && selection.type === M52_INSTRUCTION + ? selection.node + : undefined + ), + onSliceOvered: onM52NodeOvered, + onSliceSelected: onM52NodeSelected + }), + React.createElement(RDFISelector, { rdfi, onRDFIChange }), + React.createElement(AggregatedViz, { + aggregatedHierarchical, + aggregatedHighlightedNodes, + selectedNode: ( + selection && selection.type === AGGREGATED_INSTRUCTION + ? selection.node + : undefined + ), + rdfi, + dfView, + onSliceOvered: onAggregatedNodeOvered, + onSliceSelected: onAggregatedNodeSelected, + onAggregatedDFViewChange + }) ), - selection ? React.createElement(TextualSelected, {selection}) : undefined, - React.createElement(TextualAggregated, {M52Instruction, aggregatedInstruction}) - ) : React.createElement('div', {}); + selection + ? React.createElement(TextualSelected, { selection }) + : undefined, + React.createElement(TextualAggregated, { + M52Instruction, + aggregatedInstruction + }) + ) + : React.createElement('div', {}); } diff --git a/client/js/finance/afterCSVCleanup.js b/client/js/finance/afterCSVCleanup.js index ac0116ab..cffbc386 100644 --- a/client/js/finance/afterCSVCleanup.js +++ b/client/js/finance/afterCSVCleanup.js @@ -5,22 +5,20 @@ * * This function should be the only one allowed to mutate the data acquired from the CSV file */ -export default function(rows){ - rows.forEach(function(row){ - row["Montant"] = Number(row["Montant"]); - - if(row["Exercice"]) - row["Exercice"] = Number(row["Exercice"]); - - if(row["Année"]) - row["Exercice"] = Number(row["Année"]); - - row["Article"] = row["Article"].trim() - row["Rubrique fonctionnelle"] = row["Rubrique fonctionnelle"].trim() - row["Réel/Ordre id/Ordre diff"] = row["Réel/Ordre id/Ordre diff"].trim(); +export default function(rows) { + rows.forEach(function(row) { + row['Montant'] = Number(row['Montant']); - Object.freeze(row); - }); + if (row['Exercice']) row['Exercice'] = Number(row['Exercice']); - return rows; -} \ No newline at end of file + if (row['Année']) row['Exercice'] = Number(row['Année']); + + row['Article'] = row['Article'].trim(); + row['Rubrique fonctionnelle'] = row['Rubrique fonctionnelle'].trim(); + row['Réel/Ordre id/Ordre diff'] = row['Réel/Ordre id/Ordre diff'].trim(); + + Object.freeze(row); + }); + + return rows; +} diff --git a/client/js/finance/constants.js b/client/js/finance/constants.js index d468bf2d..216a186e 100644 --- a/client/js/finance/constants.js +++ b/client/js/finance/constants.js @@ -2,4 +2,4 @@ export const PAR_PUBLIC_VIEW = 'PAR_PUBLIC_VIEW'; export const PAR_PRESTATION_VIEW = 'PAR_PRESTATION_VIEW'; export const M52_INSTRUCTION = 'M52_INSTRUCTION'; -export const AGGREGATED_INSTRUCTION = 'AGGREGATED_INSTRUCTION'; \ No newline at end of file +export const AGGREGATED_INSTRUCTION = 'AGGREGATED_INSTRUCTION'; diff --git a/client/js/finance/hierarchicalAggregated.js b/client/js/finance/hierarchicalAggregated.js index 57cf07a4..74a5f610 100644 --- a/client/js/finance/hierarchicalAggregated.js +++ b/client/js/finance/hierarchicalAggregated.js @@ -1,231 +1,224 @@ -import {Set as ImmutableSet} from 'immutable'; +import { Set as ImmutableSet } from 'immutable'; -import {rules} from './m52ToAggregated'; -import {PAR_PUBLIC_VIEW, PAR_PRESTATION_VIEW} from './constants'; +import { rules } from './m52ToAggregated'; +import { PAR_PUBLIC_VIEW, PAR_PRESTATION_VIEW } from './constants'; const ruleIds = Object.freeze(Object.keys(rules)); const DFparPublicChild = { - id: 'DF-2', - label: "Actions sociales par publics", - children: ruleIds.filter(id => id.startsWith('DF-2')) + id: 'DF-2', + label: 'Actions sociales par publics', + children: ruleIds.filter(id => id.startsWith('DF-2')) }; const DFparPrestationChild = { - id: 'DF-1', - label: "Actions sociales par prestations", - children: [ - { - id: 'DF-1-1', - label: "Frais d'hébergement", - children: ruleIds.filter(id => id.startsWith('DF-1-1')) - }, - 'DF-1-2', - 'DF-1-3', - 'DF-1-4', - { - id: 'DF-1-5', - label: "Divers enfants", - children: ruleIds.filter(id => id.startsWith('DF-1-5')) - }, - 'DF-1-6', - { - id: 'DF-1-7', - label: "Divers social", - children: ruleIds.filter(id => id.startsWith('DF-1-7')) - } - ] -} - - - -const levelsByRDFI = { - 'RF': { - id: 'RF', - label: 'Recettes de fonctionnement', - children: [ - { - id: 'RF-1', - label: "Fiscalité directe", - children: ruleIds.filter(id => id.startsWith('RF-1')) - }, - { - id: 'RF-2', - label: "Fiscalité transférée", - children: ruleIds.filter(id => id.startsWith('RF-2')) - }, - { - id: 'RF-3', - label: "Droits de mutation à titre onéreux (DMTO)", - children: ruleIds.filter(id => id.startsWith('RF-3')) - }, - { - id: 'RF-4', - label: "Autres fiscalités", - children: ruleIds.filter(id => id.startsWith('RF-4')) - }, - { - id: 'RF-5', - label: "Dotations de l’État et compensations", - children: ruleIds.filter(id => id.startsWith('RF-5')) - }, - { - id: 'RF-6', - label: "Recettes sociales", - children: ruleIds.filter(id => id.startsWith('RF-6')) - }, - { - id: 'RF-7', - label: "Péréquation sociale", - children: ruleIds.filter(id => id.startsWith('RF-7')) - }, - { - id: 'RF-8', - label: "Péréquation horizontale", - children: ruleIds.filter(id => id.startsWith('RF-8')) - }, - { - id: 'RF-9', - label: "Recettes diverses", - children: ruleIds.filter(id => id.startsWith('RF-9')) - } - ] + id: 'DF-1', + label: 'Actions sociales par prestations', + children: [ + { + id: 'DF-1-1', + label: "Frais d'hébergement", + children: ruleIds.filter(id => id.startsWith('DF-1-1')) }, - 'DF': Object.freeze({ - id: 'DF', - label: 'Dépenses de fonctionnement', - children: new ImmutableSet([ - { - id: 'DF-3', - label: "Actions d’intervention", - children: ruleIds.filter(id => id.startsWith('DF-3')) - }, - { - id: 'DF-4', - label: "Frais de personnel", - children: ruleIds.filter(id => id.startsWith('DF-4')) - }, - { - id: 'DF-5', - label: "Péréquation verticale", - children: ruleIds.filter(id => id.startsWith('DF-5')) - }, - { - id: 'DF-6', - label: "Autres charges", - children: ruleIds.filter(id => id.startsWith('DF-6')) - }, - { - id: 'DF-7', - label: "Frais généraux", - children: ruleIds.filter(id => id.startsWith('DF-7')) - }, - { - id: 'DF-8', - label: "Frais financiers", - children: ruleIds.filter(id => id.startsWith('DF-8')) - } - ]) - }), - 'RI': { - id: 'RI', - label: 'Recettes d’investissement', - children: ruleIds - .filter(id => id.match(/RI-\d+/)) - .concat([ - { - id: 'RI-EM', - label: 'Emprunt contracté', - children: ruleIds.filter(id => id.startsWith('RI-EM')) - } - ]) + 'DF-1-2', + 'DF-1-3', + 'DF-1-4', + { + id: 'DF-1-5', + label: 'Divers enfants', + children: ruleIds.filter(id => id.startsWith('DF-1-5')) }, - 'DI': { - id: 'DI', - label: 'Dépenses d’investissement', - children: [ - { - id: 'DI-1', - label: "Equipements Propres", - children: ruleIds.filter(id => id.startsWith('DI-1')) - }, - { - id: 'DI-2', - label: "Subventions", - children: ruleIds.filter(id => id.startsWith('DI-2')) - }, - { - id: 'DI-EM', - label: 'Emprunt remboursé', - children: ruleIds.filter(id => id.startsWith('DI-EM')) - } - ] + 'DF-1-6', + { + id: 'DF-1-7', + label: 'Divers social', + children: ruleIds.filter(id => id.startsWith('DF-1-7')) } + ] }; - +const levelsByRDFI = { + RF: { + id: 'RF', + label: 'Recettes de fonctionnement', + children: [ + { + id: 'RF-1', + label: 'Fiscalité directe', + children: ruleIds.filter(id => id.startsWith('RF-1')) + }, + { + id: 'RF-2', + label: 'Fiscalité transférée', + children: ruleIds.filter(id => id.startsWith('RF-2')) + }, + { + id: 'RF-3', + label: 'Droits de mutation à titre onéreux (DMTO)', + children: ruleIds.filter(id => id.startsWith('RF-3')) + }, + { + id: 'RF-4', + label: 'Autres fiscalités', + children: ruleIds.filter(id => id.startsWith('RF-4')) + }, + { + id: 'RF-5', + label: 'Dotations de l’État et compensations', + children: ruleIds.filter(id => id.startsWith('RF-5')) + }, + { + id: 'RF-6', + label: 'Recettes sociales', + children: ruleIds.filter(id => id.startsWith('RF-6')) + }, + { + id: 'RF-7', + label: 'Péréquation sociale', + children: ruleIds.filter(id => id.startsWith('RF-7')) + }, + { + id: 'RF-8', + label: 'Péréquation horizontale', + children: ruleIds.filter(id => id.startsWith('RF-8')) + }, + { + id: 'RF-9', + label: 'Recettes diverses', + children: ruleIds.filter(id => id.startsWith('RF-9')) + } + ] + }, + DF: Object.freeze({ + id: 'DF', + label: 'Dépenses de fonctionnement', + children: new ImmutableSet([ + { + id: 'DF-3', + label: 'Actions d’intervention', + children: ruleIds.filter(id => id.startsWith('DF-3')) + }, + { + id: 'DF-4', + label: 'Frais de personnel', + children: ruleIds.filter(id => id.startsWith('DF-4')) + }, + { + id: 'DF-5', + label: 'Péréquation verticale', + children: ruleIds.filter(id => id.startsWith('DF-5')) + }, + { + id: 'DF-6', + label: 'Autres charges', + children: ruleIds.filter(id => id.startsWith('DF-6')) + }, + { + id: 'DF-7', + label: 'Frais généraux', + children: ruleIds.filter(id => id.startsWith('DF-7')) + }, + { + id: 'DF-8', + label: 'Frais financiers', + children: ruleIds.filter(id => id.startsWith('DF-8')) + } + ]) + }), + RI: { + id: 'RI', + label: 'Recettes d’investissement', + children: ruleIds.filter(id => id.match(/RI-\d+/)).concat([ + { + id: 'RI-EM', + label: 'Emprunt contracté', + children: ruleIds.filter(id => id.startsWith('RI-EM')) + } + ]) + }, + DI: { + id: 'DI', + label: 'Dépenses d’investissement', + children: [ + { + id: 'DI-1', + label: 'Equipements Propres', + children: ruleIds.filter(id => id.startsWith('DI-1')) + }, + { + id: 'DI-2', + label: 'Subventions', + children: ruleIds.filter(id => id.startsWith('DI-2')) + }, + { + id: 'DI-EM', + label: 'Emprunt remboursé', + children: ruleIds.filter(id => id.startsWith('DI-EM')) + } + ] + } +}; /** * rows : ImmutableSet> */ export default function(aggRows, rdfi, view = PAR_PUBLIC_VIEW) { - const rdfiId = rdfi.rd + rdfi.fi; - let levels = levelsByRDFI[rdfiId]; - - if(rdfiId === 'DF'){ - switch(view){ - case PAR_PUBLIC_VIEW: - levels = Object.assign({}, levels); - levels.children = levels.children.add(DFparPublicChild); - break; - case PAR_PRESTATION_VIEW: - levels = Object.assign({}, levels); - levels.children = levels.children.add(DFparPrestationChild); - break; - default: - throw new Error('Misunderstood view ('+view+')') - } + const rdfiId = rdfi.rd + rdfi.fi; + let levels = levelsByRDFI[rdfiId]; + + if (rdfiId === 'DF') { + switch (view) { + case PAR_PUBLIC_VIEW: + levels = Object.assign({}, levels); + levels.children = levels.children.add(DFparPublicChild); + break; + case PAR_PRESTATION_VIEW: + levels = Object.assign({}, levels); + levels.children = levels.children.add(DFparPrestationChild); + break; + default: + throw new Error('Misunderstood view (' + view + ')'); } - - function makeCorrespondingSubtree(sourceNode){ - const correspondingTargetNode = { - id: sourceNode.id, - label: sourceNode.label, - ownValue: 0, - total: 0, - children: new Set(), - elements: new Set() - }; - - // build the tree first - sourceNode.children.forEach(child => { - if(typeof child === 'string'){ - // aggRows.find over all tree nodes is O(n²) - const childRow = aggRows.find(r => r.id === child); - correspondingTargetNode.elements.add(childRow); - correspondingTargetNode.total += childRow["Montant"]; - - correspondingTargetNode.children.add({ - id: child, - label: childRow['Libellé'], - ownValue: childRow["Montant"], - total: childRow["Montant"], - elements: new Set([childRow]) - }); - } - else{ - correspondingTargetNode.children.add(makeCorrespondingSubtree(child)); - } + } + + function makeCorrespondingSubtree(sourceNode) { + const correspondingTargetNode = { + id: sourceNode.id, + label: sourceNode.label, + ownValue: 0, + total: 0, + children: new Set(), + elements: new Set() + }; + + // build the tree first + sourceNode.children.forEach(child => { + if (typeof child === 'string') { + // aggRows.find over all tree nodes is O(n²) + const childRow = aggRows.find(r => r.id === child); + correspondingTargetNode.elements.add(childRow); + correspondingTargetNode.total += childRow['Montant']; + + correspondingTargetNode.children.add({ + id: child, + label: childRow['Libellé'], + ownValue: childRow['Montant'], + total: childRow['Montant'], + elements: new Set([childRow]) }); + } else { + correspondingTargetNode.children.add(makeCorrespondingSubtree(child)); + } + }); - // then compute total and elements - correspondingTargetNode.children.forEach(child => { - correspondingTargetNode.total += child.total; - child.elements.forEach(e => correspondingTargetNode.elements.add(e)); - }) + // then compute total and elements + correspondingTargetNode.children.forEach(child => { + correspondingTargetNode.total += child.total; + child.elements.forEach(e => correspondingTargetNode.elements.add(e)); + }); - return correspondingTargetNode; - } + return correspondingTargetNode; + } - return makeCorrespondingSubtree(levels); -}; \ No newline at end of file + return makeCorrespondingSubtree(levels); +} diff --git a/client/js/finance/hierarchicalM52.js b/client/js/finance/hierarchicalM52.js index c8d20f72..c3ea2200 100644 --- a/client/js/finance/hierarchicalM52.js +++ b/client/js/finance/hierarchicalM52.js @@ -1,23 +1,23 @@ -const rubriqueIdToLabel = require('./finance/m52FonctionLabels.json'); +const rubriqueIdToLabel = require('./finance/m52FonctionLabels.json'); const levelCategories = [ - r => { - const R = r['Rubrique fonctionnelle']; - return R.slice(0, 2); - }, - r => { - const R = r['Rubrique fonctionnelle']; - return R[2] ? R.slice(0, 3) : undefined; - }, - r => { - const R = r['Rubrique fonctionnelle']; - return R[3] ? R.slice(0, 4) : undefined; - }, - r => { - const R = r['Rubrique fonctionnelle']; - return R[4] ? R.slice(0, 5) : undefined; - } -] + r => { + const R = r['Rubrique fonctionnelle']; + return R.slice(0, 2); + }, + r => { + const R = r['Rubrique fonctionnelle']; + return R[2] ? R.slice(0, 3) : undefined; + }, + r => { + const R = r['Rubrique fonctionnelle']; + return R[3] ? R.slice(0, 4) : undefined; + }, + r => { + const R = r['Rubrique fonctionnelle']; + return R[4] ? R.slice(0, 5) : undefined; + } +]; /** * Transforms an M52 instruction to its hierarchical form so it can be represented visually with hierarchy @@ -27,19 +27,18 @@ const levelCategories = [ * https://www.datalocale.fr/dataset/comptes-administratifs-du-departement-de-la-gironde/resource/c32d35f0-3998-40c9-babe-b70af4576baa */ export default function(rows, rdfi) { - rows = rows.filter(row => { - return row['Dépense/Recette'] === rdfi.rd && row['Investissement/Fonctionnement'] === rdfi.fi; - }); - - const root = { - id: 'M52', - label: "Instruction M52", - elements: rows, - }; + rows = rows.filter(row => { + return row['Dépense/Recette'] === rdfi.rd && + row['Investissement/Fonctionnement'] === rdfi.fi; + }); + const root = { + id: 'M52', + label: 'Instruction M52', + elements: rows + }; - - /* TreeNode : HierarchicalData + /* TreeNode : HierarchicalData { id: '', label: 'str' @@ -51,55 +50,53 @@ export default function(rows, rdfi) { } */ - // create first level. - // for all categories in first level, create next level. - - function buildTree(node, parentCategory, level){ - const categorizer = levelCategories[level]; - const children = new Map(); - let total = 0; - let ownValue = 0; + // create first level. + // for all categories in first level, create next level. - node.elements.forEach(r => { - const category = categorizer(r); - total += r["Montant"]; + function buildTree(node, parentCategory, level) { + const categorizer = levelCategories[level]; + const children = new Map(); + let total = 0; + let ownValue = 0; - if(category){ - if(category === parentCategory){ - // value belongs to ownValue, not children - ownValue += r["Montant"] - } - else{ - let categoryChild = children.get(category); - if(!categoryChild){ - const label = rubriqueIdToLabel[category] + node.elements.forEach(r => { + const category = categorizer(r); + total += r['Montant']; - if(!label){ - console.warn('No label for rubrique', category); - } + if (category) { + if (category === parentCategory) { + // value belongs to ownValue, not children + ownValue += r['Montant']; + } else { + let categoryChild = children.get(category); + if (!categoryChild) { + const label = rubriqueIdToLabel[category]; - categoryChild = { - id: category, - label, - elements: new Set() - } - children.set(category, categoryChild); - } - categoryChild.elements.add(r); - } + if (!label) { + console.warn('No label for rubrique', category); } - }); - node.total = total; - node.ownValue = ownValue; - node.children = children; - children.forEach((child, category) => { - if(levelCategories[level+1]) - buildTree(child, category, level+1) - }); - } + categoryChild = { + id: category, + label, + elements: new Set() + }; + children.set(category, categoryChild); + } + categoryChild.elements.add(r); + } + } + }); + + node.total = total; + node.ownValue = ownValue; + node.children = children; + children.forEach((child, category) => { + if (levelCategories[level + 1]) buildTree(child, category, level + 1); + }); + } - buildTree(root, root.id, 0); + buildTree(root, root.id, 0); - return root; -}; \ No newline at end of file + return root; +} diff --git a/client/js/finance/m52ToAggregated.js b/client/js/finance/m52ToAggregated.js index 42cfecea..e67af728 100644 --- a/client/js/finance/m52ToAggregated.js +++ b/client/js/finance/m52ToAggregated.js @@ -1,4 +1,8 @@ -import {Record, OrderedSet as ImmutableSet, Map as ImmutableMap} from 'immutable' +import { + Record, + OrderedSet as ImmutableSet, + Map as ImmutableMap +} from 'immutable'; /* This file's very French and even Gironde-specific. @@ -12,887 +16,1071 @@ import {Record, OrderedSet as ImmutableSet, Map as ImmutableMap} from 'immutable * amount of money and % over M52 total) */ -export function isOR(m52Row){ - return m52Row["Réel/Ordre id/Ordre diff"] === 'OR'; +export function isOR(m52Row) { + return m52Row['Réel/Ordre id/Ordre diff'] === 'OR'; } -export function isRF(m52Row){ - return m52Row['Dépense/Recette'] === 'R' && m52Row['Investissement/Fonctionnement'] === 'F'; +export function isRF(m52Row) { + return m52Row['Dépense/Recette'] === 'R' && + m52Row['Investissement/Fonctionnement'] === 'F'; } -export function isDF(m52Row){ - return m52Row['Dépense/Recette'] === 'D' && m52Row['Investissement/Fonctionnement'] === 'F'; +export function isDF(m52Row) { + return m52Row['Dépense/Recette'] === 'D' && + m52Row['Investissement/Fonctionnement'] === 'F'; } -export function isRI(m52Row){ - return m52Row['Dépense/Recette'] === 'R' && m52Row['Investissement/Fonctionnement'] === 'I'; +export function isRI(m52Row) { + return m52Row['Dépense/Recette'] === 'R' && + m52Row['Investissement/Fonctionnement'] === 'I'; } -export function isDI(m52Row){ - return m52Row['Dépense/Recette'] === 'D' && m52Row['Investissement/Fonctionnement'] === 'I'; +export function isDI(m52Row) { + return m52Row['Dépense/Recette'] === 'D' && + m52Row['Investissement/Fonctionnement'] === 'I'; } - - export const rules = Object.freeze({ - - /** + /** * Recettes de fonctionnement */ - 'RF-1-1' : { - label: 'Taxe Foncière sur les propriétés bâties+rôles supplémentaires', - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A73111', 'A7318', 'A7875', 'A7788'].includes(m52Row['Article']); - } - }, - 'RF-1-2' :{ - label: 'Cotisation sur la valeur ajoutée des entreprises (CVAE)', - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A7311', 'A73112'].includes(m52Row['Article']); - } - }, - 'RF-1-3': { - label: 'Imposition forfaitaire pour les entreprises de réseaux (IFER)', - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A73114'; - } - }, - 'RF-2-1': { - label: 'Taxe Intérieure de Consommation sur les Produits Energétiques (TICPE)', - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && 'A7352' === m52Row['Article']; - } - }, - 'RF-2-2': { - label: 'Taxe Sur les Contrats d’Assurance (TSCA)', - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7342'; - } - }, - 'RF-3': { - label: "Droits de mutation à titre onéreux (DMTO)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A7321', 'A7322', 'A7482'].includes(m52Row['Article']); - } - }, - 'RF-4-1': { - label: "Taxe d'aménagement (incluant les anciennes Taxe Départementale Espaces Naturels Sensibles (TDENS) et financement CAUE)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A7323', 'A7324', 'A7327'].includes(m52Row['Article']); - } - }, - 'RF-4-2': { - label: "Taxe sur l’électricité", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7351'; - } - }, - 'RF-4-3': { - label: "Redevance des mines", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7353'; - } - }, - 'RF-4-4': { - label: "autres fiscalités", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A7362', 'A7353', 'A7388'].includes(m52Row['Article']); - } - }, - 'RF-5-1': { - label: "Dotation Globale de Fonctionnement (DGF)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A7411', 'A74122', 'A74123'].includes(m52Row['Article']); - } - }, - 'RF-5-2': { - label: "Dotation Globale de Décentralisation (DGD)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && 'A7461' === m52Row['Article']; - } - }, - 'RF-5-3': { - label: "Compensations fiscales", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A74833', 'A74834', 'A74835'].includes(m52Row['Article']); - } - }, - 'RF-5-4': { - label: "Dotation de compensation de la réforme de la taxe professionnelle (DCRTP)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && 'A74832' === m52Row['Article']; - } - }, - 'RF-5-5': { - label: "Fonds National de Garantie Individuelle de Ressources (FNGIR)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && 'A73121' === m52Row['Article']; - } - }, - 'RF-6-1': { - label: "Indus RSA", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A75342', 'A75343'].includes(m52Row['Article']); - } - }, - 'RF-6-2': { - label: "Contributions de la Caisse nationale de solidarité pour l'autonomie (CNSA)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A747811', 'A747812'].includes(m52Row['Article']); - } - }, - 'RF-6-3': { - label: "Recouvrements sur bénéficiaires", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7513'; - } - }, - 'RF-6-4': { - label: "Autres (dont dotation conférence des financeurs)", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle'][1]; - const otherRecetteSocialeIds = Object.keys(rules) - .filter(id => id !== 'RF-6-4' && id.startsWith('RF-6')) - - return isOR(m52Row) && isRF(m52Row) && - (fonction === '4' || fonction === '5') && - otherRecetteSocialeIds.every( - id => !rules[id].filter(m52Row) - ) - } - }, - 'RF-7-1': { - label: "Fonds de Mobilisation Départementale pour l'Insertion (FMDI)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A74783'; - } - }, - 'RF-7-2': { - label: "Dotation de compensation peréquée DCP", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A73125'; - } - }, - 'RF-8-1': { - label: "Fonds de Péréquation DMTO et Fonds de solidarité", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A7326', 'A73261', 'A73262'].includes(m52Row['Article']); - } - }, - 'RF-8-2': { - label: "Fonds exceptionnel pour les départements en difficulté", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A74718'; - } - }, - 'RF-9-1': { - label: "Produits des domaines (ventes)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A752'; - } - }, - 'RF-9-2': { - label: "Produits exceptionnels", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && [ - 'A7817', 'A7875', 'A7711', 'A7714', 'A7718', - 'A773', 'A775', 'A7788' - ].includes(m52Row['Article']); - } - }, - 'RF-9-3': { - label: "Dotations et participations (dont fonds européens)", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && [ - 'A7475', 'A7476', 'A74771', 'A74772', 'A74778', - 'A74788', 'A74888', 'A74718', 'A7474', 'A7472', - 'A7473' - ].includes(m52Row['Article']); - } - }, - 'RF-9-4': { - label: "Restauration collège", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A74881'; - } - }, - 'RF-9-5': { - label: "Produits des services", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'].startsWith('A70'); - } - }, - 'RF-9-6': { - label: "Remboursement charges de personnels", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && ['A6419', 'A6459', 'A6479'].includes(m52Row['Article']); - } - }, - 'RF-9-7': { - label: "Autres produits de gestions courants", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'].startsWith('A75') && m52Row['Article'] !== 'A752'; - } - }, - 'RF-9-8': { - label: "Produits financiers", - filter(m52Row){ - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'].startsWith('A76'); - } - }, - - /** + 'RF-1-1': { + label: 'Taxe Foncière sur les propriétés bâties+rôles supplémentaires', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A73111', 'A7318', 'A7875', 'A7788'].includes(m52Row['Article']); + } + }, + 'RF-1-2': { + label: 'Cotisation sur la valeur ajoutée des entreprises (CVAE)', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A7311', 'A73112'].includes(m52Row['Article']); + } + }, + 'RF-1-3': { + label: 'Imposition forfaitaire pour les entreprises de réseaux (IFER)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A73114'; + } + }, + 'RF-2-1': { + label: 'Taxe Intérieure de Consommation sur les Produits Energétiques (TICPE)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && 'A7352' === m52Row['Article']; + } + }, + 'RF-2-2': { + label: 'Taxe Sur les Contrats d’Assurance (TSCA)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7342'; + } + }, + 'RF-3': { + label: 'Droits de mutation à titre onéreux (DMTO)', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A7321', 'A7322', 'A7482'].includes(m52Row['Article']); + } + }, + 'RF-4-1': { + label: "Taxe d'aménagement (incluant les anciennes Taxe Départementale Espaces Naturels Sensibles (TDENS) et financement CAUE)", + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A7323', 'A7324', 'A7327'].includes(m52Row['Article']); + } + }, + 'RF-4-2': { + label: 'Taxe sur l’électricité', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7351'; + } + }, + 'RF-4-3': { + label: 'Redevance des mines', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7353'; + } + }, + 'RF-4-4': { + label: 'autres fiscalités', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A7362', 'A7353', 'A7388'].includes(m52Row['Article']); + } + }, + 'RF-5-1': { + label: 'Dotation Globale de Fonctionnement (DGF)', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A7411', 'A74122', 'A74123'].includes(m52Row['Article']); + } + }, + 'RF-5-2': { + label: 'Dotation Globale de Décentralisation (DGD)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && 'A7461' === m52Row['Article']; + } + }, + 'RF-5-3': { + label: 'Compensations fiscales', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A74833', 'A74834', 'A74835'].includes(m52Row['Article']); + } + }, + 'RF-5-4': { + label: 'Dotation de compensation de la réforme de la taxe professionnelle (DCRTP)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && 'A74832' === m52Row['Article']; + } + }, + 'RF-5-5': { + label: 'Fonds National de Garantie Individuelle de Ressources (FNGIR)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && 'A73121' === m52Row['Article']; + } + }, + 'RF-6-1': { + label: 'Indus RSA', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A75342', 'A75343'].includes(m52Row['Article']); + } + }, + 'RF-6-2': { + label: "Contributions de la Caisse nationale de solidarité pour l'autonomie (CNSA)", + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A747811', 'A747812'].includes(m52Row['Article']); + } + }, + 'RF-6-3': { + label: 'Recouvrements sur bénéficiaires', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A7513'; + } + }, + 'RF-6-4': { + label: 'Autres (dont dotation conférence des financeurs)', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle'][1]; + const otherRecetteSocialeIds = Object.keys(rules).filter( + id => id !== 'RF-6-4' && id.startsWith('RF-6') + ); + + return isOR(m52Row) && + isRF(m52Row) && + (fonction === '4' || fonction === '5') && + otherRecetteSocialeIds.every(id => !rules[id].filter(m52Row)); + } + }, + 'RF-7-1': { + label: "Fonds de Mobilisation Départementale pour l'Insertion (FMDI)", + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A74783'; + } + }, + 'RF-7-2': { + label: 'Dotation de compensation peréquée DCP', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A73125'; + } + }, + 'RF-8-1': { + label: 'Fonds de Péréquation DMTO et Fonds de solidarité', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A7326', 'A73261', 'A73262'].includes(m52Row['Article']); + } + }, + 'RF-8-2': { + label: 'Fonds exceptionnel pour les départements en difficulté', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A74718'; + } + }, + 'RF-9-1': { + label: 'Produits des domaines (ventes)', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A752'; + } + }, + 'RF-9-2': { + label: 'Produits exceptionnels', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + [ + 'A7817', + 'A7875', + 'A7711', + 'A7714', + 'A7718', + 'A773', + 'A775', + 'A7788' + ].includes(m52Row['Article']); + } + }, + 'RF-9-3': { + label: 'Dotations et participations (dont fonds européens)', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + [ + 'A7475', + 'A7476', + 'A74771', + 'A74772', + 'A74778', + 'A74788', + 'A74888', + 'A74718', + 'A7474', + 'A7472', + 'A7473' + ].includes(m52Row['Article']); + } + }, + 'RF-9-4': { + label: 'Restauration collège', + filter(m52Row) { + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A74881'; + } + }, + 'RF-9-5': { + label: 'Produits des services', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + m52Row['Article'].startsWith('A70'); + } + }, + 'RF-9-6': { + label: 'Remboursement charges de personnels', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + ['A6419', 'A6459', 'A6479'].includes(m52Row['Article']); + } + }, + 'RF-9-7': { + label: 'Autres produits de gestions courants', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + m52Row['Article'].startsWith('A75') && + m52Row['Article'] !== 'A752'; + } + }, + 'RF-9-8': { + label: 'Produits financiers', + filter(m52Row) { + return isOR(m52Row) && + isRF(m52Row) && + m52Row['Article'].startsWith('A76'); + } + }, + /** * Dépenses de fonctionnement isOR(m52Row) && */ - 'DF-1-1-1': { - label: "Pour les personnes handicapées", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A652221', 'A65242'].includes(m52Row['Article']); - } - }, - 'DF-1-1-2': { - label: "Frais d’hébergement - Pour les personnes âgées", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A652224', 'A65243'].includes(m52Row['Article']); - } - }, - 'DF-1-1-3': { - label: "Liés à l’enfance", - status: 'TEMPORARY', // en attente de validation formule finale - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - - return isOR(m52Row) && isDF(m52Row) && - fonction.slice(0, 3) === 'R51' && - [ - "A6132", "A6135", "A6184", - "A62268", "A6234", "A6245", "A62878", - "A6475", - "A65111", "A65221", "A652222", "A65223", "A65228", - "A6523", "A652411", "A652412", "A652415", "A652418", - "A6574", "A65821", - "A6718", "A673" - ].includes(m52Row['Article']); - } - }, - 'DF-1-2': { - label: "Revenu de Solidarité Active (RSA)", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A6515', 'A65171', 'A65172'].includes(m52Row['Article']); - } - }, - 'DF-1-3': { - label: "Prestation Compensatoire du Handicap (PCH) + Allocation Compensatrice aux Tierces Personnes (ACTP)", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A6511211', 'A6511212', 'A651128', 'A651122'].includes(m52Row['Article']); - } - }, - 'DF-1-4': { - label: "Allocation Personnalisée d’Autonomie (APA)", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A651141', 'A651142', 'A651143', 'A651144', 'A651148'].includes(m52Row['Article']); - } - }, - 'DF-1-5-1': { - label: "Préventions enfants", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - - return isOR(m52Row) && isDF(m52Row) && - fonction.slice(0, 3) === 'R51' && - ['A652416', 'A6514', 'A65111', 'A6718'].includes(m52Row['Article']); - } - }, - 'DF-1-5-2': { - label: "Autres divers enfants", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - - return isOR(m52Row) && isDF(m52Row) && - fonction.slice(0, 3) === 'R51' && - [ - "A6068", "A6188", "A616", "A62268", "A6227", - "A62878", "A654", "A6541", "A6542", "A6568", - "A65734", "A65737", "A6574", "A6718", "A673", - "A6745" - ].includes(m52Row['Article']); - } - }, - 'DF-1-6': { - label: "Subventions sociales", - status: 'TEMPORARY', // en attente de validation formule - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const art = m52Row['Article'] - const f2 = fonction.slice(0, 2); - return isOR(m52Row) && isDF(m52Row) && - (f2 === 'R4' || f2 === 'R5') && - art.startsWith('A657') && - fonction !== 'R51'; - } - }, - 'DF-1-7-1': { - label: "Transports des étudiants et des élèves handicapés", - status: 'TEMPORARY', - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - return isOR(m52Row) && isDF(m52Row) && - fonction.slice(0, 3) === 'R52' && - ["A6245", "A6513", "A6568", "A673"].includes(m52Row['Article']); - } - }, - 'DF-1-7-2': { - label: "Divers social - Autres", - status: 'TEMPORARY', - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const art = m52Row['Article']; - const f2 = fonction.slice(0, 2); - const parPrestationRuleIds = Object.keys(rules).filter(id => id.startsWith('DF-1-') && id !== 'DF-1-7-2') - - // missing : A6358 R01 ? - return isOR(m52Row) && isDF(m52Row) && - (f2 === 'R4' || f2 === 'R5') && - ( - art.startsWith('A60') || art.startsWith('A61') || art.startsWith('A62') || - art.startsWith('A63') || art.startsWith('A65') || art.startsWith('A67') - ) && - parPrestationRuleIds.every( - id => !rules[id].filter(m52Row) - ) && - !(art === 'A6526' && (fonction === 'R58' || fonction === 'R51')); - } - }, - - 'DF-2-1': { - label: "Personnes en difficultés", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - return isOR(m52Row) && isDF(m52Row) && (f3 === 'R54' || f3 === 'R56'); - } - }, - 'DF-2-2': { - label: "Personnes handicapées", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - return isOR(m52Row) && isDF(m52Row) && f3 === 'R52'; - } - }, - 'DF-2-3': { - label: "Personnes âgées", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - return isOR(m52Row) && isDF(m52Row) && (f3 === 'R55' || f3 === 'R53'); - } - }, - 'DF-2-4': { - label: "Enfance", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - return isOR(m52Row) && isDF(m52Row) && f3 === 'R51'; - } - }, - 'DF-2-5': { - label: "Autres", - filter(m52Row){ - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - - return isOR(m52Row) && isDF(m52Row) && - ['R58', 'R50', 'R40', 'R41', 'R42', 'R48'].includes(f3) && - !m52Row['Article'].startsWith('A64'); - } - }, - - 'DF-3-1': { - label: "Service Départemental d'Incendie et de Secours (SDIS)", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && m52Row['Article'] === 'A6553'; - } - }, - 'DF-3-2': { - label: "Transports", - filter(m52Row){ - const f2 = m52Row['Rubrique fonctionnelle'] - .slice(0, 2); - - return isOR(m52Row) && isDF(m52Row) && f2 === 'R8'; - } - }, - 'DF-3-3': { - label: "Prévention spécialisée", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A6526', 'A6563'].includes(m52Row['Article']); - } - }, - 'DF-3-4': { - label: "Dotation de fonctionnement des collèges", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && ['A65511', 'A65512'].includes(m52Row['Article']); - } - }, - 'DF-3-5': { - label: "Fond social logement FSL", - filter(m52Row){ - const f3 = m52Row['Rubrique fonctionnelle'].slice(0, 3); - - return isOR(m52Row) && isDF(m52Row) && f3 === 'R72' && ['A6556', 'A65561'].includes(m52Row['Article']); - } - }, - 'DF-3-6': { - label: "Subventions de fonctionnement", - filter(m52Row){ - const art = m52Row['Article']; - const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); - return isOR(m52Row) && isDF(m52Row) && - f2 !== 'R4' && f2 !== 'R5' && f2 !== 'R8' && - [ - "A65731", "A65732", "A65733", "A65734", "A65735", - "A65736", "A65737", "A65738", "A6574" - ].includes(m52Row['Article']); - } - }, - 'DF-4': { - label: "Frais de personnel", - filter(m52Row){ - const chap = m52Row['Chapitre']; - const art = m52Row['Article']; - const f3 = m52Row['Rubrique fonctionnelle'].slice(0, 3); - - return isOR(m52Row) && isDF(m52Row) && - ( - chap === 'C012' || - ( - (art.startsWith('A64') || art === 'A6218' || art === 'A6336') && - (chap === 'C15' || chap === 'C16' || chap === 'C17') - ) - ); - } - }, - 'DF-5-1': { - label: "Versement au fonds de peréquations", - filter(m52Row){ - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && (art === 'A73914' || art === 'A73926'); - } - }, - 'DF-6-1': { - label: "FAJ/CAP'J", - filter(m52Row){ - const art = m52Row['Article']; - return isOR(m52Row) && isDF(m52Row) && (art === 'A65562' || art === 'A6556'); - } - }, - 'DF-6-2': { - label: "Bourses départementales", - filter(m52Row){ - const f3 = m52Row['Rubrique fonctionnelle'].slice(0, 3); - return isOR(m52Row) && isDF(m52Row) && m52Row['Article'] === 'A6513' && f3 !== 'R52'; - } - }, - 'DF-6-3': { - label: "Participation diverses", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && - [ - "A6512", "A654", "A6541", "A6542", "A65568", - "A6561", "A6568", "A6581", "A65821", "A65888" - ].includes(m52Row['Article']); - } - }, - 'DF-6-4': { - label: "Questure/indemnités des élus", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && - [ - "A65861", "A65862", "A6531", "A6532", "A6533", - "A6534", "A65372" - ].includes(m52Row['Article']); - } - }, - 'DF-6-5': { - label: "Charges exceptionnelles et provisions", - filter(m52Row){ - const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && - (art.startsWith('A67') || art.startsWith('A68')) && - !(['R4', 'R5', 'R8'].includes(f2)); - } - }, - 'DF-6-6': { - label: "Autres", - filter(m52Row){ - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && - art.startsWith('A73') && - art !== 'A73914' && - art !== 'A73926'; - } - }, - 'DF-7-1': { - label: "Achats et fournitures", - filter(m52Row){ - const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); - const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && - art.startsWith('A60') && - !(['R4', 'R5', 'R8'].includes(f2)) && - !(f4 === 'R621'); - } - }, - 'DF-7-2': { - label: "Prestations de services", - filter(m52Row){ - const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); - const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && - art.startsWith('A61') && - !(['R4', 'R5', 'R8'].includes(f2)) && - !(f4 === 'R621'); - } - }, - 'DF-7-3': { - label: "Frais divers (frais de gestion liés à la dette …)", - filter(m52Row){ - const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); - const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && - art.startsWith('A62') && - !(['R4', 'R5', 'R8'].includes(f2)) && - !(f4 === 'R621'); - } - }, - 'DF-7-4': { - label: "Autres impôts et taxes", - filter(m52Row){ - const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); - const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); - const art = m52Row['Article']; - - return isOR(m52Row) && isDF(m52Row) && - art.startsWith('A63') && - !(['R4', 'R5', 'R8'].includes(f2)) && - !(f4 === 'R621'); - } - }, - 'DF-7-5': { - label: "Dépenses de voirie", - status: 'TEMPORARY', - filter(m52Row){ - const art = m52Row['Article']; - const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); - - return isOR(m52Row) && isDF(m52Row) && - f4 === 'R621' && - (art.startsWith('A60') || art.startsWith('A61') || art.startsWith('A62') || art.startsWith('A63')) - } - }, - 'DF-8-1': { - label: "Intérêts des emprunts", - filter(m52Row){ - return isOR(m52Row) && isDF(m52Row) && m52Row['Article'].startsWith('A66'); - } - }, - - /** + 'DF-1-1-1': { + label: 'Pour les personnes handicapées', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A652221', 'A65242'].includes(m52Row['Article']); + } + }, + 'DF-1-1-2': { + label: 'Frais d’hébergement - Pour les personnes âgées', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A652224', 'A65243'].includes(m52Row['Article']); + } + }, + 'DF-1-1-3': { + label: 'Liés à l’enfance', + status: 'TEMPORARY', // en attente de validation formule finale + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + + return isOR(m52Row) && + isDF(m52Row) && + fonction.slice(0, 3) === 'R51' && + [ + 'A6132', + 'A6135', + 'A6184', + 'A62268', + 'A6234', + 'A6245', + 'A62878', + 'A6475', + 'A65111', + 'A65221', + 'A652222', + 'A65223', + 'A65228', + 'A6523', + 'A652411', + 'A652412', + 'A652415', + 'A652418', + 'A6574', + 'A65821', + 'A6718', + 'A673' + ].includes(m52Row['Article']); + } + }, + 'DF-1-2': { + label: 'Revenu de Solidarité Active (RSA)', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A6515', 'A65171', 'A65172'].includes(m52Row['Article']); + } + }, + 'DF-1-3': { + label: 'Prestation Compensatoire du Handicap (PCH) + Allocation Compensatrice aux Tierces Personnes (ACTP)', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A6511211', 'A6511212', 'A651128', 'A651122'].includes( + m52Row['Article'] + ); + } + }, + 'DF-1-4': { + label: 'Allocation Personnalisée d’Autonomie (APA)', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A651141', 'A651142', 'A651143', 'A651144', 'A651148'].includes( + m52Row['Article'] + ); + } + }, + 'DF-1-5-1': { + label: 'Préventions enfants', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + + return isOR(m52Row) && + isDF(m52Row) && + fonction.slice(0, 3) === 'R51' && + ['A652416', 'A6514', 'A65111', 'A6718'].includes(m52Row['Article']); + } + }, + 'DF-1-5-2': { + label: 'Autres divers enfants', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + + return isOR(m52Row) && + isDF(m52Row) && + fonction.slice(0, 3) === 'R51' && + [ + 'A6068', + 'A6188', + 'A616', + 'A62268', + 'A6227', + 'A62878', + 'A654', + 'A6541', + 'A6542', + 'A6568', + 'A65734', + 'A65737', + 'A6574', + 'A6718', + 'A673', + 'A6745' + ].includes(m52Row['Article']); + } + }, + 'DF-1-6': { + label: 'Subventions sociales', + status: 'TEMPORARY', // en attente de validation formule + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const art = m52Row['Article']; + const f2 = fonction.slice(0, 2); + return isOR(m52Row) && + isDF(m52Row) && + (f2 === 'R4' || f2 === 'R5') && + art.startsWith('A657') && + fonction !== 'R51'; + } + }, + 'DF-1-7-1': { + label: 'Transports des étudiants et des élèves handicapés', + status: 'TEMPORARY', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + return isOR(m52Row) && + isDF(m52Row) && + fonction.slice(0, 3) === 'R52' && + ['A6245', 'A6513', 'A6568', 'A673'].includes(m52Row['Article']); + } + }, + 'DF-1-7-2': { + label: 'Divers social - Autres', + status: 'TEMPORARY', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const art = m52Row['Article']; + const f2 = fonction.slice(0, 2); + const parPrestationRuleIds = Object.keys(rules).filter( + id => id.startsWith('DF-1-') && id !== 'DF-1-7-2' + ); + + // missing : A6358 R01 ? + return isOR(m52Row) && + isDF(m52Row) && + (f2 === 'R4' || f2 === 'R5') && + (art.startsWith('A60') || + art.startsWith('A61') || + art.startsWith('A62') || + art.startsWith('A63') || + art.startsWith('A65') || + art.startsWith('A67')) && + parPrestationRuleIds.every(id => !rules[id].filter(m52Row)) && + !(art === 'A6526' && (fonction === 'R58' || fonction === 'R51')); + } + }, + 'DF-2-1': { + label: 'Personnes en difficultés', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + return isOR(m52Row) && isDF(m52Row) && (f3 === 'R54' || f3 === 'R56'); + } + }, + 'DF-2-2': { + label: 'Personnes handicapées', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + return isOR(m52Row) && isDF(m52Row) && f3 === 'R52'; + } + }, + 'DF-2-3': { + label: 'Personnes âgées', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + return isOR(m52Row) && isDF(m52Row) && (f3 === 'R55' || f3 === 'R53'); + } + }, + 'DF-2-4': { + label: 'Enfance', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + return isOR(m52Row) && isDF(m52Row) && f3 === 'R51'; + } + }, + 'DF-2-5': { + label: 'Autres', + filter(m52Row) { + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + + return isOR(m52Row) && + isDF(m52Row) && + ['R58', 'R50', 'R40', 'R41', 'R42', 'R48'].includes(f3) && + !m52Row['Article'].startsWith('A64'); + } + }, + 'DF-3-1': { + label: "Service Départemental d'Incendie et de Secours (SDIS)", + filter(m52Row) { + return isOR(m52Row) && isDF(m52Row) && m52Row['Article'] === 'A6553'; + } + }, + 'DF-3-2': { + label: 'Transports', + filter(m52Row) { + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + + return isOR(m52Row) && isDF(m52Row) && f2 === 'R8'; + } + }, + 'DF-3-3': { + label: 'Prévention spécialisée', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A6526', 'A6563'].includes(m52Row['Article']); + } + }, + 'DF-3-4': { + label: 'Dotation de fonctionnement des collèges', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + ['A65511', 'A65512'].includes(m52Row['Article']); + } + }, + 'DF-3-5': { + label: 'Fond social logement FSL', + filter(m52Row) { + const f3 = m52Row['Rubrique fonctionnelle'].slice(0, 3); + + return isOR(m52Row) && + isDF(m52Row) && + f3 === 'R72' && + ['A6556', 'A65561'].includes(m52Row['Article']); + } + }, + 'DF-3-6': { + label: 'Subventions de fonctionnement', + filter(m52Row) { + const art = m52Row['Article']; + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + return isOR(m52Row) && + isDF(m52Row) && + f2 !== 'R4' && + f2 !== 'R5' && + f2 !== 'R8' && + [ + 'A65731', + 'A65732', + 'A65733', + 'A65734', + 'A65735', + 'A65736', + 'A65737', + 'A65738', + 'A6574' + ].includes(m52Row['Article']); + } + }, + 'DF-4': { + label: 'Frais de personnel', + filter(m52Row) { + const chap = m52Row['Chapitre']; + const art = m52Row['Article']; + const f3 = m52Row['Rubrique fonctionnelle'].slice(0, 3); + + return isOR(m52Row) && + isDF(m52Row) && + (chap === 'C012' || + (art.startsWith('A64') || art === 'A6218' || art === 'A6336') && + (chap === 'C15' || chap === 'C16' || chap === 'C17')); + } + }, + 'DF-5-1': { + label: 'Versement au fonds de peréquations', + filter(m52Row) { + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + (art === 'A73914' || art === 'A73926'); + } + }, + 'DF-6-1': { + label: "FAJ/CAP'J", + filter(m52Row) { + const art = m52Row['Article']; + return isOR(m52Row) && + isDF(m52Row) && + (art === 'A65562' || art === 'A6556'); + } + }, + 'DF-6-2': { + label: 'Bourses départementales', + filter(m52Row) { + const f3 = m52Row['Rubrique fonctionnelle'].slice(0, 3); + return isOR(m52Row) && + isDF(m52Row) && + m52Row['Article'] === 'A6513' && + f3 !== 'R52'; + } + }, + 'DF-6-3': { + label: 'Participation diverses', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + [ + 'A6512', + 'A654', + 'A6541', + 'A6542', + 'A65568', + 'A6561', + 'A6568', + 'A6581', + 'A65821', + 'A65888' + ].includes(m52Row['Article']); + } + }, + 'DF-6-4': { + label: 'Questure/indemnités des élus', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + [ + 'A65861', + 'A65862', + 'A6531', + 'A6532', + 'A6533', + 'A6534', + 'A65372' + ].includes(m52Row['Article']); + } + }, + 'DF-6-5': { + label: 'Charges exceptionnelles et provisions', + filter(m52Row) { + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + (art.startsWith('A67') || art.startsWith('A68')) && + !['R4', 'R5', 'R8'].includes(f2); + } + }, + 'DF-6-6': { + label: 'Autres', + filter(m52Row) { + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + art.startsWith('A73') && + art !== 'A73914' && + art !== 'A73926'; + } + }, + 'DF-7-1': { + label: 'Achats et fournitures', + filter(m52Row) { + const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + art.startsWith('A60') && + !['R4', 'R5', 'R8'].includes(f2) && + !(f4 === 'R621'); + } + }, + 'DF-7-2': { + label: 'Prestations de services', + filter(m52Row) { + const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + art.startsWith('A61') && + !['R4', 'R5', 'R8'].includes(f2) && + !(f4 === 'R621'); + } + }, + 'DF-7-3': { + label: 'Frais divers (frais de gestion liés à la dette …)', + filter(m52Row) { + const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + art.startsWith('A62') && + !['R4', 'R5', 'R8'].includes(f2) && + !(f4 === 'R621'); + } + }, + 'DF-7-4': { + label: 'Autres impôts et taxes', + filter(m52Row) { + const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); + const f2 = m52Row['Rubrique fonctionnelle'].slice(0, 2); + const art = m52Row['Article']; + + return isOR(m52Row) && + isDF(m52Row) && + art.startsWith('A63') && + !['R4', 'R5', 'R8'].includes(f2) && + !(f4 === 'R621'); + } + }, + 'DF-7-5': { + label: 'Dépenses de voirie', + status: 'TEMPORARY', + filter(m52Row) { + const art = m52Row['Article']; + const f4 = m52Row['Rubrique fonctionnelle'].slice(0, 4); + + return isOR(m52Row) && + isDF(m52Row) && + f4 === 'R621' && + (art.startsWith('A60') || + art.startsWith('A61') || + art.startsWith('A62') || + art.startsWith('A63')); + } + }, + 'DF-8-1': { + label: 'Intérêts des emprunts', + filter(m52Row) { + return isOR(m52Row) && + isDF(m52Row) && + m52Row['Article'].startsWith('A66'); + } + }, + /** * Recettes d’investissement */ - 'RI-1':{ - label: "Fonds de Compensation de la Taxe sur la Valeur Ajoutée (FCTVA)", - filter(m52Row){ - return isOR(m52Row) && isRI(m52Row) && m52Row['Article'] === 'A10222'; - } - }, - 'RI-2':{ - label: "Dotation Décentralisée pour l’Equipement des collèges (DDEC)", - filter(m52Row){ - return isOR(m52Row) && isRI(m52Row) && m52Row['Article'] === 'A1332'; - } - }, - 'RI-3':{ - label: "Dotation Globale d’Equipement (DGE)", - filter(m52Row){ - return isOR(m52Row) && isRI(m52Row) && (m52Row['Article'] === 'A1341' || m52Row['Article'] === 'A10221'); - } - }, - 'RI-4': { - label: "Subventions", - filter(m52Row){ - const art = m52Row['Article']; - return isOR(m52Row) && isRI(m52Row) && - art !== 'A1332' && art !== 'A1341' && art !== 'A1335' && art !== 'A1345' && - (art.startsWith('A13') || art.startsWith('A204')) - } - }, - 'RI-5': { - label: "Produit des amendes radars autos", - filter(m52Row){ - const article = m52Row['Article']; - - return isOR(m52Row) && isRI(m52Row) && (article === 'A1335' || article === 'A1345') - } - }, - 'RI-6': { - label: "RI - Divers", - status: 'TEMPORARY', - filter(m52Row){ - const article = m52Row['Article']; - - return isOR(m52Row) && isRI(m52Row) && - article !== 'A204' && - ( - ["A165", "A1676", "A454121", "A454421", "A454428", "A45821"].includes(article) || - ['C20', 'C21', 'C22', 'C23', 'C26', 'C27'].includes(m52Row['Chapitre']) - ) - } - }, - 'RI-7': { - label: "Cessions", - filter(m52Row){ - // The choice of picking from "Recette de Fonctionnement" (RF) and not RI is deliberate - return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A775'; - } - }, - 'RI-EM-1': { - label: "Emprunt nouveau", - filter(m52Row){ - return isOR(m52Row) && isRI(m52Row) && ["A1641", "A16311", "A167", "A168"].includes(m52Row['Article']); - } - }, - 'RI-EM-2': { - label: "OCLT", - status: 'TEMPORARY', - filter(m52Row){ - return isOR(m52Row) && isRI(m52Row) && ["A16441", "A16449"].includes(m52Row['Article']); - } - }, - - /** + 'RI-1': { + label: 'Fonds de Compensation de la Taxe sur la Valeur Ajoutée (FCTVA)', + filter(m52Row) { + return isOR(m52Row) && isRI(m52Row) && m52Row['Article'] === 'A10222'; + } + }, + 'RI-2': { + label: 'Dotation Décentralisée pour l’Equipement des collèges (DDEC)', + filter(m52Row) { + return isOR(m52Row) && isRI(m52Row) && m52Row['Article'] === 'A1332'; + } + }, + 'RI-3': { + label: 'Dotation Globale d’Equipement (DGE)', + filter(m52Row) { + return isOR(m52Row) && + isRI(m52Row) && + (m52Row['Article'] === 'A1341' || m52Row['Article'] === 'A10221'); + } + }, + 'RI-4': { + label: 'Subventions', + filter(m52Row) { + const art = m52Row['Article']; + return isOR(m52Row) && + isRI(m52Row) && + art !== 'A1332' && + art !== 'A1341' && + art !== 'A1335' && + art !== 'A1345' && + (art.startsWith('A13') || art.startsWith('A204')); + } + }, + 'RI-5': { + label: 'Produit des amendes radars autos', + filter(m52Row) { + const article = m52Row['Article']; + + return isOR(m52Row) && + isRI(m52Row) && + (article === 'A1335' || article === 'A1345'); + } + }, + 'RI-6': { + label: 'RI - Divers', + status: 'TEMPORARY', + filter(m52Row) { + const article = m52Row['Article']; + + return isOR(m52Row) && + isRI(m52Row) && + article !== 'A204' && + (['A165', 'A1676', 'A454121', 'A454421', 'A454428', 'A45821'].includes( + article + ) || + ['C20', 'C21', 'C22', 'C23', 'C26', 'C27'].includes( + m52Row['Chapitre'] + )); + } + }, + 'RI-7': { + label: 'Cessions', + filter(m52Row) { + // The choice of picking from "Recette de Fonctionnement" (RF) and not RI is deliberate + return isOR(m52Row) && isRF(m52Row) && m52Row['Article'] === 'A775'; + } + }, + 'RI-EM-1': { + label: 'Emprunt nouveau', + filter(m52Row) { + return isOR(m52Row) && + isRI(m52Row) && + ['A1641', 'A16311', 'A167', 'A168'].includes(m52Row['Article']); + } + }, + 'RI-EM-2': { + label: 'OCLT', + status: 'TEMPORARY', + filter(m52Row) { + return isOR(m52Row) && + isRI(m52Row) && + ['A16441', 'A16449'].includes(m52Row['Article']); + } + }, + /** * Dépenses d’investissement */ + 'DI-1-1': { + label: 'Collèges', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f4 = fonction.slice(0, 4); - 'DI-1-1': { - label: "Collèges", - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f4 = fonction.slice(0, 4); - - return isOR(m52Row) && isDI(m52Row) && - ( - article.startsWith('A20') || - article.startsWith('A21') || - article.startsWith('A23') - ) && - !article.startsWith('A204') && - f4 === 'R221' - } - }, - 'DI-1-2': { - label: "Routes", - status: 'TEMPORARY', - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f4 = fonction.slice(0, 4); - - return isOR(m52Row) && isDI(m52Row) && - ( - article.startsWith('A20') || - article.startsWith('A21') || - article.startsWith('A23') - ) && - !article.startsWith('A204') && - ['R621', 'R622', 'R628'].includes(f4) - } - }, - 'DI-1-3': { - label: "Bâtiments", - status: 'TEMPORARY', - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f4 = fonction.slice(0, 4); - - return isOR(m52Row) && isDI(m52Row) && - ( - article.startsWith('A20') || - article.startsWith('A21') || - article.startsWith('A23') - ) && - !article.startsWith('A204') && - !['R221', 'R621', 'R622', 'R628', 'R738'].includes(f4) - } - }, - 'DI-1-4': { - label: "Aménagement", - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f4 = fonction.slice(0, 4); - - return isOR(m52Row) && isDI(m52Row) && - [ - "A1311", "A1321", "A2031", "A2111", "A2157", - "A2182", "A21848", "A2312", "A231351", "A2314", - "A23152", "A23153" - ].includes(m52Row['Article']) && - f4 === 'R738'; - } - }, - 'DI-1-5': { - label: "Autres", - filter(m52Row){ - const article = m52Row['Article']; - //const otherEquipementPropreRuleIds = Object.keys(rules).filter(id => id.startsWith('DI-1') && id !== 'DI-1-4'); - - return isOR(m52Row) && isDI(m52Row) && article === 'A1675'; - /* && + return isOR(m52Row) && + isDI(m52Row) && + (article.startsWith('A20') || + article.startsWith('A21') || + article.startsWith('A23')) && + !article.startsWith('A204') && + f4 === 'R221'; + } + }, + 'DI-1-2': { + label: 'Routes', + status: 'TEMPORARY', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f4 = fonction.slice(0, 4); + + return isOR(m52Row) && + isDI(m52Row) && + (article.startsWith('A20') || + article.startsWith('A21') || + article.startsWith('A23')) && + !article.startsWith('A204') && + ['R621', 'R622', 'R628'].includes(f4); + } + }, + 'DI-1-3': { + label: 'Bâtiments', + status: 'TEMPORARY', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f4 = fonction.slice(0, 4); + + return isOR(m52Row) && + isDI(m52Row) && + (article.startsWith('A20') || + article.startsWith('A21') || + article.startsWith('A23')) && + !article.startsWith('A204') && + !['R221', 'R621', 'R622', 'R628', 'R738'].includes(f4); + } + }, + 'DI-1-4': { + label: 'Aménagement', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f4 = fonction.slice(0, 4); + + return isOR(m52Row) && + isDI(m52Row) && + [ + 'A1311', + 'A1321', + 'A2031', + 'A2111', + 'A2157', + 'A2182', + 'A21848', + 'A2312', + 'A231351', + 'A2314', + 'A23152', + 'A23153' + ].includes(m52Row['Article']) && + f4 === 'R738'; + } + }, + 'DI-1-5': { + label: 'Autres', + filter(m52Row) { + const article = m52Row['Article']; + //const otherEquipementPropreRuleIds = Object.keys(rules).filter(id => id.startsWith('DI-1') && id !== 'DI-1-4'); + + return isOR(m52Row) && isDI(m52Row) && article === 'A1675'; + /* && otherEquipementPropreRuleIds.every( id => !rules[id].filter(m52Row) );*/ - } - }, - - - - 'DI-2-1': { - label: "subventions aux communes", - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - - return isOR(m52Row) && isDI(m52Row) && - f3 !== 'R72' && - ['A20414', 'A204141', 'A204142'].includes(article); - } - }, - 'DI-2-2': { - label: "logement", - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - - return isOR(m52Row) && isDI(m52Row) && - ['A204142', 'A204182', 'A20422'].includes(article) && - f3 === 'R72'; - } - }, - 'DI-2-3': { - label: "sdis", - filter(m52Row){ - const article = m52Row['Article']; - - return isOR(m52Row) && isDI(m52Row) && article === 'A2041781'; - } - }, - 'DI-2-4': { - label: "subventions tiers (hors sdis)", - status: 'TEMPORARY', - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - - return isOR(m52Row) && isDI(m52Row) && - [ - "A204112", "A204113", "A204122", "A204131", "A204132", - "A204151", "A204152", "A204162", "A2041721", "A2041722", - "A2041782", "A204181", "A204182", "A204182", "A20421", - "A20422", "A20431" - ].includes(article) && - f3 !== 'R72' && f3 !== 'R68' && f3 !== 'R93'; - } - }, - 'DI-2-5': { - label: "LGV", - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - - return isOR(m52Row) && isDI(m52Row) && - "A204182" === article && - f3 === 'R68'; - } - }, - 'DI-2-6': { - label: "GIRONDE Numérique", - filter(m52Row){ - const article = m52Row['Article']; - const fonction = m52Row['Rubrique fonctionnelle']; - const f3 = fonction.slice(0, 3); - - return isOR(m52Row) && isDI(m52Row) && - "A204152" === article && - f3 === 'R93'; - } - }, - - 'DI-EM-1': { - label: "Amortissement emprunt", - status: 'TEMPORARY', - filter(m52Row){ - const article = m52Row['Article']; - - return isOR(m52Row) && isDI(m52Row) && ["A1641", "A16311", "A167", "A168"].includes(article); - } - }, - 'DI-EM-2': { - label: "Amortissement OCLT", - status: 'TEMPORARY', // + ou - ? - filter(m52Row){ - const article = m52Row['Article']; - - return isOR(m52Row) && isDI(m52Row) && ["A16441", "A16449"].includes(article); - } } -}); + }, + 'DI-2-1': { + label: 'subventions aux communes', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + + return isOR(m52Row) && + isDI(m52Row) && + f3 !== 'R72' && + ['A20414', 'A204141', 'A204142'].includes(article); + } + }, + 'DI-2-2': { + label: 'logement', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + + return isOR(m52Row) && + isDI(m52Row) && + ['A204142', 'A204182', 'A20422'].includes(article) && + f3 === 'R72'; + } + }, + 'DI-2-3': { + label: 'sdis', + filter(m52Row) { + const article = m52Row['Article']; + + return isOR(m52Row) && isDI(m52Row) && article === 'A2041781'; + } + }, + 'DI-2-4': { + label: 'subventions tiers (hors sdis)', + status: 'TEMPORARY', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + + return isOR(m52Row) && + isDI(m52Row) && + [ + 'A204112', + 'A204113', + 'A204122', + 'A204131', + 'A204132', + 'A204151', + 'A204152', + 'A204162', + 'A2041721', + 'A2041722', + 'A2041782', + 'A204181', + 'A204182', + 'A204182', + 'A20421', + 'A20422', + 'A20431' + ].includes(article) && + f3 !== 'R72' && + f3 !== 'R68' && + f3 !== 'R93'; + } + }, + 'DI-2-5': { + label: 'LGV', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + + return isOR(m52Row) && + isDI(m52Row) && + 'A204182' === article && + f3 === 'R68'; + } + }, + 'DI-2-6': { + label: 'GIRONDE Numérique', + filter(m52Row) { + const article = m52Row['Article']; + const fonction = m52Row['Rubrique fonctionnelle']; + const f3 = fonction.slice(0, 3); + + return isOR(m52Row) && + isDI(m52Row) && + 'A204152' === article && + f3 === 'R93'; + } + }, + 'DI-EM-1': { + label: 'Amortissement emprunt', + status: 'TEMPORARY', + filter(m52Row) { + const article = m52Row['Article']; + + return isOR(m52Row) && + isDI(m52Row) && + ['A1641', 'A16311', 'A167', 'A168'].includes(article); + } + }, + 'DI-EM-2': { + label: 'Amortissement OCLT', + status: 'TEMPORARY', // + ou - ? + filter(m52Row) { + const article = m52Row['Article']; + return isOR(m52Row) && + isDI(m52Row) && + ['A16441', 'A16449'].includes(article); + } + } +}); const AggregatedInstructionRowRecord = Record({ - "id": undefined, - "Libellé": undefined, - "Statut": undefined, - "M52Rows": undefined, - "Montant": undefined -}) - -function makeAggregatedInstructionRowRecord(id, m52Instruction){ - const rule = rules[id]; - - const m52Rows = m52Instruction.filter(rule.filter); - - return AggregatedInstructionRowRecord({ - id, - "Libellé": rule.label, - "Statut": rule.status, - "M52Rows": m52Rows, - "Montant": m52Rows.reduce(((acc, curr) => { - return acc + curr["Montant"] - }), 0) - }) -} + id: undefined, + Libellé: undefined, + Statut: undefined, + M52Rows: undefined, + Montant: undefined +}); + +function makeAggregatedInstructionRowRecord(id, m52Instruction) { + const rule = rules[id]; -export default function convert(m52Instruction){ - return ImmutableSet( - Object.keys(rules).map(id => makeAggregatedInstructionRowRecord(id, m52Instruction)) + const m52Rows = m52Instruction.filter(rule.filter); + + return AggregatedInstructionRowRecord({ + id, + Libellé: rule.label, + Statut: rule.status, + M52Rows: m52Rows, + Montant: m52Rows.reduce( + (acc, curr) => { + return acc + curr['Montant']; + }, + 0 ) -} \ No newline at end of file + }); +} + +export default function convert(m52Instruction) { + return ImmutableSet( + Object.keys(rules).map(id => + makeAggregatedInstructionRowRecord(id, m52Instruction)) + ); +} diff --git a/client/js/finance/visitHierarchical.js b/client/js/finance/visitHierarchical.js index 006d3875..adc44968 100644 --- a/client/js/finance/visitHierarchical.js +++ b/client/js/finance/visitHierarchical.js @@ -1,17 +1,15 @@ +export default function visit(node, f) { + f(node); - -export default function visit(node, f){ - f(node) - - if(node.children){ - Array.from(node.children.values()).forEach(child => { - visit(child, f); - }) - } + if (node.children) { + Array.from(node.children.values()).forEach(child => { + visit(child, f); + }); + } } -export function flattenTree(node){ - const result = []; - visit(node, n => result.push(n)); - return result; -} \ No newline at end of file +export function flattenTree(node) { + const result = []; + visit(node, n => result.push(n)); + return result; +} diff --git a/client/js/main.js b/client/js/main.js index 46bc0f8c..24141751 100644 --- a/client/js/main.js +++ b/client/js/main.js @@ -1,322 +1,336 @@ import { createStore } from 'redux'; import React from 'react'; import ReactDOM from 'react-dom'; -import {csvParse} from 'd3-dsv'; -import {Record, OrderedSet as ImmutableSet, Map as ImmutableMap} from 'immutable'; +import { csvParse } from 'd3-dsv'; +import { + Record, + OrderedSet as ImmutableSet, + Map as ImmutableMap +} from 'immutable'; import memoize from 'lodash.memoize'; -import { connect, Provider } from 'react-redux' +import { connect, Provider } from 'react-redux'; import hierarchicalM52 from './finance/hierarchicalM52.js'; import hierarchicalAggregated from './finance/hierarchicalAggregated.js'; import m52ToAggregated from './finance/m52ToAggregated.js'; import afterCSVCleanup from './finance/afterCSVCleanup.js'; import visitHierarchical from './finance/visitHierarchical.js'; -import {PAR_PUBLIC_VIEW, M52_INSTRUCTION, AGGREGATED_INSTRUCTION} from './finance/constants'; +import { + PAR_PUBLIC_VIEW, + M52_INSTRUCTION, + AGGREGATED_INSTRUCTION +} from './finance/constants'; import objectId from './objectId'; import TopLevel from './components/TopLevel.js'; +function reducer(state, action) { + const { type } = action; -function reducer(state, action){ - const {type} = action; - - switch(type){ + switch (type) { case 'M52_INSTRUCTION_RECEIVED': - return state.set('M52Instruction', action.M52Instruction); + return state.set('M52Instruction', action.M52Instruction); case 'M52_INSTRUCTION_USER_NODE_OVERED': - return state - .set('over', action.node ? - new InstructionNodeRecord({ - type: M52_INSTRUCTION, - node: action.node - }) : - undefined - ); + return state.set( + 'over', + action.node + ? new InstructionNodeRecord({ + type: M52_INSTRUCTION, + node: action.node + }) + : undefined + ); case 'AGGREGATED_INSTRUCTION_USER_NODE_OVERED': - return state - .set('over', action.node ? - new InstructionNodeRecord({ - type: AGGREGATED_INSTRUCTION, - node: action.node - }) : - undefined - ); + return state.set( + 'over', + action.node + ? new InstructionNodeRecord({ + type: AGGREGATED_INSTRUCTION, + node: action.node + }) + : undefined + ); case 'M52_INSTRUCTION_USER_NODE_SELECTED': { - const { node } = action; - const {node: alreadySelectedNode} = state.set('selection') || {}; - - return state - .set('selection', node && node !== alreadySelectedNode ? - new InstructionNodeRecord({ - type: M52_INSTRUCTION, - node - }) : - undefined - ); + const { node } = action; + const { node: alreadySelectedNode } = state.set('selection') || {}; + + return state.set( + 'selection', + node && node !== alreadySelectedNode + ? new InstructionNodeRecord({ + type: M52_INSTRUCTION, + node + }) + : undefined + ); } case 'AGGREGATED_INSTRUCTION_USER_NODE_SELECTED': { - const { node } = action; - const {node: alreadySelectedNode} = state.set('selection') || {}; - - return state - .set('selection', node && node !== alreadySelectedNode ? - new InstructionNodeRecord({ - type: AGGREGATED_INSTRUCTION, - node - }) : - undefined - ); + const { node } = action; + const { node: alreadySelectedNode } = state.set('selection') || {}; + + return state.set( + 'selection', + node && node !== alreadySelectedNode + ? new InstructionNodeRecord({ + type: AGGREGATED_INSTRUCTION, + node + }) + : undefined + ); } case 'RDFI_CHANGE': - return state - .set('RDFI', action.rdfi) - .set('over', undefined) - .set('selection', undefined); + return state + .set('RDFI', action.rdfi) + .set('over', undefined) + .set('selection', undefined); case 'DF_VIEW_CHANGE': - return state - .set('DF_VIEW', action.dfView) - .set('over', undefined) - .set('selection', undefined); + return state + .set('DF_VIEW', action.dfView) + .set('over', undefined) + .set('selection', undefined); default: - console.warn('Unknown action type', type); - return state; - } + console.warn('Unknown action type', type); + return state; + } } const M52RowRecord = Record({ - 'Département': undefined, - 'Budget': undefined, - 'Type nomenclature': undefined, - 'Exercice': undefined, - 'Type fichier': undefined, - 'Date vote': undefined, - 'Dépense/Recette': undefined, - 'Investissement/Fonctionnement': undefined, - 'Réel/Ordre id/Ordre diff': undefined, - 'Chapitre': undefined, - 'Sous-chapitre': undefined, - 'Opération': undefined, - 'Article': undefined, - 'Rubrique fonctionnelle': undefined, - 'Libellé': undefined, - 'Code devise': undefined, - 'Montant': undefined + Département: undefined, + Budget: undefined, + 'Type nomenclature': undefined, + Exercice: undefined, + 'Type fichier': undefined, + 'Date vote': undefined, + 'Dépense/Recette': undefined, + 'Investissement/Fonctionnement': undefined, + 'Réel/Ordre id/Ordre diff': undefined, + Chapitre: undefined, + 'Sous-chapitre': undefined, + Opération: undefined, + Article: undefined, + 'Rubrique fonctionnelle': undefined, + Libellé: undefined, + 'Code devise': undefined, + Montant: undefined }); fetch('./data/cedi_2015_CA.csv') - .then(resp => resp.text()) - .then(csvParse) - .then(afterCSVCleanup) - .then(caData => { - const M52Instruction = ImmutableSet( - caData.map(M52RowRecord) - ); - - store.dispatch({ - type: 'M52_INSTRUCTION_RECEIVED', - M52Instruction, - }); - + .then(resp => resp.text()) + .then(csvParse) + .then(afterCSVCleanup) + .then(caData => { + const M52Instruction = ImmutableSet(caData.map(M52RowRecord)); + + store.dispatch({ + type: 'M52_INSTRUCTION_RECEIVED', + M52Instruction }); + }); +let childToParent; +function findSelectedNodeAncestors(tree, selectedNode) { + if (!selectedNode) return undefined; + if (!childToParent) childToParent = new WeakMap(); -let childToParent; - -function findSelectedNodeAncestors(tree, selectedNode){ - if(!selectedNode) - return undefined; - - if(!childToParent) - childToParent = new WeakMap(); - - if(tree === selectedNode){ - let result = []; - let current = selectedNode; - while(current !== undefined){ - result.push(current); - current = childToParent.get(current); - } - return new ImmutableSet(result); - } - - let ret; - - if(tree.children){ - Array.from(tree.children.values()).forEach(child => { - childToParent.set(child, tree); - const ancestors = findSelectedNodeAncestors(child, selectedNode); - if(ancestors) - ret = ancestors; - }) + if (tree === selectedNode) { + let result = []; + let current = selectedNode; + while (current !== undefined) { + result.push(current); + current = childToParent.get(current); } + return new ImmutableSet(result); + } - return ret; + let ret; + + if (tree.children) { + Array.from(tree.children.values()).forEach(child => { + childToParent.set(child, tree); + const ancestors = findSelectedNodeAncestors(child, selectedNode); + if (ancestors) ret = ancestors; + }); + } + + return ret; } -function findSelectedAggregatedNodesByM52Rows(aggregatedNode, m52Rows){ - let result = []; +function findSelectedAggregatedNodesByM52Rows(aggregatedNode, m52Rows) { + let result = []; - visitHierarchical(aggregatedNode, n => { - const elements = Array.from(n.elements); - if(m52Rows.some(row => elements.some(el => el["M52Rows"].has(row)))){ - result.push(n); - } - }); + visitHierarchical(aggregatedNode, n => { + const elements = Array.from(n.elements); + if (m52Rows.some(row => elements.some(el => el['M52Rows'].has(row)))) { + result.push(n); + } + }); - return new ImmutableSet(result); + return new ImmutableSet(result); } -function findSelectedM52NodesByM52Rows(M52Node, m52Rows){ - let result = []; +function findSelectedM52NodesByM52Rows(M52Node, m52Rows) { + let result = []; - visitHierarchical(M52Node, n => { - if(m52Rows.some(row => n.elements.has(row))){ - result.push(n); - } - }); + visitHierarchical(M52Node, n => { + if (m52Rows.some(row => n.elements.has(row))) { + result.push(n); + } + }); - return new ImmutableSet(result);; + return new ImmutableSet(result); } -function hierarchMemoizeResolver(o, rdfi, view){ - return objectId(o) + rdfi.rd + rdfi.fi + (view ? view : ''); +function hierarchMemoizeResolver(o, rdfi, view) { + return objectId(o) + rdfi.rd + rdfi.fi + (view ? view : ''); } -const memoizedHierarchicalM52 = memoize(hierarchicalM52, hierarchMemoizeResolver); -const memoizedHierarchicalAggregated = memoize(hierarchicalAggregated, hierarchMemoizeResolver); +const memoizedHierarchicalM52 = memoize( + hierarchicalM52, + hierarchMemoizeResolver +); +const memoizedHierarchicalAggregated = memoize( + hierarchicalAggregated, + hierarchMemoizeResolver +); const memoizedM52ToAggregated = memoize(m52ToAggregated); const m52InstrRDFIToFiltered = new WeakMap(); -function mapStateToProps(state){ - const M52Instruction = state.get('M52Instruction'); - const rdfi = state.get('RDFI'); - const dfView = state.get('DF_VIEW'); - const over = state.get('over'); - const selection = state.get('selection'); - const {type: overType, node: overedNode} = over || {}; - const {type: selectedType, node: selectedNode} = selection || {}; - - if(!M52Instruction) - return {}; - - - const mainHighlightNode = overedNode || selectedNode; - const mainHighlightType = overType || selectedType; - - const aggregatedInstruction = memoizedM52ToAggregated(M52Instruction); - const M52Hierarchical = memoizedHierarchicalM52(M52Instruction, rdfi); - const aggregatedHierarchical = memoizedHierarchicalAggregated(aggregatedInstruction, rdfi, dfView); - - let M52HighlightedNodes; - let aggregatedHighlightedNodes; - - - if(mainHighlightType === M52_INSTRUCTION){ - M52HighlightedNodes = findSelectedNodeAncestors(M52Hierarchical, mainHighlightNode); - aggregatedHighlightedNodes = findSelectedAggregatedNodesByM52Rows(aggregatedHierarchical, Array.from(mainHighlightNode.elements)) - } - else{ - if(mainHighlightType === AGGREGATED_INSTRUCTION){ - aggregatedHighlightedNodes = findSelectedNodeAncestors(aggregatedHierarchical, mainHighlightNode); - let m52Rows = new ImmutableSet(); - mainHighlightNode.elements.forEach(e => m52Rows = m52Rows.union(e["M52Rows"])); - - M52HighlightedNodes = findSelectedM52NodesByM52Rows(M52Hierarchical, m52Rows); - } +function mapStateToProps(state) { + const M52Instruction = state.get('M52Instruction'); + const rdfi = state.get('RDFI'); + const dfView = state.get('DF_VIEW'); + const over = state.get('over'); + const selection = state.get('selection'); + const { type: overType, node: overedNode } = over || {}; + const { type: selectedType, node: selectedNode } = selection || {}; + + if (!M52Instruction) return {}; + + const mainHighlightNode = overedNode || selectedNode; + const mainHighlightType = overType || selectedType; + + const aggregatedInstruction = memoizedM52ToAggregated(M52Instruction); + const M52Hierarchical = memoizedHierarchicalM52(M52Instruction, rdfi); + const aggregatedHierarchical = memoizedHierarchicalAggregated( + aggregatedInstruction, + rdfi, + dfView + ); + + let M52HighlightedNodes; + let aggregatedHighlightedNodes; + + if (mainHighlightType === M52_INSTRUCTION) { + M52HighlightedNodes = findSelectedNodeAncestors( + M52Hierarchical, + mainHighlightNode + ); + aggregatedHighlightedNodes = findSelectedAggregatedNodesByM52Rows( + aggregatedHierarchical, + Array.from(mainHighlightNode.elements) + ); + } else { + if (mainHighlightType === AGGREGATED_INSTRUCTION) { + aggregatedHighlightedNodes = findSelectedNodeAncestors( + aggregatedHierarchical, + mainHighlightNode + ); + let m52Rows = new ImmutableSet(); + mainHighlightNode.elements.forEach( + e => m52Rows = m52Rows.union(e['M52Rows']) + ); + + M52HighlightedNodes = findSelectedM52NodesByM52Rows( + M52Hierarchical, + m52Rows + ); } + } + + return { + rdfi, + dfView, + M52Instruction, + aggregatedInstruction, + M52Hierarchical, + M52HighlightedNodes, + aggregatedHierarchical, + aggregatedHighlightedNodes, + over, + selection + }; +} - return { - rdfi, dfView, - M52Instruction, aggregatedInstruction, - M52Hierarchical, M52HighlightedNodes, - aggregatedHierarchical, aggregatedHighlightedNodes, - over, selection - }; -}; - -function mapDispatchToProps(dispatch){ - return { - onM52NodeOvered(node){ - store.dispatch({ - type: 'M52_INSTRUCTION_USER_NODE_OVERED', - node - }); - }, - onAggregatedNodeOvered(node){ - store.dispatch({ - type: 'AGGREGATED_INSTRUCTION_USER_NODE_OVERED', - node - }); - }, - onM52NodeSelected(node){ - console.log('onM52NodeSelected', node) - store.dispatch({ - type: 'M52_INSTRUCTION_USER_NODE_SELECTED', - node - }); - }, - onAggregatedNodeSelected(node){ - store.dispatch({ - type: 'AGGREGATED_INSTRUCTION_USER_NODE_SELECTED', - node - }); - }, - onRDFIChange(rdfi){ - store.dispatch({ - type: 'RDFI_CHANGE', - rdfi - }); - }, - onAggregatedDFViewChange(dfView){ - console.log('onAggregatedDFViewChange', dfView) - store.dispatch({ - type: 'DF_VIEW_CHANGE', - dfView - }); - } +function mapDispatchToProps(dispatch) { + return { + onM52NodeOvered(node) { + store.dispatch({ + type: 'M52_INSTRUCTION_USER_NODE_OVERED', + node + }); + }, + onAggregatedNodeOvered(node) { + store.dispatch({ + type: 'AGGREGATED_INSTRUCTION_USER_NODE_OVERED', + node + }); + }, + onM52NodeSelected(node) { + console.log('onM52NodeSelected', node); + store.dispatch({ + type: 'M52_INSTRUCTION_USER_NODE_SELECTED', + node + }); + }, + onAggregatedNodeSelected(node) { + store.dispatch({ + type: 'AGGREGATED_INSTRUCTION_USER_NODE_SELECTED', + node + }); + }, + onRDFIChange(rdfi) { + store.dispatch({ + type: 'RDFI_CHANGE', + rdfi + }); + }, + onAggregatedDFViewChange(dfView) { + console.log('onAggregatedDFViewChange', dfView); + store.dispatch({ + type: 'DF_VIEW_CHANGE', + dfView + }); } + }; } -const BoundTopLevel = connect( - mapStateToProps, - mapDispatchToProps -)(TopLevel) - +const BoundTopLevel = connect(mapStateToProps, mapDispatchToProps)(TopLevel); const InstructionNodeRecord = Record({ - type: undefined, - node: undefined -}) + type: undefined, + node: undefined +}); const StoreRecord = Record({ - M52Instruction: undefined, - selection: undefined, - over: undefined, - RDFI: undefined, - DF_VIEW: undefined -}) - -const store = createStore( - reducer, - new StoreRecord({ - RDFI: { - rd: 'D', - fi: 'F' - }, - DF_VIEW: PAR_PUBLIC_VIEW - }) -) + M52Instruction: undefined, + selection: undefined, + over: undefined, + RDFI: undefined, + DF_VIEW: undefined +}); +const store = createStore(reducer, new StoreRecord({ + RDFI: { + rd: 'D', + fi: 'F' + }, + DF_VIEW: PAR_PUBLIC_VIEW +})); ReactDOM.render( - React.createElement( - Provider, - {store}, - React.createElement(BoundTopLevel) - ), - document.querySelector('.react-container') + React.createElement(Provider, { store }, React.createElement(BoundTopLevel)), + document.querySelector('.react-container') ); - diff --git a/client/js/objectId.js b/client/js/objectId.js index 06c1b97d..d2f7d338 100644 --- a/client/js/objectId.js +++ b/client/js/objectId.js @@ -1,9 +1,9 @@ import memoize from 'lodash.memoize'; import uuid from 'uuid'; -const Cache = memoize.Cache +const Cache = memoize.Cache; memoize.Cache = WeakMap; const ret = memoize(o => uuid()); memoize.Cache = Cache; -export default ret; \ No newline at end of file +export default ret; diff --git a/client/js/remember.js b/client/js/remember.js index 31727e7d..caa1680f 100644 --- a/client/js/remember.js +++ b/client/js/remember.js @@ -1,57 +1,54 @@ -export default function remember(...args){ - const [key, value] = args; - - if(args.length === 0){ - return Promise.reject('Missing key argument'); - } - - if(args.length === 1){ - // recall - return new Promise(resolve => { - setTimeout(() => { - const val = localStorage.getItem(key); - - try{ - resolve(JSON.parse(val)) - } - catch(e){ - resolve(val); - } - }) - }); - } - - // args.length >= 2 - // retain - return new Promise( (resolve, reject) => { - setTimeout(() => { - let toStore = value; - try{ - if(Object(value) === value) - toStore = JSON.stringify(value); - } - catch(e){ reject(e) } - - try{ - localStorage.setItem(key, toStore); - resolve(key); - } - catch(e){ - reject(e); - } - }) +export default function remember(...args) { + const [key, value] = args; + + if (args.length === 0) { + return Promise.reject('Missing key argument'); + } + + if (args.length === 1) { + // recall + return new Promise(resolve => { + setTimeout(() => { + const val = localStorage.getItem(key); + + try { + resolve(JSON.parse(val)); + } catch (e) { + resolve(val); + } + }); }); + } + + // args.length >= 2 + // retain + return new Promise((resolve, reject) => { + setTimeout(() => { + let toStore = value; + try { + if (Object(value) === value) toStore = JSON.stringify(value); + } catch (e) { + reject(e); + } + + try { + localStorage.setItem(key, toStore); + resolve(key); + } catch (e) { + reject(e); + } + }); + }); } -export function forget(key){ - return new Promise( (resolve, reject) => { - setTimeout(() => { - try{ - resolve(localStorage.removeItem(key)) - } - catch(e){ - reject(e); - } - }) +export function forget(key) { + return new Promise((resolve, reject) => { + setTimeout(() => { + try { + resolve(localStorage.removeItem(key)); + } catch (e) { + reject(e); + } }); -} \ No newline at end of file + }); +} diff --git a/package.json b/package.json index f768d52d..a6832c56 100644 --- a/package.json +++ b/package.json @@ -5,20 +5,19 @@ "description": "...", "main": "index.js", "scripts": { + "beautify": "prettier --write --single-quote --no-trailing-comma 'client/**/*.js'", "build": "browserify client/js/main.js -t rollupify -o client/js/browserify-bundle.js -d", "watch": "watchify client/js/main.js -t rollupify -o client/js/browserify-bundle.js -d -v", - "lint": "eslint --ignore-path .gitignore client/js", - "lint-fix": "npm run lint -- --fix", "start": "serve --cors -p 3000", "test": "exit 0", - "posttest": "npm run lint" + "precommit": "npm run beautify" }, "repository": { "type": "git", "url": "https://github.com/dtc-innovation/dataviz-finances-gironde" }, "author": "dtc innovation", - "license": "ISC", + "license": "MIT", "bugs": { "url": "https://github.com/dtc-innovation/dataviz-finances-gironde/issues" }, @@ -41,6 +40,8 @@ "devDependencies": { "browserify": "^13.1.1", "eslint": "^3.14.0", + "husky": "^0.13.1", + "prettier": "^0.16.0", "serve": "^2.4.3", "watchify": "^3.8.0" }