---
name: evolution-creditnote-commit-xero
description: "Credit note commit is a JSON AJAX endpoint; xero.php echoes pollute it, and pushCreditNoteToXero fires on first commit with an empty tenant (silent sync failure)"
metadata: 
  node_type: memory
  type: project
  originSessionId: 6504af14-cee7-4971-a65a-985acc406116
---

The credit-note editor (`creditnoteadd.inc` / `.js` / `creditnotesave.php`, AR+AP) is a JSON AJAX feature. Two gotchas surfaced 2026-07-15:

**1. xero.php echoes corrupt JSON responses.** `library/xero.php` reports progress/errors with raw `echo` (`"Processing Credit Note <br>"`, `"Exception, invalid tenantid ..."`, `"Failed Adding Client"`, etc.). When a JSON endpoint calls into the Xero layer, that text prepends the body → jQuery's auto JSON-parse fails → `error` handler fires ("Commit failed") even though the GL commit succeeded. Fixed for commit by buffering `commitCreditNote()` output in `creditnotesave.php` (ob_start/ob_get_clean → error_log). **Any other JSON endpoint that reaches xero.php needs the same buffering** (e.g. `creditnotesave.php` `newFromSource` → `creditnote::importFrom*` which `include`s xero.php).

**2. pushCreditNoteToXero is mis-wired vs pushInvoiceToXero (OPEN).** `accounts::pushInvoiceToXero` (accounting.php ~435) only pushes when `!empty($invoice->externalRef)` — i.e. after the doc is already linked to Xero. `pushCreditNoteToXero` (~1801) has **no guard**: it pushes on *every* commit and sets `xeroTenantId = $creditnote->externalTenant`, which is EMPTY on a first commit → Xero "invalid tenantid". Worse, `putCreditNote` signals failure by `echo`+`return false` (never throws), so `commitCreditNote`'s `catch(Exception)` never fires and `review_flag` stays 0 → **credit notes silently never sync to Xero**. Real fix (Xero workstream, see [[xero_validation_project]]): resolve a valid tenant id from the connection/`addonXeroTenants` instead of the empty column, and make `putCreditNote` throw (or return a checked status) so review_flag=2 on failure.

Also fixed same day: `creditnoteadd.inc:96` selected non-existent `inventory.description` — the column is `` `desc` `` (aliased to `description`). See [[evolution_getfieldarray_deprecated]] for the DB:: query style used here.
