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
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS (57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash commands, memory files, architecture docs, and deploy procedures. Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted 155 .strftime() calls across 71 templates so timestamps display in Polish timezone regardless of server timezone. Also includes: created_by_id tracking, abort import fix, ICS calendar fix for missing end times, Pros Poland data cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
202 lines
9.7 KiB
HTML
202 lines
9.7 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Raport składek {{ year }} - Norda Biznes Partner{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.report-container { max-width: 900px; margin: 0 auto; }
|
|
.report-header { margin-bottom: var(--spacing-xl); }
|
|
.report-header h1 { font-size: var(--font-size-2xl); }
|
|
.report-meta { font-size: var(--font-size-sm); color: var(--text-secondary); margin-top: var(--spacing-xs); }
|
|
|
|
.stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--spacing-md); margin-bottom: var(--spacing-xl); }
|
|
.stat-box { background: var(--surface); padding: var(--spacing-md); border-radius: var(--radius-lg); box-shadow: var(--shadow); text-align: center; border-top: 3px solid var(--border); }
|
|
.stat-box.green { border-top-color: var(--success); }
|
|
.stat-box.orange { border-top-color: var(--warning); }
|
|
.stat-box.red { border-top-color: var(--error); }
|
|
.stat-box.blue { border-top-color: var(--primary); }
|
|
.stat-num { font-size: var(--font-size-2xl); font-weight: 700; color: var(--text-primary); }
|
|
.stat-label { font-size: var(--font-size-xs); color: var(--text-secondary); margin-top: 2px; }
|
|
|
|
.section { background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-lg); box-shadow: var(--shadow); margin-bottom: var(--spacing-xl); }
|
|
.section h2 { font-size: var(--font-size-lg); margin-bottom: var(--spacing-md); }
|
|
|
|
.progress-bar-bg { background: #e5e7eb; border-radius: 999px; height: 28px; overflow: hidden; position: relative; }
|
|
.progress-bar-fill { height: 100%; border-radius: 999px; display: flex; align-items: center; justify-content: center; font-size: var(--font-size-sm); font-weight: 600; color: white; transition: width 0.5s; }
|
|
.progress-bar-fill.green { background: var(--success); }
|
|
.progress-bar-fill.orange { background: var(--warning); }
|
|
.progress-bar-fill.red { background: var(--error); }
|
|
|
|
.month-row { display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm) 0; border-bottom: 1px solid var(--border); }
|
|
.month-row:last-child { border-bottom: none; }
|
|
.month-name { width: 90px; font-weight: 500; font-size: var(--font-size-sm); }
|
|
.month-bar { flex: 1; }
|
|
.month-nums { width: 180px; text-align: right; font-size: var(--font-size-sm); color: var(--text-secondary); }
|
|
|
|
.category-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-md); }
|
|
.cat-card { padding: var(--spacing-md); border-radius: var(--radius); border: 1px solid var(--border); }
|
|
.cat-card h3 { font-size: var(--font-size-base); margin-bottom: var(--spacing-xs); }
|
|
.cat-num { font-size: var(--font-size-2xl); font-weight: 700; }
|
|
.cat-num.green { color: var(--success); }
|
|
.cat-num.orange { color: var(--warning); }
|
|
.cat-num.red { color: var(--error); }
|
|
.cat-num.blue { color: #3b82f6; }
|
|
|
|
@media (max-width: 640px) {
|
|
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
|
.category-grid { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="report-container">
|
|
<div class="report-header">
|
|
<a href="{{ url_for('reports.reports_index') }}" style="color: var(--text-secondary); text-decoration: none; font-size: var(--font-size-sm);">← Raporty</a>
|
|
<h1>Składki członkowskie {{ year }}</h1>
|
|
<div class="report-meta">
|
|
Wygenerowano: {{ generated_at|local_time('%d.%m.%Y %H:%M') }} |
|
|
<select onchange="location.href='?year='+this.value" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:2px 6px;font-size:var(--font-size-sm);">
|
|
{% for y in years %}<option value="{{ y }}" {{ 'selected' if y == year }}>{{ y }}</option>{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Execution bar -->
|
|
<div class="section">
|
|
<h2>Wykonanie roczne</h2>
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:var(--spacing-xs);font-size:var(--font-size-sm);">
|
|
<span>Zebrano: <strong>{{ total_paid }} zł</strong></span>
|
|
<span>Do zebrania: <strong>{{ outstanding }} zł</strong></span>
|
|
</div>
|
|
<div class="progress-bar-bg">
|
|
<div class="progress-bar-fill {{ 'green' if execution_pct >= 70 else 'orange' if execution_pct >= 40 else 'red' }}" style="width: {{ execution_pct }}%; min-width: 40px;">
|
|
{{ execution_pct }}%
|
|
</div>
|
|
</div>
|
|
<div style="text-align:center;margin-top:var(--spacing-xs);font-size:var(--font-size-sm);color:var(--text-secondary);">
|
|
Plan roczny: {{ total_due }} zł
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary stats -->
|
|
<div class="stats-row">
|
|
<div class="stat-box blue">
|
|
<div class="stat-num">{{ total_companies_with_fees }}</div>
|
|
<div class="stat-label">Firm z danymi</div>
|
|
</div>
|
|
<div class="stat-box green">
|
|
<div class="stat-num">{{ fully_paid }}</div>
|
|
<div class="stat-label">Opłacone w całości</div>
|
|
</div>
|
|
<div class="stat-box orange">
|
|
<div class="stat-num">{{ partially_paid }}</div>
|
|
<div class="stat-label">Częściowo opłacone</div>
|
|
</div>
|
|
<div class="stat-box red">
|
|
<div class="stat-num">{{ no_payments }}</div>
|
|
<div class="stat-label">Brak wpłat</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Membership structure -->
|
|
<div class="section">
|
|
<h2>Struktura członków Izby</h2>
|
|
<div class="stats-row">
|
|
<div class="stat-box blue">
|
|
<div class="stat-num">{{ total_active }}</div>
|
|
<div class="stat-label">Wszystkie aktywne firmy</div>
|
|
</div>
|
|
<div class="stat-box green">
|
|
<div class="stat-num">{{ paying_companies }}</div>
|
|
<div class="stat-label">Płacące składki</div>
|
|
</div>
|
|
<div class="stat-box" style="border-top-color: #8b5cf6;">
|
|
<div class="stat-num" style="color: #8b5cf6;">{{ subsidiaries_count }}</div>
|
|
<div class="stat-label">Podspółki (składka w spółce głównej)</div>
|
|
</div>
|
|
<div class="stat-box red">
|
|
<div class="stat-num">{{ resigned_count }}</div>
|
|
<div class="stat-label">Rezygnacje</div>
|
|
</div>
|
|
</div>
|
|
<div class="category-grid" style="margin-top: var(--spacing-md);">
|
|
<div class="cat-card">
|
|
<h3>Składka standardowa (200 zł/mies.)</h3>
|
|
<div class="cat-num" style="color: #3b82f6;">{{ standard_fee }}</div>
|
|
<div class="stat-label">firm</div>
|
|
</div>
|
|
<div class="cat-card">
|
|
<h3>Składka podwyższona (300 zł/mies.)</h3>
|
|
<div class="cat-num" style="color: #8b5cf6;">{{ premium_fee }}</div>
|
|
<div class="stat-label">firm</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories -->
|
|
<div class="section">
|
|
<h2>Podział firm</h2>
|
|
<div class="category-grid">
|
|
<div class="cat-card">
|
|
<h3>Opłacone w całości</h3>
|
|
<div class="cat-num green">{{ fully_paid }}</div>
|
|
<div class="stat-label">firm uregulowało składki za cały rok</div>
|
|
</div>
|
|
<div class="cat-card">
|
|
<h3>Częściowo opłacone</h3>
|
|
<div class="cat-num orange">{{ partially_paid }}</div>
|
|
<div class="stat-label">firm ma wpłaty, ale nie za wszystkie miesiące</div>
|
|
</div>
|
|
<div class="cat-card">
|
|
<h3>Brak wpłat</h3>
|
|
<div class="cat-num red">{{ no_payments }}</div>
|
|
<div class="stat-label">firm nie dokonało żadnej wpłaty</div>
|
|
</div>
|
|
<div class="cat-card">
|
|
<h3>Nieprawidłowe kwoty</h3>
|
|
<div class="cat-num blue">{{ wrong_amounts }}</div>
|
|
<div class="stat-label">firm z niedopłatami (niepełne kwoty)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Monthly breakdown -->
|
|
<div class="section">
|
|
<h2>Wykonanie miesięczne</h2>
|
|
{% for ms in monthly_stats %}
|
|
<div class="month-row">
|
|
<div class="month-name">{{ months_names[ms.month] }}</div>
|
|
<div class="month-bar">
|
|
<div class="progress-bar-bg" style="height:20px;">
|
|
<div class="progress-bar-fill {{ 'green' if ms.pct >= 70 else 'orange' if ms.pct >= 40 else 'red' }}" style="width:{{ ms.pct }}%;min-width:30px;font-size:11px;">
|
|
{{ ms.pct }}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="month-nums">{{ ms.paid }} / {{ ms.due }} zł</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Key numbers -->
|
|
<div class="section" style="text-align:center;">
|
|
<h2>Podsumowanie</h2>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:var(--spacing-lg);margin-top:var(--spacing-md);">
|
|
<div>
|
|
<div style="font-size:var(--font-size-3xl);font-weight:700;color:var(--success);">{{ total_paid }} zł</div>
|
|
<div class="stat-label">Zebrano</div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:var(--font-size-3xl);font-weight:700;color:var(--warning);">{{ outstanding }} zł</div>
|
|
<div class="stat-label">Pozostało do zebrania</div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:var(--font-size-3xl);font-weight:700;color:var(--primary);">{{ total_due }} zł</div>
|
|
<div class="stat-label">Plan roczny</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|