{% extends "base.html" %} {% block title %}Składki Członkowskie - Norda Biznes Partner{% endblock %} {% block extra_css %} {% endblock %} {% block content %}

Składki Członkowskie

{{ total_companies }}
Firm członkowskich
{{ paid_count }}
Opłaconych{% if month %} w tym miesiącu{% else %} składek (łącznie){% endif %}
{{ pending_count }}
Oczekujących{% if month %} w tym miesiącu{% else %} składek (łącznie){% endif %}
{{ total_paid|int }} zł
Zebrano
{{ (total_due - total_paid)|int }} zł
Do zebrania
Opłacone 1 Niepełna wpłata 1 Oczekujące Zaległości z lat poprzednich (kliknij aby wpisać lub edytować)
{% if month %} {% endif %}

Lista firm {% if month %}({{ dict(months).get(month, month) }} {{ year }}){% else %}({{ year }}){% endif %} {{ companies_fees|length }}

{% if month %} {% endif %}
{% if month %}{% endif %} {% if month %} {% else %} {% endif %} {% set ns = namespace(separator_shown=false) %} {% for cf in companies_fees %} {% if not month and not cf.has_data and not ns.separator_shown %} {% set ns.separator_shown = true %} {% endif %} {% if month %} {% else %} {% for m in range(1, 13) %} {% endfor %} {% endif %} {% endfor %}
FirmaStatus Kwota Zapłacono Data płatności AkcjeIIIIIIIVVVI VIIVIIIIXXXIXII Zaległ.
z lat poprz.
Firmy bez danych o składkach
{% if cf.fee %} {% endif %} {{ cf.company.name }} {% if cf.status == 'paid' %}Opłacone {% elif cf.status == 'pending' %}Oczekuje {% elif cf.status == 'overdue' %}Zaległe {% elif cf.status == 'partial' %}Częściowe {% else %}Brak {% endif %} {% if cf.fee %}{{ cf.fee.amount }} zł{% else %}-{% endif %} {% if cf.fee and cf.fee.amount_paid %}{{ cf.fee.amount_paid }} zł{% else %}-{% endif %} {% if cf.fee and cf.fee.payment_date %}{{ cf.fee.payment_date }}{% else %}-{% endif %} {% if cf.fee and cf.status != 'paid' %} {% elif not cf.fee %} Brak rekordu {% endif %} {{ cf.company.name }} {% if cf.monthly_rate and cf.monthly_rate > 200 %} {{ cf.monthly_rate }} zł {% endif %} {% set fee = cf.months.get(m) %} {% if fee %} {{ m }}{% if fee.status == 'partial' %}{{ fee.amount_paid|int }}{% endif %} {% else %} - {% endif %} {% set debt = cf.company.previous_years_debt|default(0)|float %} {% if debt > 0 %} {{ debt|int }} zł {% else %} + wpisz {% endif %} {% if cf.reminder %} {% if cf.reminder.is_read %} {% else %} {% endif %} {% endif %} {% if cf.has_data and cf.months.values()|selectattr('status', 'in', ['pending', 'partial', 'overdue'])|list %} {% endif %}
{% endblock %} {% block extra_js %} // Modal system let confirmResolve = null; function showConfirm(message, options = {}) { return new Promise(resolve => { confirmResolve = resolve; document.getElementById('confirmModalIcon').textContent = options.icon || '❓'; document.getElementById('confirmModalTitle').textContent = options.title || 'Potwierdzenie'; document.getElementById('confirmModalMessage').innerHTML = message; document.getElementById('confirmModalOk').textContent = options.okText || 'OK'; document.getElementById('confirmModalOk').className = 'btn ' + (options.okClass || 'btn-primary'); document.getElementById('confirmModal').classList.add('active'); }); } function closeConfirm(result) { document.getElementById('confirmModal').classList.remove('active'); if (confirmResolve) { confirmResolve(result); confirmResolve = null; } } document.getElementById('confirmModalOk').addEventListener('click', () => closeConfirm(true)); document.getElementById('confirmModalCancel').addEventListener('click', () => closeConfirm(false)); document.getElementById('confirmModal').addEventListener('click', e => { if (e.target.id === 'confirmModal') closeConfirm(false); }); function showToast(message, type = 'info', duration = 4000) { const container = document.getElementById('toastContainer'); const icons = { success: '✓', error: '✕', warning: '⚠', info: 'ℹ' }; const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.innerHTML = `${icons[type]||'ℹ'}${message}`; container.appendChild(toast); setTimeout(() => { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, duration); } async function generateFees() { const confirmed = await showConfirm('Czy na pewno chcesz wygenerować rekordy składek dla wszystkich firm na wybrany miesiąc?', { icon: '📋', title: 'Generowanie składek', okText: 'Generuj', okClass: 'btn-success' }); if (!confirmed) return; const formData = new FormData(); formData.append('year', {{ year }}); formData.append('month', {{ month or 'null' }}); try { const response = await fetch('{{ url_for("admin.admin_fees_generate") }}', { method: 'POST', body: formData, headers: { 'X-CSRFToken': '{{ csrf_token() }}' } }); const data = await response.json(); if (data.success) { showToast(data.message, 'success'); setTimeout(() => location.reload(), 1500); } else { showToast('Błąd: ' + data.error, 'error'); } } catch (err) { showToast('Błąd: ' + err, 'error'); } } function openPaymentModal(feeId, companyName, amount) { document.getElementById('modalFeeId').value = feeId; document.getElementById('modalCompanyName').value = companyName; document.getElementById('modalAmount').value = amount; document.getElementById('modalDate').value = new Date().toISOString().split('T')[0]; document.getElementById('paymentModal').classList.add('active'); } function closePaymentModal() { document.getElementById('paymentModal').classList.remove('active'); } document.getElementById('paymentForm').addEventListener('submit', async function(e) { e.preventDefault(); const feeId = document.getElementById('modalFeeId').value; const formData = new FormData(this); try { const response = await fetch('/admin/fees/' + feeId + '/mark-paid', { method: 'POST', body: formData, headers: { 'X-CSRFToken': '{{ csrf_token() }}' } }); const data = await response.json(); if (data.success) { closePaymentModal(); showToast(data.message, 'success'); setTimeout(() => location.reload(), 1500); } else { showToast('Błąd: ' + data.error, 'error'); } } catch (err) { showToast('Błąd: ' + err, 'error'); } }); function toggleSelectAll() { const selectAll = document.getElementById('selectAll'); const checkboxes = document.querySelectorAll('.fee-checkbox:not(:disabled)'); checkboxes.forEach(cb => cb.checked = selectAll.checked); } async function bulkMarkPaid() { const checkboxes = document.querySelectorAll('.fee-checkbox:checked'); if (checkboxes.length === 0) { showToast('Zaznacz przynajmniej jedną składkę', 'warning'); return; } const confirmed = await showConfirm(`Czy na pewno chcesz oznaczyć ${checkboxes.length} składek jako opłacone?`, { icon: '💰', title: 'Oznaczanie płatności', okText: 'Oznacz', okClass: 'btn-success' }); if (!confirmed) return; const formData = new FormData(); checkboxes.forEach(cb => formData.append('fee_ids[]', cb.value)); try { const response = await fetch('{{ url_for("admin.admin_fees_bulk_mark_paid") }}', { method: 'POST', body: formData, headers: { 'X-CSRFToken': '{{ csrf_token() }}' } }); const data = await response.json(); if (data.success) { showToast(data.message, 'success'); setTimeout(() => location.reload(), 1500); } else { showToast('Błąd: ' + data.error, 'error'); } } catch (err) { showToast('Błąd: ' + err, 'error'); } } // Inline debt editing function editDebt(el, companyId, currentDebt) { const td = el.parentElement; const input = document.createElement('input'); input.type = 'number'; input.value = currentDebt || ''; input.placeholder = '0'; input.style.cssText = 'width:80px;padding:2px 4px;font-size:12px;border:1px solid var(--primary);border-radius:4px;text-align:right;'; input.min = '0'; input.step = '1'; td.innerHTML = ''; td.appendChild(input); const suffix = document.createElement('span'); suffix.textContent = ' zł'; suffix.style.fontSize = '11px'; td.appendChild(suffix); input.focus(); input.select(); async function saveDebt() { const val = parseFloat(input.value) || 0; const fd = new FormData(); fd.append('company_id', companyId); fd.append('debt', val); try { const resp = await fetch('{{ url_for("admin.admin_fees_update_debt") }}', { method: 'POST', body: fd, headers: {'X-CSRFToken': '{{ csrf_token() }}'} }); const data = await resp.json(); if (data.success) { if (val > 0) { td.innerHTML = '' + val + ' zł'; } else { td.innerHTML = '+ wpisz'; } showToast(data.message, 'success'); } else { showToast(data.error, 'error'); } } catch (err) { showToast('Błąd: ' + err, 'error'); } } input.addEventListener('blur', saveDebt); input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); input.blur(); } if (e.key === 'Escape') { location.reload(); } }); } // Close modal on outside click document.getElementById('paymentModal').addEventListener('click', function(e) { if (e.target === this) { closePaymentModal(); } }); // Reminder functions async function openReminderModal(companyId, companyName, year) { var fd = new FormData(); fd.append('company_id', companyId); fd.append('year', year); try { var resp = await fetch('/admin/fees/reminder-preview', { method: 'POST', body: fd, headers: {'X-CSRFToken': '{{ csrf_token() }}'} }); var data = await resp.json(); if (!data.success) { showToast(data.error, 'error'); return; } document.getElementById('reminderCompanyName').textContent = data.company_name; document.getElementById('reminderAmount').textContent = data.total_due; document.getElementById('reminderPeriod').textContent = data.period; document.getElementById('reminderMessagePreview').innerHTML = data.message; // Populate portal recipient dropdown var portalSel = document.getElementById('reminderPortalRecipient'); portalSel.innerHTML = ''; (data.linked_users || []).forEach(function(u) { var opt = document.createElement('option'); opt.value = u.id; opt.textContent = u.name + (u.role ? ' (' + u.role + ')' : ''); portalSel.appendChild(opt); }); if (data.manager_user_id) portalSel.value = data.manager_user_id; // Populate email checkboxes var emailBox = document.getElementById('reminderEmailCheckboxes'); emailBox.innerHTML = ''; var emails = data.available_emails || []; emails.forEach(function(e, i) { var label = document.createElement('label'); label.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer;'; var cb = document.createElement('input'); cb.type = 'checkbox'; cb.className = 'reminder-email-cb'; cb.value = e.email; cb.checked = (i === 0); label.appendChild(cb); label.appendChild(document.createTextNode(e.label)); emailBox.appendChild(label); }); if (emails.length === 0) { emailBox.innerHTML = 'Brak adresów email w systemie'; } document.getElementById('reminderEmailCustom').value = ''; document.getElementById('reminderCompanyId').value = companyId; document.getElementById('reminderSubject').value = data.subject; document.getElementById('reminderMessage').value = data.message; document.getElementById('reminderModal').style.display = 'flex'; } catch(e) { showToast('Błąd: ' + e, 'error'); } } function closeReminderModal() { document.getElementById('reminderModal').style.display = 'none'; } async function sendReminder() { var btn = document.getElementById('reminderSendBtn'); btn.disabled = true; btn.textContent = 'Wysyłanie...'; var fd = new FormData(); fd.append('company_id', document.getElementById('reminderCompanyId').value); fd.append('manager_user_id', document.getElementById('reminderPortalRecipient').value); fd.append('subject', document.getElementById('reminderSubject').value); fd.append('message', document.getElementById('reminderMessage').value); // Collect all checked emails + custom var selectedEmails = []; document.querySelectorAll('.reminder-email-cb:checked').forEach(function(cb) { selectedEmails.push(cb.value); }); var custom = document.getElementById('reminderEmailCustom').value.trim(); if (custom && selectedEmails.indexOf(custom) === -1) { selectedEmails.push(custom); } if (selectedEmails.length > 0) { fd.append('send_email', 'on'); fd.append('company_email', selectedEmails.join(',')); } try { var resp = await fetch('/admin/fees/send-reminder', { method: 'POST', body: fd, headers: {'X-CSRFToken': '{{ csrf_token() }}'} }); var data = await resp.json(); if (data.success) { closeReminderModal(); showToast(data.message, 'success'); setTimeout(function() { location.reload(); }, 1500); } else { showToast(data.error, 'error'); } } catch(e) { showToast('Błąd: ' + e, 'error'); } btn.disabled = false; btn.textContent = 'Wyślij przypomnienie'; } document.getElementById('reminderModal').addEventListener('click', function(e) { if (e.target === this) closeReminderModal(); }); {% endblock %}