{"id":50816954,"url":"https://github.com/doughknee/cbg-invoices","last_synced_at":"2026-06-13T10:05:10.737Z","repository":{"id":353829843,"uuid":"1220213771","full_name":"doughknee/cbg-invoices","owner":"doughknee","description":"Temporary standalone invoice tool for CambridgeBG; planned to be folded into cambridgebg.","archived":false,"fork":false,"pushed_at":"2026-05-06T21:44:25.000Z","size":693,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T01:18:50.431Z","etag":null,"topics":["cambridgebg","client-work","invoicing","relentnet","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doughknee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T17:04:07.000Z","updated_at":"2026-05-10T19:43:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/doughknee/cbg-invoices","commit_stats":null,"previous_names":["brandon-relentnet/cbg-invoices","doughknee/cbg-invoices"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/doughknee/cbg-invoices","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doughknee%2Fcbg-invoices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doughknee%2Fcbg-invoices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doughknee%2Fcbg-invoices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doughknee%2Fcbg-invoices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doughknee","download_url":"https://codeload.github.com/doughknee/cbg-invoices/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doughknee%2Fcbg-invoices/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34279899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cambridgebg","client-work","invoicing","relentnet","typescript"],"created_at":"2026-06-13T10:05:10.192Z","updated_at":"2026-06-13T10:05:10.729Z","avatar_url":"https://github.com/doughknee.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cambridge Invoice Portal\n\nInternal AP portal for Cambridge Building Group. Receives vendor invoices via\nemail or manual upload, extracts fields with Claude vision, routes them through\nPM review, and posts approved invoices as **Bills** to QuickBooks Online with\nthe PDF attached and the project tagged.\n\n## Stack\n\n- **Backend**: FastAPI (async), SQLAlchemy 2.x, PostgreSQL 16, Alembic\n- **Frontend**: React 19 + Vite, Tailwind v4, TanStack Router/Query, Motion\n- **Auth**: [Logto](https://logto.io) (self-hosted in the compose stack)\n- **Extraction**: Anthropic Claude (`claude-sonnet-4-5`) with vision\n- **Storage**: Cloudflare R2 (S3-compatible)\n- **Email intake**: Postmark Inbound webhook\n- **Deploy**: Docker Compose on Coolify\n\n## Quick Start (Local)\n\n### 1. Copy and fill in environment\n\n```bash\ncp .env.example .env\n```\n\nFill in the secrets you have — **at minimum**:\n- `POSTGRES_PASSWORD` (any strong value)\n- `ANTHROPIC_API_KEY` (from https://console.anthropic.com)\n- `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`,\n  `R2_ENDPOINT` (from Cloudflare R2 dashboard)\n\nYou can leave `POSTMARK_WEBHOOK_SECRET` and `QBO_*` blank and wire them up\nlater — the portal will still start and you can upload PDFs manually.\n\n### 2. Start the stack\n\n```bash\ndocker compose up -d\n```\n\nAll four services (postgres, logto, backend, frontend) will boot. Backend\nwill run migrations automatically.\n\n### 3. Bootstrap Logto (one-time, ~2 minutes)\n\n1. Open **http://localhost:3002** — Logto admin console. Create the initial\n   admin account (takes ~90 seconds).\n2. In the admin console, go to **Applications → Create Application** and\n   create a Machine-to-Machine app called *Bootstrap*. Assign the \"Logto\n   Management API\" role. Copy the app ID and secret into `.env`:\n   ```\n   LOGTO_M2M_APP_ID=xxx\n   LOGTO_M2M_APP_SECRET=xxx\n   ```\n3. Run:\n   ```bash\n   make logto-setup\n   ```\n   This creates the SPA application and API resource. It will print something\n   like:\n   ```\n   LOGTO_APP_ID=abc123\n   LOGTO_RESOURCE=https://invoice-api.cambridgebg.com\n   ```\n   Append those to `.env` (or copy from the generated `.env.logto` file).\n4. Restart so the new env is picked up:\n   ```bash\n   make restart\n   ```\n5. Apply the Cambridge brand to Logto's hosted sign-in screen:\n   ```bash\n   make logto-css\n   ```\n   Re-run any time you tweak `backend/scripts/logto-theme/cambridge.css`.\n6. In Logto admin, **create at least one user** (Users → Add user) so you can\n   log in. Alternatively enable a social connector.\n\n### 4. Log in\n\nOpen **http://localhost:5173** — the portal redirects you through Logto's\nsign-in page, then lands on `/invoices`.\n\n### 5. Connect QuickBooks Online\n\n1. Create a QBO developer app at https://developer.intuit.com/app/developer/myapps\n2. Add `http://localhost:8000/api/qbo/callback` as a redirect URI.\n3. Copy the Client ID and Client Secret into `.env` as `QBO_CLIENT_ID` /\n   `QBO_CLIENT_SECRET`. Keep `QBO_ENVIRONMENT=sandbox` until production cut-over.\n4. Restart: `make restart`.\n5. In the portal, open **Settings → Connect to QuickBooks Online**, authorize\n   your sandbox company, then pick a default expense account and sync vendors\n   + projects.\n\n### 6. (Optional) Configure inbound email\n\nSet up Postmark Inbound:\n- Create a Postmark server, enable inbound, and point the webhook URL at:\n  `https://\u003cuser\u003e:\u003cPOSTMARK_WEBHOOK_SECRET\u003e@your-backend.example.com/api/webhooks/postmark`\n  (Basic Auth; the `\u003cuser\u003e` part is ignored and just needs to be present.)\n- Set `POSTMARK_WEBHOOK_SECRET` in `.env`.\n- Forward a vendor invoice PDF to your Postmark inbound address — an Invoice\n  appears in the queue within a few seconds.\n\n## Services\n\n| Service    | Port (dev) | Purpose                            |\n|------------|-----------|------------------------------------|\n| postgres   | 5432      | App + Logto databases (separate)   |\n| logto      | 3001      | Logto user-facing (sign-in, OIDC)  |\n| logto      | 3002      | Logto admin console                |\n| backend    | 8000      | FastAPI                            |\n| frontend   | 5173      | Vite dev server                    |\n\n## Make Targets\n\n```bash\nmake up              # Start all services\nmake down            # Stop and remove containers\nmake restart         # Restart backend + frontend (env changes)\nmake logs            # Tail all logs\nmake logs-backend    # Tail only backend\nmake migrate         # Run alembic upgrade head\nmake logto-setup     # Bootstrap Logto app registration\nmake logto-css       # Apply Cambridge brand CSS to Logto sign-in screen\nmake shell           # Open shell in backend container\nmake test            # Run backend pytest\nmake migration msg=\"add foo\"  # Create a new alembic revision\n```\n\n## Repository Layout\n\n```\ncambridge-invoice-portal/\n├── backend/                 # FastAPI + SQLAlchemy\n│   ├── app/\n│   │   ├── models/          # SQLAlchemy ORM\n│   │   ├── schemas/         # Pydantic DTOs\n│   │   ├── routers/         # HTTP endpoints\n│   │   ├── services/        # Business logic (extraction, QBO, storage, audit)\n│   │   ├── prompts/         # LLM prompts (invoice_extraction)\n│   │   ├── config.py        # pydantic-settings\n│   │   └── main.py          # FastAPI app factory\n│   ├── alembic/             # Migrations\n│   └── scripts/             # logto_setup.py\n├── frontend/                # React 19 + Vite\n│   ├── src/\n│   │   ├── routes/          # TanStack Router flat-file routes\n│   │   ├── components/      # layout/, invoices/, ui/\n│   │   ├── lib/             # API client, hooks, formatters\n│   │   └── assets/css/      # Tailwind v4 @theme + utilities\n│   └── nginx.conf           # Prod static serving\n├── infra/\n│   └── postgres-init/       # Creates logto DB on first boot\n├── docker-compose.yml       # Dev\n├── docker-compose.prod.yml  # Coolify override\n└── Makefile\n```\n\n## Key Design Decisions\n\n- **Money as integers (cents).** Never floats. Conversion only at the display edge.\n- **Audit everything.** Every status transition, edit, QBO call recorded in\n  `audit_logs` with before/after diffs.\n- **Idempotency.** Postmark webhook dedups on MessageID. Approval is safe to\n  retry — QBO bill creation skips if `qbo_bill_id` is already set and only\n  re-attempts the attachment.\n- **Background jobs via `BackgroundTasks`.** No Redis/Celery — volume is low\n  enough (~dozens of invoices/day) that FastAPI's built-in suffices.\n- **No secrets in frontend.** LLM calls server-side only; R2 URLs are\n  presigned (15-min TTL) by the backend.\n\n## Deployment\n\nSee [DEPLOY.md](./DEPLOY.md) for the Coolify walkthrough.\n\n## License\n\nProprietary — Cambridge Building Group.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoughknee%2Fcbg-invoices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoughknee%2Fcbg-invoices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoughknee%2Fcbg-invoices/lists"}