Skip to content

Commit 51ed082

Browse files
Pas: Granular improvements based on feedback.
TYPE: Feature LINK: OGC-2941
1 parent 3b7d41a commit 51ed082

File tree

10 files changed

+127
-79
lines changed

10 files changed

+127
-79
lines changed

src/onegov/pas/assets/js/custom.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
document.addEventListener("DOMContentLoaded", function () {
22
handleBulkAddCommission();
33
handleAttendanceFormSync();
4+
handleParliamentarianCounter();
45
});
56

67

@@ -115,6 +116,31 @@ function handleBulkAddCommission() {
115116
}
116117

117118

119+
function handleParliamentarianCounter() {
120+
if (!window.location.href.includes('/new-bulk')) {
121+
return;
122+
}
123+
124+
var $list = $('#parliamentarian_id');
125+
if (!$list.length || !$list.is('ul')) {
126+
return;
127+
}
128+
129+
var $counter = $('<li><strong></strong></li>');
130+
$list.prepend($counter);
131+
132+
function update() {
133+
var $boxes = $list.find("input[type='checkbox']");
134+
var total = $boxes.length;
135+
var checked = $boxes.filter(':checked').length;
136+
$counter.find('strong').text(checked + ' / ' + total);
137+
}
138+
139+
update();
140+
$list.on('change', 'input[type="checkbox"]', update);
141+
}
142+
143+
118144
function handleAttendanceFormSync() {
119145
// Pre-restrict interdependent dropdown values to prevent invalid
120146
// combinations. When a user selects a commission or parliamentarian,

src/onegov/pas/export_single_parliamentarian.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from onegov.pas.models.attendence import TYPES
1212
from onegov.pas.utils import is_commission_president
1313
from onegov.pas.utils import format_swiss_number
14+
from onegov.pas.utils import round_to_five_rappen
1415
from onegov.core.utils import module_path
1516
from weasyprint import HTML, CSS # type: ignore[import-untyped]
1617
from weasyprint.text.fonts import ( # type: ignore[import-untyped]
@@ -83,7 +84,8 @@ def generate_parliamentarian_settlement_pdf(
8384
<p>Staatskanzlei, Seestrasse 2, 6300 Zug</p><br>
8485
</div>
8586
<div class="address">
86-
{parliamentarian.formal_greeting}<br>
87+
{parliamentarian.formal_greeting.split()[0]}<br>
88+
{parliamentarian.first_name} {parliamentarian.last_name}<br>
8789
{parliamentarian.shipping_address}<br>
8890
{parliamentarian.shipping_address_zip_code}
8991
{parliamentarian.shipping_address_city}
@@ -123,8 +125,9 @@ def generate_parliamentarian_settlement_pdf(
123125
<td>{entry.date.strftime('%d.%m.%Y')}</td>
124126
<td>{entry.type_description}</td>
125127
<td class="numeric">{format_swiss_number(
126-
entry.calculated_value)}</td>
127-
<td class="numeric">{format_swiss_number(entry.base_rate)}</td>
128+
round_to_five_rappen(entry.calculated_value))}</td>
129+
<td class="numeric">{format_swiss_number(
130+
round_to_five_rappen(entry.base_rate))}</td>
128131
</tr>
129132
"""
130133
if entry.type_description not in ['Total', 'Auszahlung']:
@@ -163,25 +166,29 @@ def generate_parliamentarian_settlement_pdf(
163166
entry.calculated_value
164167
for entry in type_totals[type_key]['entries']
165168
)
169+
total_value_rounded = round_to_five_rappen(total_value)
166170
total_value_str = (
167-
format_swiss_number(total_value) if type_key != 'expenses' else '-'
171+
format_swiss_number(total_value_rounded)
172+
if type_key != 'expenses'
173+
else '-'
168174
)
169175
base_total = type_totals[type_key]['total']
170-
# Apply cost of living adjustment
171176
total_chf = base_total * cola_multiplier
172-
total += total_chf
177+
total_chf_rounded = round_to_five_rappen(total_chf)
178+
total += total_chf_rounded
173179
html += f"""
174180
<tr>
175181
<td>{type_name}</td>
176182
<td class="numeric">{total_value_str}</td>
177-
<td class="numeric">{format_swiss_number(total_chf)}</td>
183+
<td class="numeric">{format_swiss_number(
184+
total_chf_rounded)}</td>
178185
</tr>
179186
"""
180187
html += f"""
181188
<tr class="merge-cells">
182189
<td>Auszahlung</td>
183-
<td colspan="2" class="numeric">{format_swiss_number(total)}
184-
</td>
190+
<td colspan="2" class="numeric">{format_swiss_number(
191+
total)}</td>
185192
</tr>
186193
</tbody>
187194
</table>

src/onegov/pas/theme/styles/pas.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ pre {
245245
transform: scale(1.1);
246246
}
247247

248+
ul#parliamentarian_id {
249+
display: grid;
250+
grid-template-columns: repeat(3, 1fr);
251+
gap: 0;
252+
}
253+
248254
#parliamentarian_id li.animate-in {
249255
animation: bubble-in 0.4s ease forwards;
250256
}

