Initial commit: Handoff Pro MCP server for Kellow Construction
This commit is contained in:
54
lib/qbo.py
Normal file
54
lib/qbo.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""QuickBooks Online API helper. Tokens from http://192.168.86.11:18801."""
|
||||
import json
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
TOKEN_URL = "http://192.168.86.11:18801/kellow-tokens.json"
|
||||
REFRESH_URL = "http://192.168.86.11:18803/qbo/refresh?tenant=kellow"
|
||||
QBO_BASE = "https://quickbooks.api.intuit.com/v3/company"
|
||||
|
||||
def get_token():
|
||||
"""Fetch valid QBO access token + realm_id, auto-refresh if expired."""
|
||||
with urllib.request.urlopen(TOKEN_URL, timeout=10) as resp:
|
||||
tokens = json.loads(resp.read())
|
||||
if time.time() >= tokens.get("expires_at_epoch", 0):
|
||||
req = urllib.request.Request(REFRESH_URL, method="POST")
|
||||
urllib.request.urlopen(req, timeout=15)
|
||||
with urllib.request.urlopen(TOKEN_URL, timeout=10) as resp:
|
||||
tokens = json.loads(resp.read())
|
||||
return tokens["access_token"], tokens["realm_id"]
|
||||
|
||||
def qbo_request(method, endpoint, body=None):
|
||||
"""Make authenticated QBO API request. Returns parsed JSON."""
|
||||
access_token, realm_id = get_token()
|
||||
url = f"{QBO_BASE}/{realm_id}/{endpoint}"
|
||||
data = json.dumps(body).encode() if body else None
|
||||
req = urllib.request.Request(url, data=data, method=method, headers={
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
return json.loads(resp.read())
|
||||
|
||||
def create_invoice(customer_id, line_items, due_date=None):
|
||||
"""Create invoice in QBO. line_items: [{description, amount, qty}]"""
|
||||
lines = []
|
||||
for i, item in enumerate(line_items, 1):
|
||||
lines.append({
|
||||
"LineNum": i,
|
||||
"Amount": item["amount"],
|
||||
"DetailType": "SalesItemLineDetail",
|
||||
"Description": item.get("description", ""),
|
||||
"SalesItemLineDetail": {
|
||||
"Qty": item.get("qty", 1),
|
||||
"UnitPrice": item["amount"] / item.get("qty", 1),
|
||||
}
|
||||
})
|
||||
invoice = {
|
||||
"CustomerRef": {"value": customer_id},
|
||||
"Line": lines,
|
||||
}
|
||||
if due_date:
|
||||
invoice["DueDate"] = due_date
|
||||
return qbo_request("POST", "invoice", invoice)
|
||||
Reference in New Issue
Block a user