v2.6.0

<bmx-reconciliation>

Two lists of money movements side by side - a bank statement and the ledger, invoices and payments, a card feed and expense claims, an intercompany pair - matched by rules and by hand, with every difference explained, an audit trail and sign-off by a second person.

12 properties · 2 events · 18 methods · 5 parts

Example

The bank statement against the cash ledger of Harbour & Pine Ltd. Choose Run rules, review what they propose, then match the Harbour Joinery payment by hand with an adjustment for its early-payment discount.

Show markup
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block-end: 0.75rem">
  <label for="ex-recon-user">Working as</label>
  <select id="ex-recon-user">
    <option value="Priya Shah">Priya Shah (prepares)</option>
    <option value="Tom Reid">Tom Reid (approves)</option>
  </select>
</div>
<bmx-reconciliation id="ex-recon" label="Main current account, September 2026" user="Priya Shah"></bmx-reconciliation>
<p class="note" id="ex-recon-out" role="status">The bank statement against the cash ledger of Harbour &amp; Pine Ltd. Choose Run rules, review what they propose, then match the Harbour Joinery payment by hand with an adjustment for its early-payment discount.</p>

<script type="module">
  await customElements.whenDefined('bmx-reconciliation');
  const el = document.getElementById('ex-recon');
  const out = document.getElementById('ex-recon-out');
  const i = (id, date, amount, description, extra = {}) => ({ id, date: `2026-09-${date}`, amount, description, ...extra });
  el.data = {
    currency: 'GBP',
    asOf: '2026-09-30',
    left: {
      name: 'Bank statement',
      items: [
        i('B01', '01', -2400, 'DD RENT SEPT QUAYSIDE'),
        i('B02', '02', 1250, 'FP INV-2041 BLUEFIN CAFE'),
        i('B03', '03', -318.4, 'CARD 4411 OFFICE SUPPLIES', { counterparty: 'Paperstack' }),
        i('B04', '05', 4860, 'FP MARLOW HOTELS REMIT 5521'),
        i('B05', '08', 1486.35, 'CARD SETTLEMENT', { reference: 'STL-0908' }),
        i('B06', '10', -12640.55, 'BACS PAYROLL SEPT'),
        i('B07', '12', 860, 'FP INV-2049 KESTREL DESIGN'),
        i('B08', '15', -1980, 'FP TIMBERLINE SUPPLIES PO 7781'),
        i('B09', '18', -45, 'BANK CHARGES AUGUST'),
        i('B10', '22', 3.12, 'CREDIT INTEREST'),
        i('B11', '24', 640, 'FP BLUEFIN CAFE'),
        i('B12', '25', -1495, 'FP HARBOUR JOINERY'),
        i('B13', '26', -720, 'CHQ 000318'),
        i('B14', '29', -96, 'DD LINKWAVE MOBILE'),
      ],
    },
    right: {
      name: 'Cash ledger',
      items: [
        i('G01', '01', -2400, 'September rent', { reference: 'RENT-SEP', counterparty: 'Quayside Properties' }),
        i('G02', '01', 1250, 'Catering, August', { reference: 'INV-2041', counterparty: 'Bluefin Café' }),
        i('G03', '03', -318.4, 'Stationery', { reference: 'PO 7770', counterparty: 'Paperstack Ltd' }),
        i('G04', '03', 1620, 'Conference furniture', { reference: 'INV-2044', counterparty: 'Marlow Hotels' }),
        i('G05', '03', 2040, 'Lobby benches', { reference: 'INV-2045', counterparty: 'Marlow Hotels' }),
        i('G06', '04', 1200, 'Repairs', { reference: 'INV-2047', counterparty: 'Marlow Hotels' }),
        i('G07', '08', 1490, 'Card takings', { reference: 'STL0908' }),
        i('G08', '10', -3210.2, 'Net pay: operations', { reference: 'PAY-SEP' }),
        i('G09', '10', -4105.35, 'Net pay: sales', { reference: 'PAY-SEP' }),
        i('G10', '10', -2890, 'Net pay: workshop', { reference: 'PAY-SEP' }),
        i('G11', '10', -2435, 'Net pay: administration', { reference: 'PAY-SEP' }),
        i('G12', '11', 860, 'Brand refresh, deposit', { reference: 'INV-2049', counterparty: 'Kestrel Design' }),
        i('G13', '14', -1980, 'Oak boards', { reference: 'PO 7781', counterparty: 'Timberline Supplies' }),
        i('G14', '23', 640, 'Catering, September', { reference: 'INV-2052', counterparty: 'Bluefin Cafe Ltd' }),
        i('G15', '24', -1500, 'Joinery subcontract', { reference: 'HJ-9921', counterparty: 'Harbour Joinery' }),
        i('G16', '22', -720, 'Van service', { reference: 'CHQ 318', counterparty: 'Quayside Motors' }),
        i('G17', '28', -96, 'Phones', { counterparty: 'Linkwave Mobile' }),
        i('G18', '29', -1150, 'Brochures', { reference: 'CHQ 321', counterparty: 'Harlow Print' }),
        i('G19', '30', 2300, 'Workshop day', { reference: 'INV-2058', counterparty: 'Oakmere School' }),
      ],
    },
  };
  document.getElementById('ex-recon-user').addEventListener('change', e => (el.user = e.target.value));
  el.addEventListener('bmxChange', async e => {
    const s = await el.getSummary();
    const share = Math.round(s.matchedShare * 100);
    out.textContent = `${e.detail.reason}: ${share}% matched, ${s.proposed} to review, ${s.leftUnmatched} open on the statement, ${s.rightUnmatched} in the ledger. Status: ${e.detail.data.status}.`;
  });
