"""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)}