135 lines
4.5 KiB
Python
135 lines
4.5 KiB
Python
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
from servicem8_quote_template_parser import (
|
|
QUOTE_TEMPLATE_FORM_UUID,
|
|
STATE_DB_PATH,
|
|
init_state_db,
|
|
load_input_file,
|
|
parse_quote_template_form_response,
|
|
record_generated_job_material,
|
|
)
|
|
|
|
BASE_URL = os.getenv("SERVICEM8_BASE_URL", "https://api.servicem8.com/api_1.0")
|
|
ACCESS_TOKEN = os.getenv("SERVICEM8_ACCESS_TOKEN", "")
|
|
REQUEST_TIMEOUT = int(os.getenv("SERVICEM8_TIMEOUT", "30"))
|
|
|
|
|
|
def build_payload(job_uuid: str, row: dict) -> dict:
|
|
return {
|
|
"job_uuid": job_uuid,
|
|
"material_uuid": row["material_uuid"],
|
|
"name": row["name"],
|
|
"quantity": row["quantity"],
|
|
"price": row["price"],
|
|
"displayed_amount": row["displayed_amount"],
|
|
"displayed_amount_is_tax_inclusive": row["displayed_amount_is_tax_inclusive"],
|
|
"sort_order": row["sort_order"],
|
|
}
|
|
|
|
|
|
def create_job_material(session: requests.Session, payload: dict) -> str:
|
|
response = session.post(f"{BASE_URL}/jobmaterial.json", json=payload, timeout=REQUEST_TIMEOUT)
|
|
if not response.ok:
|
|
raise RuntimeError(f"Create failed: HTTP {response.status_code} :: {response.text}")
|
|
record_uuid = response.headers.get("x-record-uuid", "")
|
|
if not record_uuid:
|
|
raise RuntimeError("Create succeeded but x-record-uuid header was missing")
|
|
return record_uuid
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Create ServiceM8 jobMaterials from a Quote Template form response")
|
|
parser.add_argument("input", help="Path to JSON file containing full form response payload or a data object with field_data")
|
|
parser.add_argument("--apply", action="store_true", help="Actually create records in ServiceM8. Default is dry-run.")
|
|
parser.add_argument("--pretty", action="store_true", help="Pretty-print output JSON")
|
|
args = parser.parse_args()
|
|
|
|
init_state_db(STATE_DB_PATH)
|
|
|
|
payload = load_input_file(args.input)
|
|
parsed = parse_quote_template_form_response(payload)
|
|
|
|
form_uuid = parsed.get("form_uuid", "")
|
|
if form_uuid and form_uuid != QUOTE_TEMPLATE_FORM_UUID:
|
|
raise SystemExit(f"Not a Quote Template form response: {form_uuid}")
|
|
|
|
job_uuid = parsed.get("job_uuid", "")
|
|
form_response_uuid = parsed.get("form_response_uuid", "")
|
|
desired_rows = parsed.get("desired_job_materials", [])
|
|
|
|
if not job_uuid:
|
|
raise SystemExit("Missing job_uuid / regarding_object_uuid in form response")
|
|
|
|
result = {
|
|
"mode": "apply" if args.apply else "dry-run",
|
|
"job_uuid": job_uuid,
|
|
"form_response_uuid": form_response_uuid,
|
|
"count": len(desired_rows),
|
|
"rows": [],
|
|
"state_db_path": str(STATE_DB_PATH),
|
|
}
|
|
|
|
if not args.apply:
|
|
for row in desired_rows:
|
|
result["rows"].append(
|
|
{
|
|
"action": "would_create",
|
|
"kind": row["kind"],
|
|
"payload": build_payload(job_uuid, row),
|
|
"source_question": row.get("source_question", ""),
|
|
"source_field_uuid": row.get("source_field_uuid", ""),
|
|
}
|
|
)
|
|
print(json.dumps(result, indent=2 if args.pretty else None, ensure_ascii=False))
|
|
return
|
|
|
|
if not ACCESS_TOKEN:
|
|
raise SystemExit("SERVICEM8_ACCESS_TOKEN is required for --apply")
|
|
|
|
session = requests.Session()
|
|
session.headers.update(
|
|
{
|
|
"X-Api-Key": ACCESS_TOKEN,
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json",
|
|
}
|
|
)
|
|
|
|
for row in desired_rows:
|
|
api_payload = build_payload(job_uuid, row)
|
|
created_uuid = create_job_material(session, api_payload)
|
|
record_generated_job_material(
|
|
job_uuid=job_uuid,
|
|
form_response_uuid=form_response_uuid,
|
|
job_material_uuid=created_uuid,
|
|
kind=row.get("kind", ""),
|
|
source_field_uuid=row.get("source_field_uuid", ""),
|
|
source_question=row.get("source_question", ""),
|
|
source_text=row.get("name", ""),
|
|
db_path=STATE_DB_PATH,
|
|
)
|
|
result["rows"].append(
|
|
{
|
|
"action": "created",
|
|
"kind": row["kind"],
|
|
"job_material_uuid": created_uuid,
|
|
"payload": api_payload,
|
|
}
|
|
)
|
|
|
|
print(json.dumps(result, indent=2 if args.pretty else None, ensure_ascii=False))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except Exception as exc:
|
|
print(str(exc), file=sys.stderr)
|
|
sys.exit(1)
|