157 lines
3.5 KiB
Python
157 lines
3.5 KiB
Python
|
|
"""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}))
|