From 55d9d803e0cc415b99d5a5d6e5329d2e0a39f0c7 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Wed, 6 May 2026 14:20:32 -0500 Subject: [PATCH] fix --- src/services/accounting-service.js | 71 ++++++++++++++++++------------ 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/services/accounting-service.js b/src/services/accounting-service.js index 3da165c..b8830ae 100644 --- a/src/services/accounting-service.js +++ b/src/services/accounting-service.js @@ -135,21 +135,43 @@ async function getRegister({ accountId, startDate, endDate }) { } /** - * Normalisiert die QBO TransactionList Report Antwort in eine flache - * Liste mit { date, type, docNum, payee, account, memo, amount, qboId }. - * - * Der Report liefert Columns dynamisch — wir bauen eine Index-Map und - * lesen die Zellen darüber aus. + * Normalisiert die QBO TransactionList Report Antwort in eine flache Liste. + * Wir mappen über ColTitle (immer vorhanden), nicht über ColType (manchmal leer). */ function normalizeTransactionListReport(report) { const columns = (report.Columns && report.Columns.Column) || []; + + // Map: ColTitle (lowercase) → Index const colIndex = {}; columns.forEach((c, i) => { - // ColType ist z.B. "tx_date", "txn_type", "doc_num", "name", "account_name", - // "memo", "subt_nat_amount", "split_acc" - if (c.ColType) colIndex[c.ColType] = i; + if (c.ColTitle) colIndex[c.ColTitle.toLowerCase()] = i; + if (c.ColType) colIndex[c.ColType.toLowerCase()] = i; // Fallback, falls vorhanden }); + // Resolver: probiert mehrere mögliche Titel/Typen + const resolve = (...candidates) => { + for (const k of candidates) { + const idx = colIndex[k.toLowerCase()]; + if (idx != null) return idx; + } + return null; + }; + + const idxDate = resolve('Date', 'tx_date'); + const idxType = resolve('Transaction Type', 'Type', 'txn_type'); + const idxDocNum = resolve('Num', 'No.', 'doc_num'); + const idxPayee = resolve('Name', 'Payee', 'name'); + const idxAccount = resolve('Account', 'account_name'); + const idxMemo = resolve('Memo/Description', 'Memo', 'memo'); + const idxSplit = resolve('Split', 'split_acc'); + const idxAmount = resolve('Amount', 'subt_nat_amount', 'subt_nat_home_amount'); + + const cellAt = (colData, idx) => { + if (idx == null) return null; + const c = colData[idx]; + return c || null; + }; + const rows = []; function walk(rowGroup) { @@ -160,28 +182,20 @@ function normalizeTransactionListReport(report) { walk(r.Rows && r.Rows.Row); continue; } - // Data row if (!r.ColData) continue; - const cell = (key) => { - const idx = colIndex[key]; - if (idx == null) return null; - const c = r.ColData[idx]; - return c ? c : null; - }; - const dateCell = cell('tx_date'); - const typeCell = cell('txn_type'); - const docCell = cell('doc_num'); - const payeeCell = cell('name'); - const acctCell = cell('account_name'); - const memoCell = cell('memo'); - const amtCell = cell('subt_nat_amount'); - const splitCell = cell('split_acc'); - // QBO setzt die qbo Txn Id meistens als value im Date-Cell oder DocNum-Cell. - // Wir greifen sicherheitshalber an mehreren Stellen. + const dateCell = cellAt(r.ColData, idxDate); + const typeCell = cellAt(r.ColData, idxType); + const docCell = cellAt(r.ColData, idxDocNum); + const payeeCell = cellAt(r.ColData, idxPayee); + const acctCell = cellAt(r.ColData, idxAccount); + const memoCell = cellAt(r.ColData, idxMemo); + const splitCell = cellAt(r.ColData, idxSplit); + const amtCell = cellAt(r.ColData, idxAmount); + const qboId = (dateCell && dateCell.id) || - (docCell && docCell.id) || + (docCell && docCell.id) || (typeCell && typeCell.id) || null; @@ -192,7 +206,8 @@ function normalizeTransactionListReport(report) { payee: payeeCell ? payeeCell.value : null, account: acctCell ? acctCell.value : null, memo: memoCell ? memoCell.value : null, - amount: amtCell && amtCell.value !== '' ? Number(amtCell.value) : null, + amount: amtCell && amtCell.value !== '' && amtCell.value != null + ? Number(amtCell.value) : null, splitAccount: splitCell ? splitCell.value : null, qboId }); @@ -209,7 +224,7 @@ function normalizeTransactionListReport(report) { currency: report.Header && report.Header.Currency, time: report.Header && report.Header.Time }, - columns: columns.map(c => ({ title: c.ColTitle, type: c.ColType })), + columns: columns.map(c => ({ title: c.ColTitle, type: c.ColType || c.type })), rows }; }