Skip to content
Merged

PAS #2353

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/onegov/pas/assets/js/custom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
document.addEventListener("DOMContentLoaded", function () {
handleBulkAddCommission();
handleAttendanceFormSync();
handleParliamentarianCounter();
});


Expand Down Expand Up @@ -115,6 +116,31 @@ function handleBulkAddCommission() {
}


function handleParliamentarianCounter() {
if (!window.location.href.includes('/new-bulk')) {
return;
}

var $list = $('#parliamentarian_id');
if (!$list.length || !$list.is('ul')) {
return;
}

var $counter = $('<li><strong></strong></li>');
$list.prepend($counter);

function update() {
var $boxes = $list.find("input[type='checkbox']");
var total = $boxes.length;
var checked = $boxes.filter(':checked').length;
$counter.find('strong').text(checked + ' / ' + total);
}

update();
$list.on('change', 'input[type="checkbox"]', update);
}


function handleAttendanceFormSync() {
// Pre-restrict interdependent dropdown values to prevent invalid
// combinations. When a user selects a commission or parliamentarian,
Expand Down
25 changes: 16 additions & 9 deletions src/onegov/pas/export_single_parliamentarian.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from onegov.pas.models.attendence import TYPES
from onegov.pas.utils import is_commission_president
from onegov.pas.utils import format_swiss_number
from onegov.pas.utils import round_to_five_rappen
from onegov.core.utils import module_path
from weasyprint import HTML, CSS # type: ignore[import-untyped]
from weasyprint.text.fonts import ( # type: ignore[import-untyped]
Expand Down Expand Up @@ -83,7 +84,8 @@ def generate_parliamentarian_settlement_pdf(
<p>Staatskanzlei, Seestrasse 2, 6300 Zug</p><br>
</div>
<div class="address">
{parliamentarian.formal_greeting}<br>
{parliamentarian.formal_greeting.split()[0]}<br>
{parliamentarian.first_name} {parliamentarian.last_name}<br>
{parliamentarian.shipping_address}<br>
{parliamentarian.shipping_address_zip_code}
{parliamentarian.shipping_address_city}
Expand Down Expand Up @@ -123,8 +125,9 @@ def generate_parliamentarian_settlement_pdf(
<td>{entry.date.strftime('%d.%m.%Y')}</td>
<td>{entry.type_description}</td>
<td class="numeric">{format_swiss_number(
entry.calculated_value)}</td>
<td class="numeric">{format_swiss_number(entry.base_rate)}</td>
round_to_five_rappen(entry.calculated_value))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(entry.base_rate))}</td>
</tr>
"""
if entry.type_description not in ['Total', 'Auszahlung']:
Expand Down Expand Up @@ -163,25 +166,29 @@ def generate_parliamentarian_settlement_pdf(
entry.calculated_value
for entry in type_totals[type_key]['entries']
)
total_value_rounded = round_to_five_rappen(total_value)
total_value_str = (
format_swiss_number(total_value) if type_key != 'expenses' else '-'
format_swiss_number(total_value_rounded)
if type_key != 'expenses'
else '-'
)
base_total = type_totals[type_key]['total']
# Apply cost of living adjustment
total_chf = base_total * cola_multiplier
total += total_chf
total_chf_rounded = round_to_five_rappen(total_chf)
total += total_chf_rounded
html += f"""
<tr>
<td>{type_name}</td>
<td class="numeric">{total_value_str}</td>
<td class="numeric">{format_swiss_number(total_chf)}</td>
<td class="numeric">{format_swiss_number(
total_chf_rounded)}</td>
</tr>
"""
html += f"""
<tr class="merge-cells">
<td>Auszahlung</td>
<td colspan="2" class="numeric">{format_swiss_number(total)}
</td>
<td colspan="2" class="numeric">{format_swiss_number(
total)}</td>
</tr>
</tbody>
</table>
Expand Down
6 changes: 6 additions & 0 deletions src/onegov/pas/theme/styles/pas.scss
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ pre {
transform: scale(1.1);
}

ul#parliamentarian_id {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0;
}

