commit 45424e6fd6102a698edd46f515e0fbd10853f682 Author: BlackMark Date: Mon Nov 2 16:52:27 2026 +0100 Vermietungs-Tool A local web app for a small room-rental business in Austria: guest register, room ledger, occupancy plan, invoices, the monthly tourist-tax report and the quarterly VAT list. Application code only; it ships no data. Co-Authored-By: Claude Opus 5.5 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6f6095b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Text is stored and checked out with LF on every machine +* text=auto eol=lf + +*.png binary +*.jpg binary +*.jpeg binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0753f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Local settings: database URLs +.env + +# Python environment and caches +.venv/ +__pycache__/ +*.pyc +.pytest_cache/ + +# Invoice logos and photos (app/static/invoice/) belong to whoever runs the app +app/static/invoice/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fb3837 --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Vermietungs-Tool + +A local web app for running a small room-rental business in Austria: the guest register, the room +ledger, invoices, the monthly tourist-tax report and the quarterly VAT list, all from one database. +It is built for one non-technical user — German UI, no training needed — and handles the bookkeeping +rules that make this work tedious by hand. + +This repository presents the tool. It contains the application code only; it ships no data. + +![Belegungsplan — the occupancy timeline](docs/screenshots/plan.png) + +*All names, companies and addresses in the screenshots are fictitious.* + +## What it does + +| Page | Purpose | +| --- | --- | +| **Gästeblätter** | The guest register: one registration sheet per guest and stay (Meldegesetz), with room, employer, main residence and tourist-tax liability. A room change continues the same sheet in another room. | +| **Zimmerbelegung** | The room ledger: what each room is billed for, per period and person — weeks, nights or months, Ortstaxe, payer, invoice number and payments (also in parts). | +| **Belegungsplan** | A rooms × days timeline of who is in the house, today's free beds, and bookings ahead. | +| **Rechnungen** | Invoices generated from ledger or register rows: per-person lines, VAT breakdown, cancellation fees as non-taxable damages, Ortstaxe, A4 layout for window envelopes, PDF download. | +| **Firmen** | Companies with all their spellings and address versions; old invoices keep the address that was true at the time. | +| **TVB-Meldung** | The monthly tourist-tax report (Sammelmeldung) per business, ready to type into Feratel: persons and nights by country or Austrian/German region, liable and exempt. | +| **Umsatzsteuer** | The quarterly VAT list (cash basis): every receipt in the quarter it was paid, part payments split across quarters, non-taxable items kept out of the base. | +| **Datenprüfung** | Data-quality checks — register and ledger disagreeing, departures never billed, recipients without an address, duplicate invoice numbers — and merge suggestions for near-duplicate companies and guests. | + +### Rules it applies so nobody has to + +- **Billing in weeks**: a stay of four nights or more is billed in whole weeks, rounded up; shorter stays + are billed by the night. +- **Ortstaxe**: charged per person and night at the rate in force on the arrival date, and waived for + professional stays of more than ten nights — judged on the *whole* stay, across room changes and month-end + invoices, and never for a guest who is still in the house. +- **The ledger follows the register**: a departure entered on a guest sheet closes that guest's ledger row + with quantity, amount and Ortstaxe; a guest leaving before the others gets a row of their own, and rows that + end up alike merge again. Month-end billing closes a row and continues the stay in a new one. +- **The ledger follows the invoice**: when an invoice settles a price or period differently, the ledger rows it + was made from are corrected to match; when a ledger row changes after invoicing, the invoice is flagged and + can be regenerated in one click. +- **Issued invoices never change as a side effect**: recording a payment touches only the payment fields, and + payment information from the ledger is offered, not applied. +- **Invoice numbers follow the dates** within each business and year; saving an invoice out of order, or one + whose recipient has no postal address, asks first. +- **Payments**: a date in "bezahlt am" marks a row paid, payments in parts keep it open until amount and + Ortstaxe are covered, and a paid invoice pays its ledger rows. + +## Screenshots + +| | | +| --- | --- | +| ![Zimmerbelegung](docs/screenshots/ledger.png) | ![Gästeblätter](docs/screenshots/register.png) | +| **Zimmerbelegung** — the room ledger | **Gästeblätter** — the guest register | +| ![TVB-Meldung](docs/screenshots/tvb.png) | ![Datenprüfung](docs/screenshots/quality.png) | +| **TVB-Meldung** — the monthly tourist-tax report | **Datenprüfung** — data-quality checks | + +![Rechnung](docs/screenshots/invoice.png) + +## Under the hood + +- **Flask + Jinja2**, server-rendered pages; all UI text comes from [locales/de.json](locales/de.json). +- **PostgreSQL** through psycopg, behind a thin adapter ([app/db.py](app/db.py)) that gives the code a + sqlite3-style interface (`?` placeholders, `lastrowid`, rows by name or index). Two roles: the app's role may + only read and write data; the owner applies migrations and takes snapshots. +- **Single source of truth**: every value lives in one place; totals, nights, quantities and payment states are + computed at read time ([app/compute.py](app/compute.py), [app/repo.py](app/repo.py)). Money is summed at full + precision and rounded once. +- **Audit log** ([migrations/002_audit_log.sql](migrations/002_audit_log.sql), [app/audit.py](app/audit.py)): + a trigger records every row change with before/after values and who made it; the app's role can read the log + but not alter it. `python -m app.audit revert` undoes chosen entries or a whole transaction, refusing if the + rows changed since. +- **A grid engine in plain JavaScript** ([app/static/grids.js](app/static/grids.js)) for lists of thousands of + rows: rows ship in an inert `