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
@@ -0,0 +1,123 @@
|
||||
{% 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>
|
||||
<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 %}
|
||||
Reference in new issue
Block a user