import logging from datetime import datetime, timezone from sqlalchemy.orm import selectinload from sqlmodel import Session, select from app.models.mail_log import MailLog, MailLogStatus from app.models.mail_template import MailEventType, MailTemplate from app.models.order import Order from app.models.order_line import OrderLine from app.models.participant import Participant from app.models.purchase_round import PurchaseRound from app.models.route import Route from app.models.wine_offering import WineOffering from app.services.mail_rendering import ( CatalogRow, compute_order_total_eur, format_currency, format_datetime_da, render_order_receipt_html, render_template, ) from app.services.postal import PostalSendError, send_mail logger = logging.getLogger(__name__) def send_order_event_mail( session: Session, route: Route, purchase_round: PurchaseRound, participant: Participant, order: Order, order_lines: list[OrderLine], event_type: MailEventType, ) -> None: template = session.exec( select(MailTemplate).where(MailTemplate.route_id == route.id, MailTemplate.event_type == event_type) ).first() if template is None: logger.warning("No %s template for route %s — skipping mail", event_type.value, route.id) return if not route.sender_name or not route.sender_email: logger.warning("Route %s has no sender_name/sender_email — skipping mail", route.id) return all_offerings = session.exec( select(WineOffering) .where(WineOffering.purchase_round_id == purchase_round.id) .options(selectinload(WineOffering.category)) ).all() all_offerings_sorted = sorted(all_offerings, key=lambda o: (o.category.sort_order, o.id)) ordered_qty_by_offering = {ol.wine_offering_id: ol.quantity for ol in order_lines} catalog_rows = [ CatalogRow( category_name=o.category.name, wine_name=o.name, unit_price_eur=o.price, ordered_quantity=ordered_qty_by_offering.get(o.id), ) for o in all_offerings_sorted ] total_eur = compute_order_total_eur(catalog_rows) total_dkk = total_eur * purchase_round.eur_dkk_rate if purchase_round.eur_dkk_rate else None variables = { "participant_name": participant.name, "route_name": route.name, "round_name": purchase_round.name, "order_number": str(order.order_number), "order_deadline_at": format_datetime_da(purchase_round.order_deadline_at) if purchase_round.order_deadline_at else "", "pickup_at": format_datetime_da(purchase_round.pickup_at) if purchase_round.pickup_at else "", "pickup_info_text": purchase_round.pickup_info_text or "", "order_lines_html": render_order_receipt_html(catalog_rows), "order_total_eur": format_currency(total_eur), "order_total_dkk": format_currency(total_dkk) if total_dkk is not None else "", } rendered_subject = render_template(template.subject, variables) rendered_body = render_template(template.body_html, variables) log = MailLog( participant_id=participant.id, purchase_round_id=purchase_round.id, mail_template_id=template.id, event_type=event_type, rendered_subject=rendered_subject, status=MailLogStatus.FAILED, ) try: postal_id, postal_token = send_mail( to=participant.email, from_name=route.sender_name, from_email=route.sender_email, subject=rendered_subject, html_body=rendered_body, tag=event_type.value, ) log.status = MailLogStatus.SENT log.postal_message_id = postal_id log.postal_token = postal_token log.sent_at = datetime.now(timezone.utc) except PostalSendError as exc: log.error_message = str(exc) session.add(log) session.commit()