121 lines
7.8 KiB
HTML
121 lines
7.8 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}<i class="fa-solid fa-square-plus me-2 text-primary"></i>{{ 'Edytuj fakturę' if editing_invoice else 'Wystaw fakturę' }}{% endblock %}
|
|
{% block content %}
|
|
{% set eyebrow='Sprzedaż' %}{% set heading=('Edytuj fakturę' if editing_invoice else 'Wystaw fakturę') %}{% set description='Widok uproszczony, dopasowany do stylu panelu administracyjnego.' %}
|
|
{% include 'partials/page_header.html' with context %}
|
|
<div class="row g-4">
|
|
<div class="col-xl-8">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
{% if read_only_mode %}<div class="alert alert-warning">Tryb tylko do odczytu - wystawianie faktur jest zablokowane.</div>{% endif %}
|
|
<form method="post">
|
|
{{ form.hidden_tag() }}
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
{{ form.customer_id.label(class='form-label mb-0') }}
|
|
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#customerQuickAddModal"><i class="fa-solid fa-plus me-1"></i>Dodaj</button>
|
|
</div>
|
|
{{ form.customer_id(class='form-select', disabled=read_only_mode) }}
|
|
<div class="form-text"><a href="{{ url_for('invoices.customers') }}">Pełna kartoteka klientów</a></div>
|
|
</div>
|
|
<div class="col-md-6">{{ form.numbering_template.label(class='form-label') }}{{ form.numbering_template(class='form-select', disabled=read_only_mode) }}</div>
|
|
<div class="col-md-6">{{ form.invoice_number.label(class='form-label') }}{{ form.invoice_number(class='form-control', disabled=read_only_mode, placeholder=preview_number) }}<div class="form-text">Puste pole = numer zostanie nadany automatycznie.</div></div>
|
|
<div class="col-md-6 d-flex align-items-end"><div class="alert alert-secondary w-100 mb-0 small">Proponowany numer: <strong>{{ preview_number or '—' }}</strong></div></div>
|
|
<div class="col-md-6">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
{{ form.product_id.label(class='form-label mb-0') }}
|
|
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#productQuickAddModal"><i class="fa-solid fa-plus me-1"></i>Dodaj</button>
|
|
</div>
|
|
<select class="form-select" id="productField" name="{{ form.product_id.name }}" {% if read_only_mode %}disabled{% endif %}>
|
|
{% for product in products %}
|
|
<option value="{{ product.id }}" data-net-price="{{ product.net_price }}" data-vat-rate="{{ product.vat_rate }}" data-split-payment="{{ 'true' if product.split_payment_default else 'false' }}" {{ 'selected' if form.product_id.data == product.id else '' }}>{{ product.name }} - {{ product.net_price }} PLN</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="form-text"><a href="{{ url_for('invoices.products') }}">Pełna kartoteka towarów i usług</a></div>
|
|
</div>
|
|
<div class="col-md-3">{{ form.quantity.label(class='form-label') }}{{ form.quantity(class='form-control', disabled=read_only_mode, id='quantityField') }}</div>
|
|
<div class="col-md-3">{{ form.unit_net.label(class='form-label') }}{{ form.unit_net(class='form-control', disabled=read_only_mode, id='unitNetField') }}</div>
|
|
<div class="col-12">
|
|
<div class="form-check form-switch">
|
|
{{ form.split_payment(class='form-check-input', disabled=read_only_mode, id='splitPaymentField') }}
|
|
{{ form.split_payment.label(class='form-check-label') }}
|
|
</div>
|
|
<div class="form-text" id="splitPaymentHint">Domyślnie włączane dla usług oznaczonych w kartotece. Dla faktur powyżej 15 000 PLN brutto jest wymuszane.</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 d-flex gap-2 flex-wrap">{% if editing_invoice %}<button class="btn btn-outline-primary" name="save_submit" type="submit" {% if read_only_mode %}disabled{% endif %}>Zapisz zmiany</button><button class="btn btn-primary" name="submit" type="submit" {% if read_only_mode %}disabled{% endif %}>Zapisz i wyślij do KSeF</button>{% else %}{{ form.save_submit(class='btn btn-outline-primary', disabled=read_only_mode) }}{{ form.submit(class='btn btn-primary', disabled=read_only_mode) }}{% endif %}</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-4">
|
|
<div class="card h-100">
|
|
<div class="card-header"><i class="fa-solid fa-circle-info me-2"></i>Podpowiedzi</div>
|
|
<div class="card-body small text-secondary d-flex flex-column gap-3">
|
|
<div>Dodawanie klientów działa teraz przez wspólne formularze dostępne także w formularzu NFZ. (jeśli moduł włączony)</div>
|
|
<div>Po zapisaniu nowy klient lub towar zostanie automatycznie podstawiony do formularza.</div>
|
|
<div>Pełne kartoteki nadal są dostępne z linków pod polami wyboru.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% set quick_return_endpoint = 'invoices.issued_edit' if editing_invoice else 'invoices.issued_new' %}
|
|
{% set quick_invoice_id = editing_invoice.id if editing_invoice else None %}
|
|
{% include 'partials/invoice_quick_add_modals.html' %}
|
|
<script>
|
|
(() => {
|
|
const productField = document.getElementById('productField');
|
|
const quantityField = document.getElementById('quantityField');
|
|
const unitNetField = document.getElementById('unitNetField');
|
|
const splitPaymentField = document.getElementById('splitPaymentField');
|
|
const splitPaymentHint = document.getElementById('splitPaymentHint');
|
|
if (!productField || !quantityField || !unitNetField || !splitPaymentField) return;
|
|
|
|
let userModifiedSplit = false;
|
|
|
|
const parseNumber = (value) => {
|
|
const normalized = String(value || '').replace(/\s+/g, '').replace(',', '.');
|
|
const parsed = Number.parseFloat(normalized);
|
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
};
|
|
|
|
const syncSplitPayment = (resetToProductDefault = false) => {
|
|
const selected = productField.options[productField.selectedIndex];
|
|
const vatRate = parseNumber(selected?.dataset?.vatRate || 0);
|
|
const defaultSplit = (selected?.dataset?.splitPayment || 'false') === 'true';
|
|
const gross = parseNumber(quantityField.value) * parseNumber(unitNetField.value) * (1 + vatRate / 100);
|
|
const forced = gross > 15000;
|
|
if (forced) {
|
|
splitPaymentField.checked = true;
|
|
splitPaymentField.disabled = true;
|
|
if (splitPaymentHint) splitPaymentHint.textContent = 'Split payment jest wymagany, ponieważ wartość brutto faktury przekracza 15 000 PLN.';
|
|
return;
|
|
}
|
|
splitPaymentField.disabled = {{ 'true' if read_only_mode else 'false' }};
|
|
if (resetToProductDefault || !userModifiedSplit) {
|
|
splitPaymentField.checked = defaultSplit;
|
|
}
|
|
if (splitPaymentHint) {
|
|
splitPaymentHint.textContent = defaultSplit
|
|
? 'Dla wybranej usługi split payment jest domyślnie włączony. Możesz go zmienić dla tej faktury.'
|
|
: 'Split payment możesz włączyć ręcznie. Dla faktur powyżej 15 000 PLN brutto jest wymuszany.';
|
|
}
|
|
};
|
|
|
|
productField.addEventListener('change', () => {
|
|
const selected = productField.options[productField.selectedIndex];
|
|
if (selected?.dataset?.netPrice && !unitNetField.value) unitNetField.value = selected.dataset.netPrice;
|
|
userModifiedSplit = false;
|
|
syncSplitPayment(true);
|
|
});
|
|
quantityField.addEventListener('input', () => syncSplitPayment(false));
|
|
unitNetField.addEventListener('input', () => syncSplitPayment(false));
|
|
splitPaymentField.addEventListener('change', () => {
|
|
userModifiedSplit = true;
|
|
});
|
|
syncSplitPayment(false);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|