Source code for translator_directory.models.ticket

from __future__ import annotations


from decimal import Decimal
from onegov.translator_directory.models.time_report import TranslatorTimeReport

from functools import cached_property
from markupsafe import Markup, escape
from onegov.core.elements import Link, LinkGroup
from onegov.core.templates import render_macro
from onegov.core.utils import linkify
from onegov.org import _
from onegov.org.models.ticket import OrgTicketMixin
from onegov.ticket import Handler
from onegov.ticket import handlers
from onegov.ticket import Ticket
from onegov.core.elements import Intercooler
from onegov.translator_directory.collections.documents import (
    TranslatorDocumentCollection)
from onegov.translator_directory.constants import (
    TIME_REPORT_INTERPRETING_TYPES,
    TIME_REPORT_SURCHARGE_LABELS,
)
from onegov.translator_directory.layout import AccreditationLayout
from onegov.translator_directory.layout import TranslatorLayout
from onegov.translator_directory.models.accreditation import Accreditation
from onegov.translator_directory.models.mutation import TranslatorMutation
from onegov.translator_directory.models.translator import Translator
from onegov.translator_directory.constants import TRANSLATOR_FA_ICON
from onegov.translator_directory.utils import get_custom_text

from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from onegov.org.request import OrgRequest
    from onegov.translator_directory.request import TranslatorAppRequest