</script>

WHAT IT DOES

  • Import each side from a CSV export (columns found from the header; 1,234.56, 1.234,56, (250.00), CR and DR all read), or set the data.
  • Rules run in order: same amount and reference, same amount on close dates, within a small fee, one payment for several invoices, several items paid together, groups sharing a reference. Each has its own tolerance, date window, reference and counterparty tests, and a confidence at which its matches are confirmed straight away.
  • Everything else waits in a review list with its confidence and reasons, to confirm or reject - one at a time or every one above a confidence.
  • Choose items on both sides to match them by hand, with a running difference; a difference left over needs an adjustment with a reason and an account, and becomes a line in the adjustments journal. Suggestions show what one chosen item might go with, groups included.
  • Totals, matched share, open items by age, what nothing explains.
  • Submit for approval when nothing is unexplained; the preparer cannot approve their own. Submitted and approved reconciliations are locked.
  • Every run, match, confirmation, rejection and sign-off is in the audit trail. Matches, open items, adjustments as CSV; everything as JSON.

All of it is plain JSON on the element (data, bmxChange); nothing is sent anywhere.

Properties

PropertyAttributeTypeDefaultDescription
allowSelfApproval allow-self-approval boolean false Let the preparer approve their own reconciliation.
confirmThreshold confirm-threshold number 0.9 The confidence (0 to 1) the "confirm all at" button uses.
data data Partial<BmxReconData> | string — The two sides, matches, rules, sign-off and audit trail (an object, or its JSON). Kept up to date.
dateOrder date-order 'dmy' | 'mdy' | 'ymd' 'dmy' How dates in imported CSV are written when not YYYY-MM-DD.
fileName file-name string 'reconciliation' The name exported files are saved under, without the extension.
label label string — The element's accessible name, and its heading.
locale locale string — The language for numbers and dates (the page's when unset).
pageSize page-size number 100 Rows shown at first in each list; more on request.
readonly readonly boolean false Look but do not change.
strings strings Partial<BmxReconciliationStrings> | string — Replacements for any of the wording (an object, or its JSON).
tab tab BmxReconTab 'work' The view shown.
user user string — Who is working, for the audit trail and sign-off.

Events

EventDetailDescription
bmxChange BmxReconChangeDetail Fired when anything changes: a run, a match, a confirmation, an import, sign-off, rules, undo.
bmxSelectionChange BmxReconSelectionDetail Fired when the items chosen change.

Methods

MethodSignatureDescription
approve approve(note?: string) => Promise<string | null> Approves a submitted reconciliation. Resolves with why it was refused, or null.
confirmMatches confirmMatches(which: string[] | { minConfidence: number; }) => Promise<number> Confirms proposed matches: those named, or every one at or above a confidence.
download download(kind?: "matches" | "unmatched" | "adjustments" | "json") => Promise<void> Saves an export as a file.
exportCsv exportCsv(kind?: "matches" | "proposed" | "unmatched" | "adjustments") => Promise<string> Matches, open items or adjustments as CSV.
getData getData() => Promise<BmxReconData> The reconciliation as it is now.
getSelected getSelected() => Promise<BmxReconSelectionDetail> The items chosen and their difference.
getSummary getSummary() => Promise<BmxReconSummary> The figures.
importCsv importCsv(side: BmxReconSide, text: string, options?: { dateOrder?: "dmy" | "mdy" | "ymd"; }) => Promise<BmxReconImportResult> Adds items to a side from a CSV export.
matchItems matchItems(left: string[], right: string[], adjustment?: BmxReconAdjustment, note?: string) => Promise<BmxReconRefusal | null> Matches items by hand. Resolves with why it was refused, or null.
redo redo() => Promise<void> Puts back what undo took.
reopen reopen(note?: string) => Promise<string | null> Sends a submitted reconciliation back, or reopens an approved one.
runRules runRules(ruleIds?: string[]) => Promise<{ proposed: number; confirmed: number; }> Runs the matching rules (all enabled ones, or those named).
setData setData(data: Partial<BmxReconData> | string) => Promise<void> Replaces the reconciliation. Undo goes back to what was there.
setSelection setSelection(left: string[], right: string[]) => Promise<void> Chooses items, as if ticked.
submitForApproval submitForApproval() => Promise<string | null> Sends the reconciliation for approval. Resolves with why it was refused, or null.
suggest suggest(side: BmxReconSide, itemId: string, limit?: number) => Promise<BmxReconSuggestion[]> What an item might go with on the other side.
undo undo() => Promise<void> Takes back the last change.
unmatch unmatch(matchId: string) => Promise<void> Rejects a proposed match, or undoes a confirmed one.

CSS shadow parts

PartDescription
header The title, status, figures and actions.
list Each side's table of items.
review The matches waiting for review.
selection The bar for the items chosen.
tabs The tab list.

CSS custom properties

PropertyDescription
--bmx-reconciliation-list-height The most a side's table, or a list of matches, grows before it scrolls.
--bmx-reconciliation-negative The colour of amounts below zero.
--bmx-reconciliation-radius Corner radius of the frame.