src/onegov/pas/utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from onegov.pas.models.party import Party
88
from onegov.pas.models.parliamentarian import PASParliamentarian
99
from onegov.pas.models.parliamentarian_role import PASParliamentarianRole
10-
from decimal import Decimal
10+
from decimal import Decimal, ROUND_HALF_UP
1111
from babel.numbers import format_decimal
1212
from datetime import date
1313
from uuid import UUID
@@ -32,6 +32,16 @@ def format_swiss_number(value: Decimal | int) -> str:
3232
return format_decimal(value, format='#,##0.00', locale='de_CH')
3333

3434

35+
def round_to_five_rappen(value: Decimal | int) -> Decimal:
36+
"""Round a decimal value to the nearest 5 Rappen (0.05 CHF)."""
37+
if isinstance(value, int):
38+
value = Decimal(value)
39+
40+
return (value / Decimal('0.05')).quantize(
41+
Decimal('1'), rounding=ROUND_HALF_UP
42+
) * Decimal('0.05')
43+
44+
3545
def is_commission_president(
3646
parliamentarian: PASParliamentarian,
3747
attendance_or_commission_id: Attendence | UUID,

src/onegov/pas/views/abschlussliste.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from onegov.pas.utils import (
1414
get_parliamentarians_with_settlements,
1515
is_commission_president,
16+
round_to_five_rappen,
1617
)
1718
from onegov.pas.models.parliamentarian_role import PASParliamentarianRole
1819
from onegov.pas.models.party import Party
@@ -399,10 +400,23 @@ def generate_buchungen_abrechnungslauf_xlsx(
399400
worksheet.write(row_num, 2, row_data['party'], cell_format)
400401
worksheet.write(row_num, 3, row_data['wahlkreis'], cell_format)
401402
worksheet.write(row_num, 4, row_data['booking_type'], cell_format)
402-
worksheet.write(row_num, 5, float(row_data['value']), cell_format)
403-
worksheet.write(row_num, 6, float(row_data['chf']), cell_format)
404403
worksheet.write(
405-
row_num, 7, float(row_data['chf_with_cola']), cell_format
404+
row_num,
405+
5,
406+
float(round_to_five_rappen(row_data['value'])),
407+
cell_format,
408+
)
409+
worksheet.write(
410+
row_num,
411+
6,
412+
float(round_to_five_rappen(row_data['chf'])),
413+
cell_format,
414+
)
415+
worksheet.write(
416+
row_num,
417+
7,
418+
float(round_to_five_rappen(row_data['chf_with_cola'])),
419+
cell_format,
406420
)
407421

408422
# Auto-adjust column widths

src/onegov/pas/views/attendence.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ def add_plenary_attendence(self: AttendenceCollection,
519519
request: PasRequest,
520520
form: AttendenceAddPlenaryForm
521521
) -> RenderData | Response:
522+
request.include('custom')
522523

523524
if not request.is_admin:
524525
request.alert(_('You do not have permission to add plenary sessions.'))

src/onegov/pas/views/pas_excel_export_nr_3_lohnart_fibu.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
AttendenceCollection,
99
)
1010
from onegov.pas.custom import get_current_rate_set
11-
from decimal import Decimal, ROUND_HALF_UP
11+
from decimal import Decimal
1212
from onegov.pas.models.attendence import TYPES
13-
from onegov.pas.utils import is_commission_president
13+
from onegov.pas.utils import is_commission_president, round_to_five_rappen
1414

1515

1616
from typing import TYPE_CHECKING
@@ -117,8 +117,8 @@ def generate_fibu_export_rows(
117117
attendance.commission.type if attendance.commission else None
118118
)
119119
)
120-
rate_with_cola = (Decimal(str(base_rate)) * cola_multiplier).quantize(
121-
Decimal('0.01'), rounding=ROUND_HALF_UP
120+
rate_with_cola = round_to_five_rappen(
121+
Decimal(str(base_rate)) * cola_multiplier
122122
)
123123

124124
# Get fibu konto based on attendance type

src/onegov/pas/views/settlement_run.py

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
get_parliamentarians_with_settlements,
4343
get_parties_with_settlements,
4444
is_commission_president,
45+
round_to_five_rappen,
4546
)
4647
from onegov.pas.views.abschlussliste import (
4748
generate_abschlussliste_xlsx,
@@ -628,17 +629,21 @@ def generate_settlement_pdf(
628629
entity_type: Literal['all', 'commission', 'party', 'parliamentarian'],
629630
entity: PASCommission | Party | PASParliamentarian | None = None,
630631
) -> bytes:
631-
""" Entry point for almost all settlement PDF generations. """
632+
""" Entry point for almost all settlement PDF generations excluding
633+
parliamentarians addressed personally"""
632634
font_config = FontConfiguration()
633635
css_path = module_path('onegov.pas', 'views/templates/settlement_pdf.css')
634636
with open(css_path) as f:
635637
css = CSS(string=f.read())
636638

639+
subtitle = 'Einträge Journal'
640+
637641
if entity_type == 'commission' and isinstance(entity, PASCommission):
638642
settlement_data = _get_commission_settlement_data(
639643
settlement_run, request, entity
640644
)
641645
totals = _get_commission_totals(settlement_run, request, entity)
646+
subtitle = f'Einträge Sitzungen: «{entity.name}»'
642647

643648
elif entity_type == 'party' and isinstance(entity, Party):
644649
settlement_data = _get_party_settlement_data(
@@ -656,7 +661,7 @@ def generate_settlement_pdf(
656661
html = _generate_settlement_html(
657662
settlement_data=settlement_data,
658663
totals=totals,
659-
subtitle='Einträge Journal',
664+
subtitle=subtitle,
660665
)
661666

662667
return HTML(string=html).write_pdf(
@@ -726,35 +731,34 @@ def _generate_settlement_html(
726731
<html>
727732
<head><meta charset="utf-8"></head>
728733
<body>
734+
<div class="table-title">{subtitle}</div>
729735
<table class="journal-table">
730736
<thead>
731737
<tr>
732-
<th colspan="7">{subtitle}</th>
733-
</tr>
734-
<tr>
735-
<th>Datum</th>
736-
<th>Pers-Nr</th>
737-
<th>Person</th>
738-
<th>Typ</th>
739-
<th>Wert</th>
740-
<th>CHF</th>
741-
<th>CHF + TZ</th>
738+
<th style="width:40pt">Datum</th>
739+
<th style="width:90pt">Person</th>
740+
<th style="width:252pt">Typ</th>
741+
<th style="width:15pt">Wert</th>
742+
<th style="width:35pt">CHF</th>
743+
<th style="width:35pt">CHF + TZ</th>
742744
</tr>
743745
</thead>
744746
<tbody>
745747
"""
746748

747749
for settlement_row in settlement_data:
748750
name = f'{settlement_row[1].first_name} {settlement_row[1].last_name}'
751+
chf_rounded = round_to_five_rappen(settlement_row[4])
752+
chf_cola_rounded = round_to_five_rappen(settlement_row[5])
749753
html += f"""
750754
<tr>
751755
<td>{settlement_row[0].strftime('%d.%m.%Y')}</td>
752-
<td>{settlement_row[1].personnel_number}</td>
753756
<td>{name}</td>
754757
<td>{settlement_row[2]}</td>
755758
<td class="numeric">{settlement_row[3]}</td>
756-
<td class="numeric">{settlement_row[4]:,.2f}</td>
757-
<td class="numeric">{settlement_row[5]:,.2f}</td>
759+
<td class="numeric">{format_swiss_number(chf_rounded)}</td>
760+
<td class="numeric">{format_swiss_number(
761+
chf_cola_rounded)}</td>
758762
</tr>
759763
"""
760764

@@ -779,11 +783,16 @@ def _generate_settlement_html(
779783
html += f"""
780784
<tr>
781785
<td>{total_row[0]}</td>
782-
<td class="numeric">{format_swiss_number(total_row[1])}</td>
783-
<td class="numeric">{format_swiss_number(total_row[2])}</td>
784-
<td class="numeric">{format_swiss_number(total_row[3])}</td>
785-
<td class="numeric">{format_swiss_number(total_row[4])}</td>
786-
<td class="numeric">{format_swiss_number(total_row[5])}</td>
786+
<td class="numeric">{format_swiss_number(
787+
round_to_five_rappen(total_row[1]))}</td>
788+
<td class="numeric">{format_swiss_number(
789+
round_to_five_rappen(total_row[2]))}</td>
790+
<td class="numeric">{format_swiss_number(
791+
round_to_five_rappen(total_row[3]))}</td>
792+
<td class="numeric">{format_swiss_number(
793+
round_to_five_rappen(total_row[4]))}</td>
794+
<td class="numeric">{format_swiss_number(
795+
round_to_five_rappen(total_row[5]))}</td>
787796
</tr>
788797
"""
789798

@@ -793,11 +802,16 @@ def _generate_settlement_html(
793802
html += f"""
794803
<tr class="total-row">
795804
<td>{final_row[0]}</td>
796-
<td class="numeric">{format_swiss_number(final_row[1])}</td>
797-
<td class="numeric">{format_swiss_number(final_row[2])}</td>
798-
<td class="numeric">{format_swiss_number(final_row[3])}</td>
799-
<td class="numeric">{format_swiss_number(final_row[4])}</td>
800-
<td class="numeric">{format_swiss_number(final_row[5])}</td>
805+
<td class="numeric">{format_swiss_number(
806+
round_to_five_rappen(final_row[1]))}</td>
807+
<td class="numeric">{format_swiss_number(
808+
round_to_five_rappen(final_row[2]))}</td>
809+
<td class="numeric">{format_swiss_number(
810+
round_to_five_rappen(final_row[3]))}</td>
811+
<td class="numeric">{format_swiss_number(
812+
round_to_five_rappen(final_row[4]))}</td>
813+
<td class="numeric">{format_swiss_number(
814+
round_to_five_rappen(final_row[5]))}</td>
801815
</tr>
802816
"""
803817

src/onegov/pas/views/templates/parliamentarian_settlement_pdf.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@page {
22
size: A4;
3-
margin: 2.5cm 0.75cm 2cm 0.75cm; /* top right bottom left */
3+
margin: 6cm 0.75cm 2cm 3cm; /* top right bottom left */
44
@top-right {
55
content: "Staatskanzlei";
66
font-family: Helvetica, Arial, sans-serif;
@@ -17,19 +17,19 @@ body {
1717
.first-line {
1818
font-size: 7pt;
1919
text-decoration: underline;
20-
margin-left: 1.0cm;
20+
margin-left: 0;
2121
margin-bottom: 0.2cm;
2222
}
2323

2424
.address {
25-
margin-left: 1.0cm;
25+
margin-left: 0;
2626
margin-bottom: 0.5cm;
2727
font-size: 8pt;
2828
line-height: 1.4;
2929
}
3030

3131
.date {
32-
margin-left: 1.0cm;
32+
margin-left: 0;
3333
margin-bottom: 2cm;
3434
font-size: 8pt;
3535
}

0 commit comments

Comments
 (0)