[docs] class TranslatorMutationTicket(OrgTicketMixin, Ticket):
[docs] __mapper_args__ = {'polymorphic_identity': 'TRN'} # type:ignore
if TYPE_CHECKING:
[docs] handler: TranslatorMutationHandler
[docs] def reference_group(self, request: OrgRequest) -> str: return self.title
@handlers.registered_handler('TRN')
[docs] class TranslatorMutationHandler(Handler):
[docs] handler_title = _('Translator')
[docs] code_title = _('Translators')
@cached_property
[docs] def translator(self) -> Translator | None: return self.session.query(Translator).filter_by( id=self.data['handler_data'].get('id') ).first()
@cached_property
[docs] def mutation(self) -> TranslatorMutation | None: if self.translator: return TranslatorMutation( self.session, self.translator.id, self.ticket.id ) return None
@property
[docs] def deleted(self) -> bool: return self.translator is None
@property
[docs] def ticket_deletable(self) -> bool: # For now we don't support this because lots of functionality # depends on data in translator tickets if self.deleted: return True return False
@cached_property
[docs] def email(self) -> str: return self.data['handler_data'].get('submitter_email', '')
@cached_property
[docs] def message(self) -> str: return self.data['handler_data'].get('submitter_message', '')
@cached_property
[docs] def proposed_changes(self) -> dict[str, Any]: return self.data['handler_data'].get('proposed_changes', {})
@cached_property
[docs] def state(self) -> str | None: return self.data.get('state')
@cached_property
[docs] def uploaded_files(self) -> list[Any]: from onegov.file import File file_ids = self.data['handler_data'].get('file_ids', []) if not file_ids: return [] return [ self.session.query(File).filter_by(id=fid).first() for fid in file_ids ]
@property
[docs] def title(self) -> str: return self.translator.title if self.translator else '<Deleted>'
@property
[docs] def subtitle(self) -> str: return _('Mutation')
@cached_property
[docs] def group(self) -> str: return _('Translator')
[docs] def get_summary( self, request: TranslatorAppRequest # type:ignore[override] ) -> Markup: assert self.mutation is not None assert self.translator is not None layout = TranslatorLayout(self.translator, request) changes = self.mutation.translated(request, self.proposed_changes) return render_macro( layout.macros['display_translator_mutation'], request, { 'translator': self.translator, 'message': linkify(self.message).replace('\n', Markup('<br>')), 'changes': changes, 'layout': layout, 'uploaded_files': self.uploaded_files, }, )
[docs] class TimeReportTicket(OrgTicketMixin, Ticket):
[docs] __mapper_args__ = {'polymorphic_identity': 'TRP'} # type:ignore
[docs] es_type_name = 'translator_time_reports'
[docs] def reference_group(self, request: OrgRequest) -> str: return self.title
@handlers.registered_handler('TRP')
[docs] class TimeReportHandler(Handler):
[docs] handler_title = _('Time Report')
[docs] code_title = _('Time Reports')
@cached_property
[docs] def translator(self) -> Translator | None: return ( self.session.query(Translator) .filter_by(id=self.data['handler_data'].get('translator_id')) .first() )
@cached_property
[docs] def time_report(self) -> TranslatorTimeReport | None: time_report_id = self.data['handler_data'].get('time_report_id') if not time_report_id: return None return ( self.session.query(TranslatorTimeReport) .filter_by(id=time_report_id) .first() )
@property
[docs] def deleted(self) -> bool: return self.translator is None
@property
[docs] def ticket_deletable(self) -> bool: if self.deleted: return True if self.time_report and self.time_report.status == 'confirmed': return True return False
@cached_property
[docs] def email(self) -> str: return self.data['handler_data'].get('submitter_email', '')
@cached_property
[docs] def state(self) -> str | None: return self.data.get('state')
@property
[docs] def title(self) -> str: return self.translator.title if self.translator else '<Deleted>'
@property
[docs] def subtitle(self) -> str: return _('Time Report')
@cached_property
[docs] def group(self) -> str: return _('Time Report')
[docs] def get_summary( self, request: TranslatorAppRequest # type:ignore[override] ) -> Markup: if not self.translator or not self.time_report: return Markup('') layout = TranslatorLayout(self.translator, request) report = self.time_report # Status badge at the top if report.status == 'confirmed': status_class = 'success' status_text = request.translate(_('Confirmed')) else: status_class = 'warning' status_text = request.translate(_('Pending')) status_badge = ( f'<div class="alert-box callout {status_class}" ' f'style="margin-bottom: 1rem;">' f'<strong>{request.translate(_("Status"))}: </strong>' f'{status_text}' f'</div>' ) assignment_type_key = report.assignment_type assignment_type_translated = '-' if assignment_type_key: assignment_type_translated = request.translate( TIME_REPORT_INTERPRETING_TYPES[assignment_type_key] ) assignment_date_formatted = escape( layout.format_date(report.assignment_date, 'date') ) summary_parts = [ status_badge, "<dl class='field-display'>", f"<dt>{request.translate(_('Assignment Date'))}</dt>", f'<dd>{assignment_date_formatted}</dd>', f"<dt>{request.translate(_('Type'))}</dt>", f'<dd>{escape(assignment_type_translated)}</dd>', ] if report.case_number: summary_parts.extend( [ f"<dt>{request.translate(_('Case Number'))}</dt>", f'<dd>{escape(report.case_number)}</dd>', ] ) summary_parts.extend( [ f"<dt>{request.translate(_('Hourly Rate'))}</dt>", f'<dd>{layout.format_currency(report.hourly_rate)}</dd>', f"<dt>{request.translate(_('Duration'))}</dt>", f'<dd>{report.duration_hours} h</dd>', ] ) base_without_surcharge = report.hourly_rate * report.duration_hours summary_parts.extend( [ f"<dt>{request.translate(_('Base pay'))} " f"({layout.format_currency(report.hourly_rate)} × " f"{report.duration_hours} h)</dt>", f'<dd>{layout.format_currency(base_without_surcharge)}</dd>', ] ) effective_surcharge_pct = report.effective_surcharge_percentage if effective_surcharge_pct > 0: if report.surcharge_types: for surcharge_type in report.surcharge_types: label = TIME_REPORT_SURCHARGE_LABELS.get(surcharge_type) if label: rate = report.SURCHARGE_RATES.get(surcharge_type) if surcharge_type == 'urgent': surcharge_label = request.translate(label) else: surcharge_label = label summary_parts.extend( [ f'<dt>{escape(surcharge_label)}</dt>', f'<dd>+{rate}%</dd>', ] ) elif report.surcharge_percentage: summary_parts.extend( [ f"<dt>{request.translate(_('Surcharge'))}</dt>", f'<dd>+{report.surcharge_percentage}%</dd>', ] ) surcharge_amount = ( base_without_surcharge * effective_surcharge_pct / Decimal(100) ) summary_parts.extend( [ f"<dt>{request.translate(_('Surcharge amount'))} " f"({layout.format_currency(base_without_surcharge)} × " f"{effective_surcharge_pct}%)</dt>", f'<dd>{layout.format_currency(surcharge_amount)}</dd>', ] ) base_comp = report.base_compensation summary_parts.extend( [ f"<dt><strong>" f"{request.translate(_('Subtotal (work compensation)'))} " f"</strong></dt>", f'<dd><strong>' f'{layout.format_currency(base_comp)}' f'</strong></dd>', ] ) travel_label = request.translate(_('Travel')) if report.translator.drive_distance: travel_label = ( f"{request.translate(_('Travel'))} " f"({report.translator.drive_distance} km \u00d7 2)" ) summary_parts.extend( [ f'<dt>{travel_label}</dt>', f'<dd>{layout.format_currency(report.travel_compensation)}' f'</dd>', ] ) if report.meal_allowance: meal_label = request.translate(_('Meal Allowance (6+ hours)')) summary_parts.extend( [ f'<dt>{meal_label}</dt>', f'<dd>{layout.format_currency(report.meal_allowance)}</dd>', ] ) calculation_parts = [layout.format_currency(base_comp)] if report.travel_compensation > 0: calculation_parts.append( layout.format_currency(report.travel_compensation) ) if report.meal_allowance > 0: calculation_parts.append( layout.format_currency(report.meal_allowance) ) calculation_formula = ' + '.join(calculation_parts) summary_parts.extend( [ f"<dt><strong>{request.translate(_('Total'))}</strong> " f"({calculation_formula})</dt>", f'<dd><strong>' f'{layout.format_currency(report.total_compensation)}' f'</strong></dd>', '</dl>', ] ) return Markup(''.join(summary_parts)) # nosec: B704
[docs] class AccreditationTicket(OrgTicketMixin, Ticket):
[docs] __mapper_args__ = {'polymorphic_identity': 'AKK'} # type:ignore
if TYPE_CHECKING:
[docs] handler: AccreditationHandler
[docs] def reference_group(self, request: OrgRequest) -> str: return self.title
@handlers.registered_handler('AKK')
[docs] class AccreditationHandler(Handler):
[docs] handler_title = _('Accreditation')
[docs] code_title = _('Accreditations')
@cached_property
[docs] def translator(self) -> Translator | None: return self.session.query(Translator).filter_by( id=self.data['handler_data'].get('id') ).first()
@cached_property
[docs] def accreditation(self) -> Accreditation | None: if self.translator is None: return None return Accreditation(self.session, self.translator.id, self.ticket.id)
@property
[docs] def deleted(self) -> bool: return self.translator is None
@property
[docs] def ticket_deletable(self) -> bool: # For now we don't support this because lot's of functionality # depends on data in translator tickets if self.deleted: return True return False
@cached_property
[docs] def email(self) -> str: return self.data['handler_data'].get('submitter_email', '')
@cached_property
[docs] def state(self) -> str | None: return self.data.get('state')
@property
[docs] def title(self) -> str: return self.translator.title if self.translator else '<Deleted>'
@property
[docs] def subtitle(self) -> str: return _('Request Accreditation')
@cached_property
[docs] def group(self) -> str: return _('Accreditation')
[docs] def get_summary( self, request: TranslatorAppRequest # type:ignore[override] ) -> Markup: layout = AccreditationLayout(self.translator, request) locale = request.locale.split('_')[0] if request.locale else None locale = 'de' if locale == 'de' else 'en' agreement_text = get_custom_text( request, f'({locale}) Custom admission course agreement') return render_macro( layout.macros['display_accreditation'], request, { 'translator': self.translator, 'ticket_data': self.data['handler_data'], 'layout': layout, 'agreement_text': agreement_text } )