Source code for pas.views.settlement_run

from onegov.core.elements import Link
from onegov.core.security import Private
from onegov.pas import _
from onegov.pas import PasApp
from onegov.pas.calculate_pay import calculate_rate
from onegov.pas.collections import SettlementRunCollection,\
    AttendenceCollection, CommissionCollection
from onegov.pas.custom import get_current_rate_set
from onegov.pas.export_single_parliamentarian import\
    generate_parliamentarian_settlement_pdf
from onegov.pas.forms import SettlementRunForm
from onegov.pas.layouts import SettlementRunCollectionLayout
from onegov.pas.layouts import SettlementRunLayout
from onegov.pas.models import (
    SettlementRun,
    Party,
    Commission, ParliamentarianRole,
)
from webob import Response
from decimal import Decimal
from weasyprint import HTML, CSS  # type: ignore[import-untyped]
from weasyprint.text.fonts import (  # type: ignore[import-untyped]
    FontConfiguration)
from onegov.pas.models.attendence import TYPES, Attendence
from onegov.pas.path import SettlementRunExport, SettlementRunAllExport
from onegov.pas.models import Parliamentarian
from onegov.pas.utils import format_swiss_number


from typing import TYPE_CHECKING, Literal
if TYPE_CHECKING:
    from onegov.core.types import RenderData
    from onegov.town6.request import TownRequest
    from datetime import date
