synq-core-os/scripts/odoo_mock_bridge.py
2026-05-07 19:28:50 -07:00

316 lines
11 KiB
Python
Executable file

#!/usr/bin/env python3
"""Minimal mock Synq Desktop Connector for testing the Odoo bridge."""
from flask import Flask, request, jsonify
import random
from datetime import datetime, timedelta
app = Flask(__name__)
DEMO_TOKEN = "demo"
DEMO_SECRET = "demo"
def check_auth():
token = request.headers.get("X-Synq-Desktop-Token")
secret = request.headers.get("X-Synq-Desktop-Secret")
return token == DEMO_TOKEN and secret == DEMO_SECRET
def jsonrpc_wrap(result):
return {"jsonrpc": "2.0", "id": random.randint(1, 999999), "result": result}
@app.route("/synq/desktop/v1/appointments/upcoming", methods=["POST"])
def appointments_upcoming():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
now = datetime.now()
appts = []
for i in range(4):
start = now + timedelta(days=i, hours=i % 3)
appts.append({
"id": i + 1,
"name": f"Consultation {i+1}",
"state": "confirmed",
"start": start.isoformat(),
"stop": (start + timedelta(hours=1)).isoformat(),
"duration": 1.0,
"appointment_location": "Room A" if i % 2 == 0 else "Room B",
"is_virtual": False,
"type_name": "Follow-up",
"physician_name": "Dr. Qazi",
"assigned_ma_names": ["MA Sarah"],
"patient": {"id": 100 + i, "name": f"Patient {i+1}", "email": f"p{i+1}@test.com", "phone": "555-0100"},
"display_start": start.strftime("%b %d, %Y, %I:%M %p"),
})
return jsonrpc_wrap({"status": "ok", "count": len(appts), "appointments": appts})
@app.route("/synq/desktop/v1/patients/search", methods=["POST"])
def patients_search():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
data = request.get_json(force=True, silent=True) or {}
params = data.get("params", {})
query = params.get("q", "")
patients = [
{
"id": 1,
"name": "Smith, John - 12345 - 1988-12-11",
"patient_name": "Smith, John - 12345 - 1988-12-11",
"email": "john.smith@test.com",
"phone": "555-0101",
"mobile": "555-0102",
"date_of_birth": "1988-12-11",
"sex": "Male",
"race": "Caucasian",
"marital_status": "Single",
"receivable": 1250.0,
"membership_name": "Gold",
"loyalty_points": 450,
"photo_consent_status": "granted",
"medical_patient_id": 101,
"partner_id": 1,
"tag_ids": [1, 2],
},
{
"id": 2,
"name": "Doe, Jane - 12346 - 1990-05-22",
"patient_name": "Doe, Jane - 12346 - 1990-05-22",
"email": "jane.doe@test.com",
"phone": "555-0201",
"mobile": None,
"date_of_birth": "1990-05-22",
"sex": "Female",
"race": "Asian",
"marital_status": "Married",
"receivable": 0.0,
"membership_name": "Silver",
"loyalty_points": 120,
"photo_consent_status": "pending",
"medical_patient_id": 102,
"partner_id": 2,
"tag_ids": [3],
},
]
if query:
patients = [p for p in patients if query.lower() in p["name"].lower()]
return jsonrpc_wrap({"status": "ok", "count": len(patients), "patients": patients})
@app.route("/synq/desktop/v1/treatments", methods=["POST"])
def treatments_list():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
treatments = [
{
"id": 1,
"name": "Before/After Photo Set",
"state": "open",
"date_deadline": (datetime.now() + timedelta(days=3)).isoformat(),
"patient_name": "Smith, John",
"patient": {"id": 1, "name": "Smith, John"},
"appointment": None,
"is_before_after": True,
"after_before_project": True,
"is_internal": False,
"internal_project": False,
"description": "Capture photos",
"provider_name": "Dr. Qazi",
},
{
"id": 2,
"name": "Internal Review",
"state": "open",
"date_deadline": (datetime.now() + timedelta(days=7)).isoformat(),
"patient_name": None,
"patient": None,
"appointment": None,
"is_before_after": False,
"after_before_project": False,
"is_internal": True,
"internal_project": True,
"description": "Staff training",
"provider_name": "Office Manager",
},
]
return jsonrpc_wrap({"status": "ok", "count": len(treatments), "treatments": treatments})
@app.route("/synq/desktop/v1/messages", methods=["POST"])
def messages_list():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
messages = [
{
"id": 1,
"patient_name": "Smith, John",
"subject": "Appointment Reminder",
"content": "Your appointment is tomorrow at 9:00 AM.",
"date_time": (datetime.now() - timedelta(hours=2)).isoformat(),
"entered_by": "System",
"read_status": "unread",
"has_attachment": False,
"method": "SMS",
"method_id": {"id": 1, "name": "SMS"},
},
{
"id": 2,
"patient_name": "Doe, Jane",
"subject": "Follow-up",
"content": "Please complete your post-op survey.",
"date_time": (datetime.now() - timedelta(days=1)).isoformat(),
"entered_by": "Dr. Qazi",
"read_status": "read",
"has_attachment": False,
"method": "Email",
"method_id": {"id": 2, "name": "Email"},
},
]
return jsonrpc_wrap({"status": "ok", "count": len(messages), "messages": messages})
@app.route("/synq/desktop/v1/billing", methods=["POST"])
def billing_list():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
invoices = [
{
"id": 1,
"name": "INV/2024/0001",
"partner_name": "Smith, John",
"amount_total": 2500.0,
"amount_residual": 1250.0,
"state": "posted",
"payment_state": "partial",
"invoice_date": "2024-01-15",
"invoice_date_due": "2024-02-15",
},
{
"id": 2,
"name": "INV/2024/0002",
"partner_name": "Doe, Jane",
"amount_total": 1800.0,
"amount_residual": 0.0,
"state": "posted",
"payment_state": "paid",
"invoice_date": "2024-01-10",
"invoice_date_due": "2024-02-10",
},
]
return jsonrpc_wrap({"status": "ok", "count": len(invoices), "invoices": invoices})
@app.route("/synq/desktop/v1/sale_orders", methods=["POST"])
def sale_orders():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
orders = [
{"id": 1, "name": "SO/2024/0001", "partner_name": "Smith, John", "amount_total": 2500.0, "state": "sale", "date_order": "2024-01-15"},
{"id": 2, "name": "SO/2024/0002", "partner_name": "Doe, Jane", "amount_total": 1800.0, "state": "sale", "date_order": "2024-01-10"},
]
return jsonrpc_wrap({"status": "ok", "count": len(orders), "sale_orders": orders})
@app.route("/synq/desktop/v1/quotations", methods=["POST"])
def quotations():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
quotes = [
{"id": 1, "name": "QUO/2024/0001", "partner_name": "Smith, John", "amount_total": 2500.0, "state": "sent", "date_order": "2024-01-15", "validity_date": "2024-02-15"},
]
return jsonrpc_wrap({"status": "ok", "count": len(quotes), "quotations": quotes})
@app.route("/synq/desktop/v1/patient/<int:patient_id>", methods=["POST"])
def patient_detail(patient_id):
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
patient = {
"id": patient_id,
"name": f"Patient {patient_id}",
"email": f"p{patient_id}@test.com",
"phone": "555-0100",
"mobile": "555-0101",
"date_of_birth": "1988-12-11",
"photo_consent_status": "granted",
"membership_name": "Gold",
"loyalty_points": 450,
"receivable": 1250.0,
"payable": 0.0,
"dms_directory_id": None,
"dms_subdirectories": [],
}
return jsonrpc_wrap({"status": "ok", "patient": patient})
@app.route("/synq/desktop/v1/authenticate", methods=["POST"])
def authenticate():
if not check_auth():
return jsonrpc_wrap({"status": "error", "message": "Invalid credentials"}), 401
return jsonrpc_wrap({
"status": "ok",
"user": {"id": 2, "name": "Administrator", "login": "admin", "tz": "America/Los_Angeles"},
"company": {"id": 1, "name": "QCC"},
})
@app.route("/jsonrpc", methods=["POST"])
def jsonrpc_handler():
"""Handle Odoo-style JSON-RPC probes and proxy calls."""
data = request.get_json(force=True, silent=True) or {}
method = data.get("method", "")
params = data.get("params", {})
service = params.get("service", "")
method_name = params.get("method", "")
# Handle common.version probe used by check_odoo_connection
if service == "common" and method_name == "version":
return jsonify({
"jsonrpc": "2.0",
"id": data.get("id", 1),
"result": {
"server_version": "18.0",
"server_version_info": [18, 0, 0, "final", 0, ""],
"server_serie": "18.0",
"protocol_version": 1,
}
})
# Handle object.execute_kw for common read/search operations
if service == "object" and method_name == "execute_kw":
args = params.get("args", [])
if len(args) >= 3:
model = args[0]
op = args[3] if len(args) > 3 else "search_read"
if model == "res.partner" and op == "search_read":
return jsonify({
"jsonrpc": "2.0",
"id": data.get("id", 1),
"result": [
{"id": 1, "name": "Smith, John", "email": "john.smith@test.com", "phone": "555-0101"},
{"id": 2, "name": "Doe, Jane", "email": "jane.doe@test.com", "phone": "555-0201"},
]
})
if model == "medical.patient" and op == "search_read":
return jsonify({
"jsonrpc": "2.0",
"id": data.get("id", 1),
"result": [
{"id": 101, "name": "Smith, John", "date_of_birth": "1988-12-11", "sex": "Male"},
{"id": 102, "name": "Doe, Jane", "date_of_birth": "1990-05-22", "sex": "Female"},
]
})
return jsonify({"jsonrpc": "2.0", "id": data.get("id", 1), "result": []})
# Default fallback
return jsonify({
"jsonrpc": "2.0",
"id": data.get("id", 1),
"result": {"status": "ok", "message": "Mock bridge JSON-RPC"}
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8019)