Repository navigation
Books: monthly journal export and yearly tax report - #18
Merged
Merged
Conversation
The foundation for keeping a founder's books from synced data (docs/BOOKS.md): workspace incorporation date, country and region; who pays each connection and manual cost; one-time and equipment costs; base-currency gross, fees and refunds and the service period on revenue lines; payouts; and closed months. src/lib/books.ts turns a month of lines into balanced journal entries: payments and fees against each provider's balance, revenue earned over its service period, usage against the provider balance, and the payer as owner contributions, owed to founder or the company bank. src/server/books.server.ts loads a month and exports it. Exporting a whole month closes it: it exports the same way again, and later changes go out as adjustments in the next export. A Books page with stub sections holds the export, the tax report and the settings.
Conversions into CAD use the Bank of Canada daily rate, which the CRA names in Income Tax Folio S5-F4-C1 ¶1.4. A date without a quote (weekend, holiday) takes the closest preceding day. One Valet request covers a year on each side of the date, so a two-year sync makes a few requests. Currencies the Bank doesn't publish (DKK, CZK and others) still convert through ECB, and so do conversions out of CAD. Migration 0011 deletes the cached ECB rates into CAD. Stored lines keep their base amounts until the next full sync of their connection, or until the workspace changes its base currency.
The sync writes gross, fees and refunds in base currency at each line's rate, and the period a payment covers where the provider reports one: Stripe invoice line periods and Paddle's billing_period. Lemon Squeezy's subscription invoices carry no period, so its lines stay earned on their date. Polar revenue comes from orders and refunds instead of daily metrics. Each paid order is one line: the price after discounts and before tax, Polar's fee and the tax, with one subscription interval from the order's date as its period. Each refund is a line dated when it happened. Orders are reread 21 days back, as long as Polar retries a failed renewal. Tokens without orders:read or refunds:read keep the daily metrics lines. A connection that still has daily lines in its history runs a full sync, which replaces them. Stripe payouts come from payout balance transactions with the payout expanded, so keys need no new permission. Each is stored on its arrival date and deleted once it fails or is canceled. Polar's payouts endpoint accepts only signed-in users, not organization tokens, so Polar reports none. The Polar docs list the two new scopes.
Workspace creation has an "incorporated company" checkbox that sets incorporated_on to the browser's date. The Books page holds the settings: the incorporation switch and date, the country (Canada, United States, Other) with its province or state, and, for incorporated workspaces, who pays each cost connection and manual cost and since when. Cost connection pages show the same picker. The cost form adds One time, a category and, for incorporated workspaces, Paid with. A one-time cost lists with its date and no per-month suffix. MCP: the flat cost tools take and return category, paid_with and paid_with_since. get_books_settings and update_books_settings read and change incorporated_on, country and region. Adds the shadcn Checkbox, tuned to the tokens.
The Books page's Export dialog downloads one month as the lines CSV, a QuickBooks Online journal entries import, a Xero manual journal import or a plain journal. The product defaults to the switcher's for CSV and to All for journals. A one-product journal says it leaves out shared and unassigned costs. Before a whole month's first export, the dialog says that exporting closes the month and later changes come as adjustments. QuickBooks and Xero files leave out entries that move money through the company bank account, since the bank feed brings those in. QuickBooks gets one journal number per entry and products as classes. Xero gets the template's headers, signed amounts and products as a tracking category. For those two formats, users can rename accounts (Xero takes codes) and set Xero's tracking category and tax rate. Both are saved to workspaces.book_accounts. GET /api/journal.csv takes the same session or API token as /api/export.csv, which now also accepts ?product=. The MCP export_journal tool returns the same file. Dialogs scroll when taller than the window. scripts/check-books-export.ts checks that each journal balances, the bank entries stay out of QuickBooks and Xero, products land in the class and tracking columns, and renamed accounts appear.
# Conflicts: # src/server/mcp.server.ts
Stripe's payout_minimum_balance_hold and _release balance transactions counted as revenue; they are money held back from a payout and released later. The CSV export dated a one-time cost on the 1st of its month instead of its own date. A new one-time cost now defaults to today instead of the 1st.
The Books page shows the year's numbers on the lines of the form the workspace files: T2125 (plus TP-80-V in Quebec) for a Canadian sole proprietor, GIFI schedules 125 and 100 for a Canadian company, Schedule C for a US sole proprietor, Form 1120 for a US company (with Schedule L from $250,000), and a plain profit and loss elsewhere. A workspace that incorporated during the year gets two parts, split at the incorporation date. src/lib/tax.ts holds the rules, with the official source next to each table. In Canada, equipment is class 50: 100% the first year for computers acquired after April 15, 2024 and in use before 2027 (enacted by Bill C-15), 55% declining balance after. In the US, items up to $2,500 fall under the de minimis safe harbor and larger ones get the 100% special allowance for property acquired after January 19, 2025. December's journal now books the year's depreciation. The report marks lines that no official guidance names, writes the de minimis election statement, and links each provider's invoice page. docs/BOOKS.md now matches the verified lines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenProfit can now keep a solo founder's books from the data it already syncs. The Books page exports a month as a journal for QuickBooks, Xero or any accounting software, and lists the year's numbers on the lines of the founder's tax form. The spec is in
docs/BOOKS.md; the user docs are at/docs/books.What's in it
src/lib/books.ts): balanced entries per month. Payments and fees go against each provider's balance, revenue is earned over its service period, usage is an expense against the provider's balance, and the payer is owner contributions, owed to founder or the company bank depending on incorporation and card.src/server/books.server.ts): a whole-month export closes the month. It exports the same way again, and later provider changes go out as an adjustment in the next export./api/journal.csv, an export dialog with account renaming, and anexport_journalMCP tool. The QuickBooks and Xero files leave out money moving through the company bank account, which the bank feed records.src/lib/tax.ts.Checked
scripts/check-books.ts(journal rules),check-books-close.ts(closing and adjustments),check-books-export.ts(each file balances per journal, bank entries are left out, classes and renames), andcheck-books-tax.ts(Canada, Quebec, companies, the US, a mid-year incorporation, depreciation). The last three run on a scratch database:DATABASE_URL=/tmp/x SYNC_SCHEDULER=off npx tsx scripts/<name>.ts.pnpm check,tsc --noEmitandpnpm buildpass. The production build served/docs/booksand the changelog, and/api/journal.csvrefuses requests without a session.Before merging