feat(reports): add membership structure breakdown to fees report
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

Shows: total active, paying members, subsidiaries (fee in parent),
resignations, standard vs premium fee tier counts.
All dynamic from database.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-31 11:48:57 +02:00
parent 0936e11fe8
commit 3171ea001a
2 changed files with 66 additions and 0 deletions

View File

@ -219,6 +219,32 @@ def fees_report():
total_companies_with_fees = len(by_company) total_companies_with_fees = len(by_company)
total_active = db.query(Company).filter(Company.status == 'active').count() total_active = db.query(Company).filter(Company.status == 'active').count()
# Membership structure breakdown
subsidiaries_count = db.query(Company).filter(
Company.status == 'active',
Company.fee_included_in_parent == True
).count()
paying_companies = db.query(Company).filter(
Company.status == 'active',
Company.fee_included_in_parent != True
).count()
# Fee tier breakdown: standard (200) vs premium (300+)
from sqlalchemy import func, distinct
standard_fee = 0
premium_fee = 0
for company_id, company_fees in by_company.items():
if company_fees:
avg_amount = sum(float(f.amount) for f in company_fees) / len(company_fees)
if avg_amount > 200:
premium_fee += 1
else:
standard_fee += 1
resigned_count = db.query(Company).filter(
Company.membership_status == 'resigned'
).count()
fully_paid = 0 fully_paid = 0
partially_paid = 0 partially_paid = 0
no_payments = 0 no_payments = 0
@ -279,6 +305,11 @@ def fees_report():
monthly_stats=monthly_stats, monthly_stats=monthly_stats,
months_names=MONTHS_NAMES, months_names=MONTHS_NAMES,
generated_at=datetime.now(), generated_at=datetime.now(),
subsidiaries_count=subsidiaries_count,
paying_companies=paying_companies,
standard_fee=standard_fee,
premium_fee=premium_fee,
resigned_count=resigned_count,
) )
finally: finally:
db.close() db.close()

View File

@ -99,6 +99,41 @@
</div> </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 --> <!-- Categories -->
<div class="section"> <div class="section">
<h2>Podział firm</h2> <h2>Podział firm</h2>