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:
BlackMarkandClaude Opus 5.5 committed 2026-11-02 16:52:27 +01:00
commit 45424e6fd6
47 files changed
+6701

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
{% extends "base.html" %}
{% block main_class %}grid-page{% endblock %}
{% block content %}
{% set unit_opts = rows|map(attribute='unit')|select|unique|sort %}
{% set nat_opts = rows|map(attribute='nationality')|select|unique|sort %}
<h1>{{ t('register_title') }}</h1>
<div class="toolbar">
<input class="search live-search" data-grid="reg" type="text" placeholder="{{ t('search_placeholder') }}">
<select class="live-filter" data-grid="reg" data-col="{{ t('col_unit') }}"><option value="">{{ t('filter_all', col=t('col_unit')) }}</option>
{% for u in unit_opts %}<option>{{ u }}</option>{% endfor %}</select>
<select class="live-filter" data-grid="reg" data-col="{{ t('col_departure') }}"><option value="">{{ t('filter_all', col=t('filter_stay')) }}</option>
<option value="__empty__">{{ t('filter_still_here') }}</option>
<option value="__filled__">{{ t('filter_departed') }}</option></select>
<select class="live-filter" data-grid="reg" data-col="{{ t('col_nationality') }}"><option value="">{{ t('filter_all', col=t('col_nationality')) }}</option>
{% for nat in nat_opts %}<option>{{ nat }}</option>{% endfor %}</select>
<span class="count"><span class="grid-count" data-grid="reg">{{ rows|length }}</span> {{ t('showing_count', count='')|trim }}</span>
<span class="spacer"></span>
<button class="btn secondary" type="button" onclick="invoiceFromSelection()">{{ t('btn_invoice_from_selection') }}</button>
<button class="btn secondary" type="button" onclick="gridPrint('reg')">{{ t('btn_print') }}</button>
<a class="btn" href="{{ url_for('stay_form') }}">+ {{ t('btn_add_stay') }}</a>
</div>
<p class="printinfo" data-grid="reg"></p>
<div class="tablewrap">
{# data-pin: checkbox, Einheit, G-Blatt, Name and Vorname stay on screen while scrolling #}
<table id="reg" class="grid" data-scroll="end" data-pin="1 2 3 4 5">
<thead><tr>
<th class="nosort selcol"></th><th>{{ t('col_unit') }}</th><th>{{ t('col_sheetno') }}</th>
<th>{{ t('col_lastname') }}</th><th>{{ t('col_firstname') }}</th><th>{{ t('col_birth') }}</th>
<th>{{ t('col_residence') }}</th><th>{{ t('col_room') }}</th>
<th>{{ t('col_arrival') }}</th><th>{{ t('col_departure') }}</th><th class="num">{{ t('col_days') }}</th>
<th>{{ t('col_tourism_liable') }}</th><th>{{ t('col_nationality') }}</th><th>{{ t('col_document') }}</th>
<th>{{ t('col_docnr') }}</th><th>{{ t('col_profession') }}</th><th>{{ t('col_address') }}</th>
<th>{{ t('col_postcode') }}</th><th>{{ t('col_city') }}</th><th>{{ t('col_country') }}</th>
<th>{{ t('col_phone') }}</th><th>{{ t('col_employer') }}</th><th>{{ t('col_employer_address') }}</th>
<th>{{ t('col_notes_long') }}</th>
<th class="nosort actions"></th>
</tr></thead>
<tbody>
{# rows ship inside an inert <template>: grids.js renders only the visible window (see freezeLayout) #}
<template>
{% for r in rows %}
<tr>
<td class="selcol"><input type="checkbox" class="rowsel" value="{{ r.stay_id }}"></td>
<td>{% if r.unit %}<span class="pill unit">{{ r.unit }}</span>{% endif %}</td>
<td>{{ r.sheet_number or '' }}</td><td>{{ r.last_name or '' }}</td><td>{{ r.first_name or '' }}</td>
<td>{{ r.birth_date|date }}</td>
<td>{{ 'HWS' if r.hauptwohnsitz == 1 else ('NWS' if r.hauptwohnsitz == 0 else '') }}</td>
<td>{{ r.room or '' }}</td>
{#- the weekday before the date; data-k = the ISO date, or the column would sort by "Mo/Di/Mi" -#}
<td{% if r.arrival %} data-k="{{ r.arrival|replace('-', '') }}"{% endif %}>{{ r.arrival|datewd }}</td><td{% if r.departure %} data-k="{{ r.departure|replace('-', '') }}"{% endif %}>{{ r.departure|datewd }}</td>
<td class="num">{{ r.days if r.days is not none else '' }}</td>
<td>{{ 'Ja' if r.tourism_liable == 1 else ('-' if r.tourism_liable == 0 else '') }}</td>
<td>{{ r.nationality or '' }}</td><td>{{ r.doc_type or '' }}</td>
<td>{{ r.doc_number or '' }}</td><td>{{ r.profession or '' }}</td><td>{{ r.home_street or '' }}</td>
<td>{{ r.home_postcode or '' }}</td><td>{{ r.home_city or '' }}</td><td>{{ r.home_country or '' }}</td>
<td>{{ r.phone or '' }}</td><td>{{ r.employer or '' }}</td>
<td>{{ r.employer_address or '' }}</td>
<td>{{ r.notes or '' }}</td>
<td class="actions"><a class="btn secondary small" href="{{ url_for('stay_form', stay_id=r.stay_id) }}">{{ t('btn_edit') }}</a></td>
</tr>
{% endfor %}
</template>
{% if not rows %}
<tr data-empty="1"><td class="empty" colspan="25">{{ t('no_results') }}</td></tr>
{% endif %}
</tbody>
</table>
</div>
<script>
function invoiceFromSelection() {
var ids = window.gridChecked('reg'); // from the grid model — most rows are not in the DOM
if (!ids.length) { alert('Bitte zuerst Zeilen auswählen.'); return; }
window.location = '{{ url_for("invoice_new") }}?stays=' + ids.join(',');
}
</script>
{% endblock %}