Skip to content

Phase 3: invoice-first billing documents, company checkout and admin panel - #41

Merged
arudaev merged 41 commits into
mainfrom
feat/phase-3-billing
Sep 25, 2026
Merged

arudaev merged 41 commits into
mainfrom
feat/phase-3-billing

Conversation

@arudaev

@arudaev arudaev commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

Phase 3 for ITC1: billing documents for both payment models, shared company accounts at the iPad, a reworked admin panel, and a staging environment so previews never touch production. What was built and why: docs/phase-3-billing.md. Environments and migrations: docs/environments.md.

Documents (both billing modes kept equal)

  • Whoever pays gets the invoice or statement; the other side gets an overview. Members of paying companies get an information copy with no payment details.
  • Only invoices have attachments: the payer's invoice carries the official PDF (company invoices with Anlage – Verzehr je Person) and an Excel of every entry, with IBAN and reference in both the email and the PDF. Statements, information copies and the other party's overview are plain emails, one line per item.
  • The administration gets a monthly report: change vs. last month, most-consumed items for restocking, warnings. Only the CEO gets the ZIP archive of every document sent.
  • Invoice mode needs a recorded authorisation (invoice_mode_authorized + note) and stays off in production.
  • Every send is recorded in document_deliveries. Re-sending reuses the stored number.

iPad

  • checkout_mode: member, company (4process: no name step), both (ITC1/PBI/Level51: Für die Firma buchen tile above the names).

Admin

  • Dokumente page, settings in five tabs, members grouped by company with company paid ticks, export (CSV/Excel/PDF) over live and archived months.
  • The invoice matrix shows invoices as soon as invoice mode is switched on, and explains why they aren't sent yet.

Data and security

  • Price snapshot on every transaction. The archive is no longer pruned. Same-name colleagues no longer merge.
  • 038 makes production's API privileges explicit. A database built from the migrations let the public key call set_admin_pin and other server-only functions.

Environments

  • Previews, vercel dev and local runs use the staging Supabase project (Vercel Preview/Development variables).
  • Outside production, mail goes only to allowlisted addresses and on to MAIL_SINK. A non-production build pointed at production refuses to start.
  • CI now lints migrations (deploy: pre|post tags, numbering) and runs the SQL guards on a schema built without seeds.
  • Workflows: Database - staging (PRs), Database - production (on merge to main). Both use the Supabase Management API.

Deploy

  1. Set the GitHub secrets and variable listed in docs/environments.md (SUPABASE_ACCESS_TOKEN, STAGING_PROJECT_REF, Environment production). Without them the production migration job fails on merge instead of applying anything.
  2. Merge. Database - production applies 030, 031, 033–039 (all deploy: pre) while Vercel builds main.
  3. After production is live and the iPads have reloaded: merge chore(db): post-deploy hardening for phase 3 (archive, company contacts) #42 (040, post-deploy: archive DELETE revoke, anon company columns).

Do not enable invoice mode in production without ITC1's written authority.

Test plan

  • typecheck (app + API), eslint, vitest (270 tests), vite build
  • CI: migration lint + all migrations on an empty Postgres 17 + SQL guards (bare schema, anon privileges, both-checkout)
  • Staging migrated through the Management API; schema compared with production. Production matches 001–029 apart from two inert leftovers (documented).
  • Settings matrix previews: email, PDF (rendered) and Excel sheets, including invoice variants while invoice mode is incomplete
  • Admin tabs walked at 1280 px against staging: Übersicht, Einträge, Dokumente, Unternehmen, Mitarbeitende, Artikel, all five settings tabs
  • iPad flow at 1180×820 and 820×1180: 4process (no name step), ITC1 both booking on the shared account, verified in staging
  • Staging monthly runs for 2026-08: statement mode (19 emails, no attachments) and invoice mode with test issuer data in staging only (19 documents, invoices with PDF + Excel, 0 missing, 17.7 s); all mail to the sink
  • Time a full-volume run on the Vercel preview before the first production send

…livery ledger

Migrations 030-037 with a throwaway-Postgres test harness. Deploy order:
030, 031, 033-037, then the code, then 032 (revokes archive delete).
Invoices and reports for both billing modes, CEO archive with manifest and
campus roll-up, legal gate on invoice mode, filtered CSV/Excel/PDF export
over live and archived months, re-send without renumbering, and PDF
rendering with bounded concurrency. Stops pruning the archive.
Documents page, settings tabs with per-tab save, grouped members with
company paid ticks, filter bars and export dialog, like-for-like month
comparison, and company checkout in the member flow.
@vercel

vercel Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
kaffeelisten Ready Ready Preview Sep 17, 2026 9:46am UTC

Request Review

