Source code for translator_directory.forms.time_report

from __future__ import annotations

import math

from datetime import date
from decimal import Decimal
from onegov.form import Form
from onegov.form.fields import ChosenSelectField
from onegov.translator_directory import _
from onegov.translator_directory.constants import INTERPRETING_TYPES
from wtforms.fields import BooleanField
from wtforms.fields import DateField
from wtforms.fields import DecimalField
from wtforms.fields import SelectField
from wtforms.fields import StringField
from wtforms.fields import TextAreaField
from wtforms.validators import InputRequired
from wtforms.validators import NumberRange
from wtforms.validators import Optional


from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from onegov.translator_directory.models.time_report import (
        TranslatorTimeReport,
    )
    from onegov.translator_directory.models.translator import Translator
    from onegov.translator_directory.request import TranslatorAppRequest
    from wtforms.fields.choices import _Choice


[docs] class TranslatorTimeReportForm(Form): """Form for creating/editing translator time reports."""
[docs] request: TranslatorAppRequest
[docs] assignment_type = ChosenSelectField( label=_('Type of translation/interpreting'), choices=[], validators=[Optional()], )
[docs] duration = DecimalField( label=_('Duration (hours)'), validators=[ InputRequired(), NumberRange(min=0.5, message=_('Minimum 0.5 hours')), ], render_kw={'step': '0.5', 'min': '0.5'}, )
[docs] case_number = StringField( label=_('Case number (Police)'), validators=[Optional()], description=_('Geschäftsnummer Police for linking if needed'), )
[docs] assignment_date = DateField( label=_('Assignment date'), validators=[InputRequired()], default=date.today, )
[docs] is_night_work = BooleanField( label=_('Night work (20:00 - 06:00)'), description=_('50% surcharge'), default=False, )
[docs] is_weekend_holiday = BooleanField( label=_('Weekend or holiday'), description=_('25% surcharge'), default=False, )
[docs] is_urgent = BooleanField( label=_('Exceptionally urgent'), description=_('25% surcharge'), default=False, )
[docs] travel_distance = SelectField( label=_('Travel distance'), choices=[], validators=[Optional()] )
[docs] notes = TextAreaField( label=_('Notes'), validators=[Optional()], render_kw={'rows': 3} )
[docs] def on_request(self) -> None: self.assignment_type.choices = self.get_assignment_type_choices() self.travel_distance.choices = self.get_travel_choices()
[docs] def get_assignment_type_choices(self) -> list[_Choice]: """Return assignment type choices.""" return [ (key, self.request.translate(value)) for key, value in INTERPRETING_TYPES.items() ]
[docs] def get_travel_choices(self) -> list[_Choice]: """Return travel distance choices with compensation.""" return [ ('0', self.request.translate(_('No travel'))), ('20', self.request.translate(_('Up to 25 km (CHF 20)'))), ('50', self.request.translate(_('25-50 km (CHF 50)'))), ('100', self.request.translate(_('50-100 km (CHF 100)'))), ('150', self.request.translate(_('Over 100 km (CHF 150)'))), ]
[docs] def get_hourly_rate(self, translator: Translator) -> Decimal: """Determine hourly rate based on translator certification.""" if translator.admission == 'certified': return Decimal('90.00') return Decimal('75.00')
[docs] def calculate_surcharge(self) -> Decimal: """Calculate total surcharge percentage.""" surcharge = Decimal('0') if self.is_night_work.data: surcharge += Decimal('50') if self.is_weekend_holiday.data: surcharge += Decimal('25') if self.is_urgent.data: surcharge += Decimal('25') return surcharge
[docs] def populate_obj( # type: ignore[override] self, obj: TranslatorTimeReport # type: ignore[override] ) -> None: """Populate the model from form, converting hours to minutes.""" if self.duration.data is not None: hours = float(self.duration.data) rounded_hours = math.ceil(hours * 2) / 2 obj.duration = int(rounded_hours * 60)
[docs] def process( # type: ignore[override] self, formdata: object = None, obj: object = None, **kwargs: object ) -> None: """Process form data, converting minutes to hours for display.""" super().process(formdata, obj, **kwargs) # type: ignore[arg-type] if formdata is None and obj is not None and hasattr(obj, 'duration'): duration_minutes = getattr(obj, 'duration', None) if duration_minutes is not None: self.duration.data = duration_minutes / 60.0
[docs] def update_model(self, model: TranslatorTimeReport) -> None: """Update the time report model with form data.""" assert self.duration.data is not None assert self.assignment_date.data is not None model.assignment_type = self.assignment_type.data or None hours = float(self.duration.data) rounded_hours = math.ceil(hours * 2) / 2 model.duration = int(rounded_hours * 60) model.case_number = self.case_number.data or None model.assignment_date = self.assignment_date.data model.notes = self.notes.data or None hourly_rate = self.get_hourly_rate(model.translator) model.hourly_rate = hourly_rate surcharge_pct = self.calculate_surcharge() model.surcharge_percentage = surcharge_pct travel_comp = Decimal(self.travel_distance.data or 0) model.travel_compensation = travel_comp duration_hours = Decimal(model.duration) / Decimal(60) base = hourly_rate * duration_hours surcharge_amount = base * (surcharge_pct / Decimal(100)) meal_allowance = ( Decimal('40.0') if duration_hours >= 6 else Decimal('0') ) model.total_compensation = ( base + surcharge_amount + travel_comp + meal_allowance )