fix
This commit is contained in:
@@ -113,7 +113,7 @@ async function listAccounts({ type = null, activeOnly = true } = {}) {
|
||||
* @param {string} opts.startDate - YYYY-MM-DD
|
||||
* @param {string} opts.endDate - YYYY-MM-DD
|
||||
*/
|
||||
async function getRegister({ accountId, startDate, endDate }) {
|
||||
async function getRegister({ accountId, startDate, endDate, includeSplits = true }) {
|
||||
if (!accountId) throw new Error('accountId is required');
|
||||
|
||||
const { companyId, baseUrl } = getClientInfo();
|
||||
@@ -121,7 +121,6 @@ async function getRegister({ accountId, startDate, endDate }) {
|
||||
const params = new URLSearchParams();
|
||||
if (startDate) params.set('start_date', startDate);
|
||||
if (endDate) params.set('end_date', endDate);
|
||||
// account filter: TransactionList akzeptiert eine kommaseparierte Liste
|
||||
params.set('source_account', String(accountId));
|
||||
params.set('minorversion', QBO_MINOR_VERSION);
|
||||
|
||||
@@ -131,7 +130,24 @@ async function getRegister({ accountId, startDate, endDate }) {
|
||||
const data = getJson(response);
|
||||
throwIfFault(data, 'TransactionList report');
|
||||
|
||||
return normalizeTransactionListReport(data);
|
||||
const result = normalizeTransactionListReport(data);
|
||||
|
||||
// ── NEU: Split-Details nachladen ──
|
||||
if (includeSplits) {
|
||||
const splitRows = result.rows.filter(r =>
|
||||
r.splitAccount === '-Split-' && r.qboId
|
||||
);
|
||||
if (splitRows.length) {
|
||||
const splits = await fetchSplitDetails(splitRows);
|
||||
result.rows.forEach(r => {
|
||||
if (r.qboId && splits[r.qboId]) {
|
||||
r.splits = splits[r.qboId];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +180,7 @@ function normalizeTransactionListReport(report) {
|
||||
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 idxCleared = resolve('Cleared', 'cleared_status', 'clr');
|
||||
|
||||
const cellAt = (colData, idx) => {
|
||||
if (idx == null) return null;
|
||||
@@ -192,6 +208,7 @@ function normalizeTransactionListReport(report) {
|
||||
const memoCell = cellAt(r.ColData, idxMemo);
|
||||
const splitCell = cellAt(r.ColData, idxSplit);
|
||||
const amtCell = cellAt(r.ColData, idxAmount);
|
||||
const clrCell = cellAt(r.ColData, idxCleared);
|
||||
|
||||
const qboId =
|
||||
(dateCell && dateCell.id) ||
|
||||
@@ -199,6 +216,14 @@ function normalizeTransactionListReport(report) {
|
||||
(typeCell && typeCell.id) ||
|
||||
null;
|
||||
|
||||
// ── NEU: Cleared-Status normalisieren ──
|
||||
// QBO liefert "R" (reconciled), "C" (cleared) oder leer (uncleared)
|
||||
let clearedStatus = null;
|
||||
if (clrCell && clrCell.value) {
|
||||
const v = String(clrCell.value).trim().toUpperCase();
|
||||
if (v === 'R' || v === 'C') clearedStatus = v;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
date: dateCell ? dateCell.value : null,
|
||||
type: typeCell ? typeCell.value : null,
|
||||
@@ -209,6 +234,7 @@ function normalizeTransactionListReport(report) {
|
||||
amount: amtCell && amtCell.value !== '' && amtCell.value != null
|
||||
? Number(amtCell.value) : null,
|
||||
splitAccount: splitCell ? splitCell.value : null,
|
||||
clearedStatus,
|
||||
qboId
|
||||
});
|
||||
}
|
||||
@@ -278,12 +304,87 @@ async function getBalanceSheet({ asOfDate, accountingMethod = 'Accrual' } = {})
|
||||
throwIfFault(data, 'BalanceSheet report');
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* Lädt für eine Liste von Split-Transaktionen die Einzel-Lines aus QBO.
|
||||
* Wir laden nur das, was als Split markiert ist und einen qboId hat.
|
||||
*
|
||||
* Returns: Map<qboId, Array<{account, amount, description}>>
|
||||
*/
|
||||
async function fetchSplitDetails(splitRows) {
|
||||
if (!splitRows || splitRows.length === 0) return {};
|
||||
|
||||
const { companyId, baseUrl } = getClientInfo();
|
||||
|
||||
// Group by Type, weil QBO unterschiedliche Endpoints für Purchase/Deposit/JournalEntry hat
|
||||
const result = {};
|
||||
|
||||
for (const row of splitRows) {
|
||||
if (!row.qboId) continue;
|
||||
|
||||
// Type → QBO endpoint name
|
||||
const endpoint = mapTypeToEndpoint(row.type);
|
||||
if (!endpoint) continue;
|
||||
|
||||
try {
|
||||
const url = withMinorVersion(
|
||||
`${baseUrl}/v3/company/${companyId}/${endpoint}/${row.qboId}`
|
||||
);
|
||||
const response = await makeQboApiCall({ url, method: 'GET' });
|
||||
const data = getJson(response);
|
||||
|
||||
// Response shape: { Purchase: {...} } or { Deposit: {...} } etc.
|
||||
const txn = data[capitalize(endpoint)] || data[endpoint];
|
||||
if (!txn || !txn.Line) continue;
|
||||
|
||||
const lines = txn.Line
|
||||
.filter(l => l.DetailType !== 'SubTotalLineDetail')
|
||||
.map(l => {
|
||||
const detail = l.AccountBasedExpenseLineDetail
|
||||
|| l.DepositLineDetail
|
||||
|| l.JournalEntryLineDetail
|
||||
|| {};
|
||||
const acctRef = detail.AccountRef || {};
|
||||
return {
|
||||
account: acctRef.name || null,
|
||||
amount: l.Amount != null ? Number(l.Amount) : null,
|
||||
description: l.Description || null
|
||||
};
|
||||
})
|
||||
.filter(l => l.account || l.amount != null);
|
||||
|
||||
if (lines.length) result[row.qboId] = lines;
|
||||
} catch (err) {
|
||||
// Einzelne Fehler nicht den ganzen Register killen lassen
|
||||
console.warn(`Split detail fetch failed for ${row.type} ${row.qboId}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function mapTypeToEndpoint(type) {
|
||||
if (!type) return null;
|
||||
const t = type.toLowerCase();
|
||||
if (t.includes('expense') || t.includes('check')) return 'purchase';
|
||||
if (t.includes('deposit')) return 'deposit';
|
||||
if (t.includes('journal')) return 'journalentry';
|
||||
if (t.includes('bill payment')) return 'billpayment';
|
||||
if (t.includes('bill')) return 'bill';
|
||||
if (t.includes('credit card')) return 'purchase';
|
||||
if (t.includes('paycheck') || t.includes('payroll')) return null; // QBO blockt Paycheck-API
|
||||
if (t.includes('tax payment')) return null; // dito
|
||||
return null;
|
||||
}
|
||||
|
||||
function capitalize(s) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
module.exports = {
|
||||
listAccounts,
|
||||
getRegister,
|
||||
getProfitAndLoss,
|
||||
getBalanceSheet,
|
||||
fetchSplitDetails,
|
||||
// exposed for testing/debugging
|
||||
normalizeTransactionListReport
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user