Skip to content

Commit 5df34e5

Browse files
committed
Apparie amortissements et dépréciations par racine de compte (revue #66)
L'appariement « 28 + suffixe » échouait sur les numéros complétés (2810000 cherché pour 210000) : l'amortissement partait sur une ligne orpheline — total exact mais présentation trompeuse — et les 29x n'étaient jamais appariés. Un 28x/29x sans immobilisation brute détectée disparaissait en outre entièrement du bilan, tout le bloc étant sous « if (immoAccounts.length > 0) ». Appariement par racine : zéros finaux retirés puis 8/9 en 2e position (281000→210000, 281830→218300, 28183→2183, 291000→210000). Les comptes réellement orphelins gardent leur ligne propre et restent affichés même sans immobilisation brute. Tests de non-régression ajoutés à test:calc — 210000/281000, 218300/281830, dépréciation 29x, 28x orphelin — chacun vu échouer avant le correctif. Bilan du journal d'exemple byte-identique avant/après ; npm run test:calc OK.
1 parent de329d0 commit 5df34e5

2 files changed

Lines changed: 102 additions & 21 deletions

File tree

scripts/generate-statements.js

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -270,20 +270,30 @@ function generateBilan(accounts, company, pcgNames, plData) {
270270
let totalImmoBrut = 0;
271271
let totalAmort = 0;
272272

273-
if (immoAccounts.length > 0) {
274-
md += '| **ACTIF IMMOBILISE** | | | |\n';
273+
// 281/291 suivent la meme ventilation que le compte d'immobilisation (PCG),
274+
// mais les exports completent les numeros a 6 chiffres : 281000 amortit
275+
// 210000, 281830 amortit 218300, 28183 amortit 2183. On apparie donc par
276+
// racine (zeros finaux retires, 8/9 en 2e position retire).
277+
const rootOf = (acct) => acct.replace(/0+$/, '') || acct;
278+
const immoByRoot = new Map(immoAccounts.map(([acct]) => [rootOf(acct), acct]));
279+
const amortByImmo = new Map();
280+
const orphanAmort = [];
281+
for (const [acct, bal] of amortAccounts) {
282+
const amount = round2(bal.credit - bal.debit);
283+
const target = immoByRoot.get('2' + rootOf(acct).substring(2));
284+
if (target !== undefined) {
285+
amortByImmo.set(target, round2((amortByImmo.get(target) || 0) + amount));
286+
} else if (Math.abs(amount) > 0.01) {
287+
orphanAmort.push([acct, amount]);
288+
}
289+
}
275290

276-
const matchedAmort = new Set();
291+
if (immoAccounts.length > 0 || orphanAmort.length > 0) {
292+
md += '| **ACTIF IMMOBILISE** | | | |\n';
277293

278294
for (const [acct, bal] of immoAccounts) {
279295
const brut = round2(bal.debit - bal.credit);
280-
// Find corresponding amortization account (28 + suffix)
281-
const amortAcct = '28' + acct.substring(1);
282-
let amort = 0;
283-
if (accounts[amortAcct]) {
284-
matchedAmort.add(amortAcct);
285-
amort = round2(accounts[amortAcct].credit - accounts[amortAcct].debit);
286-
}
296+
const amort = amortByImmo.get(acct) || 0;
287297
const net = round2(brut - amort);
288298
const name = pcgNames[acct] || acct;
289299

@@ -292,16 +302,12 @@ function generateBilan(accounts, company, pcgNames, plData) {
292302
totalAmort += amort;
293303
}
294304

295-
// 28x/29x accounts not matched above (subdivided charts use e.g. 281000 for
296-
// 210000, and 29x were counted nowhere): own line so the total stays exact
297-
for (const [acct, bal] of amortAccounts) {
298-
if (matchedAmort.has(acct)) continue;
299-
const amort = round2(bal.credit - bal.debit);
300-
if (Math.abs(amort) > 0.01) {
301-
const name = pcgNames[acct] || acct;
302-
md += '|   ' + acct + ' — ' + name + ' | | ' + fmt(amort) + ' | ' + fmt(round2(-amort)) + ' |\n';
303-
totalAmort += amort;
304-
}
305+
// 28x/29x sans immobilisation brute appariee : ligne propre pour que la
306+
// depreciation reste visible et que le total actif reste exact
307+
for (const [acct, amort] of orphanAmort) {
308+
const name = pcgNames[acct] || acct;
309+
md += '|   ' + acct + ' — ' + name + ' | | ' + fmt(amort) + ' | ' + fmt(round2(-amort)) + ' |\n';
310+
totalAmort += amort;
305311
}
306312

307313
md += '| **Total actif immobilise** | **' + fmt(totalImmoBrut) + '** | **' + fmt(totalAmort) + '** | **' + fmt(round2(totalImmoBrut - totalAmort)) + '** |\n';
@@ -582,4 +588,8 @@ function main() {
582588
}
583589
}
584590

585-
main();
591+
if (require.main === module) {
592+
main();
593+
}
594+
595+
module.exports = { computeBalances, generatePL, generateBilan, generateBalance };

scripts/test-deterministic-calculations.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,73 @@ function testProrata() {
7474
assert.match(output, /Resultat: 136,99 EUR/);
7575
}
7676

77+
// ---------------------------------------------------------------------------
78+
// Bilan : appariement immobilisations / amortissements-depreciations (issue #65)
79+
// ---------------------------------------------------------------------------
80+
81+
const { generateBilan } = require(path.join(ROOT, "scripts", "generate-statements.js"));
82+
83+
const FIXTURE_COMPANY = {
84+
fiscal_year: { start: "2025-01-01", end: "2025-12-31", is_first_year: false },
85+
};
86+
87+
function runBilan(accounts) {
88+
return generateBilan(accounts, FIXTURE_COMPANY, {}, { resultatNet: 0 });
89+
}
90+
91+
function bilanRow(md, acct) {
92+
const line = md.split("\n").find((l) => l.includes(acct + " —"));
93+
assert.ok(line, "ligne " + acct + " absente du bilan:\n" + md);
94+
return line;
95+
}
96+
97+
function testBilanAmortComptesCompletes() {
98+
// Plan de comptes a 6 chiffres : 281000 amortit 210000 (et non 2810000)
99+
const { md } = runBilan({
100+
"210000": { debit: 10000, credit: 0 },
101+
"281000": { debit: 0, credit: 1200 },
102+
});
103+
const row = bilanRow(md, "210000");
104+
assert.match(row, /1\s?200,00/, "amortissement 281000 non apparie a 210000: " + row);
105+
assert.match(row, /8\s?800,00/, "net attendu 8 800,00: " + row);
106+
assert.ok(!md.includes("281000 —"), "281000 ne doit pas avoir de ligne separee:\n" + md);
107+
}
108+
109+
function testBilanAmortSubdivision() {
110+
// Subdivision : 281830 amortit 218300
111+
const { md } = runBilan({
112+
"218300": { debit: 3000, credit: 0 },
113+
"281830": { debit: 0, credit: 500 },
114+
});
115+
const row = bilanRow(md, "218300");
116+
assert.match(row, /500,00/, "amortissement 281830 non apparie a 218300: " + row);
117+
assert.match(row, /2\s?500,00/, "net attendu 2 500,00: " + row);
118+
assert.ok(!md.includes("281830 —"), "281830 ne doit pas avoir de ligne separee:\n" + md);
119+
}
120+
121+
function testBilanDepreciation29x() {
122+
// Depreciation 29x apparie a l'immobilisation correspondante
123+
const { md } = runBilan({
124+
"210000": { debit: 10000, credit: 0 },
125+
"291000": { debit: 0, credit: 700 },
126+
});
127+
const row = bilanRow(md, "210000");
128+
assert.match(row, /700,00/, "depreciation 291000 non appariee a 210000: " + row);
129+
assert.match(row, /9\s?300,00/, "net attendu 9 300,00: " + row);
130+
assert.ok(!md.includes("291000 —"), "291000 ne doit pas avoir de ligne separee:\n" + md);
131+
}
132+
133+
function testBilanAmortOrphelin() {
134+
// 28x sans immobilisation brute : doit rester visible au bilan, pas disparaitre
135+
const { md, totalActif } = runBilan({
136+
"281000": { debit: 0, credit: 1200 },
137+
"512000": { debit: 1200, credit: 0 },
138+
});
139+
const row = bilanRow(md, "281000");
140+
assert.match(row, /1\s?200,00/, "amortissement orphelin absent: " + row);
141+
assert.strictEqual(totalActif, 0, "total actif attendu 0 (1200 - 1200): " + totalActif);
142+
}
143+
77144
function main() {
78145
testCCA();
79146
testAmortissementLineaire();
@@ -83,6 +150,10 @@ function main() {
83150
testISProratedReducedRate();
84151
testTVAAcomptesRS();
85152
testProrata();
153+
testBilanAmortComptesCompletes();
154+
testBilanAmortSubdivision();
155+
testBilanDepreciation29x();
156+
testBilanAmortOrphelin();
86157
console.log("Deterministic calculation tests passed.");
87158
}
88159

0 commit comments

Comments
 (0)