import re from typing import Any, Dict, Optional from core.errors import SAPServiceLayerError from core.sap import SAPServiceLayerClient from domain.rut import formatear_rut, normalizar_rut, validar_rut RUT_PATTERN = re.compile(r"^\d{1,2}\.?\d{3}\.?\d{3}-?[\dkK]$") BUSINESS_PARTNER_GROUP_CODE = 100 # "Clientes", mismo grupo que usa el CardCode genérico def _nombre_completo(persona: Dict[str, Any]) -> str: nombre = f"{persona.get('first_name', '') or ''} {persona.get('last_name', '') or ''}".strip() return nombre or persona.get("name", "") or "" def buscar_rut_en_order(order: Dict[str, Any]) -> Optional[str]: """ Busca el RUT en note_attributes de la orden, donde algunas tiendas lo capturan en el checkout (ej. {"name": "rut", "value": "11111109-K"}). No requiere llamada extra a Shopify: ya viene en el detalle de la orden. """ for attr in order.get("note_attributes") or []: nombre = str(attr.get("name", "")).strip().lower() if "rut" not in nombre: continue valor = str(attr.get("value", "")).strip() if RUT_PATTERN.match(valor) and validar_rut(valor): return normalizar_rut(valor) return None def _bp_address(tipo: str, nombre: str, direccion: Dict[str, Any]) -> Dict[str, Any]: address = { "AddressName": nombre, "AddressType": tipo, "Street": direccion.get("address1"), "Block": direccion.get("address2"), "City": direccion.get("city"), "ZipCode": direccion.get("zip"), "County": direccion.get("province"), } if direccion.get("country_code") == "CL": address["Country"] = "CL" if tipo == "bo_ShipTo": address["TaxCode"] = "IVA" return address def crear_business_partner(sap: SAPServiceLayerClient, rut_sap: str, order: Dict[str, Any]) -> str: """ Crea un BusinessPartner (cliente) nuevo para este RUT. CardCode = "C" + rut_sap. Devuelve el CardCode creado. """ billing = order.get("billing_address") or {} shipping = order.get("shipping_address") or {} customer = order.get("customer") or {} card_code = f"C{rut_sap}" payload = { "CardCode": card_code, "CardName": _nombre_completo(billing) or _nombre_completo(customer) or "Cliente Shopify", "CardType": "cCustomer", "GroupCode": BUSINESS_PARTNER_GROUP_CODE, "FederalTaxID": rut_sap, "Phone1": billing.get("phone") or customer.get("phone"), "EmailAddress": order.get("email") or order.get("contact_email"), "BPAddresses": [ _bp_address("bo_BillTo", "BillTo", billing), _bp_address("bo_ShipTo", "ShipTo", shipping or billing), ], } sap.forward("POST", "BusinessPartners", json_data=payload) return card_code def resolver_card_code( sap: SAPServiceLayerClient, store_config: Dict[str, Any], rut: Optional[str], order: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """ Si hay un RUT válido, busca el BusinessPartner en SAP por FederalTaxID (con guión, que es el formato que usa SAP: "NNNNNNNN-D"). Si existe, devuelve su CardCode. Si no existe, crea uno nuevo (CardCode "C"+rut). Si no hay RUT, o falla la creación, usa el CardCode genérico configurado para la tienda. Siempre devuelve "rut_sap" (el RUT en formato SAP) cuando hay RUT, para poder sobrescribir el FederalTaxID del documento aunque se use el CardCode genérico. """ generic_card_code = store_config["sap"]["generic_card_code"] if not rut: return {"card_code": generic_card_code, "rut": None, "rut_sap": None, "encontrado_por_rut": False} rut_sap = formatear_rut(rut) resp = sap.forward( "GET", "BusinessPartners", params={"$select": "CardCode", "$filter": f"FederalTaxID eq '{rut_sap}'"}, ) resultados = resp.json().get("value", []) if resultados: return {"card_code": resultados[0]["CardCode"], "rut": rut, "rut_sap": rut_sap, "encontrado_por_rut": True} try: card_code = crear_business_partner(sap, rut_sap, order or {}) return {"card_code": card_code, "rut": rut, "rut_sap": rut_sap, "encontrado_por_rut": True} except SAPServiceLayerError: return {"card_code": generic_card_code, "rut": rut, "rut_sap": rut_sap, "encontrado_por_rut": False}