Skip to content

chore: upgrade ferro-orm to 0.14 - #15

Merged
0x054 merged 5 commits into
mainfrom
chore/ferro-0-14
Jul 7, 2026
Merged

chore: upgrade ferro-orm to 0.14#15
0x054 merged 5 commits into
mainfrom
chore/ferro-0-14

Conversation

@0x054

@0x054 0x054 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bump ferro-orm from 0.12.x to 0.14.x
  • Pin all datetime model fields with db_type="timestamp" so Ferro 0.14's timestamptz default does not alter existing SQLite storage
  • Codebase was already 0.14-ready (lambda predicates, session routing); no service-layer changes required

Test plan

  • just check
  • pytest tests/test_storage tests/test_invoicing tests/test_interchange tests/test_taxes
  • Production ledger connects and auto-migrates cleanly on 0.14
  • Full uv run pytest (11 CLI invoice/tax tests fail on date-sensitive _seed using yesterday vs --month 2026-06 — pre-existing, unrelated to Ferro)

Made with Cursor

0x054 and others added 5 commits June 24, 2026 11:19
Adopt session-scoped queries, migrate_updates at connect, and lambda
where() predicates so TTD is ready for v0.14 and drops the hand-rolled
column-add shim.

Co-authored-by: Cursor <cursoragent@cursor.com>
… revamp (#14)

This branch grew well beyond its original "billable expenses" scope. It
now bundles four related pieces of invoicing/expense work. Base is
`chore/ferro-0-12-migration` (the feature depends on the ferro-orm 0.12
migration).

## 1. Billable expenses (client chargebacks)
Record purchased items against a project and bill them back as
**untaxed, pass-through** line items on invoices, with optional
receipts.
- New `Expense` / `ExpenseReceipt` / `InvoiceExpenseLine` models
(plain-id FK convention; receipts stored base64 in a side table because
raw bytes can't round-trip through the ORM — ferro-orm#160).
- `ttd expense` CLI: `add` / `list` / `edit` / `rm` + a `receipt` group;
history-based recall.
- Invoicing lifecycle: expenses lock on invoice, release on void, and
participate in refresh. Tax applies only to the time subtotal; `total =
subtotal + tax + expenses_subtotal`. Invoices with no expenses render
byte-identically to before.
- Rendering: PDF/markdown expense section; **opt-in receipt pages**
merged into the PDF via `pypdf`; invoice format choice (default PDF)
with markdown blocked when receipts are included.
- JSON backup (envelope v2) round-trips expenses + receipts (incl.
non-UTF-8 binary); v1 backups still import.

## 2. TUI "log" page (re-scoped timesheet)
The underused timesheet screen became a month-scoped **log** page (nav
2) with stacked **time** and **expense** sections: `[`/`]` cycle months,
`tab` switches section, `e`/`x` edit/delete the focused row, `l` adds
(time or expense via a chooser). Expenses also show in the new-invoice
draft preview.

## 3. Flexible invoice periods
Richer `--period` parsing (invoice CLI + TUI): `this week`/`last week`,
rolling `last N days/weeks/months`, and month-name ranges (`june 16 to
june 30`, `jun`, `june`, `june 16 - 30`) with a
closest-year-never-future rule and cross-year wrap. Separately, an
invoice's recorded period now **derives from the billed items** (min–max
of line dates), not the requested window — so a mid-month re-invoice
reads the true span. Refresh re-derives.

## 4. TUI invoice render format modal
`e` on the invoices screen now opens a **render chooser** (checkboxes:
Include receipts / PDF / Markdown): receipts embed into the PDF, the
receipts option is disabled unless the invoice has receipts, and
enabling it locks out markdown. A shared `load_invoice_receipts` helper
backs both the CLI and TUI.

## Testing
Full suite green throughout: **397 passing, 84.59% coverage, `ty` +
`ruff` clean.** Built brainstorm → spec → plan → subagent implementation
with per-task review and an opus whole-branch review per feature.
Specs/plans under `docs/superpowers/`.

## Known follow-ups (not in this PR)
- TUI: attach a receipt when adding an expense (drag-and-drop path into
the form) — designed-but-deferred to a future session.
- `invoice_has_receipts` / some services use `.all()`+filter scans
(codebase convention; tied to the relationship migration in #13).
- Multi-currency expense-total footer on the log page (edge case).

## Related issues
- ferro-orm#160 (raw `bytes` can't be saved via the ORM — drove the
base64 receipt design).
- #13 (migrate models to Ferro relationships + cascades).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The global `t`=theme binding shadowed the invoices screen's `t`=mark-sent.
Move the theme picker to ctrl+t (matching the palette convention), freeing
`t` for mark-sent. Updated theme-picker tests to ctrl+t and added a
regression test that `t` on the invoices screen marks the invoice sent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump ferro-orm from 0.12.x to 0.14.x. Pin all datetime fields with
db_type="timestamp" so 0.14's timestamptz default does not alter
existing SQLite storage.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace relative "yesterday" log specs so invoice --month 2026-06
tests stay stable regardless of the runner's current date.

Co-authored-by: Cursor <cursoragent@cursor.com>
@0x054
0x054 merged commit c5a6a09 into main Jul 7, 2026
6 checks passed
@0x054
0x054 deleted the chore/ferro-0-14 branch July 7, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant