Added the creation of jobmaterials script and the creation script ofr live updates to the ServiceM8 server (default is -dry-run). Altered inspector.py to now let us view jobmaterials DB.

This commit is contained in:
2026-04-28 16:41:06 +10:00
parent debc442081
commit 3472e770d6
3 changed files with 336 additions and 3 deletions
@@ -0,0 +1,134 @@
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)