Quotient is a private commercial-document and payment tracker for an independent professional. It keeps the complete path from customer and project setup through quotations, allocated invoices, payments, and branded PDFs.
The application is built with Next.js and deployed through OpenNext to Cloudflare Workers. Relational data is stored in D1, while versioned business logos are stored in R2. Cloudflare Access protects the application; Quotient does not maintain its own user or password database.
- Manage customers and single-currency projects.
- Create draft quotations and invoices with decimal quantities and line items.
- Issue documents with permanent yearly numbers such as
Q-2026-0001andINV-2026-0001. - Record quotation acceptance, rejection, and delivery time; derive expiry from the validity date.
- Create partial or complete invoices from accepted quotation quantities.
- Track invoice payments, balances, settlement state, and overdue status.
- Revise issued documents while preserving their number and visible revision.
- Preserve customer and sender snapshots for historical document accuracy.
- Generate branded PDFs and export all application data as JSON.
| Area | Technology |
|---|---|
| Application | Next.js 16, React 19, TypeScript |
| UI | Tailwind CSS 4, Base UI |
| Hosting | Cloudflare Workers through OpenNext |
| Database | Cloudflare D1, Drizzle ORM, SQLite migrations |
| Object storage | Cloudflare R2 |
| Validation | Zod |
| Tests | Vitest and Playwright |
Install dependencies, apply the D1 migrations to the local Wrangler database, and start the development server:
npm install
npm run db:migrate:local
npm run devOpen http://localhost:3000. Wrangler emulates the D1
and R2 bindings configured in wrangler.jsonc. On first use, Quotient creates a
Business Profile with placeholder values; update it from Settings before
issuing real documents.
Local state lives under .wrangler/ and is intentionally ignored by Git.
| Command | Purpose |
|---|---|
npm run dev |
Start the Next.js development server |
npm test |
Run Vitest domain tests once |
npm run test:watch |
Run Vitest in watch mode |
npm run test:e2e |
Run the Playwright customer-to-payment workflow |
npm run typecheck |
Type-check without emitting files |
npm run lint |
Run ESLint |
npm run build |
Create a standard Next.js production build |
npm run build:worker |
Build the OpenNext Cloudflare Worker output |
npm run preview |
Preview the Cloudflare build locally |
npm run db:generate |
Generate a migration from the Drizzle schema |
npm run db:migrate:local |
Apply migrations to local D1 state |
npm run cf-typegen |
Regenerate Cloudflare binding types |
npm run deploy |
Deploy the OpenNext Worker to Cloudflare |
The Playwright test writes records to the local D1 database used by its test server.
The application uses Next.js server components and server actions. The main data relationships are:
Customer -> Project -> Document -> Line Item
| |
| +-> Billing Allocation
+-> Payment (Invoice only)
Projects own the currency used by all of their documents. Documents are either
Quotations or Invoices and move through draft, issued, and void
lifecycles. Customer-facing identity data is copied into snapshots when a
document is created. Commercial guards are enforced in server actions and, for
critical allocation and payment constraints, by D1 triggers.
Important locations:
src/app/- routes, server actions, and API handlerssrc/components/- shared interactive and document form componentssrc/db/- Drizzle schema and Cloudflare binding accesssrc/lib/- domain calculations, formatting, dates, IDs, and validationmigrations/- append-only D1 migrations and integrity triggerstests/e2e/- browser-level workflow coverageCONTEXT.md- canonical domain terminologydocs/adr/- accepted architecture decisions
-
Create the production resources:
npx wrangler d1 create quotient npx wrangler r2 bucket create quotient-logos
-
Replace
replace-with-production-d1-idinwrangler.jsoncwith the D1 ID returned by Wrangler. Keep the configured database and bucket names aligned with the resources. -
Apply migrations to production:
npx wrangler d1 migrations apply DB --remote
-
Configure a custom hostname and protect it with a Cloudflare Access email one-time-code policy for the owner.
-
Keep
workers_devdisabled so the Access-protected hostname cannot be bypassed. -
Build, verify, and deploy:
npm test npm run typecheck npm run lint npm run build:worker npm run deploy
All application routes assume Cloudflare Access has already authenticated the owner. A deployment without that policy exposes private commercial data.