From 9f22f27738bb1756cc08e910357b0a5b2dc1a76c Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Fri, 10 Apr 2026 15:15:10 +0200 Subject: [PATCH] feat: fee analysis with parent/child brands on skladki page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Shows expected fee per company (200 zł for 1 brand, 300 zł for 2+) - Child companies shown with striped "nie dotyczy" tiles - Rate change month displayed (e.g., "I-III: 200 zł, od IV: 300 zł") - Expandable brand list under parent company name - Children grouped after their parent in the table Co-Authored-By: Claude Opus 4.6 (1M context) --- blueprints/board/routes.py | 76 +++++++++++++++++++++++++++--- templates/board/fees_readonly.html | 60 ++++++++++++++++++++--- 2 files changed, 123 insertions(+), 13 deletions(-) diff --git a/blueprints/board/routes.py b/blueprints/board/routes.py index f732075..a25bc17 100644 --- a/blueprints/board/routes.py +++ b/blueprints/board/routes.py @@ -828,16 +828,37 @@ def board_fees(): status_filter = request.args.get('status', '') companies = db.query(Company).filter( - Company.status == 'active', - Company.fee_included_in_parent != True + Company.status == 'active' ).order_by(Company.name).all() fee_query = db.query(MembershipFee).filter(MembershipFee.fee_year == year) fees = {(f.company_id, f.fee_month): f for f in fee_query.all()} - companies_fees = [] + # Build parent/child relationship data for fee calculation + # Fee model: 1 brand = 200 zł (reduced), 2+ brands = 300 zł (standard) + all_companies = db.query(Company).filter(Company.status == 'active').all() + children_by_parent = {} # parent_id -> [(child, created_at)] + for c in all_companies: + if c.parent_company_id: + children_by_parent.setdefault(c.parent_company_id, []).append(c) + + # Build fee data per company + parent_fees_data = [] # non-child companies + child_companies_set = set() + for c in all_companies: + if c.fee_included_in_parent or c.parent_company_id: + child_companies_set.add(c.id) + for company in companies: - company_data = {'company': company, 'months': {}, 'monthly_rate': 0, 'has_data': False} + is_child = company.id in child_companies_set + company_data = { + 'company': company, 'months': {}, 'monthly_rate': 0, + 'has_data': False, 'is_child': is_child, + 'child_brands': [], 'child_count': 0, + 'expected_fees': {}, 'rate_change_month': None, + 'parent_months': {}, + } + for m in range(1, 13): fee = fees.get((company.id, m)) company_data['months'][m] = fee @@ -845,10 +866,51 @@ def board_fees(): company_data['has_data'] = True if not company_data['monthly_rate']: company_data['monthly_rate'] = int(fee.amount) - companies_fees.append(company_data) - # Sort: companies with fee data first - companies_fees.sort(key=lambda cf: (0 if cf.get('has_data') else 1, cf['company'].name)) + if not is_child: + # Parent/standalone: calculate expected fees + child_brands = children_by_parent.get(company.id, []) + company_data['child_brands'] = child_brands + company_data['child_count'] = len(child_brands) + + expected_fees = {} + rate_change_month = None + for m in range(1, 13): + month_date = datetime(year, m, 1) + active_children = sum( + 1 for ch in child_brands + if ch.created_at and ch.created_at.replace(day=1) <= month_date + ) + total_brands = 1 + active_children + expected_fees[m] = 300 if total_brands >= 2 else 200 + if total_brands >= 2 and rate_change_month is None: + rate_change_month = m + company_data['expected_fees'] = expected_fees + company_data['rate_change_month'] = rate_change_month + else: + # Child: copy parent's months for visual reference + if company.parent_company_id: + for m in range(1, 13): + company_data['parent_months'][m] = fees.get((company.parent_company_id, m)) + + parent_fees_data.append(company_data) + + # Sort: non-children with data first, then without data, children will be inserted after parents + non_children = [cf for cf in parent_fees_data if not cf['is_child']] + non_children.sort(key=lambda cf: (0 if cf.get('has_data') else 1, cf['company'].name)) + + # Insert child companies right after their parent + companies_fees = [] + children_by_pid = {} + for cf in parent_fees_data: + if cf['is_child'] and cf['company'].parent_company_id: + children_by_pid.setdefault(cf['company'].parent_company_id, []).append(cf) + + for cf in non_children: + companies_fees.append(cf) + # Add child rows after parent + for child_cf in sorted(children_by_pid.get(cf['company'].id, []), key=lambda x: x['company'].name): + companies_fees.append(child_cf) # Filters if status_filter: diff --git a/templates/board/fees_readonly.html b/templates/board/fees_readonly.html index 9d69183..35f492e 100644 --- a/templates/board/fees_readonly.html +++ b/templates/board/fees_readonly.html @@ -233,6 +233,7 @@ Firma + Stawka IIIIIIIVVVI VIIVIIIIXXXIXII Zaległ.
z lat poprz. @@ -241,28 +242,74 @@ {% set ns = namespace(separator_shown=false) %} {% for cf in companies_fees %} - {% if not cf.has_data and not ns.separator_shown %} + {% if not cf.has_data and not ns.separator_shown and not cf.is_child %} {% set ns.separator_shown = true %} - Firmy bez danych o składkach + Firmy bez danych o składkach {% endif %} + + {% if cf.is_child %} + {# Firma córka — wiersz z przekreślonymi kafelkami #} + + + ↳ {{ cf.company.name }} + firma córka + + + 0 zł + + {% for m in range(1, 13) %} + + {% set parent_fee = cf.parent_months.get(m) %} + + - + + + {% endfor %} + + + + {% else %} + {# Firma normalna lub matka #} {{ cf.company.name }} - {% if cf.monthly_rate and cf.monthly_rate > 200 %} - {{ cf.monthly_rate }} zł + {% if cf.child_count > 0 %} +
+ {{ cf.child_count }} {{ 'marka' if cf.child_count == 1 else ('marki' if cf.child_count <= 4 else 'marek') }} zależnych +
+ {% for ch in cf.child_brands|sort(attribute='name') %} +
{{ ch.name }} (od {{ ch.created_at.strftime('%m/%Y') if ch.created_at else '?' }})
+ {% endfor %} +
+
+ {% endif %} + + + {% if cf.child_count > 0 %} + {% set rate_change_month = cf.rate_change_month %} + {% if rate_change_month and rate_change_month > 1 %} +
+ I-{{ ['','I','II','III','IV','V','VI','VII','VIII','IX','X','XI','XII'][rate_change_month - 1] }}: 200 zł
+ od {{ ['','I','II','III','IV','V','VI','VII','VIII','IX','X','XI','XII'][rate_change_month] }}: 300 zł +
+ {% else %} + 300 zł + {% endif %} + {% else %} + 200 zł {% endif %} {% for m in range(1, 13) %} {% set fee = cf.months.get(m) %} {% if fee %} - + {{ m }}{% if fee.status == 'partial' %}{{ fee.amount_paid|int }}{% endif %} {% else %} - - + - {% endif %} {% endfor %} @@ -275,6 +322,7 @@ {% endif %} + {% endif %} {% endfor %}