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 <noreply@anthropic.com>
This commit is contained in:
commit
45424e6fd6
47 files changed
+6701
No files matched your search
+75
@@ -0,0 +1,75 @@
|
||||
"""Quarterly Umsatzsteuer-Voranmeldung list, computed from the Zimmerbelegung.
|
||||
|
||||
- A receipt belongs to the quarter it was PAID in (Ist-Besteuerung), not the one it was lived or invoiced
|
||||
in. A row paid in several goes counts each payment on its own day; otherwise paid = the same rule the
|
||||
Zimmerbelegung shows (`repo._payment`): the row's own date, else the date its invoice was paid.
|
||||
- Only the Betrag counts. The Ortstaxe is a durchlaufender Posten and never appears; payments cover the
|
||||
Betrag first (175,00 paid of 175,00 Betrag + 16,00 Ortstaxe: the Betrag is in, the Ortstaxe open).
|
||||
- One Betrieb at a time — each unit files separately. A Betrieb includes its predecessors.
|
||||
- Everything is 10 % USt: Beherbergung, and the Garçonnières' residential rent alike. A Storno is nicht
|
||||
steuerbar (`repo.is_storno`): listed, because the money came in, but kept out of the base.
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from app import db, repo
|
||||
|
||||
VAT_RATE = 0.10
|
||||
|
||||
|
||||
def previous_quarter(today):
|
||||
"""The UVA is filed for the quarter that has just ended."""
|
||||
q = (today.month - 1) // 3 # 0 = we are in the 1st quarter → last year's 4th
|
||||
return (today.year, q) if q else (today.year - 1, 4)
|
||||
|
||||
|
||||
def _bounds(year, quarter):
|
||||
first = datetime.date(year, 3 * quarter - 2, 1)
|
||||
after = datetime.date(year + 1, 1, 1) if quarter == 4 else datetime.date(year, 3 * quarter + 1, 1)
|
||||
return first.isoformat(), (after - datetime.timedelta(days=1)).isoformat()
|
||||
|
||||
|
||||
def receipts(row):
|
||||
"""When this row's money came in: [(date, part of the Betrag, part of the Ortstaxe)].
|
||||
|
||||
Recorded payments count one by one, each on its own day — an invoice paid half in May and half in
|
||||
October belongs to two quarters. They cover the Betrag first and the Ortstaxe last. Without payments, a
|
||||
paid row came in whole on its 'bezahlt am'."""
|
||||
if row["payments"]:
|
||||
out, left, taxe = [], row["amount"] or 0, row["tourism_tax"] or 0
|
||||
for on, amount in row["payments"]:
|
||||
part = round(min(amount, left), 2)
|
||||
tax = round(min(amount - part, taxe), 2)
|
||||
left, taxe = left - part, taxe - tax
|
||||
if part > 0 or tax > 0:
|
||||
out.append((on, part, tax))
|
||||
return out
|
||||
if row["payment"] in ("paid", "cash") and row["paid_on"]:
|
||||
return [(row["paid_on"], row["amount"] or 0, row["tourism_tax"] or 0)]
|
||||
return []
|
||||
|
||||
|
||||
def units(conn):
|
||||
return db.rows(conn, "SELECT code, name FROM unit WHERE is_current=1 ORDER BY id")
|
||||
|
||||
|
||||
def quarterly_report(conn, unit_code, year, quarter):
|
||||
first, last = _bounds(year, quarter)
|
||||
codes = {r["code"] for r in db.rows(conn, "SELECT code FROM unit WHERE code=? OR succeeded_by_id="
|
||||
"(SELECT id FROM unit WHERE code=?)", (unit_code, unit_code))}
|
||||
rooms = {}
|
||||
for r in repo.ledger_rows(conn):
|
||||
if r["unit"] not in codes:
|
||||
continue
|
||||
for on, amount, _ in receipts(r):
|
||||
if amount and first <= on <= last:
|
||||
rooms.setdefault(r["room"] or "", []).append(
|
||||
dict(r, received_on=on, received=amount, storno=repo.is_storno(r)))
|
||||
sections = [{"room": room, "rows": sorted(rows, key=lambda x: (x["received_on"], x["charge_id"])),
|
||||
"sum": round(sum(x["received"] for x in rows), 2)}
|
||||
for room, rows in sorted(rooms.items())]
|
||||
total = round(sum(s["sum"] for s in sections), 2)
|
||||
storno = round(sum(x["received"] for s in sections for x in s["rows"] if x["storno"]), 2)
|
||||
gross = round(total - storno, 2)
|
||||
net = round(gross / (1 + VAT_RATE), 2)
|
||||
return {"first": first, "last": last, "sections": sections, "total": total, "storno": storno,
|
||||
"gross": gross, "net": net, "vat": round(gross - net, 2)}
|
||||
Reference in new issue
Block a user