From 45424e6fd6102a698edd46f515e0fbd10853f682 Mon Sep 17 00:00:00 2001 From: BlackMark Date: Mon, 2 Nov 2026 16:52:27 +0100 Subject: [PATCH] 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 --- .gitattributes | 6 + .gitignore | 11 + README.md | 109 ++ app/__init__.py | 0 app/audit.py | 116 +++ app/compute.py | 167 ++++ app/config.py | 23 + app/db.py | 277 +++++ app/i18n.py | 11 + app/money.py | 45 + app/normalize.py | 53 + app/pdf.py | 25 + app/plan.py | 128 +++ app/quality.py | 309 ++++++ app/repo.py | 1499 ++++++++++++++++++++++++++++ app/static/grids.js | 644 ++++++++++++ app/static/style.css | 288 ++++++ app/templates/base.html | 62 ++ app/templates/client_form.html | 126 +++ app/templates/clients.html | 39 + app/templates/error.html | 5 + app/templates/home.html | 17 + app/templates/invoice_form.html | 123 +++ app/templates/invoice_payment.html | 37 + app/templates/invoice_print.html | 174 ++++ app/templates/invoices.html | 54 + app/templates/ledger.html | 96 ++ app/templates/ledger_form.html | 167 ++++ app/templates/plan.html | 87 ++ app/templates/quality.html | 85 ++ app/templates/register.html | 77 ++ app/templates/stay_form.html | 114 +++ app/templates/tvb.html | 89 ++ app/templates/vat.html | 65 ++ app/tvb.py | 218 ++++ app/vat.py | 75 ++ app/web.py | 737 ++++++++++++++ docs/screenshots/invoice.png | Bin 0 -> 42379 bytes docs/screenshots/ledger.png | Bin 0 -> 109755 bytes docs/screenshots/plan.png | Bin 0 -> 64777 bytes docs/screenshots/quality.png | Bin 0 -> 105174 bytes docs/screenshots/register.png | Bin 0 -> 109366 bytes docs/screenshots/tvb.png | Bin 0 -> 100927 bytes locales/de.json | 311 ++++++ migrations/001_schema.sql | 180 ++++ migrations/002_audit_log.sql | 49 + requirements.txt | 3 + 47 files changed, 6701 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/__init__.py create mode 100644 app/audit.py create mode 100644 app/compute.py create mode 100644 app/config.py create mode 100644 app/db.py create mode 100644 app/i18n.py create mode 100644 app/money.py create mode 100644 app/normalize.py create mode 100644 app/pdf.py create mode 100644 app/plan.py create mode 100644 app/quality.py create mode 100644 app/repo.py create mode 100644 app/static/grids.js create mode 100644 app/static/style.css create mode 100644 app/templates/base.html create mode 100644 app/templates/client_form.html create mode 100644 app/templates/clients.html create mode 100644 app/templates/error.html create mode 100644 app/templates/home.html create mode 100644 app/templates/invoice_form.html create mode 100644 app/templates/invoice_payment.html create mode 100644 app/templates/invoice_print.html create mode 100644 app/templates/invoices.html create mode 100644 app/templates/ledger.html create mode 100644 app/templates/ledger_form.html create mode 100644 app/templates/plan.html create mode 100644 app/templates/quality.html create mode 100644 app/templates/register.html create mode 100644 app/templates/stay_form.html create mode 100644 app/templates/tvb.html create mode 100644 app/templates/vat.html create mode 100644 app/tvb.py create mode 100644 app/vat.py create mode 100644 app/web.py create mode 100644 docs/screenshots/invoice.png create mode 100644 docs/screenshots/ledger.png create mode 100644 docs/screenshots/plan.png create mode 100644 docs/screenshots/quality.png create mode 100644 docs/screenshots/register.png create mode 100644 docs/screenshots/tvb.png create mode 100644 locales/de.json create mode 100644 migrations/001_schema.sql create mode 100644 migrations/002_audit_log.sql create mode 100644 requirements.txt 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 `