Private web app for collecting invoices, reconciling them with bank transactions, and handing a complete monthly package to an accountant.
- requires Google sign-in and keeps business data scoped to the signed-in user
- connects to Google Drive through the same Google authorization flow
- uploads PDFs or imports existing Drive month folders
- supports selecting multiple PDFs at once and choosing which files belong in the accountant export
- detects invoice attachments, links them to one primary document, and names them with
_att_01,_att_02, and so on - blocks byte-identical duplicate PDFs before they are uploaded to Drive
- manages a per-user vehicle list in Settings and includes the selected vehicle registration in expense filenames
- extracts invoice metadata with deterministic parsing and optional LLM assistance
- classifies documents as invoices, receipts, or other documents
- fetches bank transactions on demand and supports deterministic, learned, suggested, and manual matching
- stores optional accountant comments on individual documents
- exports a month as a ZIP or copies documents into an accountant Drive folder
- optionally adds the monthly bank statement to the accountant export
- previews and sends a summary email through Mailjet, including document comments and a BCC to the signed-in user
- provides an Exportable tab for progressive Drive handoffs of documents paired with bank payments; progressive handoffs never send email
- supports manually marking documents as exported in Edit Invoice without copying files or changing their payment match
- sends the final completion email with notes from all handed-over documents in the selected month, including earlier and manual handoffs
- supports editable organization name, email subject template, message template, sender identity, and accountant recipient in Settings
- FastAPI, SQLAlchemy, and SQLite
- React, TypeScript, and Vite
- Google OAuth and Google Drive API
- Fio API for transactions and monthly statements
- optional OpenRouter-based PDF extraction
- optional Mailjet transactional email
Copy .env.example to .env.
Required for sign-in and Drive access:
GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRETGOOGLE_AUTH_REDIRECT_URISECRET_KEYTRUSTED_HOSTS
Access control is optional but strongly recommended for any deployed instance:
ALLOWED_EMAIL_ADDRESSESALLOWED_EMAIL_DOMAINS
If both allowlists are empty, any Google account that can complete sign-in is accepted.
Optional integrations:
OPENROUTER_API_KEYandOPENROUTER_MODELenable LLM-assisted extractionMAILJET_API_KEYandMAILJET_SECRET_KEYenable accountant summary emailsENCRYPTION_KEYprovides a dedicated server-side key for stored Google credentials;SECRET_KEYis used as the fallback
Mailjet sender details, organization name, subject and message templates, recipient, and Drive folder IDs are configured per user in the app Settings. They are not environment variables.
For HTTPS deployments, set SESSION_COOKIE_SECURE=true and use the public HTTPS callback URL in GOOGLE_AUTH_REDIRECT_URI.
Prebuilt image:
docker compose up -dLocal image build:
docker compose -f docker-compose.build.yml up -d --buildDevelopment backend:
uv sync
uv run uvicorn web.main:app --reload --port 8000Development frontend:
cd frontend
npm install
npm run dev- production-style app: http://localhost:8000
- frontend development server: http://localhost:5173
- health check: http://localhost:8000/api/health
- Sign in with Google and grant Drive access.
- In Settings, choose the invoice parent folder and accountant shared root folder.
- Add the vehicles used for fuel and other car expenses.
- Save the Fio token in the encrypted vault if transaction fetching or monthly statement export is needed.
- If email handoff is enabled, configure the organization name, subject template, sender, recipient, and message template.
- Import existing Drive folders or upload invoice PDFs.
The default email subject template is:
{company_name} - Doklady za obdobie {period}
Both subject tokens are required. The message template supports {company_name}, {period}, and {comments}. When no document has a comment, the standalone {comments} line and excess spacing are removed.
- SQLite data and cached PDFs live in the persistent
/app/dataDocker volume. - Google credentials are encrypted server-side before storage.
- The Fio token is encrypted in the browser with AES-GCM using a key derived with Argon2id; only the encrypted payload is stored by the server.
- The Fio vault password can be remembered for the current tab or, when explicitly selected, on the current device.
- Mutating API requests require a valid authenticated session and CSRF token.
- OpenRouter and Mailjet API keys remain server-side environment variables.
Back up the persistent data volume before replacing or migrating a deployment.
- uploaded files go into Drive month folders named
YYYYMM - uploaded files are renamed to
YYYY-MM-DD-NNN_payment-type_vendor-slug.pdf - vehicle expenses use the active vehicle dropdown; with one active vehicle it is selected automatically
- vehicle-expense filenames add the selected registration before
.pdf - changing a vehicle in Settings affects future uploads while existing documents keep their stored registration
- files excluded from accountant export remain available as internal references and are not bank-matched
- supporting attachments do not store their own amount or payment type and do not consume invoice sequence numbers
- cash invoices use the
cashstatus and do not require a bank transaction match - accountant exports route files by document type into
POKLADNICNE_DOKLADY,DOSLE_FAKTURY, andOSTATNE - the optional monthly statement is stored in
OSTATNE - accountant handoffs include only unexported documents with a matched bank payment; cash and unpaired documents are excluded
- successful Drive copies (including files already present by name) are automatically marked exported
- ZIP downloads include only the remaining paired documents; the UI leaves their status unchanged for manual handoff
- final handoff asks you to review unresolved expense payments and withholds completion email if a file or requested statement fails
- if the final email fails, retry final handoff: already exported PDFs are omitted and their notes remain in the email
- email preview shows From, To, BCC, subject, and the editable per-send message body
- editing the preview body does not overwrite the saved message template
Backend tests:
uv run python -m unittest discover -s tests -vFrontend production build:
cd frontend
npm run buildMIT