diff --git a/SKILL.md b/SKILL.md index b72faac..8a29816 100644 --- a/SKILL.md +++ b/SKILL.md @@ -13,6 +13,7 @@ Double-entry accounting for sole proprietorship. Every transaction creates balan |------|--------| | Record vendor invoice | Use template from `templates/`, then audit | | Record payment | Use template from `templates/`, then audit | +| Reconcile bank account | See [reconciliation.md](references/reconciliation.md) | | Query balances | Use `sql_query` on Accounts table | | Generate reports | See [queries.md](references/queries.md) | @@ -28,6 +29,8 @@ Double-entry accounting for sole proprietorship. Every transaction creates balan | Invoice not yet paid | [bill-unpaid.json](templates/bill-unpaid.json) | | Pay existing bill | [pay-existing-bill.json](templates/pay-existing-bill.json) | | Direct expense (no bill) | [direct-expense.json](templates/direct-expense.json) | +| Bank import - deposit | [bank-import-deposit.json](templates/bank-import-deposit.json) | +| Bank import - withdrawal | [bank-import-expense.json](templates/bank-import-expense.json) | Templates contain only writable fields. See [templates.md](references/templates.md) for usage guide. @@ -38,6 +41,7 @@ Templates contain only writable fields. See [templates.md](references/templates. | **Invoice/Bill from vendor** | Bill + BillLines + Transaction + TransactionLines | | **Receipt showing payment** | BillPayment + attach Receipt to existing Bill | | **Bank statement entry** | Transaction + TransactionLines only | +| **Bank export file** | Run bank reconciliation workflow (see below) | | **Journal adjustment** | Transaction + TransactionLines only | **Key Rule:** If there's a vendor invoice number, always create a Bill record. @@ -114,6 +118,18 @@ Same as above but Cr Due to Owner (id=22) instead of Checking For detailed code: see [workflows.md](references/workflows.md) +## Bank Reconciliation Workflow (4 Phases) + +Import bank transactions, match against ledger, create missing entries, and verify balances. + +**Phase 1 — Import:** Read bank export file (Schwab JSON), parse dates/amounts, present summary +**Phase 2 — Match:** Fetch TransactionLines for bank account via `get_records`, match by amount + date (±3 days) +**Phase 3 — Create:** For unmatched bank entries, check BankRules, suggest offset account by Type, create Transaction + TransactionLines (Status="Cleared") +**Phase 4 — Reconcile:** Create Reconciliation record, calculate cleared balance, compare to statement balance + +For detailed reference: see [reconciliation.md](references/reconciliation.md) +For workflow code examples: see [workflows.md](references/workflows.md) + ## Validation Checklist After entering bills: @@ -195,3 +211,4 @@ For full audit queries and remediation: see [audit.md](references/audit.md) | [references/workflows.md](references/workflows.md) | Detailed code examples | | [references/queries.md](references/queries.md) | SQL queries and financial reports | | [references/audit.md](references/audit.md) | Audit queries and remediation | +| [references/reconciliation.md](references/reconciliation.md) | Bank reconciliation workflow | diff --git a/references/queries.md b/references/queries.md index 58ff36c..61e77bd 100644 --- a/references/queries.md +++ b/references/queries.md @@ -5,10 +5,7 @@ Common queries and financial report templates. ## Contents - [Common Queries](#common-queries) - [Financial Reports](#financial-reports) - - [Balance Sheet](#balance-sheet) - - [Income Statement](#income-statement) - - [Trial Balance](#trial-balance) - - [AP Aging](#accounts-payable-aging) +- [Reconciliation Queries](#reconciliation-queries) ## Common Queries @@ -179,3 +176,54 @@ JOIN Vendors v ON b.Vendor = v.id WHERE b.Status IN ('Open', 'Partial') ORDER BY b.DueDate ``` + +## Reconciliation Queries + +### Cleared Balance for Bank Account + +Use `get_records` to avoid the `Transaction` reserved word issue: + +```python +# Step 1: Get all TransactionLines for Checking (id=14) +lines = get_records("TransactionLines", filter={"Account": [14]}) + +# Step 2: Get cleared transaction IDs +txn_ids = list(set(line["Transaction"] for line in lines)) +cleared = sql_query(f"SELECT id FROM Transactions WHERE Status = 'Cleared' AND id IN (...)") +cleared_ids = set(row["id"] for row in cleared) + +# Step 3: Sum Debit - Credit for cleared lines +cleared_balance = sum( + line["Debit"] - line["Credit"] + for line in lines + if line["Transaction"] in cleared_ids +) +``` + +### Outstanding Items (Not Cleared by Bank) + +```sql +SELECT t.id, t.Date, t.Description, t.Status, + tl.Debit, tl.Credit +FROM TransactionLines tl +JOIN Transactions t ON tl.Transaction = t.id +WHERE tl.Account = 14 + AND t.Status != 'Cleared' +ORDER BY t.Date +``` + +### Reconciliation History + +```sql +SELECT id, StatementDate, StatementBalance, + ClearedBalance, Difference, Status +FROM Reconciliations +WHERE Account = 14 +ORDER BY StatementDate DESC +``` + +### Active Bank Rules + +```python +get_records("BankRules", filter={"Account": [14], "IsActive": [true]}) +``` diff --git a/references/reconciliation.md b/references/reconciliation.md new file mode 100644 index 0000000..b731f92 --- /dev/null +++ b/references/reconciliation.md @@ -0,0 +1,242 @@ +# Bank Reconciliation + +Import bank transactions, match against ledger entries, create missing transactions, and reconcile balances. + +## Contents +- [Bank File Parsing](#bank-file-parsing) +- [Transaction Matching](#transaction-matching) +- [Creating Missing Transactions](#creating-missing-transactions) +- [Balance Reconciliation](#balance-reconciliation) +- [Queries](#queries) +- [Edge Cases](#edge-cases) + +## Bank File Parsing + +### Schwab JSON Format + +Schwab exports a JSON file with this structure: + +```json +{ + "FromDate": "MM/DD/YYYY", + "ToDate": "MM/DD/YYYY", + "PendingTransactions": [], + "PostedTransactions": [ + { + "CheckNumber": null, + "Description": "Interest Paid", + "Date": "MM/DD/YYYY", + "RunningBalance": "$1,500.01", + "Withdrawal": "", + "Deposit": "$0.01", + "Type": "INTADJUST" + } + ] +} +``` + +### Parsing Rules + +1. **Date**: Convert `MM/DD/YYYY` → Unix timestamp. Example: `01/14/2026` → `1736812800` +2. **Amount**: Parse `$` strings, remove commas. `Deposit` = positive, `Withdrawal` = negative. Empty string = no amount. +3. **Sort**: By date ascending after parsing +4. **Zero-amount entries**: Flag for user review (e.g., `INTADJUST` with no Deposit/Withdrawal) +5. **Statement ending balance**: Extract from last entry's `RunningBalance` (chronologically latest) + +### Date Conversion Reference + +Use Jan 1, 2026 = 1767312000 as base, add `days * 86400`. + +| Date String | Unix Timestamp | Calculation | +|-------------|---------------|-------------| +| 12/31/2025 | 1767225600 | Jan 1 - 1 day | +| 01/14/2026 | 1768435200 | Jan 1 + 13 days | +| 01/30/2026 | 1769817600 | Jan 1 + 29 days | +| 02/17/2026 | 1771372800 | Jan 1 + 47 days | + +### Type Field Mapping + +| Bank Type | Suggested Offset Account | Notes | +|-----------|-------------------------|-------| +| `INTADJUST` | Interest Income (4010, id=26) | Bank interest payments | +| `TRANSFER` | Owner's Investment (3001, id=23) | Incoming transfers — confirm with user | +| `ATM` | Owner's Draws (3002, id=24) | ATM withdrawals | +| `CHECK` | Prompt user | Check payments | +| `ACH` | Prompt user | ACH debits | +| `DEBIT` | Prompt user | Debit card transactions | + +## Transaction Matching + +### Algorithm + +For each bank transaction, find a matching ledger entry: + +1. Fetch all TransactionLines for the bank account using `get_records` (not `sql_query` — `Transaction` is a reserved word) +2. For each bank transaction with an amount: + - Find ledger entries where: **exact amount match** AND **date within ±3 days** AND **not already matched** + - Deposits match TransactionLines where `Debit > 0` (money into checking = debit to asset) + - Withdrawals match TransactionLines where `Credit > 0` (money out of checking = credit to asset) +3. Mark each bank entry: **Matched**, **Unmatched**, or **Skipped** (no amount) +4. Mark each ledger entry: **Matched** or **Outstanding** + +### Matching with get_records + +```python +# Fetch all TransactionLines for Checking Account (id=14) +get_records("TransactionLines", filter={"Account": [14]}) + +# Then fetch transaction details for date comparison +# Get unique transaction IDs from the lines, then: +sql_query("SELECT id, Date, Description, Status FROM Transactions WHERE id IN (id1, id2, ...)") +``` + +### Match Classification + +| Bank Entry | Ledger Entry | Classification | +|------------|-------------|----------------| +| Has match | Has match | **Matched** — both confirmed | +| No match | — | **Unmatched (bank only)** — needs new ledger entry | +| No amount | — | **Skipped** — review manually | +| — | No match | **Outstanding** — in ledger but not cleared by bank | + +## Creating Missing Transactions + +For each unmatched bank transaction: + +### 1. Check BankRules + +```python +get_records("BankRules", filter={"Account": [14], "IsActive": [true]}) +``` + +For each rule, check if the bank description matches the pattern: +- **Contains**: pattern is a substring of bank description +- **Starts With**: bank description starts with pattern +- **Exact**: bank description equals pattern + +If a rule matches, auto-fill the offset account and description. + +### 2. Determine Offset Account + +Use the Type field mapping above as a suggestion. Always confirm with user for ambiguous types. + +### 3. Create Transaction + +Use the bank-import templates: +- **Deposits** → `bank-import-deposit.json` (Dr Checking, Cr Offset) +- **Withdrawals** → `bank-import-expense.json` (Dr Offset, Cr Checking) + +All imported transactions use `Status = "Cleared"` since the bank confirms them. + +### 4. Optionally Save BankRule + +If user agrees, create a BankRule for future auto-categorization: + +```python +add_records("BankRules", [{ + "Account": 14, + "Pattern": "Interest Paid", + "MatchType": "Contains", + "OffsetAccount": 26, + "TransactionDescription": "Bank interest income", + "IsActive": true +}]) +``` + +## Balance Reconciliation + +### Calculate Cleared Balance + +After matching and creating missing transactions: + +```python +# Get all TransactionLines for Checking Account +lines = get_records("TransactionLines", filter={"Account": [14]}) + +# Get transaction IDs and filter for Cleared status +txn_ids = [unique transaction IDs from lines] +cleared_txns = sql_query("SELECT id FROM Transactions WHERE Status = 'Cleared' AND id IN (...)") + +# Sum: Debit - Credit for cleared lines only +cleared_balance = sum(line.Debit - line.Credit for line in lines if line.Transaction in cleared_txn_ids) +``` + +### Reconciliation Record + +```python +# Create reconciliation record +add_records("Reconciliations", [{ + "Account": 14, + "StatementDate": statement_date_timestamp, + "StatementBalance": statement_balance, + "ClearedBalance": cleared_balance, + "Difference": statement_balance - cleared_balance, + "Status": "In Progress", + "StartedAt": today_timestamp, + "Notes": "Reconciling against Schwab export" +}]) +``` + +### Finalization + +- **Difference = $0**: Update Status to "Completed", set CompletedAt +- **Difference ≠ $0**: Report discrepancy, list outstanding items, offer options: + 1. Review unmatched items + 2. Create adjusting entry + 3. Save progress and return later + +## Queries + +### Cleared Balance for Account + +```python +# Use get_records to avoid Transaction reserved word issue +lines = get_records("TransactionLines", filter={"Account": [14]}) +# Then filter by cleared transactions and sum Debit - Credit +``` + +### Outstanding Items (in ledger, not cleared) + +```sql +SELECT t.id, t.Date, t.Description, t.Status, + tl.Debit, tl.Credit +FROM TransactionLines tl +JOIN Transactions t ON tl.Transaction = t.id +WHERE tl.Account = 14 + AND t.Status != 'Cleared' +ORDER BY t.Date +``` + +### Reconciliation History + +```sql +SELECT r.id, r.StatementDate, r.StatementBalance, + r.ClearedBalance, r.Difference, r.Status +FROM Reconciliations r +WHERE r.Account = 14 +ORDER BY r.StatementDate DESC +``` + +### Unmatched Bank Rules + +```python +get_records("BankRules", filter={"Account": [14], "IsActive": [true]}) +``` + +## Edge Cases + +### Zero-Amount Entries + +Some bank entries (e.g., `INTADJUST` with empty Deposit and Withdrawal) have no monetary value. Skip these during matching and creation but report them to the user. + +### Duplicate Amounts + +When multiple bank transactions have the same amount, use date proximity as the tiebreaker. If still ambiguous, present options to the user. + +### Re-imports + +If the same bank file is imported again, the matching phase will find existing ledger entries for previously imported transactions. Only truly new entries will be unmatched. + +### Partial Reconciliation + +If the user can't resolve all differences in one session, save the Reconciliation with Status="In Progress". Resume later by loading the record and continuing from Phase 2. diff --git a/references/schema.md b/references/schema.md index d093b50..50b4b05 100644 --- a/references/schema.md +++ b/references/schema.md @@ -84,6 +84,29 @@ Complete table schemas for the Grist accounting system. | Amount | Numeric | Payment amount | | PaymentDate | Date | Unix timestamp | +## Reconciliations +| Column | Type | Notes | +|--------|------|-------| +| Account | Ref:Accounts | Bank account reconciled | +| StatementDate | Date | Statement ending date (Unix timestamp) | +| StatementBalance | Numeric | Ending balance per bank statement | +| ClearedBalance | Numeric | Sum of cleared transactions in ledger | +| Difference | Numeric | StatementBalance - ClearedBalance | +| Status | Choice | "In Progress", "Completed", "Abandoned" | +| StartedAt | Date | Unix timestamp | +| CompletedAt | Date | Unix timestamp (null until done) | +| Notes | Text | | + +## BankRules +| Column | Type | Notes | +|--------|------|-------| +| Account | Ref:Accounts | Bank account this rule applies to | +| Pattern | Text | Substring to match against bank description | +| MatchType | Choice | "Contains", "Starts With", "Exact" | +| OffsetAccount | Ref:Accounts | Account to categorize to | +| TransactionDescription | Text | Description template for created transactions | +| IsActive | Bool | | + ## Formula Columns (Auto-Calculated) | Table.Column | Description | diff --git a/references/templates.md b/references/templates.md index 3c5c5cb..4db1043 100644 --- a/references/templates.md +++ b/references/templates.md @@ -12,6 +12,8 @@ JSON templates for common accounting scenarios. Templates contain only **writabl | Vendor invoice received but not yet paid | `bill-unpaid.json` | | Recording payment for previously entered bill | `pay-existing-bill.json` | | Bank fees, minor expenses without invoices | `direct-expense.json` | +| Bank import — unmatched deposit | `bank-import-deposit.json` | +| Bank import — unmatched withdrawal | `bank-import-expense.json` | ## Template Structure @@ -185,3 +187,5 @@ get_records("TransactionLines", filter={"Transaction": [52]}) | [bill-unpaid.json](../templates/bill-unpaid.json) | Invoice recorded but not yet paid | | [pay-existing-bill.json](../templates/pay-existing-bill.json) | Payment for previously entered bill | | [direct-expense.json](../templates/direct-expense.json) | Direct expense without vendor bill | +| [bank-import-deposit.json](../templates/bank-import-deposit.json) | Unmatched bank deposit (interest, transfer in) | +| [bank-import-expense.json](../templates/bank-import-expense.json) | Unmatched bank withdrawal (ATM, check, ACH) | diff --git a/references/workflows.md b/references/workflows.md index 137b9df..941cb66 100644 --- a/references/workflows.md +++ b/references/workflows.md @@ -12,6 +12,7 @@ Detailed code examples for common accounting operations. - [Pay Bill via Owner](#pay-bill-via-owner-reimbursement) - [Reimburse Owner](#reimburse-owner) - [Batch Operations](#batch-operations) +- [Bank Reconciliation](#bank-reconciliation) ## Create a Vendor @@ -298,5 +299,124 @@ For bank fees, minor expenses without vendor invoices: | 23 | 3001 | Owner's Investment | | 24 | 3002 | Owner's Draws | | 25 | 4001 | Service Revenue | +| 26 | 4010 | Interest Income | | 30 | 5020 | Bank & Merchant Fees | | 36 | 5080 | Software & Subscriptions | + +## Bank Reconciliation + +Import bank transactions, match against ledger, create missing entries, and reconcile. + +For full reference: see [reconciliation.md](reconciliation.md) + +### Phase 1: Import Bank File (Schwab JSON) + +```python +# Read and parse the bank export file +import json +with open("path/to/schwab_export.json") as f: + data = json.load(f) + +# Parse each PostedTransaction +for txn in data["PostedTransactions"]: + # Date: "MM/DD/YYYY" -> Unix timestamp + # Amount: parse "$X,XXX.XX" strings; Deposit = positive, Withdrawal = negative + # Empty string = no amount (skip) + pass +``` + +### Phase 2: Match Against Ledger + +```python +# Fetch existing TransactionLines for Checking (id=14) +# MUST use get_records, not sql_query (Transaction is reserved word) +get_records("TransactionLines", filter={"Account": [14]}) + +# Get transaction details for date matching +sql_query("SELECT id, Date, Description, Status FROM Transactions WHERE id IN (...)") + +# Match criteria: exact amount AND date ±3 days AND not already matched +# Deposits match Debit > 0 (money into checking = debit to asset) +# Withdrawals match Credit > 0 (money out of checking = credit to asset) +``` + +### Phase 3: Create Missing Transactions + +```python +# For unmatched deposits (e.g., interest income): +add_records("Transactions", [{ + "Date": 1738195200, + "Description": "Bank interest income", + "Reference": "Interest Paid", + "Status": "Cleared", + "Memo": "Auto-imported from bank statement" +}]) +# Returns: {"inserted_ids": [txn_id]} + +# Dr Checking, Cr Interest Income +add_records("TransactionLines", [ + {"Transaction": txn_id, "Account": 14, "Debit": 0.01, "Credit": 0, "Memo": "Bank interest"}, + {"Transaction": txn_id, "Account": 26, "Debit": 0, "Credit": 0.01, "Memo": "Bank interest"} +]) + +# For unmatched withdrawals (e.g., ATM owner draw): +add_records("Transactions", [{ + "Date": 1739750400, + "Description": "ATM withdrawal - owner draw", + "Reference": "P421164 88 ESSEX STREET NEW YORK", + "Status": "Cleared", + "Memo": "Auto-imported from bank statement" +}]) + +# Dr Owner's Draws, Cr Checking +add_records("TransactionLines", [ + {"Transaction": txn_id, "Account": 24, "Debit": 102.00, "Credit": 0, "Memo": "ATM withdrawal"}, + {"Transaction": txn_id, "Account": 14, "Debit": 0, "Credit": 102.00, "Memo": "ATM withdrawal"} +]) + +# Optionally save a BankRule for auto-categorization: +add_records("BankRules", [{ + "Account": 14, + "Pattern": "Interest Paid", + "MatchType": "Contains", + "OffsetAccount": 26, + "TransactionDescription": "Bank interest income", + "IsActive": true +}]) +``` + +### Phase 4: Reconcile + +```python +# Update matched existing transactions to Cleared status +update_records("Transactions", [ + {"id": existing_txn_id, "fields": {"Status": "Cleared"}} +]) + +# Calculate cleared balance +lines = get_records("TransactionLines", filter={"Account": [14]}) +txn_ids = list(set(l["fields"]["Transaction"] for l in lines)) +cleared = sql_query(f"SELECT id FROM Transactions WHERE Status = 'Cleared' AND id IN (...)") +cleared_ids = set(r["id"] for r in cleared) +cleared_balance = sum( + l["fields"]["Debit"] - l["fields"]["Credit"] + for l in lines + if l["fields"]["Transaction"] in cleared_ids +) + +# Create Reconciliation record +add_records("Reconciliations", [{ + "Account": 14, + "StatementDate": 1739750400, # date of last bank entry + "StatementBalance": 1398.01, + "ClearedBalance": cleared_balance, + "Difference": 1398.01 - cleared_balance, + "Status": "Completed", # if Difference == 0 + "StartedAt": 1739923200, # today + "CompletedAt": 1739923200, + "Notes": "Reconciled against Schwab export" +}]) + +# Verify +sql_query("SELECT * FROM Transactions WHERE IsBalanced = false") +``` diff --git a/templates/bank-import-deposit.json b/templates/bank-import-deposit.json new file mode 100644 index 0000000..939d606 --- /dev/null +++ b/templates/bank-import-deposit.json @@ -0,0 +1,86 @@ +{ + "_meta": { + "name": "Bank Import - Deposit", + "description": "Record a deposit found in bank statement but missing from the ledger", + "scenario": "Unmatched bank deposit (interest, incoming transfer, revenue)", + "when_to_use": [ + "Bank interest payments", + "Incoming transfers from owner", + "Revenue deposits", + "Any bank credit not yet in the ledger" + ], + "when_not_to_use": [ + "Deposits already recorded in the ledger", + "Vendor refunds with existing bill records" + ] + }, + "_variables": { + "date_timestamp": "integer - Unix timestamp for transaction date", + "amount": "number - Deposit amount (positive)", + "offset_account_id": "integer - Account to credit (e.g., 23=Owner's Investment, 25=Service Revenue)", + "description": "string - Transaction description", + "reference": "string - Bank reference or description from statement", + "memo": "string - Additional notes (optional)" + }, + "_sequence": [ + "1. Create Transaction header (Status='Cleared') -> get txn_id", + "2. Create TransactionLines (Dr Checking, Cr Offset Account)", + "3. Verify IsBalanced = true" + ], + "records": { + "transaction": { + "_doc": "Step 1: Create transaction header. Status is Cleared since bank confirms it.", + "_table": "Transactions", + "_operation": "add_records", + "payload": { + "Date": "{{date_timestamp}}", + "Description": "{{description}}", + "Reference": "{{reference}}", + "Status": "Cleared", + "Memo": "{{memo}}" + } + }, + "transaction_lines": { + "_doc": "Step 2: Debit Checking (asset increase), Credit offset account.", + "_table": "TransactionLines", + "_operation": "add_records", + "_requires": ["txn_id"], + "payload": [ + { + "Transaction": "{{txn_id}}", + "Account": 14, + "Debit": "{{amount}}", + "Credit": 0, + "Memo": "{{description}}" + }, + { + "Transaction": "{{txn_id}}", + "Account": "{{offset_account_id}}", + "Debit": 0, + "Credit": "{{amount}}", + "Memo": "{{description}}" + } + ] + } + }, + "journal_entries": { + "_doc": "Summary of journal entry created by this template", + "entry": { + "description": "Record bank deposit", + "debits": [{"account": "Checking Account (1001)", "amount": "{{amount}}"}], + "credits": [{"account": "Offset Account", "amount": "{{amount}}"}] + } + }, + "examples": { + "interest_income": { + "description": "Interest Paid", + "offset_account_id": 26, + "offset_account_name": "Interest Income (4010)" + }, + "owner_transfer": { + "description": "Transfer from owner", + "offset_account_id": 23, + "offset_account_name": "Owner's Investment (3001)" + } + } +} diff --git a/templates/bank-import-expense.json b/templates/bank-import-expense.json new file mode 100644 index 0000000..5577f8c --- /dev/null +++ b/templates/bank-import-expense.json @@ -0,0 +1,86 @@ +{ + "_meta": { + "name": "Bank Import - Withdrawal/Expense", + "description": "Record a withdrawal found in bank statement but missing from the ledger", + "scenario": "Unmatched bank withdrawal (ATM, check, ACH debit, owner draw)", + "when_to_use": [ + "ATM withdrawals (owner draws)", + "Check payments", + "ACH debits", + "Any bank debit not yet in the ledger" + ], + "when_not_to_use": [ + "Withdrawals already recorded in the ledger", + "Bill payments with existing bill records - use pay-existing-bill.json" + ] + }, + "_variables": { + "date_timestamp": "integer - Unix timestamp for transaction date", + "amount": "number - Withdrawal amount (positive, will be credited to Checking)", + "offset_account_id": "integer - Account to debit (e.g., 24=Owner's Draws, 30=Bank Fees)", + "description": "string - Transaction description", + "reference": "string - Bank reference or description from statement", + "memo": "string - Additional notes (optional)" + }, + "_sequence": [ + "1. Create Transaction header (Status='Cleared') -> get txn_id", + "2. Create TransactionLines (Dr Offset Account, Cr Checking)", + "3. Verify IsBalanced = true" + ], + "records": { + "transaction": { + "_doc": "Step 1: Create transaction header. Status is Cleared since bank confirms it.", + "_table": "Transactions", + "_operation": "add_records", + "payload": { + "Date": "{{date_timestamp}}", + "Description": "{{description}}", + "Reference": "{{reference}}", + "Status": "Cleared", + "Memo": "{{memo}}" + } + }, + "transaction_lines": { + "_doc": "Step 2: Debit offset account, Credit Checking (asset decrease).", + "_table": "TransactionLines", + "_operation": "add_records", + "_requires": ["txn_id"], + "payload": [ + { + "Transaction": "{{txn_id}}", + "Account": "{{offset_account_id}}", + "Debit": "{{amount}}", + "Credit": 0, + "Memo": "{{description}}" + }, + { + "Transaction": "{{txn_id}}", + "Account": 14, + "Debit": 0, + "Credit": "{{amount}}", + "Memo": "{{description}}" + } + ] + } + }, + "journal_entries": { + "_doc": "Summary of journal entry created by this template", + "entry": { + "description": "Record bank withdrawal", + "debits": [{"account": "Offset Account", "amount": "{{amount}}"}], + "credits": [{"account": "Checking Account (1001)", "amount": "{{amount}}"}] + } + }, + "examples": { + "owner_draw": { + "description": "ATM withdrawal - owner draw", + "offset_account_id": 24, + "offset_account_name": "Owner's Draws (3002)" + }, + "bank_fee": { + "description": "Bank service fee", + "offset_account_id": 30, + "offset_account_name": "Bank & Merchant Fees (5020)" + } + } +}