Spaces:
Sleeping
Sleeping
File size: 6,475 Bytes
8b3bef0 7084f03 8b3bef0 87f3296 a164573 8b3bef0 aa3ff72 8b3bef0 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 0f61fc8 ad7f088 aa3ff72 ad7f088 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 986c126 48772c6 aa3ff72 b237078 aa3ff72 48772c6 aa3ff72 5865513 aa3ff72 e3dcd01 aa3ff72 ad7f088 aa3ff72 ad7f088 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 48772c6 aa3ff72 e3dcd01 aa3ff72 48772c6 aa3ff72 ad7f088 e3dcd01 aa3ff72 e3dcd01 aa3ff72 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | import requests
import json
import config
from utils import normalize_date
import time
# =====================================================
# 1. AUTHENTICATION
# =====================================================
def get_headers():
try:
params = {
"refresh_token": config.REFRESH_TOKEN,
"client_id": config.CLIENT_ID,
"client_secret": config.CLIENT_SECRET,
"grant_type": "refresh_token",
"redirect_uri": "http://www.google.com"
}
# Added 10s timeout to prevent hanging
resp = requests.post(config.AUTH_URL, data=params, timeout=10)
if resp.status_code == 200:
return {"Authorization": f"Zoho-oauthtoken {resp.json().get('access_token')}"}
except Exception as e:
print(f"Auth Error: {e}")
return None
# =====================================================
# 2. DIRECT CUSTOMER RESOLVER
# =====================================================
def get_customer_id(name, headers):
"""
Strict Logic: Search -> Found? -> Return ID.
Else -> Create -> Return ID.
"""
if not name or str(name).lower() in ["unknown", "none"]:
name = "MCP Generic Customer"
print(f" π Searching Zoho for: '{name}'...")
# --- STEP 1: SEARCH ---
search_url = f"{config.API_BASE}/contacts"
search_params = {
'organization_id': config.ORGANIZATION_ID,
'contact_name_contains': name
}
try:
# 10s Timeout
res = requests.get(search_url, headers=headers, params=search_params, timeout=10)
if res.status_code == 200:
contacts = res.json().get('contacts', [])
if len(contacts) > 0:
# MATCH FOUND!
found_id = contacts[0]['contact_id']
print(f" β
Found Existing Customer: {contacts[0]['contact_name']} ({found_id})")
return found_id
else:
print(f" β οΈ Search API Status: {res.status_code} (Not 200)")
except Exception as e:
print(f" β Search Exception: {e}")
# --- STEP 2: CREATE (Only if Search failed) ---
print(f" π Not found. Creating new customer: '{name}'...")
create_payload = {
"contact_name": name,
"contact_type": "customer"
}
try:
res = requests.post(search_url, headers=headers, params={'organization_id': config.ORGANIZATION_ID}, json=create_payload, timeout=10)
if res.status_code == 201:
new_id = res.json().get('contact', {}).get('contact_id')
print(f" β
Created New: {new_id}")
return new_id
# Handle Duplicate Error (Race Condition)
if res.json().get('code') == 3062:
print(" β οΈ Zoho says Duplicate. Forcing Search on exact name...")
# Logic: If we are here, the 'contains' search failed, but it exists.
# We assume it exists and we just failed to fetch it.
# We cannot proceed without an ID.
return None
print(f" β Creation Failed: {res.text}")
except Exception as e:
print(f" β Create Exception: {e}")
return None
# =====================================================
# 3. INVOICE PUSHER
# =====================================================
def create_invoice(cid, ai_data, headers):
print(" π¦ Preparing Invoice Payload...")
# 1. Process Line Items (Handle 0 items case)
raw_items = ai_data.get('line_items', [])
clean_items = []
if raw_items:
for item in raw_items:
clean_items.append({
"name": str(item.get("name", "Service"))[:100], # Limit 100 chars
"rate": float(item.get("rate", 0)),
"quantity": float(item.get("quantity", 1))
})
# Fallback if list is empty
if not clean_items:
print(" β οΈ No items found. Using Default 'Services Rendered' Item.")
clean_items = [{
"name": "Services Rendered",
"description": "Auto-generated from document total",
"rate": float(ai_data.get("total", 0)),
"quantity": 1
}]
# 2. Build Payload
payload = {
"customer_id": cid,
"date": normalize_date(ai_data.get("date")),
"invoice_number": ai_data.get("reference_number") or f"INV-{int(time.time())}",
"line_items": clean_items,
"status": "draft"
}
# 3. Send
url = f"{config.API_BASE}/invoices?organization_id={config.ORGANIZATION_ID}"
try:
print(" π Sending Invoice to Zoho...")
res = requests.post(url, headers=headers, json=payload, timeout=15)
# Auto-Number Retry
if res.status_code != 201 and res.json().get('code') == 4097:
print(" β οΈ Auto-Numbering active. Removing ID and Retrying...")
del payload['invoice_number']
res = requests.post(url, headers=headers, json=payload, timeout=15)
if res.status_code == 201:
inv = res.json().get('invoice', {})
return f"β
SUCCESS! Invoice Created.\nNumber: {inv.get('invoice_number')}\nLink: https://books.zoho.in/app/{config.ORGANIZATION_ID}#/invoices/{inv.get('invoice_id')}"
return f"β Zoho Error: {res.text}"
except Exception as e:
return f"β Connection Error: {e}"
# =====================================================
# 4. MAIN EXECUTOR
# =====================================================
def route_and_execute(ai_output):
# 1. Get Auth
headers = get_headers()
if not headers:
yield "β Auth Failed. Check Credentials."
return
# 2. Get Data
data = ai_output.get('data', {})
vendor_name = data.get('vendor_name')
yield f"π Processing Invoice for: {vendor_name}\n"
# 3. Get Customer ID
cid = get_customer_id(vendor_name, headers)
if not cid:
# HARD FALLBACK: If name search fails, try generic
yield " β οΈ Specific customer failed. Trying 'Generic' fallback...\n"
cid = get_customer_id("MCP Generic Customer", headers)
if not cid:
yield "π Critical: Could not find/create any customer."
return
yield f" -> Customer ID: {cid}\n"
# 4. Create Invoice
result = create_invoice(cid, data, headers)
yield result |