"""Project management: jobs and estimates CRUD.""" import json, sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from lib.db import get_db, new_id, now, row_to_dict, rows_to_list def create_job(client_name, address=None, description=None, jobtread_job_id=None, jobtread_customer_id=None): jid = new_id() conn = get_db() conn.execute("""INSERT INTO jobs (id, client_name, address, description, jobtread_job_id, jobtread_customer_id, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)""", (jid, client_name, address, description, jobtread_job_id, jobtread_customer_id, "proposal", now(), now())) conn.commit(); conn.close() return {"id": jid, "client_name": client_name, "status": "proposal"} def list_jobs(status=None): conn = get_db() if status: rows = conn.execute("SELECT * FROM jobs WHERE status=? ORDER BY created_at DESC", (status,)).fetchall() else: rows = conn.execute("SELECT * FROM jobs ORDER BY created_at DESC").fetchall() conn.close() return {"jobs": rows_to_list(rows)} def get_job(job_id): conn = get_db() job = row_to_dict(conn.execute("SELECT * FROM jobs WHERE id=?", (job_id,)).fetchone()) if not job: conn.close(); return {"error": "job not found"} job["estimates"] = rows_to_list(conn.execute("SELECT * FROM estimates WHERE job_id=?", (job_id,)).fetchall()) job["invoices"] = rows_to_list(conn.execute("SELECT * FROM invoices WHERE job_id=?", (job_id,)).fetchall()) conn.close() return job def update_job(job_id, status=None, client_name=None, address=None, description=None): conn = get_db() updates = [] params = [] for field, val in [("status", status), ("client_name", client_name), ("address", address), ("description", description)]: if val is not None: updates.append(f"{field}=?"); params.append(val) if updates: updates.append("updated_at=?"); params.append(now()) params.append(job_id) conn.execute(f"UPDATE jobs SET {','.join(updates)} WHERE id=?", params) conn.commit() conn.close() return {"ok": True, "id": job_id} def create_estimate(job_id, line_items, labor_hours=None, labor_rate=None, markup_percent=20): eid = new_id() conn = get_db() materials_cost = 0 total_labor_hours = labor_hours or 0 # Calculate totals first item_rows = [] for item in line_items: lid = new_id() total = item.get("quantity", 1) * item.get("unit_cost", 0) item_rows.append((lid, eid, item.get("description",""), item.get("category",""), item.get("quantity",1), item.get("unit","ea"), item.get("unit_cost",0), total)) if item.get("category") == "labor": total_labor_hours += item.get("quantity", 0) else: materials_cost += total effective_rate = labor_rate or 85 labor_cost = total_labor_hours * effective_rate subtotal = labor_cost + materials_cost markup = subtotal * (markup_percent / 100) total_cost = subtotal + markup # Insert estimate first, then line items conn.execute("""INSERT INTO estimates (id, job_id, labor_hours, labor_rate, materials_cost, markup_percent, total_cost, status, created_at) VALUES (?,?,?,?,?,?,?,?,?)""", (eid, job_id, total_labor_hours, effective_rate, materials_cost, markup_percent, total_cost, "draft", now())) for row in item_rows: conn.execute("INSERT INTO line_items (id, estimate_id, description, category, quantity, unit, unit_cost, total) VALUES (?,?,?,?,?,?,?,?)", row) conn.commit(); conn.close() return {"id": eid, "job_id": job_id, "labor_hours": total_labor_hours, "labor_rate": effective_rate, "materials_cost": materials_cost, "markup_percent": markup_percent, "total_cost": total_cost} def get_estimate(estimate_id): conn = get_db() est = row_to_dict(conn.execute("SELECT * FROM estimates WHERE id=?", (estimate_id,)).fetchone()) if not est: conn.close(); return {"error": "estimate not found"} est["line_items"] = rows_to_list(conn.execute("SELECT * FROM line_items WHERE estimate_id=?", (estimate_id,)).fetchall()) job = row_to_dict(conn.execute("SELECT * FROM jobs WHERE id=?", (est["job_id"],)).fetchone()) est["job"] = job conn.close() return est def update_estimate_status(estimate_id, status): conn = get_db() conn.execute("UPDATE estimates SET status=? WHERE id=?", (status, estimate_id)) conn.commit(); conn.close() return {"ok": True, "id": estimate_id, "status": status} if __name__ == "__main__": from lib.db import init_db; init_db() cmd = sys.argv[1] if len(sys.argv) > 1 else "list-jobs" if cmd == "create-job": print(json.dumps(create_job(sys.argv[2], sys.argv[3] if len(sys.argv)>3 else None, sys.argv[4] if len(sys.argv)>4 else None))) elif cmd == "list-jobs": print(json.dumps(list_jobs(sys.argv[2] if len(sys.argv)>2 else None), indent=2)) elif cmd == "get-job": print(json.dumps(get_job(sys.argv[2]), indent=2)) elif cmd == "get-estimate": print(json.dumps(get_estimate(sys.argv[2]), indent=2)) elif cmd == "create-estimate": print(json.dumps(create_estimate(sys.argv[2], json.loads(sys.argv[3]), markup_percent=float(sys.argv[4]) if len(sys.argv)>4 else 20))) else: print(json.dumps({"error": f"unknown: {cmd}"}))