Refactored the quote_push process to check for any existing jobMaterials and fail with a log entry and some intelligence so that we don't overwrite anything that has been manually created. Also added another list in the inspector.py code to allow us to view this when it occurs.

This commit is contained in:
2026-05-11 14:41:26 +10:00
parent f03840c574
commit d7dc2ade06
5 changed files with 284 additions and 11 deletions
+33 -6
View File
@@ -12,7 +12,8 @@ set -euo pipefail
# By default this wrapper applies unapplied parsed responses. Use --dry-run to
# run the poll and preview each pending apply without writing to ServiceM8.
# The apply script still refuses duplicate applies unless --force is explicitly
# passed through.
# passed through. It also checks ServiceM8 for existing jobMaterial rows on
# the target job and blocks the apply unless --force-remote-existing is passed.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
POLL_SCRIPT="$SCRIPT_DIR/poll_form_responses_since.py"
@@ -24,11 +25,13 @@ QUOTE_TEMPLATE_FORM_UUID="${SERVICEM8_QUOTE_TEMPLATE_FORM_UUID:-3621b6be-1d19-47
SINCE=""
HOURS="24"
FORCE="0"
FORCE_REMOTE_EXISTING="0"
RECHECK_REMOTE_EXISTING="0"
DRY_RUN="0"
usage() {
cat <<EOF
Usage: $0 [--since 'YYYY-MM-DD HH:MM:SS'] [--hours N] [--dry-run] [--force]
Usage: $0 [--since 'YYYY-MM-DD HH:MM:SS'] [--hours N] [--dry-run] [--force] [--force-remote-existing] [--recheck-remote-existing]
Examples:
$0
@@ -43,6 +46,13 @@ This will:
With --dry-run, step 3 previews the ServiceM8 jobMaterial payloads only; it does
not write to ServiceM8 or mark responses as applied.
Safety:
--force keeps the existing local duplicate override behaviour.
--force-remote-existing allows creating rows even when the remote ServiceM8
job already has jobMaterial rows; the incident is still recorded.
--recheck-remote-existing revisits rows previously blocked because remote
jobMaterial rows existed. If the remote rows are gone, the apply can proceed.
EOF
}
@@ -60,6 +70,14 @@ while [[ $# -gt 0 ]]; do
FORCE="1"
shift
;;
--force-remote-existing)
FORCE_REMOTE_EXISTING="1"
shift
;;
--recheck-remote-existing)
RECHECK_REMOTE_EXISTING="1"
shift
;;
--dry-run)
DRY_RUN="1"
shift
@@ -90,6 +108,7 @@ echo "== ServiceM8 Quote Template poll/apply run =="
echo "Started: $(date --iso-8601=seconds)"
echo "Since: $SINCE"
echo "Mode: $([[ "$DRY_RUN" == "1" ]] && echo "dry-run" || echo "apply")"
echo "Remote existing: $([[ "$FORCE_REMOTE_EXISTING" == "1" ]] && echo "force" || ([[ "$RECHECK_REMOTE_EXISTING" == "1" ]] && echo "recheck" || echo "block"))"
echo "DB: $DB_PATH"
echo "Log: $LOG_FILE"
echo
@@ -100,20 +119,25 @@ echo "== Polling form responses =="
echo
echo "== Finding unapplied parsed Quote Template responses =="
mapfile -t FORM_RESPONSE_UUIDS < <(
python3 - "$DB_PATH" "$QUOTE_TEMPLATE_FORM_UUID" <<'PY'
python3 - "$DB_PATH" "$QUOTE_TEMPLATE_FORM_UUID" "$RECHECK_REMOTE_EXISTING" "$FORCE_REMOTE_EXISTING" <<'PY'
import sqlite3
import sys
poll_db, quote_form_uuid = sys.argv[1], sys.argv[2]
poll_db, quote_form_uuid, recheck_remote_existing, force_remote_existing = sys.argv[1:5]
include_remote_blocked = recheck_remote_existing == "1" or force_remote_existing == "1"
status_clause = "coalesce(q.process_status, '') != 'applied'"
if not include_remote_blocked:
status_clause += " and coalesce(q.process_status, '') != 'blocked_remote_existing'"
conn = sqlite3.connect(poll_db)
conn.row_factory = sqlite3.Row
rows = conn.execute(
"""
f"""
select q.form_response_uuid
from quote_template_form_responses q
left join form_responses_raw r on r.uuid = q.form_response_uuid
where q.form_uuid = ?
and coalesce(q.process_status, '') != 'applied'
and {status_clause}
order by coalesce(r.timestamp, q.discovered_at) asc, q.form_response_uuid asc
""",
(quote_form_uuid,),
@@ -143,6 +167,9 @@ fi
if [[ "$FORCE" == "1" ]]; then
APPLY_ARGS+=(--force)
fi
if [[ "$FORCE_REMOTE_EXISTING" == "1" ]]; then
APPLY_ARGS+=(--force-remote-existing)
fi
for uuid in "${FORM_RESPONSE_UUIDS[@]}"; do
echo