From 1cd634500fc0d28c50776c76aba21289cbe58854 Mon Sep 17 00:00:00 2001 From: Pedro Machado Date: Mon, 8 Jun 2026 13:44:02 -0500 Subject: [PATCH] Fix update-bank-transaction 400 when editing line items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateBankTransaction spread the entire fetched BankTransaction into the update payload, carrying Xero's read-only computed fields (subTotal, total, totalTax). When lineItems are replaced, Xero recomputes line totals but the stale subTotal/total from the spread no longer match, so the API rejects the update with HTTP 400 "The document sub total does not equal the sub total of the lines" — even when only a line description changes. Build a minimal payload with only editable fields plus the attributes Xero needs on update (bankAccount, lineAmountTypes, status, currencyCode preserved from the existing transaction), and omit the computed totals so Xero recomputes them. Per Xero's BankTransactions API docs, subTotal/total/totalTax are "calculated by Xero and cannot be overridden," and lineAmountTypes defaults to Exclusive if omitted — so preserving it avoids silently changing the tax treatment. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/handlers/update-xero-bank-transaction.handler.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/handlers/update-xero-bank-transaction.handler.ts b/src/handlers/update-xero-bank-transaction.handler.ts index 8dfdd529..7d56758d 100644 --- a/src/handlers/update-xero-bank-transaction.handler.ts +++ b/src/handlers/update-xero-bank-transaction.handler.ts @@ -36,9 +36,17 @@ async function updateBankTransaction( reference?: string, date?: string ): Promise { + // Build a minimal payload with only editable fields plus the attributes Xero + // requires on update. Do NOT spread the fetched object: it carries read-only + // computed fields (subTotal/total/totalTax, updatedDateUTC, etc.) that go + // stale when lineItems change, causing a 400 "The document sub total does not + // equal the sub total of the lines". Omitting the totals lets Xero recompute. const bankTransaction: BankTransaction = { - ...existingBankTransaction, bankTransactionID: bankTransactionId, + bankAccount: existingBankTransaction.bankAccount, + lineAmountTypes: existingBankTransaction.lineAmountTypes, + status: existingBankTransaction.status, + currencyCode: existingBankTransaction.currencyCode, type: type ? BankTransaction.TypeEnum[type] : existingBankTransaction.type, contact: contactId ? { contactID: contactId } : existingBankTransaction.contact, lineItems: lineItems ? lineItems : existingBankTransaction.lineItems,