SlowBooks Pro v2.31.4 · saved from https://www.slowbookspro.com/docs/data-model/, where the guide for the latest version always is.

Data model

Schema reference for a SlowBooks Pro company database: one SQLite file per company on the desktop and Server Edition, or a PostgreSQL database. 105 tables on a double-entry accounting foundation (Alembic head 95209e8d293c in 2.31.4: bank_rules.class_id and bank_rules.job_id, after 4810bd59e481). For migration history, see the files under migrations/versions/; for model code, see app/models/. The construction and job-cost tables are described in their own sections below the table.

TablePurpose
accountsChart of Accounts — asset, liability, equity, income, expense, COGS; parent_id for sub-accounts, detail_type (one of 15, drives the statements' grouping)
customersCustomer contacts with billing/shipping addresses; parent_customer_id and bill_with_parent for sub-customers (2.31.1)
vendorsVendor contacts
itemsProduct/service/material/labor items with rates
invoicesInvoice headers with status tracking
invoice_linesInvoice line items
estimatesEstimate headers
estimate_linesEstimate line items
paymentsPayment records
payment_allocationsMaps payments to invoices (many-to-many)
transactionsJournal entry headers: date, reference, description, memo, source_type/source_id (the document that posted it), class_id, job_id, vendor_id, is_adjusting, posted_by; from an imported ledger (2.31.2) source_ref (QuickBooks' Num), source_kind (its Type) and source_difference (what posted to 3950 Import Differences)
transaction_linesJournal entry splits (debit OR credit); cleared / reconciliation_id on bank-account lines
accounts.bank_kindbank / credit_card — which chart accounts the register, pickers and transfers use
bank_accountsA bank feed: the statement identity of a ledger account (account_id); legacy_balance = the pre-2.10 register balance until posted or dismissed
bank_transactionsStatement lines (feeds, OFX/CSV imports) with match_status and the ledger line they match (transaction_line_id)
reconciliationsReconciliation sessions over a ledger account's lines (account_id, beginning_balance, cleared_total)
settingsCompany settings key-value store
audit_logAutomatic change tracking for all entities
purchase_ordersPurchase order headers
purchase_order_linesPO line items with received quantities
billsVendor bills (AP mirror of invoices)
bill_linesBill line items with expense account tracking
bill_paymentsBill payment records
bill_payment_allocationsMaps bill payments to bills
credit_memosCustomer credit memos
credit_memo_linesCredit memo line items
vendor_creditsSupplier credits against Accounts Payable
vendor_credit_linesVendor credit line items (carry the expense account, like bill lines)
vendor_credit_applicationsWhich bill a vendor credit settled, and for how much
credit_applicationsMaps credit memos to invoices
recurring_invoicesRecurring invoice templates
recurring_invoice_linesRecurring invoice line items
email_logEmail delivery history
tax_category_mappingsAccount-to-tax-line mappings; one string per account, read against the return the entity type files (Schedule C, Form 1065, 1120 or 1120-S) (#488)
backupsBackup file records
companiesMulti-company database list
employeesEmployee records for payroll
pay_runsPay run headers with totals
pay_stubsIndividual pay stubs with withholding breakdowns
qbo_mappingsWhich local record a QuickBooks Online record became, written by the connector retired in 2.23.2; read so those documents still void and edit right
attachmentsFile attachments linked to invoices, bills, etc.
bank_rulesPattern-matching rules for auto-categorizing bank imports
budgetsBudget amounts by account and period
email_templatesCustomizable email templates
inventory_movementsPer-item qty/cost ledger (purchases, sales, adjustments)
saved_reportsNamed (report_type + parameters) tuples
document_auditsA SHA-256 hash per generated tax-form PDF (W-2/W-3/940/941), printed in the form's footer
portal_accessesAudit log for self-service portal hits (success + failure)
login_attemptsAuthentication-attempt audit log
reseller_permitsPer-entity sales-tax reseller permits with expiration + verification trail
users, user_preferencesNamed sign-ins with a role (administrator, bookkeeper, accountant, read-only), end date, one-time-password flag; per-user preferences
api_tokensScoped API tokens: label, role (never administrator), allow_backups, hash and hint, revoked flag
request_keysIdempotency keys seen on write requests
classesClasses (funds in a nonprofit) with the Uncategorized default
invoice_messagesSaved messages for emailed invoices
memorized_transactions, memorized_transaction_linesMemorized bills and journal entries with their schedule (2.31.1)
duplicate_dismissalsPossible Duplicates groups marked Not a duplicate
stored_filesFiles kept inside the company file: the logo, attachments, employee documents (since 2.18)
ocr_templatesPer-merchant receipt layouts remembered by Scan Receipt
accountants_copiesAccountant's copies sent and applied, with the dividing date
fixed_assets, fixed_asset_typesFixed assets, their type, depreciation method and runs
employee_bank_accountsDirect-deposit accounts (routing and account numbers encrypted)
benefit_codes, benefit_rates, employee_benefits, employee_groups, employee_group_benefits, benefit_ytd, pay_stub_benefitsThe benefits engine: codes with rules and effective-dated rates, enrolments by employee or group, year-to-date and per-stub amounts
garnishment_ordersGarnishment orders and their ends
onboarding_tasksAn employee's onboarding checklist
time_entriesTime entries with approval and job-cost posting
pto_policies, pto_requests, pto_accrualsTime off
payroll_opening_balances, payroll_opening_quarters, payroll_opening_state_balances, payroll_opening_benefitsOpening year-to-date payroll from an earlier system, quarter by quarter
allocation_rules, allocation_rule_targets, functional_allocations, functional_allocation_linesNonprofit functional-expense allocations
in_kind_gifts, in_kind_gift_linesIn-kind gifts
restriction_releasesReleases from restriction between net-asset classes
jobs, job_budgets, job_costs, job_cost_lines, job_cost_to_complete, job_deposits, cost_codes, cost_types, equipment, trade_packages, trade_package_sections, sov_lines, change_orders, change_order_lines, pay_applications, pay_application_lines, lien_waiversJobs, job costing and construction mode: described section by section below

jobs (Customer:Job / Projects)

columnnotes
id, customer_id (FK customers)a job belongs to exactly one customer
name, job_numbername unique per customer, case-insensitive (API-enforced)
statuspending · awarded · in_progress · closed · not_awarded
job_type, description, site_address, notesfree text
start_date, projected_end_date, end_date
contract_amountNumeric(15,2), drives billed-vs-contract
is_activeinactive = hidden from pickers, kept on history

job_id (nullable FK) lives on: transactions, transaction_lines, invoices, invoice_lines, bills, bill_lines, estimates, estimate_lines, purchase_orders, purchase_order_lines, credit_memos, credit_memo_lines, vendor_credits, vendor_credit_lines, recurring_invoices, memorized_transactions and their lines, in_kind_gifts and their lines, allocation_rule_targets, time_entries. class_id rides transactions and every document header and line that posts (transaction_lines, invoice_lines, bill_lines, credit_memo_lines, the estimate, purchase-order, vendor-credit, memorized and in-kind lines); customer_id rides transaction_lines too, for a deposit split among several payers (#606). Attribution rule for reports: coalesce(line.job_id, transaction.job_id). Migration e9f0a1b2c3d4_add_jobs.

cost_codes (job-costing chart)

columnnotes
id, code (unique, case-insensitive via API; normalized to the DD SS SS division/section shape), namee.g. 03 Concrete, 03 30 00 Cast-in-Place Concrete
leveldivision · section · null (a custom, non-standard code)
cost_typeper-code default: labor · material · subcontract · equipment · other
account_id (FK accounts, nullable)default posting account
parent_id (self FK)a section's division; set automatically
notes, is_activeinactive = hidden from pickers

cost_code_id (nullable FK) on transaction_lines, bill_lines, invoice_lines, estimate_lines, purchase_order_lines, vendor_credit_lines, time_entries. A nullable cost_type (String(20)) rides every costable line — transaction_lines, bill_lines, estimate_lines, purchase_order_lines, vendor_credit_lines — chosen per line, falling back to the code's default when blank. is_billable + billed_invoice_line_id (FK invoice_lines) on transaction_lines and bill_lines. purchase_orders.job_id / purchase_order_lines.job_id feed committed cost. Migrations f0a1b2c3d4e5_add_cost_codes, c8e4a2f1b3d5_division_section_cost_codes (#550: level, the four line cost_type columns, trade packages).

Trade packages (#550)

tablepurpose
trade_packagesid, name (unique), notes, is_active, sort_order, timestamps — the bid packages a GC awards
trade_package_sections(package_id, cost_code_id) composite PK — a package ↔ cost-code section, many-to-many

A package is what a sub bids; a section is what costs post to. Seeded by POST /api/construction/setup when the company has none.

Construction estimating (#551)

estimate_lines gains the bid shape: trade_package_id (nullable FK trade_packages, SET NULL — the bid package the line belongs to; null groups under "Unassigned"), line_kind (String(12), NOT NULL default base — one of base · allowance · alternate · unit_price), unit (String(20), nullable — SF, LF, EA, CY…) and alternate_accepted (Boolean, NOT NULL default false). estimates gains the four compounding markups gc_pct, insurance_pct, fee_pct, bond_pct (Numeric(7,4), NOT NULL default 0 — percent values, 5.5 = 5.5%), markup_allowances (Boolean, NOT NULL default false — the markups also build on allowance lines when true) and accepted_at (DateTime, nullable). job_budgets.locked (Boolean, NOT NULL default false) marks the rows an accepted estimate owns — only a change order moves them. invoice_lines gains cost_type (String(20)) and unit_cost (Numeric(12,4)), both nullable, so a converted estimate carries its cost basis onto the invoice. The bid math (lines at cost; the markup base, the four markups compounding in order, and their total; the subtotal of the included lines; the pre-tax bid total as subtotal + markups, with tax covering the lines and markups together; priced-direct and allowance totals; the package, alternate and unit-price breakdowns) is computed, not stored — app/services/estimating.py. Migration d9f6b2c4e7a1_construction_estimating (additive and nullable/defaulted; downgrade reverses).

The contract on the job (#552)

jobs gains the contract terms, all nullable (a job with no contract_type behaves exactly as before): contract_type (String(12) — lump_sum · gmp · cost_plus), original_contract_amount (Numeric(15,2) — the sum when the contract was first set, stamped once; contract_amount stays the current / revised sum so #554 shows original + changes = revised), fee_pct (Numeric(7,4)) and fee_amount (Numeric(15,2) — a fixed fee, which wins when both are set), cap_amount (Numeric(15,2) — the GMP maximum or a cost-plus not-to-exceed), contingency_amount (Numeric(15,2) — owner contingency inside the sum), retainage_pct (Numeric(5,2) — held per pay application, #555), contract_date (Date) and contract_estimate_id (FK estimates, SET NULL).

New table sov_lines — the owner's schedule of values:

columnnotes
id, job_id (FK jobs, CASCADE)the job this schedule belongs to
line_no (Integer), item_no (String(10))1-based order; printed number ("01"…), editable
description (String(200))
trade_package_id (FK, SET NULL), cost_code_id (FK, SET NULL)optional links
kind (String(16), NOT NULL default work)work · allowance · contingency · general_conditions · insurance · fee · bond
scheduled_value (Numeric(15,2), NOT NULL default 0)
source_estimate_id (FK estimates, SET NULL)set when built from an estimate

Unique (job_id, line_no). A scheduled_value may be negative (a deduct alternate nets the sum down). A line's billed / stored / retainage figures are not stored on it — a pay application bills it (#555, below), carrying those columns on pay_application_lines and reading previous from history. The contract sum and the schedule are pre-tax (tax is added per invoice). The contract summary (changes, fee, cap remaining, contingency remaining, SOV total and balance, billed to date) is computed, not stored — app/services/contracts.py. Migration e2a4c6d8f0b1_construction_contract (additive and nullable/defaulted; downgrade reverses).

Buyout and commitments (#553)

A commitment is a purchase order — no new table. Additive columns:

committed_cost is now Σ (amount − billed_amount) over non-draft, non-closed commitment lines (the remaining open value); commitment_figures adds committed_total (Σ amount of non-draft commitments) and billed_to_date (Σ billed) — app/services/jobs_service.py. Those two are whole-commitment figures, so in a date-ranged job_cost_tree they are all-time; the totals carry commitments_all_time: true to say so. Retainage is computed on the pre-tax subtotal (tax is payable in full); the shared vendor_compliance helper (app/services/compliance.py) feeds both the bills responses and the buyout rows. The buyout table (bid vs bought per package) is computed in app/services/buyout.py; vendor_compliance (Sub W-9 / COI flags) is computed from the vendor. Migration f3b5d7e9a2c4_construction_buyout (additive; backfills a closed PO's billed lines from its bill; downgrade reverses). Retainage release (a debit back to 2050) is a release bill — see Progress billing and retainage (#555).

Change orders (#554)

How a locked budget, its contract and its commitments move. Two new tables:

Additive columns: jobs.co_markup_pct Numeric(7,4) (the default markup on owner change orders), and sov_lines.change_order_id (FK change_orders.id, SET NULL — the owner CO that owns a change_order-kind SOV line; change_order is added to SOV_KINDS). The math and the apply/reverse effects live in app/services/change_orders.py; the append-only commitment helper is in app/routes/purchase_orders.py. On approve an owner CO writes job_budgets rows with source = change, locked = True (the one path that may move a locked budget), which the job-cost tree reads into its Changes column. Migrations a7c9e1f3b5d6_construction_change_orders and b8d0f2a4c6e8_change_order_revisions_markup_flag (additive; downgrade reverses). A billed change_order SOV line can no longer be voided (409 co_billed, filled by #555); the change-order log report is #556.

Progress billing and retainage (#555)

The owner's progress bill from the schedule of values. Two new tables:

New table job_deposits — owner deposits: job_id (FK, CASCADE), date, amount, deposit_to_account_id (FK accounts — the bank), memo, transaction_id (FK), voided_at. A deposit posts DR bank / CR Customer Deposits (2210, source_type job_deposit); a pay application's deposit_applied draws it down with a payment to 2210 on approve.

Additive columns: invoices.retainage_held and invoices.retainage_released (Numeric(15,2) NOT NULL default 0) and invoices.pay_application_id (FK pay_applications.id, SET NULL); bills.retainage_released and purchase_orders.retainage_released (Numeric(15,2) NOT NULL default 0). The pure math and the history helpers live in app/services/pay_applications.py; approve builds the invoice and its retainage journal (A/R net, Retainage Receivable 1150 held / released, income, tax) in app/routes/pay_applications.py, with the sub side (schedule and the 2050 release bill) in app/routes/purchase_orders.py and the deposits in app/routes/jobs.py. Migration e5c9a1f7b2d4_construction_progress_billing (additive and nullable/defaulted; downgrade reverses). The WIP schedule, cost-to-complete and the other job reports are #556.

Job cost model (milestone 3)

tablepurpose
cost_typescode (key), name, is_labor, burden_pct, burden_method (flat), default_account_id, offset_account_id, burden_offset_account_id, sort_order, is_active — the five starting types (labor/material/subcontract/equipment/other) are saved at the company's first write that needs them, never by a read (2.31.2)
cost_codes.parent_idhierarchy (self FK)
equipmentcode, name, hourly_rate, cost_code_id, recovery_account_id
job_costsnumber (JC-000001), date, job_id (NULL for allocations), memo, source manual/time_entry/allocation, status posted/void, transaction_id, total
job_cost_linesjob_id, cost_code_id, cost_type, qty/rate/amount, debit_account_id, credit_account_id, employee_id, equipment_id, time_entry_id, is_burden, is_billable
job_budgetsjob_id × (cost_code_id and/or cost_type, or neither), amount, revenue_amount, source manual/estimate/change, estimate_id — the unique key is (job, code, type, source) (#554), so a change row coexists with an estimate/manual row on the same code and type
job_cost_to_completejob_id (CASCADE), cost_code_id (the section; NULL = Unassigned), cost_type ('' for Unassigned), amount — the typed cost to complete the WIP schedule reads (#556); unique (job, code, type), migration c6e8f0a2d4b6
employees.cost_rate, burden_pct · estimate_lines.unit_cost · time_entries.job_cost_id · transaction_lines.cost_type

Migration a1b2c3d4e5f6_job_cost_model.