feat(fees): kolumna zaległości z lat poprzednich z edycją inline
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Dodano pole previous_years_debt w modelu Company. Kolumna widoczna w widoku
rocznym składek — kliknięcie kwoty otwiera pole edycji. Legenda zaktualizowana.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-20 15:11:34 +01:00
parent e97f586311
commit 3736065dda
4 changed files with 97 additions and 1 deletions

View File

@ -1144,6 +1144,31 @@ def admin_fees_send_reminder():
db.close()
@bp.route('/fees/update-debt', methods=['POST'])
@login_required
@role_required(SystemRole.OFFICE_MANAGER)
def admin_fees_update_debt():
"""Update previous years debt for a company."""
db = SessionLocal()
try:
company_id = request.form.get('company_id', type=int)
debt = request.form.get('debt', type=float, default=0)
company = db.query(Company).filter_by(id=company_id).first()
if not company:
return jsonify({'success': False, 'error': 'Firma nie znaleziona'}), 404
company.previous_years_debt = debt
db.commit()
return jsonify({'success': True, 'message': f'Zaległość zapisana: {debt:.0f}'})
except Exception as e:
db.rollback()
logger.error(f"Error updating debt: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
# ============================================================
# CALENDAR ADMIN ROUTES
# ============================================================

View File

@ -839,6 +839,7 @@ class Company(Base):
norda_biznes_url = Column(String(500))
norda_biznes_member_id = Column(String(50))
member_since = Column(Date) # Data przystąpienia do Izby NORDA
previous_years_debt = Column(Numeric(10, 2), default=0) # Zaległości z lat poprzednich (ręcznie wpisane)
# Metadata
last_updated = Column(DateTime, default=datetime.now)

View File

@ -0,0 +1,7 @@
-- Migration 088: Add previous_years_debt column to companies
-- Manual field for tracking arrears from previous years
ALTER TABLE companies ADD COLUMN IF NOT EXISTS previous_years_debt NUMERIC(10,2) DEFAULT 0;
-- Grant permissions
GRANT ALL ON TABLE companies TO nordabiz_app;

View File

@ -294,6 +294,7 @@
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell pending" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">1</span> Oczekujące</span>
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell overdue" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">1</span> Zaległe</span>
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell empty" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">-</span> Brak danych</span>
<span style="display: flex; align-items: center; gap: 4px; border-left: 1px solid var(--border); padding-left: var(--spacing-lg);"><span style="color:var(--error);font-weight:600;font-size:12px;">500 zł</span> Zaległości z lat poprzednich (kliknij kwotę aby edytować)</span>
</div>
<!-- Filters -->
@ -358,6 +359,7 @@
{% else %}
<th>Sty</th><th>Lut</th><th>Mar</th><th>Kwi</th><th>Maj</th><th>Cze</th>
<th>Lip</th><th>Sie</th><th>Wrz</th><th>Paź</th><th>Lis</th><th>Gru</th>
<th title="Zaległości z lat poprzednich — wpisywane ręcznie">Zaległości</th>
<th>Przypomnienie</th>
<th></th>
{% endif %}
@ -368,7 +370,7 @@
{% for cf in companies_fees %}
{% if not month and not cf.has_data and not ns.separator_shown %}
{% set ns.separator_shown = true %}
<tr><td colspan="15" style="background: var(--border); padding: var(--spacing-xs); text-align: center; font-size: var(--font-size-sm); color: var(--text-secondary); font-weight: 600;">Firmy bez danych o składkach</td></tr>
<tr><td colspan="16" style="background: var(--border); padding: var(--spacing-xs); text-align: center; font-size: var(--font-size-sm); color: var(--text-secondary); font-weight: 600;">Firmy bez danych o składkach</td></tr>
{% endif %}
<tr{% if not month and not cf.has_data %} style="opacity: 0.5;"{% endif %}>
{% if month %}
@ -425,6 +427,14 @@
{% endif %}
</td>
{% endfor %}
<td style="white-space:nowrap;">
{% set debt = cf.company.previous_years_debt|default(0)|float %}
{% if debt > 0 %}
<span class="debt-value" style="cursor:pointer;color:var(--error);font-weight:600;font-size:12px;" onclick="editDebt(this, {{ cf.company.id }}, {{ debt }})" title="Kliknij aby edytować">{{ debt|int }} zł</span>
{% else %}
<span class="debt-value" style="cursor:pointer;color:var(--text-secondary);font-size:11px;" onclick="editDebt(this, {{ cf.company.id }}, 0)" title="Kliknij aby wpisać zaległość"></span>
{% endif %}
</td>
<td style="font-size:11px;white-space:nowrap;">
{% if cf.reminder %}
{% if cf.reminder.is_read %}
@ -713,6 +723,59 @@ async function generateFees() {
}
}
// 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 = '<span class="debt-value" style="cursor:pointer;color:var(--error);font-weight:600;font-size:12px;" onclick="editDebt(this,' + companyId + ',' + val + ')" title="Kliknij aby edytować">' + val + ' zł</span>';
} else {
td.innerHTML = '<span class="debt-value" style="cursor:pointer;color:var(--text-secondary);font-size:11px;" onclick="editDebt(this,' + companyId + ',0)" title="Kliknij aby wpisać zaległość"></span>';
}
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) {