Files
vermietungs-tool/app/templates/quality.html
T
BlackMarkandClaude Opus 5.5 45424e6fd6 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>
2026-11-02 16:52:27 +01:00

86 lines
5.7 KiB
HTML

{% extends "base.html" %}
{% block content %}
<h1>{{ t('quality_title') }}</h1>
<p class="lead">{{ t('quality_intro') }}</p>
<h2 style="margin:26px 0 6px">{{ t('quality_gaps') }}</h2>
{% macro gaptable(title, rows, cols) %}
{# rows carrying _kind/_key (set in quality.py) get a per-row "Passt so" button: the finding is
reviewed-as-correct and stays hidden — keyed by natural key, not by row id #}
{% set dismissable = rows and rows[0]._kind is defined %}
<details style="margin:8px 0" {% if rows|length and rows|length <= 12 %}open{% endif %}>
<summary style="cursor:pointer; font-weight:600">{{ title }}
<span class="pill {{ 'open' if rows|length else 'paid' }}">{{ rows|length }}</span></summary>
{% if rows %}
<div class="tablewrap" style="max-height:280px; margin-top:6px">
<table>
<thead><tr>{% for c in cols %}<th>{{ c }}</th>{% endfor %}{% if dismissable %}<th></th>{% endif %}</tr></thead>
<tbody>
{% for r in rows[:80] %}
<tr>{% for c in cols %}<td>{{ r[c] if r[c] is not none else '' }}</td>{% endfor %}
{% if dismissable %}
<td><form method="post" action="{{ url_for('quality_dismiss') }}" style="margin:0">
<input type="hidden" name="kind" value="{{ r._kind }}"><input type="hidden" name="key" value="{{ r._key }}">
<button class="btn secondary small" type="submit">{{ t('btn_dismiss') }}</button></form></td>
{% endif %}</tr>
{% endfor %}
{% if rows|length > 80 %}<tr><td colspan="{{ cols|length + (1 if dismissable else 0) }}">{{ t('quality_more', n=rows|length - 80) }}</td></tr>{% endif %}
</tbody>
</table>
</div>
{% endif %}
</details>
{% endmacro %}
{{ gaptable(t('gap_depart_before_arrival'), q.gaps.depart_before_arrival,
['stay_id','source_sheet','name','arrival','departure']) }}
{{ gaptable(t('gap_charges_without_money'), q.gaps.charges_without_money,
['charge_id','room','name','period_from','period_to']) }}
{{ gaptable(t('gap_client_without_address'), q.gaps.client_without_address,
['name','as_payer','as_employer']) }}
{{ gaptable(t('gap_address_has_name'), q.gaps.address_has_name,
['client_id','company','name','address','postcode','city']) }}
{{ gaptable(t('gap_implausible_qty'), q.gaps.implausible_quantities,
['charge_id','room','name','period_from','period_to','quantity','amount']) }}
{{ gaptable(t('gap_rnr_unlinked'), q.gaps.rnr_unlinked, ['charge_id','room','name','period_from','rnr']) }}
{{ gaptable(t('gap_duplicate_numbers'), q.gaps.duplicate_invoice_numbers, ['unit','number','n','ids']) }}
{{ gaptable(t('gap_number_year'), q.gaps.number_year_mismatch, ['unit','number','date']) }}
{{ gaptable(t('gap_open_items'), q.gaps.open_items, ['room','name','period_from','amount']) }}
{{ gaptable(t('gap_register_ledger'), q.gaps.register_ledger_mismatch,
['room','name','sheet_number','arrival','departure','ledger_from','ledger_to']) }}
{{ gaptable(t('gap_gb_ref_unknown'), q.gaps.gb_ref_unknown,
['charge_id','room','name','period_from','gb_ref','missing']) }}
{{ gaptable(t('gap_never_billed'), q.gaps.register_never_billed,
['sheet_number','room','name','arrival','departure']) }}
{{ gaptable(t('gap_betrag_formula'), q.gaps.betrag_off_formula,
['room','name','period_from','quantity','gross_per_unit','persons','betrag','calc']) }}
{{ gaptable(t('gap_tvb_stale_open'), q.gaps.tvb_stale_open, ['room','name','sheet_number','arrival']) }}
{{ gaptable(t('gap_no_departure'), q.gaps.register_no_departure, ['name','arrival']) }}
<h2 style="margin:26px 0 6px">{{ t('quality_suggestions') }}</h2>
<p style="color:#6b756e; font-size:13px">{{ t('quality_suggestions_hint') }}</p>
{% for s in q.suggestions.companies %}
<form method="post" action="{{ url_for('quality_merge_companies') }}" style="margin:4px 0; display:flex; gap:8px; align-items:center"
onsubmit="return confirm('{{ t('confirm_merge') }}')">
<input type="hidden" name="keep" value="{{ s.a_id }}"><input type="hidden" name="drop" value="{{ s.b_id }}">
<span><b>{{ s.a }}</b> ~ <b>{{ s.b }}</b></span>
<button class="btn secondary small" type="submit">{{ t('btn_merge') }} → {{ s.a }}</button>
<a class="btn secondary small" href="{{ url_for('client_edit', company_id=s.a_id) }}">{{ t('btn_open') }}</a>
<button class="btn secondary small" type="submit" form="dp-c{{ loop.index }}">{{ t('btn_dismiss') }}</button>
</form>
<form id="dp-c{{ loop.index }}" method="post" action="{{ url_for('quality_dismiss') }}" style="display:none">
<input type="hidden" name="kind" value="{{ s._kind }}"><input type="hidden" name="key" value="{{ s._key }}"></form>
{% else %}<p>{{ t('quality_no_company_suggestions') }}</p>{% endfor %}
{% for s in q.suggestions.guests %}
<form method="post" action="{{ url_for('quality_merge_guests') }}" style="margin:4px 0; display:flex; gap:8px; align-items:center"
onsubmit="return confirm('{{ t('confirm_merge') }}')">
<input type="hidden" name="keep" value="{{ s.a_id }}"><input type="hidden" name="drop" value="{{ s.b_id }}">
<span>{{ t('quality_guest') }}: <b>{{ s.a }}</b> ~ <b>{{ s.b }}</b></span>
<button class="btn secondary small" type="submit">{{ t('btn_merge') }} → {{ s.a }}</button>
<button class="btn secondary small" type="submit" form="dp-g{{ loop.index }}">{{ t('btn_dismiss') }}</button>
</form>
<form id="dp-g{{ loop.index }}" method="post" action="{{ url_for('quality_dismiss') }}" style="display:none">
<input type="hidden" name="kind" value="{{ s._kind }}"><input type="hidden" name="key" value="{{ s._key }}"></form>
{% endfor %}
{% endblock %}