"""Mapeamento activação cliente — Spec 043 activation-field-mapping.md.""" from __future__ import annotations import re from typing import Any DEFAULT_PLAN_CODE = "ligbox-mail-business" PLAN_DEFAULTS: dict[str, dict[str, int]] = { "ligbox-mail-starter": {"seats": 10, "mail_gb": 20, "files_gb": 100}, "ligbox-mail-business": {"seats": 25, "mail_gb": 30, "files_gb": 200}, "ligbox-mail-enterprise": {"seats": 50, "mail_gb": 50, "files_gb": 500}, "ligbox-mail-custom": {"seats": 25, "mail_gb": 30, "files_gb": 200}, "ligbox-site-cms": {"seats": 1, "mail_gb": 0, "files_gb": 10}, } ACTIVATABLE_STATES = frozenset( {"awaiting_billing_validation", "provisioning", "policy_pending"} ) def _digits_only(value: str | None) -> str: if not value: return "" return re.sub(r"\D", "", str(value)) def normalize_company_profile( profile: dict[str, Any] | None, *, domain: str, ) -> dict[str, Any]: """Unifica manager_* e address vindos do webhook wizard.""" p = dict(profile or {}) addr = p.get("address") if not isinstance(addr, dict): addr = {} p["address"] = { "country": addr.get("country") or "BR", "city": addr.get("city") or "", "postal_code": addr.get("postal_code") or "", "street": addr.get("street") or "", "state": addr.get("state") or "", } p.setdefault("domain", domain.strip().lower()) if not p.get("manager_email"): p["manager_email"] = f"admin@{domain.strip().lower()}" if not p.get("manager_name"): p["manager_name"] = p.get("trade_name") or p.get("legal_name") or "" return p def split_manager_name(name: str | None) -> tuple[str, str]: parts = (name or "").strip().split(None, 1) if not parts: return ("", "") if len(parts) == 1: return (parts[0], "") return (parts[0], parts[1]) def derive_openpanel_username(domain: str, profile: dict[str, Any] | None = None) -> str: """Sugestão username hub — Spec 024 (7 chars alfanum + sufixo).""" existing = (profile or {}).get("openpanel_username") if existing: return str(existing).strip().lower() label = re.sub(r"[^a-z0-9]", "", domain.split(".")[0].lower()) or "ligbox" if len(label) <= 7: return f"{label}x"[:16] return f"{label[:7]}x"[:16] def resolve_plan_config(plan_code: str | None, config: dict[str, Any] | None = None) -> tuple[str, dict[str, int]]: code = (plan_code or DEFAULT_PLAN_CODE).strip() or DEFAULT_PLAN_CODE defaults = dict(PLAN_DEFAULTS.get(code, PLAN_DEFAULTS[DEFAULT_PLAN_CODE])) if config: for key in ("seats", "mail_gb", "files_gb"): if config.get(key) is not None: defaults[key] = int(config[key]) return code, defaults def map_foss_client(profile: dict[str, Any], domain: str) -> dict[str, Any]: first, last = split_manager_name(profile.get("manager_name")) email = (profile.get("manager_email") or profile.get("email_billing") or "").strip().lower() addr = profile.get("address") or {} return { "email": email, "first_name": first, "last_name": last, "company": profile.get("legal_name") or profile.get("trade_name") or domain, "company_vat": _digits_only(profile.get("tax_id")), "phone": profile.get("phone_mobile") or profile.get("contact_phone") or profile.get("phone_landline") or "", "address_1": addr.get("street") or "", "city": addr.get("city") or "", "state": addr.get("state") or "", "postcode": addr.get("postal_code") or "", "country": addr.get("country") or "BR", "currency": "BRL", "status": "active", "custom_ligbox_domain": domain, } def map_foss_order( *, domain: str, profile: dict[str, Any], plan_code: str, config: dict[str, int], foss_client_id: str | int | None = None, ) -> dict[str, Any]: return { "client_id": foss_client_id, "product_slug": plan_code, "period": "1M", "domain": domain, "manager_email": profile.get("manager_email"), "manager_name": profile.get("manager_name"), "config": { "seats": config.get("seats"), "mail_gb": config.get("mail_gb"), "files_gb": config.get("files_gb"), }, } def map_openpanel( *, domain: str, profile: dict[str, Any], plan_code: str, config: dict[str, int], ) -> dict[str, Any]: username = derive_openpanel_username(domain, profile) return { "username": username, "email": profile.get("manager_email"), "plan_name": plan_code, "domain": domain, "metadata": { "bundle_type": "ligbox_mail" if plan_code.startswith("ligbox-mail") else "ligbox_hosting", "max_seats": config.get("seats"), "mail_gb_per_seat": config.get("mail_gb"), "files_gb_per_seat": config.get("files_gb"), "easydmarc": True, "wizard_domain": domain, "manager_name": profile.get("manager_name"), "manager_email": profile.get("manager_email"), }, } def map_odoo_partner(profile: dict[str, Any], domain: str, foss_client_id: str | int | None = None) -> dict[str, Any]: addr = profile.get("address") or {} return { "name": profile.get("legal_name") or profile.get("trade_name") or domain, "display_name": profile.get("trade_name") or profile.get("legal_name") or domain, "vat": profile.get("tax_id") or "", "email": profile.get("email_billing") or profile.get("manager_email") or "", "phone": profile.get("phone_mobile") or profile.get("contact_phone") or "", "street": addr.get("street") or "", "city": addr.get("city") or "", "zip": addr.get("postal_code") or "", "country_code": addr.get("country") or "BR", "company_type": "company", "customer_rank": 1, "ref": domain, "comment": { "manager_email": profile.get("manager_email"), "foss_client_id": foss_client_id, "domain": domain, }, } def map_wizard_mail_bundle( *, domain: str, profile: dict[str, Any], config: dict[str, int], foss_order_id: str | int | None = None, ) -> dict[str, Any]: return { "domain": domain, "admin_email": profile.get("manager_email"), "admin_name": profile.get("manager_name"), "seats": config.get("seats"), "mail_gb_per_seat": config.get("mail_gb"), "files_gb_per_seat": config.get("files_gb"), "foss_order_id": foss_order_id, } def build_activation_preview( account: dict[str, Any], *, plan_code: str | None = None, config: dict[str, Any] | None = None, ) -> dict[str, Any]: domain = str(account.get("domain") or "").strip().lower() profile = normalize_company_profile(account.get("company_profile"), domain=domain) code, cfg = resolve_plan_config(plan_code or account.get("plan_code"), config) foss_id = account.get("external_customer_id") billing_state = account.get("billing_state") or "awaiting_billing_validation" can_activate = billing_state in ACTIVATABLE_STATES and not account.get("recurrence_active") return { "billing_account_id": account.get("id"), "domain": domain, "billing_state": billing_state, "can_activate": can_activate, "plan_code": code, "config": cfg, "company_profile": profile, "mapped": { "foss_client": map_foss_client(profile, domain), "foss_order": map_foss_order( domain=domain, profile=profile, plan_code=code, config=cfg, foss_client_id=foss_id, ), "openpanel": map_openpanel(domain=domain, profile=profile, plan_code=code, config=cfg), "odoo_partner": map_odoo_partner(profile, domain, foss_client_id=foss_id), "wizard_mail_bundle": map_wizard_mail_bundle( domain=domain, profile=profile, config=cfg, ), }, "existing_ids": { "foss_client_id": foss_id, "external_subscription_id": account.get("external_subscription_id"), "openpanel_username": profile.get("openpanel_username"), }, "notes": ( "Preview read-only — Fase 1 Spec 043. " "POST /activate implementado nas Fases 2–5." ), }