CLAUDE.md duplicated AGENTS.md and both had drifted from the product (billing ban, free tier, report location). Adds the staging/production migration rules.
The Supabase CLI applies pending migrations in order, so a post-deploy 032 could not be skipped while pre-deploy 033-037 were applied. The revoke returns as 039 once this code is live.
The SQL harness only ran locally. Migrations now carry a deploy phase tag so pre- and post-deploy changes cannot be mixed in one PR.
A preview wired to production data went unnoticed and failed with bare 500s. Non-production builds now refuse the production database, only email allowlisted addresses, and a missing migration is reported in German.
Staging migrates automatically for PRs that change migrations; production migrates only through an approved manual run that checks schema drift and then redeploys.
A database built from the migrations let the public key execute set_admin_pin and other server-only RPCs; production was only safe because of manual changes. 038 encodes production's posture. Migrations and drift checks now go through the Supabase Management API, so no database password lives in GitHub.
ITC1 found per-coffee rows unreadable. Documents now show one line per item and price; company emails list at most 15 people while the PDF adds each person's consumption and the Excel gains a per-person item sheet.
Every function crashed with ERR_REQUIRE_ESM once api/ imported runtime code from src/ (export.ts already did for csv). Shared modules now live in api/_lib and src re-exports them; a test forbids the pattern.
…only

ITC1's administration gets key figures, the month against the previous one, the most-consumed items and warnings for silent gaps. The ZIP with every person's documents now goes only to the CEO.
src/ importing api/_lib/* broke vite dev (the /api proxy swallowed the module request). shared/ has its own CommonJS package.json so Vercel functions and the browser bundle can both load it.
Months without consumption no longer show a payable checkbox, company badges stop wrapping, settings describe the short email, detailed PDF and CEO-only archive, and shared accounts are not counted as active people.
ITC1, PBI and Level51 have named and unnamed rows on the paper sheet. checkout_mode 'both' keeps the name picker and adds a tile that books on the company's house account; the admin API only allows it for paying companies with a contact.
Staging mail to example.com addresses bounces and damages the sending domain's reputation. MAIL_SINK sends every allowed message to one test inbox and names the intended recipients in the subject; production ignores it.
…op contracts

Logo now reads the theme context, so cards and designs wrap in KaffeelistenProvider. Declarations are generated with tsc so the design agent sees real props instead of empty contracts.
…ress

Browser translation replaced the dialog's bare text nodes, so React crashed with removeChild when the confirmation switched to the result and the panel went blank. The emails had gone out.
arudaev added a commit that referenced this pull request Sep 17, 2026
…anon

Post-deploy migration 040, split out of #41: revokes DELETE on transactions_archive and narrows anon's companies grant to the picker columns. Both break the code currently in production, so they ship after #41 is live.
arudaev added a commit that referenced this pull request Sep 17, 2026
040 is now the pre-deploy run-progress migration in #41; a post-deploy migration must come after every pre-deploy one.
ITC1: a paying company gets one invoice over the full amount. Who consumed what is a separate document for companies that asked for it. PDFs also stop ending in an empty page: print drops the email frame and flows the layout tables so content breaks across pages.
arudaev added a commit that referenced this pull request Sep 17, 2026
…anon

Post-deploy migration 040, split out of #41: revokes DELETE on transactions_archive and narrows anon's companies grant to the picker columns. Both break the code currently in production, so they ship after #41 is live.
arudaev added a commit that referenced this pull request Sep 17, 2026
040 is now the pre-deploy run-progress migration in #41; a post-deploy migration must come after every pre-deploy one.
arudaev added a commit that referenced this pull request Sep 17, 2026
041 is now the pre-deploy Verzehrliste opt-in in #41.
MAIL_SINK redirected every allowed message, so a tester's own address could never receive staging mail. Exact addresses in MAIL_ALLOWLIST now get the email; example.com still goes to the sink.
A 429 counted the document as failed. Quota errors are not retried. Documents that production needs Resend Pro for a monthly run.
@arudaev
arudaev added this pull request to stack #43 September 17, 2026 11:04
@arudaev
arudaev removed this pull request from stack #43 September 17, 2026 11:05
@arudaev
arudaev merged commit 5a62392 into main Sep 25, 2026
6 checks passed
arudaev added a commit that referenced this pull request Sep 25, 2026
…anon

Post-deploy migration 040, split out of #41: revokes DELETE on transactions_archive and narrows anon's companies grant to the picker columns. Both break the code currently in production, so they ship after #41 is live.
arudaev added a commit that referenced this pull request Sep 25, 2026
040 is now the pre-deploy run-progress migration in #41; a post-deploy migration must come after every pre-deploy one.
arudaev added a commit that referenced this pull request Sep 25, 2026
041 is now the pre-deploy Verzehrliste opt-in in #41.

This branch was successfully deployed

1 active deployment
Preview — 580b2860 Deployed Sep 17, 2026 by vercel[bot]
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