"""Offer helpers — compensation field list, offer-email template, Teams mail send. Pure module: no FastAPI imports and no HTTPException. `send_offer_mail` intentionally duplicates `notifications.plugins.send_confirmation_mail` rather than importing it: that helper is domain-named, and each domain owns its own mail copy and env reads. """ from __future__ import annotations import os from datetime import datetime, timezone import httpx from dotenv import load_dotenv from candidate_forms.plugins import FORM_READY_STATUSES load_dotenv() TEAMS_MAIL_API_URL = os.getenv("TEAMS_MAIL_API_URL") TEAMS_API_TOKEN = os.getenv("TEAMS_API_TOKEN") MAIL_ACCEPTED_STATUSES = {200, 202} INTERVIEW_PLUS = FORM_READY_STATUSES OFFER_SUBJECT = "Offer from UtopiaBrands Recruitement team" def non_validation_values(): fields=("base_salary","currency","salary_period","signing_bonus","annual_bonus_pct", "equity_units","equity_instrument","start_date","expiry_date", "cadre","gross_salary_in_words","subsidized_services","probation_period", "notice_period","work_location","work_timings") return fields def email_key(value) -> str: return (value or "").strip().lower() def stage_value(value) -> str: if value is None: return "" return (value.value if hasattr(value,"value") else str(value)).strip().upper() def is_interview_plus(value) -> bool: return stage_value(value) in INTERVIEW_PLUS def parse_offer_datetime(value): if value in (None,""): return None if isinstance(value,datetime): if value.tzinfo is None: return value.replace(tzinfo=timezone.utc) return value text=str(value).strip() if not text: return None if text.endswith("Z"): text=text[:-1]+"+00:00" try: parsed=datetime.fromisoformat(text) except ValueError: return None if parsed.tzinfo is None: return parsed.replace(tzinfo=timezone.utc) return parsed def _period_label(period) -> str: raw=(period or "year").strip().lower() if raw in ("annual","year"): return "per year" if raw=="month": return "per month" if raw=="hour": return "per hour" return raw def _money_line(amount, currency) -> str: cur=(currency or "USD").strip() or "USD" try: n=float(amount) pretty=f"{n:,.0f}" if n==int(n) else f"{n:,.2f}" except (TypeError,ValueError): pretty=str(amount) return f"{cur} {pretty}" def render_offer_email( candidate_name, base_salary, currency="USD", salary_period="year", *, annual_bonus_pct=None, signing_bonus=None, equity_units=None, equity_instrument=None, ) -> tuple[str, str]: name=(candidate_name or "").strip() or "Candidate" salary=_money_line(base_salary,currency) period=_period_label(salary_period) html=( f"
Dear {name},
" "We are pleased to extend an offer of employment from UtopiaBrands.
" f"Base salary: {salary} {period}.
" ) if annual_bonus_pct not in (None,""): html+=f"Annual bonus: {annual_bonus_pct}%.
" if signing_bonus not in (None,""): html+=f"Signing bonus: {_money_line(signing_bonus,currency)}.
" if equity_units not in (None,"",0): instrument=(equity_instrument or "RSU").strip() or "RSU" html+=f"Equity: {equity_units} {instrument}.
" html+="Please reply to this email if you have questions about the offer.
" return OFFER_SUBJECT,html async def send_offer_mail(to_email: str, subject: str, html: str) -> None: if not TEAMS_MAIL_API_URL or not TEAMS_API_TOKEN: raise RuntimeError("TEAMS_MAIL_API_URL and TEAMS_API_TOKEN must be set") fields = [ ("subject", (None, subject)), ("body", (None, html)), ("content_type", (None, "html")), ("save_to_sent_items", (None, "false")), ("to", (None, to_email)), ] async with httpx.AsyncClient(timeout=15.0) as client: response = await client.post( TEAMS_MAIL_API_URL, files=fields, headers={"Authorization": f"Bearer {TEAMS_API_TOKEN}"}, ) if response.status_code not in MAIL_ACCEPTED_STATUSES: raise httpx.HTTPStatusError( response.text, request=response.request, response=response, )