Source code for org.pricing_schemes.stadtschulen_zug

from __future__ import annotations

from datetime import timedelta
from decimal import Decimal
from onegov.form import move_fields
from onegov.form.fields import FieldTable
from onegov.org import _, log
from onegov.reservation import Resource, ResourcePricingScheme
from wtforms.fields import DecimalField
from wtforms.validators import InputRequired, NumberRange


from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
    from collections.abc import Collection
    from onegov.form import Form
    from onegov.org.request import OrgRequest
    from onegov.reservation import Reservation


[docs] CATEGORIES = ('A', 'B', 'C')
[docs] class StadtschulenZug( ResourcePricingScheme, name='stadtschulen_zug', label='Freizeitbetreuungsanlagen' ): @classmethod
[docs] def reservation_unit_price( cls, reservation: Reservation, resource: Resource, submission_data: dict[str, Any] | None ) -> Decimal | None: if submission_data is None: return None category = submission_data.get('kategorie') if category: category = category[0] if category not in CATEGORIES: log.error( f'Reservation for pricing scheme stadtschulen_zug is missing ' f'a valid category, we were given {category}. This probably ' f'means we have a validation bug for this pricing scheme. We ' f'will fallback to category C.' ) category = 'C' col = CATEGORIES.index(category) table = (reservation.data or {}).get('stadtschulen_zug_price_table') if table is None: table = resource.content.get('stadtschulen_zug_price_table') if table is None: log.error( f'Pricing table missing for pricing scheme stadtschulen_zug ' f'on reservation {reservation.id}' ) return None base_rates, hourly_rates, daily_maxes = table start, end = reservation.start, reservation.end assert start is not None and end is not None duration = end + timedelta(microseconds=1) - start hours = Decimal(duration.total_seconds()) / Decimal('3600') extra_hours = max(0, hours - 4) fee = base_rates[col] + extra_hours * hourly_rates[col] return min(fee, daily_maxes[col])
@classmethod
[docs] def extend_form[T: Form]( cls, form_class: type[T], request: OrgRequest ) -> type[T]: class StadtSchulenZugForm(form_class): # type:ignore stadtschulen_zug_price_table = FieldTable( DecimalField(validators=[InputRequired(), NumberRange(min=0)]), ['A', 'B', 'C'], [ 'Grundtarif (bis 4 Stunden)', 'Jede weitere Stunde', 'Max. pro Tag' ], label='Preistabelle', fieldset=_('Payments'), depends_on=('pricing_scheme', cls.name) ) def process_obj(self, obj: object) -> None: super().process_obj(obj) if not request.POST and isinstance(obj, Resource): self.stadtschulen_zug_price_table.data = obj.content.get( 'stadtschulen_zug_price_table') def populate_obj( self, obj: Resource, exclude: Collection[str] | None = None, include: Collection[str] | None = None ) -> None: exclude = {'stadtschulen_zug_price_table', *(exclude or ())} super().populate_obj(obj, exclude, include) obj.content['stadtschulen_zug_price_table'] = ( self.stadtschulen_zug_price_table.data) def ensure_valid_form_definition(self) -> bool | None: if 'pricing_scheme' not in self: return None if self['pricing_method'].data != 'pricing_scheme': return None if self['pricing_scheme'].data != 'stadtschulen_zug': return None if self['parsed'].errors: # if we failed to parse, don't pile on more errors return None parsed = self['parsed'].data field = { field.id: field for field in (parsed.fields if parsed else ()) }.get('kategorie') if ( field is None or field.type != 'radio' or not all( choice.label.startswith(('A', 'B', 'C')) for choice in field.choices ) ): self['parsed'].errors.append( 'Mit Preisschema "Freizeitbetreuungsanlagen" braucht ' 'es ein Feld mit Name "Kategorie", in welchem alle ' 'Optionen mit einem der drei Kategorie-Codes "A", "B" ' 'oder "C" beginnen.' ) return False elif not field.required: self['parsed'].errors.append( 'Mit Preisschema "Freizeitbetreuungsanlagen" darf ' 'das Feld "Kategorie" nicht optional sein. ' '(Bitte "Kategorie * =" verwenden)' ) return False return None return move_fields( StadtSchulenZugForm, ('stadtschulen_zug_price_table',), after='pricing_scheme' )