#parliamentarian_id li.animate-in {
animation: bubble-in 0.4s ease forwards;
}
Expand Down
12 changes: 11 additions & 1 deletion src/onegov/pas/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from onegov.pas.models.party import Party
from onegov.pas.models.parliamentarian import PASParliamentarian
from onegov.pas.models.parliamentarian_role import PASParliamentarianRole
from decimal import Decimal
from decimal import Decimal, ROUND_HALF_UP
from babel.numbers import format_decimal
from datetime import date
from uuid import UUID
Expand All @@ -32,6 +32,16 @@ def format_swiss_number(value: Decimal | int) -> str:
return format_decimal(value, format='#,##0.00', locale='de_CH')


def round_to_five_rappen(value: Decimal | int) -> Decimal:
"""Round a decimal value to the nearest 5 Rappen (0.05 CHF)."""
if isinstance(value, int):
value = Decimal(value)

return (value / Decimal('0.05')).quantize(
Decimal('1'), rounding=ROUND_HALF_UP
) * Decimal('0.05')


def is_commission_president(
parliamentarian: PASParliamentarian,
attendance_or_commission_id: Attendence | UUID,
Expand Down
20 changes: 17 additions & 3 deletions src/onegov/pas/views/abschlussliste.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from onegov.pas.utils import (
get_parliamentarians_with_settlements,
is_commission_president,
round_to_five_rappen,
)
from onegov.pas.models.parliamentarian_role import PASParliamentarianRole
from onegov.pas.models.party import Party
Expand Down Expand Up @@ -399,10 +400,23 @@ def generate_buchungen_abrechnungslauf_xlsx(
worksheet.write(row_num, 2, row_data['party'], cell_format)
worksheet.write(row_num, 3, row_data['wahlkreis'], cell_format)
worksheet.write(row_num, 4, row_data['booking_type'], cell_format)
worksheet.write(row_num, 5, float(row_data['value']), cell_format)
worksheet.write(row_num, 6, float(row_data['chf']), cell_format)
worksheet.write(
row_num, 7, float(row_data['chf_with_cola']), cell_format
row_num,
5,
float(round_to_five_rappen(row_data['value'])),
cell_format,
)
worksheet.write(
row_num,
6,
float(round_to_five_rappen(row_data['chf'])),
cell_format,
)
worksheet.write(
row_num,
7,
float(round_to_five_rappen(row_data['chf_with_cola'])),
cell_format,
)

# Auto-adjust column widths
Expand Down
1 change: 1 addition & 0 deletions src/onegov/pas/views/attendence.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ def add_plenary_attendence(self: AttendenceCollection,
request: PasRequest,
form: AttendenceAddPlenaryForm
) -> RenderData | Response:
request.include('custom')

if not request.is_admin:
request.alert(_('You do not have permission to add plenary sessions.'))
Expand Down
8 changes: 4 additions & 4 deletions src/onegov/pas/views/pas_excel_export_nr_3_lohnart_fibu.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
AttendenceCollection,
)
from onegov.pas.custom import get_current_rate_set
from decimal import Decimal, ROUND_HALF_UP
from decimal import Decimal
from onegov.pas.models.attendence import TYPES
from onegov.pas.utils import is_commission_president
from onegov.pas.utils import is_commission_president, round_to_five_rappen


from typing import TYPE_CHECKING
Expand Down Expand Up @@ -117,8 +117,8 @@ def generate_fibu_export_rows(
attendance.commission.type if attendance.commission else None
)
)
rate_with_cola = (Decimal(str(base_rate)) * cola_multiplier).quantize(
Decimal('0.01'), rounding=ROUND_HALF_UP
rate_with_cola = round_to_five_rappen(
Decimal(str(base_rate)) * cola_multiplier
)

# Get fibu konto based on attendance type
Expand Down
64 changes: 39 additions & 25 deletions src/onegov/pas/views/settlement_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
get_parliamentarians_with_settlements,
get_parties_with_settlements,
is_commission_president,
round_to_five_rappen,
)
from onegov.pas.views.abschlussliste import (
generate_abschlussliste_xlsx,
Expand Down Expand Up @@ -628,17 +629,21 @@ def generate_settlement_pdf(
entity_type: Literal['all', 'commission', 'party', 'parliamentarian'],
entity: PASCommission | Party | PASParliamentarian | None = None,
) -> bytes:
""" Entry point for almost all settlement PDF generations. """
""" Entry point for almost all settlement PDF generations excluding
parliamentarians addressed personally"""
font_config = FontConfiguration()
css_path = module_path('onegov.pas', 'views/templates/settlement_pdf.css')
with open(css_path) as f:
css = CSS(string=f.read())

subtitle = 'Einträge Journal'

if entity_type == 'commission' and isinstance(entity, PASCommission):
settlement_data = _get_commission_settlement_data(
settlement_run, request, entity
)
totals = _get_commission_totals(settlement_run, request, entity)
subtitle = f'Einträge Sitzungen: «{entity.name}»'

elif entity_type == 'party' and isinstance(entity, Party):
settlement_data = _get_party_settlement_data(
Expand All @@ -656,7 +661,7 @@ def generate_settlement_pdf(
html = _generate_settlement_html(
settlement_data=settlement_data,
totals=totals,
subtitle='Einträge Journal',
subtitle=subtitle,
)

return HTML(string=html).write_pdf(
Expand Down Expand Up @@ -726,35 +731,34 @@ def _generate_settlement_html(
<html>
<head><meta charset="utf-8"></head>
<body>
<div class="table-title">{subtitle}</div>
<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>
<th style="width:40pt">Datum</th>
<th style="width:90pt">Person</th>
<th style="width:252pt">Typ</th>
<th style="width:15pt">Wert</th>
<th style="width:35pt">CHF</th>
<th style="width:35pt">CHF + TZ</th>
</tr>
</thead>
<tbody>
"""

for settlement_row in settlement_data:
name = f'{settlement_row[1].first_name} {settlement_row[1].last_name}'
chf_rounded = round_to_five_rappen(settlement_row[4])
chf_cola_rounded = round_to_five_rappen(settlement_row[5])
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>
<td class="numeric">{format_swiss_number(chf_rounded)}</td>
<td class="numeric">{format_swiss_number(
chf_cola_rounded)}</td>
</tr>
"""

Expand All @@ -779,11 +783,16 @@ def _generate_settlement_html(
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>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(total_row[1]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(total_row[2]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(total_row[3]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(total_row[4]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(total_row[5]))}</td>
</tr>
"""

Expand All @@ -793,11 +802,16 @@ def _generate_settlement_html(
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>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(final_row[1]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(final_row[2]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(final_row[3]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(final_row[4]))}</td>
<td class="numeric">{format_swiss_number(
round_to_five_rappen(final_row[5]))}</td>
</tr>
"""

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@page {
size: A4;
margin: 2.5cm 0.75cm 2cm 0.75cm; /* top right bottom left */
margin: 6cm 0.75cm 2cm 3cm; /* top right bottom left */
@top-right {
content: "Staatskanzlei";
font-family: Helvetica, Arial, sans-serif;
Expand All @@ -17,19 +17,19 @@ body {
.first-line {
font-size: 7pt;
text-decoration: underline;
margin-left: 1.0cm;
margin-left: 0;
margin-bottom: 0.2cm;
}

.address {
margin-left: 1.0cm;
margin-left: 0;
margin-bottom: 0.5cm;
font-size: 8pt;
line-height: 1.4;
}

.date {
margin-left: 1.0cm;
margin-left: 0;
margin-bottom: 2cm;
font-size: 8pt;
}
Expand Down
Loading
Loading