[docs] SettlementDataRow = tuple[ 'date', Parliamentarian, str, Decimal, Decimal, Decimal ]
TotalRow = tuple[str, Decimal, Decimal, Decimal, Decimal, Decimal]
[docs] PDF_CSS = """ @page { size: A4; margin: 2.5cm 0.75cm 2cm 0.75cm; /* top right bottom left */ @top-right { content: "Staatskanzlei"; font-family: Helvetica, Arial, sans-serif; font-size: 8pt; } } body { font-family: Helvetica, Arial, sans-serif; font-size: 7pt; line-height: 1.2; } table { border-collapse: collapse; margin-top: 1cm; width: 100%; table-layout: fixed; } /* Journal entries table - updated column widths */ .journal-table th:nth-child(1), /* Date */ .journal-table td:nth-child(1) { width: 20pt; } .journal-table th:nth-child(2), /* Personnel Number */ .journal-table td:nth-child(2) { width: 20pt; } .journal-table th:nth-child(3), /* Person */ .journal-table td:nth-child(3) { width: 80pt; } .journal-table th:nth-child(4), /* Type */ .journal-table td:nth-child(4) { width: 170pt; } .journal-table th:nth-child(5), /* Value */ .journal-table td:nth-child(5), .journal-table th:nth-child(6), /* CHF */ .journal-table td:nth-child(6), .journal-table th:nth-child(7), /* CHF + TZ */ .journal-table td:nth-child(7) { width: 30pt; } /* Party summary table */ .summary-table th:nth-child(1), /* Name */ .summary-table td:nth-child(1) { width: 120pt; } .summary-table th:nth-child(2), /* Meetings */ .summary-table td:nth-child(2), .summary-table th:nth-child(3), /* Expenses */ .summary-table td:nth-child(3), .summary-table th:nth-child(4), /* Total */ .summary-table td:nth-child(4), .summary-table th:nth-child(5), /* COLA */ .summary-table td:nth-child(5), .summary-table th:nth-child(6), /* Final */ .summary-table td:nth-child(6) { width: 60pt; } /* Dark header for title row */ th[colspan="6"] { background-color: #707070; color: white; font-weight: bold; text-align: left; padding: 2pt; border: 1pt solid #000; } th:not([colspan]) { background-color: #d5d7d9; font-weight: bold; text-align: left; padding: 2pt; border: 1pt solid #000; } td { padding: 2pt; border: 1pt solid #000; } tr:nth-child(even):not(.total-row) td { background-color: #f3f3f3; } .numeric { text-align: right; } .total-row { font-weight: bold; background-color: #d5d7d9; } .summary-table { margin-top: 2cm; /* page-break-before: always; */ } """
@PasApp.html( model=SettlementRunCollection, template='settlement_runs.pt', permission=Private )
[docs] def view_settlement_runs( self: SettlementRunCollection, request: 'TownRequest' ) -> 'RenderData': layout = SettlementRunCollectionLayout(self, request) filters = {} filters['active'] = [ Link( text=request.translate(title), active=self.active == value, url=request.link(self.for_filter(active=value)) ) for title, value in ( (_('Active'), True), (_('Inactive'), False) ) ] return { 'add_link': request.link(self, name='new'), 'filters': filters, 'layout': layout, 'settlement_runs': self.query().all(), 'title': layout.title, }
@PasApp.form( model=SettlementRunCollection, name='new', template='form.pt', permission=Private, form=SettlementRunForm )
[docs] def add_settlement_run( self: SettlementRunCollection, request: 'TownRequest', form: SettlementRunForm ) -> 'RenderData | Response': if form.submitted(request): settlement_run = self.add(**form.get_useful_data()) request.success(_('Added a new settlement run')) return request.redirect(request.link(settlement_run)) layout = SettlementRunCollectionLayout(self, request) layout.breadcrumbs.append(Link(_('New'), '#')) return { 'layout': layout, 'title': _('New settlement run'), 'form': form, 'form_width': 'large' }
@PasApp.html( model=SettlementRun, template='settlement_run.pt', permission=Private )
[docs] def view_settlement_run( self: SettlementRun, request: 'TownRequest' ) -> 'RenderData': """ A page where all exports are listed and grouped by category. """ layout = SettlementRunLayout(self, request) session = request.session # Get parties active during settlement run period parties = session.query(Party).filter( Party.id.in_( session.query(ParliamentarianRole.party_id) .join(Parliamentarian) .join(Attendence) .filter( Attendence.date >= self.start, Attendence.date <= self.end ).distinct() ) ).order_by(Party.name) # Get commissions active during settlement run period com = CommissionCollection(session) commissions = com.query().order_by(Commission.name) # Get parliamentarians active during settlement run period parliamentarians = [ p for p in session.query(Parliamentarian).order_by( Parliamentarian.last_name, Parliamentarian.first_name ) if p.active_during(self.start, self.end) ] categories = { 'party': { 'title': _('Settlements by Party'), 'links': [ Link( party.title + ' Total', request.link( SettlementRunExport( self, party, category='party' ), 'run-export' ), ) for party in parties ], }, 'all': { 'title': _('All Settlements'), # Gesamtabrechnung 'links': [ Link( _('All Parties'), request.link( SettlementRunAllExport( settlement_run=self, category='all-parties' ), name='run-export' ), ) ], }, 'commissions': { 'title': _('Settlements by Commission'), 'links': [ Link( commission.title + ' Total', request.link( SettlementRunExport( self, commission, category='commission' ), 'run-export' ), ) for commission in commissions ], }, 'parliamentarians': { 'title': _('Settlements by Parliamentarian'), 'links': [ Link( f'{p.last_name} {p.first_name}', request.link( SettlementRunExport( self, p, category='parliamentarian' ), 'run-export' ), ) for p in parliamentarians ], }, } return { 'layout': layout, 'settlement_run': self, 'categories': categories, 'title': layout.title, }
[docs] def _get_commission_totals( settlement_run: SettlementRun, request: 'TownRequest', commission: Commission ) -> list['TotalRow']: """Get totals for a specific commission grouped by party.""" session = request.session rate_set = get_current_rate_set(session, settlement_run) if not rate_set: return [] # Get all attendences in period for this commission attendences = AttendenceCollection( session, date_from=settlement_run.start, date_to=settlement_run.end, commission_id=str(commission.id) ).query() cola_multiplier = Decimal( str(1 + (rate_set.cost_of_living_adjustment / 100)) ) # Initialize party totals party_totals: dict[str, dict[str, Decimal]] = {} for attendence in attendences: # Get parliamentarian's party current_party = None for role in attendence.parliamentarian.roles: if role.party and (role.end is None or role.end >= settlement_run.start): current_party = role.party break if not current_party: continue party_name = current_party.name if party_name not in party_totals: party_totals[party_name] = { 'Meetings': Decimal('0'), 'Expenses': Decimal('0'), 'Total': Decimal('0'), 'Cost-of-living Allowance': Decimal('0'), 'Final': Decimal('0') } is_president = any(r.role == 'president' for r in attendence.parliamentarian.roles) base_rate = calculate_rate( rate_set=rate_set, attendence_type=attendence.type, duration_minutes=int(attendence.duration), is_president=is_president, commission_type=commission.type ) # Update totals party_totals[party_name]['Meetings'] += Decimal(str(base_rate)) base_total = party_totals[party_name]['Meetings'] cola_amount = base_total * (cola_multiplier - 1) expenses = party_totals[party_name]['Expenses'] + base_total party_totals[party_name]['Total'] = base_total + expenses party_totals[party_name]['Cost-of-living Allowance'] = cola_amount party_totals[party_name]['Final'] = base_total + cola_amount # Convert to sorted list of tuples result = [ ( name, data['Meetings'], data['Expenses'], data['Total'], data['Cost-of-living Allowance'], data['Final'] ) for name, data in sorted(party_totals.items()) ] # Add total row totals = ( sum(r[1] for r in result), # Sessions sum(r[2] for r in result), # Expenses sum(r[3] for r in result), # Total sum(r[4] for r in result), # COLA sum(r[5] for r in result), # Final ) result.append(( f'Total {commission.name}', Decimal(str(totals[0])), Decimal(str(totals[1])), Decimal(str(totals[2])), Decimal(str(totals[3])), Decimal(str(totals[4])) )) return result
[docs] def _get_party_totals_for_export_all( self: SettlementRun, request: 'TownRequest' ) -> list[tuple[str, Decimal, Decimal, Decimal, Decimal, Decimal]]: """Get totals grouped by party.""" session = request.session rate_set = get_current_rate_set(session, self) # Get all attendences in period attendences = AttendenceCollection( session, date_from=self.start, date_to=self.end ) cola_multiplier = Decimal( str(1 + (rate_set.cost_of_living_adjustment / 100)) ) # Initialize party totals party_totals: dict[str, dict[str, Decimal]] = {} # Calculate totals for each party for attendence in attendences.query(): # Get parliamentarian's party current_party = None for role in attendence.parliamentarian.roles: if role.party and (role.end is None or role.end >= self.start): current_party = role.party break if not current_party: continue party_name = current_party.name if party_name not in party_totals: # Name Sitzungen Spesen Total Einträge Teuerungszulagen Auszahlung party_totals[party_name] = { 'Meetings': Decimal('0'), 'Expenses': Decimal('0'), # Always 0 for now (Spesen) 'Total': Decimal('0'), # Expenses + Meetings 'Cost-of-living Allowance': Decimal('0'), # Teuerungszulagen 'Final': Decimal('0') # Auszahlung } # Calculate base rate is_president = any(r.role == 'president' for r in attendence.parliamentarian.roles) base_rate = calculate_rate( rate_set=rate_set, attendence_type=attendence.type, duration_minutes=int(attendence.duration), is_president=is_president, commission_type=( attendence.commission.type if attendence.commission else None ), ) # Update totals party_totals[party_name]['Meetings'] += Decimal(str(base_rate)) base_total = party_totals[party_name]['Meetings'] cola_amount = base_total * (cola_multiplier - 1) expenses = party_totals[party_name]['Expenses'] + base_total party_totals[party_name]['Total'] = base_total + expenses party_totals[party_name]['Cost-of-living Allowance'] = cola_amount party_totals[party_name]['Final'] = base_total + cola_amount # Convert to sorted list of tuples result = [ ( name, data['Meetings'], data['Expenses'], data['Total'], data['Cost-of-living Allowance'], data['Final'] ) for name, data in sorted(party_totals.items()) ] # Add total row totals = ( Decimal(sum(r[1] for r in result)), # Sessions Decimal(sum(r[2] for r in result)), # Expenses Decimal(sum(r[3] for r in result)), # Total Decimal(sum(r[4] for r in result)), # COLA Decimal(sum(r[5] for r in result)), # Final ) result.append(('Total Parteien', *totals)) return result
[docs] def generate_settlement_pdf( settlement_run: SettlementRun, request: 'TownRequest', entity_type: Literal['all', 'commission', 'party', 'parliamentarian'], entity: Commission | Party | Parliamentarian | None = None, ) -> bytes: """ Entry point for almost all settlement PDF generations. """ font_config = FontConfiguration() css = CSS(string=PDF_CSS) if entity_type == 'commission' and isinstance(entity, Commission): settlement_data = _get_commission_settlement_data( settlement_run, request, entity ) totals = _get_commission_totals(settlement_run, request, entity) elif entity_type == 'party' and isinstance(entity, Party): settlement_data = _get_party_settlement_data( settlement_run, request, entity ) totals = get_party_specific_totals(settlement_run, request, entity) elif entity_type == 'all': settlement_data = _get_data_export_all(settlement_run, request) totals = _get_party_totals_for_export_all(settlement_run, request) assert len(totals) > 0 else: raise ValueError(f'Unsupported entity type: {entity_type}') html = _generate_settlement_html( settlement_data=settlement_data, totals=totals, subtitle='Einträge Journal', ) return HTML( string=html.replace(',', "'"), # Swiss number format ).write_pdf(stylesheets=[css], font_config=font_config)
[docs] def _get_commission_settlement_data( settlement_run: SettlementRun, request: 'TownRequest', commission: Commission ) -> list['SettlementDataRow']: """Get settlement data for a specific commission.""" session = request.session rate_set = get_current_rate_set(request.session, settlement_run) if not rate_set: return [] attendences = AttendenceCollection( session, date_from=settlement_run.start, date_to=settlement_run.end ).query().filter( Attendence.commission_id == commission.id ) cola_multiplier = Decimal( str(1 + (rate_set.cost_of_living_adjustment / 100)) ) result = [] for attendence in attendences: is_president = any(r.role == 'president' for r in attendence.parliamentarian.roles) base_rate = calculate_rate( rate_set=rate_set, attendence_type=attendence.type, duration_minutes=int(attendence.duration), is_president=is_president, commission_type=commission.type ) rate_with_cola = Decimal(str(base_rate)) * cola_multiplier result.append(( attendence.date, attendence.parliamentarian, request.translate(TYPES[attendence.type]), attendence.calculate_value(), Decimal(str(base_rate)), rate_with_cola )) return sorted(result, key=lambda x: x[0])
[docs] def _generate_settlement_html( settlement_data: list['SettlementDataRow'], totals: list['TotalRow'], subtitle: str, ) -> str: """Generate HTML for settlement PDF.""" html = f""" <!DOCTYPE html> <html> <head><meta charset="utf-8"></head> <body> <table class="journal-table"> <thead> <tr> <th colspan="7">{subtitle}</th> </tr> <tr> <th>Datum</th> <th>Pers-Nr</th> <th>Person</th> <th>Typ</th> <th>Wert</th> <th>CHF</th> <th>CHF + TZ</th> </tr> </thead> <tbody> """ for settlement_row in settlement_data: name = f'{settlement_row[1].first_name} {settlement_row[1].last_name}' html += f""" <tr> <td>{settlement_row[0].strftime('%d.%m.%Y')}</td> <td>{settlement_row[1].personnel_number}</td> <td>{name}</td> <td>{settlement_row[2]}</td> <td class="numeric">{settlement_row[3]}</td> <td class="numeric">{settlement_row[4]:,.2f}</td> <td class="numeric">{settlement_row[5]:,.2f}</td> </tr> """ html += """ </tbody> </table> <table class="summary-table"> <thead> <tr> <th>Name</th> <th>Sitzungen</th> <th>Spesen</th> <th>Total Einträge</th> <th>Teuerungszulagen</th> <th>Auszahlung</th> </tr> </thead> <tbody> """ for total_row in totals[:-1]: # All but last html += f""" <tr> <td>{total_row[0]}</td> <td class="numeric">{format_swiss_number(total_row[1])}</td> <td class="numeric">{format_swiss_number(total_row[2])}</td> <td class="numeric">{format_swiss_number(total_row[3])}</td> <td class="numeric">{format_swiss_number(total_row[4])}</td> <td class="numeric">{format_swiss_number(total_row[5])}</td> </tr> """ # Handle last row separately with total-row class if totals: final_row = totals[-1] html += f""" <tr class="total-row"> <td>{final_row[0]}</td> <td class="numeric">{format_swiss_number(final_row[1])}</td> <td class="numeric">{format_swiss_number(final_row[2])}</td> <td class="numeric">{format_swiss_number(final_row[3])}</td> <td class="numeric">{format_swiss_number(final_row[4])}</td> <td class="numeric">{format_swiss_number(final_row[5])}</td> </tr> """ html += """ </tbody> </table> </body> </html> """ return html
[docs] def _get_data_export_all( self: SettlementRun, request: 'TownRequest' ) -> list['SettlementDataRow']: session = request.session rate_set = get_current_rate_set(session, self) if not rate_set: return [] # Use collection to get all attendences in period attendences = AttendenceCollection( session, date_from=self.start, date_to=self.end ) cola_multiplier = Decimal( str(1 + (rate_set.cost_of_living_adjustment / 100)) ) settlement_data: list[SettlementDataRow] = [] # Add type hint here # Group by parliamentarian for summary parliamentarian_totals: dict[str, Decimal] = {} for attendence in attendences.query(): is_president = any(r.role == 'president' for r in attendence.parliamentarian.roles) # Calculate base rate base_rate = calculate_rate( rate_set=rate_set, attendence_type=attendence.type, duration_minutes=int(attendence.duration), is_president=is_president, commission_type=( attendence.commission.type if attendence.commission else None ), ) # Apply cost of living adjustment rate_with_cola = Decimal(base_rate * cola_multiplier) # Build description of the session/meeting translated_type = request.translate(TYPES[attendence.type]) if attendence.type == 'plenary': type_desc = translated_type elif attendence.type == 'commission' or attendence.type == 'study': commission_name = ( attendence.commission.name if attendence.commission else '' ) type_desc = f'{translated_type} - {commission_name}' else: # shortest type_desc = translated_type # Add row for this attendence settlement_data.append( ( attendence.date, attendence.parliamentarian, type_desc, attendence.calculate_value(), base_rate, rate_with_cola, ) ) # Update parliamentarian total parliamentarian_id = str(attendence.parliamentarian.id) if parliamentarian_id not in parliamentarian_totals: parliamentarian_totals[parliamentarian_id] = Decimal('0') parliamentarian_totals[parliamentarian_id] += rate_with_cola # Sort by date settlement_data.sort(key=lambda x: x[0]) return settlement_data
[docs] def get_party_specific_totals( settlement_run: SettlementRun, request: 'TownRequest', party: Party ) -> list['TotalRow']: """Get totals for a specific party.""" session = request.session rate_set = get_current_rate_set(session, settlement_run) cola_multiplier = Decimal( str(1 + (rate_set.cost_of_living_adjustment / 100)) ) # Use AttendenceCollection with party filter attendences = ( AttendenceCollection( session, date_from=settlement_run.start, date_to=settlement_run.end, party_id=str(party.id), ) .query() .all() ) parliamentarian_totals: dict[str, dict[str, Decimal]] = {} for attendence in attendences: parliamentarian = attendence.parliamentarian name = f'{parliamentarian.first_name} {parliamentarian.last_name}' if name not in parliamentarian_totals: parliamentarian_totals[name] = { 'Meetings': Decimal('0'), 'Expenses': Decimal('0'), 'Total': Decimal('0'), 'Cost-of-living Allowance': Decimal('0'), 'Final': Decimal('0'), } is_president = any( r.role == 'president' for r in parliamentarian.roles ) base_rate = calculate_rate( rate_set=rate_set, attendence_type=attendence.type, duration_minutes=int(attendence.duration), is_president=is_president, commission_type=( attendence.commission.type if attendence.commission else None ), ) parliamentarian_totals[name]['Meetings'] += Decimal(str(base_rate)) base_total = parliamentarian_totals[name]['Meetings'] cola_amount = base_total * (cola_multiplier - 1) parliamentarian_totals[name]['Total'] = base_total parliamentarian_totals[name][ 'Cost-of-living Allowance' ] = cola_amount parliamentarian_totals[name]['Final'] = base_total + cola_amount result = [ ( name, data['Meetings'], data['Expenses'], data['Total'], data['Cost-of-living Allowance'], data['Final'], ) for name, data in sorted(parliamentarian_totals.items()) ] if result: totals = ( sum(r[1] for r in result), # Meetings sum(r[2] for r in result), # Expenses sum(r[3] for r in result), # Total sum(r[4] for r in result), # COLA sum(r[5] for r in result), # Final ) result.append( ( f'Total {party.name}', Decimal(str(totals[0])), Decimal(str(totals[1])), Decimal(str(totals[2])), Decimal(str(totals[3])), Decimal(str(totals[4])), ) ) return result
[docs] def debug_party_export( settlement_run: SettlementRun, request: 'TownRequest', party: Party ) -> None: """Debug function to trace party export data retrieval""" session = request.session # 1. Check basic party info print(f'Party ID: {party.id}, Name: {party.name}') # 2. Check date range print(f'Date range: {settlement_run.start} to {settlement_run.end}') # 3. Get all attendances without party filter first base_attendances = ( AttendenceCollection(session) .query() .filter( Attendence.date >= settlement_run.start, Attendence.date <= settlement_run.end ) .all() ) print(f'Total attendances in date range: {len(base_attendances)}') # 4. Check parliamentarian roles for attendance in base_attendances: parl = attendance.parliamentarian print(f'\nParliamentarian: {parl.first_name} {parl.last_name}') print(f'Attendance date: {attendance.date}') roles = session.query(ParliamentarianRole).filter( ParliamentarianRole.parliamentarian_id == parl.id, ParliamentarianRole.party_id == party.id, ).all() print('Roles:') for role in roles: print(f'- Start: {role.start}, End: {role.end}') # Check if this attendance should be included should_include = any( (role.start is None or role.start <= attendance.date) and (role.end is None or role.end >= attendance.date) for role in roles ) print(f'Should include: {should_include}') # 5. Try the actual party filter party_attendances = ( AttendenceCollection(session) .by_party( party_id=str(party.id), start_date=settlement_run.start, end_date=settlement_run.end ) .query() .all() ) print(f'\nFinal filtered attendances: {len(party_attendances)}')
[docs] def debug_party_export2( settlement_run: SettlementRun, request: 'TownRequest', party: Party ) -> None: session = request.session print(f'Party ID: {party.id}') # Check roles directly all_roles = session.query(ParliamentarianRole).filter( ParliamentarianRole.party_id == party.id ).all() print(f'\nTotal roles for party: {len(all_roles)}') for role in all_roles: print(f'Role: {role.party_id} -> {role.parliamentarian_id}') # Check all parties all_parties = session.query(Party).all() print('\nAll parties:') for p in all_parties: print(f'ID: {p.id}, Name: {p.name}')
[docs] def _get_party_settlement_data( settlement_run: SettlementRun, request: 'TownRequest', party: Party ) -> list['SettlementDataRow']: """Get settlement data for a specific party.""" session = request.session rate_set = get_current_rate_set(session, settlement_run) # Get all attendences in period attendences = ( AttendenceCollection(session) .by_party( party_id=str(party.id), start_date=settlement_run.start, end_date=settlement_run.end ) .query() .all() ) result = [] for attendence in attendences: current_party = attendence.parliamentarian.get_party_during_period( settlement_run.start, settlement_run.end, session ) if not current_party or current_party.id != party.id: continue # found an export is_president = any(r.role == 'president' for r in attendence.parliamentarian.roles) base_rate = calculate_rate( rate_set=rate_set, attendence_type=attendence.type, duration_minutes=int(attendence.duration), is_president=is_president, commission_type=( attendence.commission.type if attendence.commission else None ) ) cola_multiplier = Decimal( str(1 + (rate_set.cost_of_living_adjustment / 100)) ) rate_with_cola = Decimal(str(base_rate)) * cola_multiplier type_desc = request.translate(TYPES[attendence.type]) if attendence.commission: type_desc = f'{type_desc} - {attendence.commission.name}' result.append(( attendence.date, attendence.parliamentarian, type_desc, attendence.calculate_value(), Decimal(str(base_rate)), rate_with_cola )) return sorted(result, key=lambda x: x[0])
@PasApp.view( model=SettlementRunAllExport, permission=Private, name='run-export', request_method='GET' )
[docs] def view_settlement_run_all_export( self: SettlementRunAllExport, request: 'TownRequest' ) -> Response: """Generate export data for a specific entity in a settlement run.""" if self.category == 'all-parties': pdf_bytes = generate_settlement_pdf( settlement_run=self.settlement_run, request=request, entity_type='all', ) filename = request.translate(_('Total all parties')) return Response( pdf_bytes, content_type='application/pdf', content_disposition=f'attachment; filename={filename}.pdf' ) else: raise NotImplementedError()
@PasApp.view( model=SettlementRunExport, permission=Private, name='run-export', request_method='GET' )
[docs] def view_settlement_run_export( self: SettlementRunExport, request: 'TownRequest' ) -> Response: """Generate export data for a specific entity (commission, party or parliamentarian) in a settlement run.""" if self.category == 'party': assert isinstance(self.entity, Party) pdf_bytes = generate_settlement_pdf( settlement_run=self.settlement_run, request=request, entity_type='party', entity=self.entity, ) filename = f'party_{self.entity.name}' elif self.category == 'commission': assert isinstance(self.entity, Commission) pdf_bytes = generate_settlement_pdf( settlement_run=self.settlement_run, request=request, entity_type='commission', entity=self.entity, ) filename = f'commission_{self.entity.name}' elif self.category == 'parliamentarian': assert isinstance(self.entity, Parliamentarian) # Parliamentarian specific export has it's own rendering function pdf_bytes = generate_parliamentarian_settlement_pdf( self.settlement_run, request, self.entity ) filename = ( f'parliamentarian_{self.entity.last_name}_{self.entity.first_name}' ) return Response( pdf_bytes, content_type='application/pdf', content_disposition=f'attachment; filename={filename}.pdf' ) else: raise NotImplementedError( f'Export category {self.category} not implemented' ) return Response( pdf_bytes, content_type='application/pdf', content_disposition=f'attachment; filename={filename}.pdf' )
@PasApp.form( model=SettlementRun, name='edit', template='form.pt', permission=Private, form=SettlementRunForm )
[docs] def edit_settlement_run( self: SettlementRun, request: 'TownRequest', form: SettlementRunForm ) -> 'RenderData | Response': if form.submitted(request): form.populate_obj(self) request.success(_('Your changes were saved')) return request.redirect(request.link(self)) form.process(obj=self) layout = SettlementRunLayout(self, request) layout.breadcrumbs.append(Link(_('Edit'), '#')) layout.editbar_links = [] return { 'layout': layout, 'title': layout.title, 'form': form, 'form_width': 'large' }
@PasApp.view( model=SettlementRun, request_method='DELETE', permission=Private )
[docs] def delete_settlement_run( self: SettlementRun, request: 'TownRequest' ) -> None: request.assert_valid_csrf_token() collection = SettlementRunCollection(request.session) collection.delete(self)