Initial commit: Handoff Pro MCP server for Kellow Construction
This commit is contained in:
156
lib/db.py
Normal file
156
lib/db.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""SQLite database initialization and access for Handoff Pro."""
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
DB_PATH = os.environ.get("HANDOFF_DB",
|
||||
os.path.join(os.path.dirname(__file__), "..", "data", "handoff.db"))
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT DEFAULT 'kellow',
|
||||
jobtread_job_id TEXT UNIQUE,
|
||||
client_name TEXT,
|
||||
jobtread_customer_id TEXT,
|
||||
address TEXT,
|
||||
description TEXT,
|
||||
status TEXT DEFAULT 'proposal',
|
||||
created_at TEXT,
|
||||
updated_at TEXT,
|
||||
synced_to_jobtread_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS estimates (
|
||||
id TEXT PRIMARY KEY,
|
||||
job_id TEXT REFERENCES jobs(id),
|
||||
jobtread_estimate_id TEXT UNIQUE,
|
||||
labor_hours REAL DEFAULT 0,
|
||||
labor_rate REAL DEFAULT 0,
|
||||
materials_cost REAL DEFAULT 0,
|
||||
markup_percent REAL DEFAULT 20,
|
||||
total_cost REAL DEFAULT 0,
|
||||
notes TEXT,
|
||||
status TEXT DEFAULT 'draft',
|
||||
created_at TEXT,
|
||||
synced_to_jobtread_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS line_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
estimate_id TEXT REFERENCES estimates(id),
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
quantity REAL DEFAULT 1,
|
||||
unit TEXT DEFAULT 'ea',
|
||||
unit_cost REAL DEFAULT 0,
|
||||
total REAL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pricing_catalogs (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT DEFAULT 'kellow',
|
||||
name TEXT,
|
||||
labor_rates TEXT,
|
||||
material_markups TEXT,
|
||||
created_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS invoices (
|
||||
id TEXT PRIMARY KEY,
|
||||
estimate_id TEXT REFERENCES estimates(id),
|
||||
job_id TEXT REFERENCES jobs(id),
|
||||
invoice_number TEXT,
|
||||
jobtread_invoice_id TEXT UNIQUE,
|
||||
qbo_invoice_id TEXT,
|
||||
amount_due REAL DEFAULT 0,
|
||||
amount_paid REAL DEFAULT 0,
|
||||
due_date TEXT,
|
||||
status TEXT DEFAULT 'draft',
|
||||
created_at TEXT,
|
||||
synced_to_jobtread_at TEXT,
|
||||
synced_to_qbo_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS daily_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
job_id TEXT REFERENCES jobs(id),
|
||||
log_date TEXT,
|
||||
crew_names TEXT,
|
||||
hours_worked REAL DEFAULT 0,
|
||||
notes TEXT,
|
||||
photos TEXT,
|
||||
created_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS signatures (
|
||||
id TEXT PRIMARY KEY,
|
||||
estimate_id TEXT REFERENCES estimates(id),
|
||||
client_name TEXT,
|
||||
signature_data TEXT,
|
||||
signed_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client_activity (
|
||||
id TEXT PRIMARY KEY,
|
||||
estimate_id TEXT REFERENCES estimates(id),
|
||||
job_id TEXT REFERENCES jobs(id),
|
||||
action TEXT,
|
||||
metadata TEXT,
|
||||
timestamp TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS jobtread_sync_log (
|
||||
id TEXT PRIMARY KEY,
|
||||
local_id TEXT,
|
||||
jobtread_id TEXT,
|
||||
entity_type TEXT,
|
||||
action TEXT,
|
||||
status TEXT DEFAULT 'pending',
|
||||
error_message TEXT,
|
||||
synced_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_milestones (
|
||||
id TEXT PRIMARY KEY,
|
||||
job_id TEXT REFERENCES jobs(id),
|
||||
phase_name TEXT,
|
||||
start_date TEXT,
|
||||
end_date TEXT,
|
||||
status TEXT DEFAULT 'pending'
|
||||
);
|
||||
"""
|
||||
|
||||
def get_db() -> sqlite3.Connection:
|
||||
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
||||
conn = sqlite3.connect(DB_PATH, timeout=10)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA busy_timeout=5000")
|
||||
return conn
|
||||
|
||||
def init_db():
|
||||
conn = get_db()
|
||||
conn.executescript(SCHEMA)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def new_id():
|
||||
return str(uuid.uuid4())[:8]
|
||||
|
||||
def now():
|
||||
return datetime.now().astimezone().isoformat()
|
||||
|
||||
def row_to_dict(row):
|
||||
if row is None:
|
||||
return None
|
||||
return dict(row)
|
||||
|
||||
def rows_to_list(rows):
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db()
|
||||
print(json.dumps({"ok": True, "db_path": DB_PATH}))
|
||||
Reference in New Issue
Block a user