<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 & 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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
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
| Method | Signature | Description |
|---|---|---|
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
| Part | Description |
|---|---|
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
| Property | Description |
|---|---|
--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. |