Files
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

124 lines
7.6 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block content %}
<style>
/* keep all 9 line columns visible without horizontal scrolling */
#lines input[type=date] { width: 132px; }
#lines td, #lines th { padding: 6px 4px; }
</style>
<h1>{{ t('form_edit_invoice') if editing else t('form_new_invoice') }}</h1>
<datalist id="dl_client">{% for v in (suggest|default({})).client|default([]) %}<option value="{{ v }}">{% endfor %}</datalist>
<form class="card" method="post" style="max-width:1240px">
<div class="grid2">
<div class="field"><label>{{ t('lbl_unit') }} *</label>
<select name="unit_id" onchange="setNumber(true)">
{% for u in units %}
<option value="{{ u.id }}" {{ 'selected' if form.get('unit_id')|string == u.id|string }}>{{ u.code }} – {{ u.name }}</option>
{% endfor %}
</select></div>
<div class="field"><label>{{ t('lbl_client') }} *</label>
<input type="text" name="client" required value="{{ form.get('client','') }}" list="dl_client"></div>
</div>
{# a recipient without an address: the companies the name may mean, or the deliberate exception #}
{% if client_choices is not none or form.get('confirm_client') %}
<div class="confirm-order client-choices">
<div>{{ t('lbl_client_choices' if client_choices else 'lbl_client_unknown', name=form.get('client','')) }}</div>
{% for c in client_choices or [] %}
<button type="button" class="btn secondary small" onclick="pickClient(this)" data-name="{{ c.name }}">
{{ c.name }} – {{ c.address }}, {{ c.postcode }} {{ c.city }}</button>
{% endfor %}
<label><input type="checkbox" name="confirm_client" value="1" {{ 'checked' if form.get('confirm_client') }}>
{{ t('lbl_confirm_client') }}</label>
</div>
{% endif %}
<div class="grid2">
<div class="field"><label>{{ t('lbl_invoice_no') }}</label>
<input type="text" name="number" value="{{ form.get('number','') }}"></div>
<div class="field"><label>{{ t('lbl_invoice_date') }}</label>
<input type="date" name="date" value="{{ form.get('date') or today|default('') }}"></div>
</div>
{# a date against the number order: the warning stands at the top, here only the deliberate exception #}
{# kept while ticked, so answering the other warning does not bring this one back #}
{% if order_warning or form.get('confirm_order') %}
<label class="confirm-order"><input type="checkbox" name="confirm_order" value="1" {{ 'checked' if form.get('confirm_order') }}> {{ t('lbl_confirm_order') }}</label>
{% endif %}
<div class="grid2">
<div class="field"><label>{{ t('lbl_paid_date') }}</label>
<input type="date" name="paid_date" value="{{ form.get('paid_date') or '' }}"></div>
<div class="field"><label>{{ t('lbl_payment_method') }}</label>
<input type="text" name="payment_method" value="{{ form.get('payment_method') or '' }}"></div>
</div>
<div class="field"><label>{{ t('lbl_invoice_note') }}</label>
<textarea name="note" rows="2" style="width:100%">{{ form.get('note') if form.get('note') is not none else t('invoice_note_default') }}</textarea></div>
{% if form.get('link_charges') %}<input type="hidden" name="link_charges" value="{{ form.get('link_charges') }}">{% endif %}
<h3 style="margin:18px 0 8px">{{ t('lbl_select_charges') }}</h3>
<div class="tablewrap" style="max-height:none">
<table id="lines">
<thead><tr>
<th style="min-width:200px">{{ t('col_name') }} *</th><th>{{ t('col_from') }}</th>
<th>{{ t('col_to') }}</th><th class="num">{{ t('col_qty') }}</th><th>{{ t('col_unit_type') }}</th>
<th>{{ t('col_art') }}</th><th class="num">{{ t('col_gross_per_unit') }} *</th>
<th class="num">{{ t('col_tourism') }}</th><th></th>
</tr></thead>
<tbody>
{% for ln in (lines or [none]) %}
<tr class="linerow">
{# 'or ""': '{{ x if ln }}' renders the STRING 'None' for an empty field and it would be
saved back as literal text (the number/date inputs just drop it, the text input keeps it) #}
<td><input type="text" name="worker" style="width:100%" value="{{ ln.worker or '' if ln else '' }}"></td>
<td><input type="date" name="period_from" value="{{ ln.period_from or '' if ln else '' }}"></td>
<td><input type="date" name="period_to" value="{{ ln.period_to or '' if ln else '' }}"></td>
<td><input type="number" name="quantity" step="any" min="0" style="width:70px" class="num" value="{{ ln.quantity if ln and ln.quantity is not none else '' }}"></td>
<td><select name="unit_type">
<option value="week" {{ 'selected' if ln and ln.unit_type=='week' }}>{{ t('unit_types').week }}</option>
<option value="day" {{ 'selected' if ln and ln.unit_type=='day' }}>{{ t('unit_types').day }}</option>
<option value="night" {{ 'selected' if ln and ln.unit_type=='night' }}>{{ t('unit_types').night }}</option>
<option value="month" {{ 'selected' if ln and ln.unit_type=='month' }}>{{ t('unit_types').month }}</option>
<option value="total" {{ 'selected' if ln and ln.unit_type=='total' }}>{{ t('unit_types').total }}</option></select></td>
<td><select name="kind">
<option value="stay" {{ 'selected' if not ln or ln.kind!='storno' }}>{{ t('line_kinds').stay }}</option>
<option value="storno" {{ 'selected' if ln and ln.kind=='storno' }}>{{ t('line_kinds').storno }}</option></select></td>
<td><input type="number" name="gross_per_unit" step="0.01" min="0" style="width:90px" class="num" value="{{ ln.gross_per_unit if ln and ln.gross_per_unit is not none else '' }}"></td>
<td><input type="number" name="tourism" step="0.01" min="0" style="width:80px" class="num" value="{{ ln.tourism_tax if ln and ln.tourism_tax is not none else '' }}"></td>
<td><button type="button" class="btn secondary small" onclick="removeLine(this)" title="{{ t('btn_remove_line') }}">✕</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p><button class="btn secondary small" type="button" onclick="addLine()">{{ t('btn_add_line') }}</button>
&nbsp;<small style="color:#6b756e">{{ t('hint_invoice_tourism') }}</small></p>
<div class="formactions">
<button class="btn" type="submit">{{ t('btn_save') }}</button>
<a class="btn secondary" href="{{ url_for('invoices') }}">{{ t('btn_cancel') }}</a>
</div>
</form>
<script>
const SUGG = {{ suggestions|tojson }};
function setNumber(force) {
const nr = document.querySelector('[name=number]');
const v = SUGG[document.querySelector('[name=unit_id]').value];
if (v && (force || !nr.value)) nr.value = v; // don't wipe an existing number on edit (no suggestions there)
}
function addLine() {
const row = document.querySelector('#lines tbody .linerow');
const copy = row.cloneNode(true);
copy.querySelectorAll('input').forEach(i => i.value = '');
copy.querySelectorAll('select').forEach(s => s.selectedIndex = 0);
document.querySelector('#lines tbody').appendChild(copy);
}
function pickClient(btn) {
btn.form.client.value = btn.dataset.name;
btn.parentNode.querySelectorAll('button').forEach(b => b.classList.toggle('picked', b === btn));
}
function removeLine(btn) {
const rows = document.querySelectorAll('#lines tbody .linerow');
if (rows.length > 1) btn.closest('tr').remove();
else btn.closest('tr').querySelectorAll('input').forEach(i => i.value = '');
}
setNumber(false);
</script>
{% endblock %}