Files
plumbing/poll_and_apply_quote_templates.sh
T

160 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Poll ServiceM8 form responses, parse Quote Template responses, then apply any
# newly/unapplied parsed quote responses to ServiceM8 jobMaterials.
#
# Default window: last 24 hours from script start, in local system time.
# Override with:
# --since 'YYYY-MM-DD HH:MM:SS'
# --hours 48
#
# 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.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
POLL_SCRIPT="$SCRIPT_DIR/poll_form_responses_since.py"
APPLY_SCRIPT="$SCRIPT_DIR/apply_polled_quote_template_jobmaterials.py"
DB_PATH="${WEBHOOK_POLL_DB_PATH:-$SCRIPT_DIR/servicem8_formresponse_poll.db}"
LOG_DIR="${WEBHOOK_RUN_LOG_DIR:-$SCRIPT_DIR/logs}"
QUOTE_TEMPLATE_FORM_UUID="${SERVICEM8_QUOTE_TEMPLATE_FORM_UUID:-3621b6be-1d19-4756-9ab4-9d5e4120f6d9}"
SINCE=""
HOURS="24"
FORCE="0"
DRY_RUN="0"
usage() {
cat <<EOF
Usage: $0 [--since 'YYYY-MM-DD HH:MM:SS'] [--hours N] [--dry-run] [--force]
Examples:
$0
$0 --hours 48
$0 --since '2026-05-04 08:00:00'
$0 --dry-run --hours 48
This will:
1. Poll /formresponse.json using timestamp gt SINCE
2. Store/parse Quote Template responses into $DB_PATH
3. Apply parsed responses that do not already have generated materials recorded
With --dry-run, step 3 previews the ServiceM8 jobMaterial payloads only; it does
not write to ServiceM8 or mark responses as applied.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--since)
SINCE="${2:-}"
shift 2
;;
--hours)
HOURS="${2:-}"
shift 2
;;
--force)
FORCE="1"
shift
;;
--dry-run)
DRY_RUN="1"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ -z "$SINCE" ]]; then
SINCE="$(date -d "-${HOURS} hours" '+%Y-%m-%d %H:%M:%S')"
fi
mkdir -p "$LOG_DIR"
RUN_ID="$(date '+%Y%m%d-%H%M%S')"
LOG_FILE="$LOG_DIR/poll-and-apply-$RUN_ID.log"
exec > >(tee -a "$LOG_FILE") 2>&1
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 "DB: $DB_PATH"
echo "Log: $LOG_FILE"
echo
echo "== Polling form responses =="
"$POLL_SCRIPT" --since "$SINCE" --summary-limit 20
echo
echo "== Finding unapplied parsed Quote Template responses =="
mapfile -t FORM_RESPONSE_UUIDS < <(
python3 - "$DB_PATH" "$QUOTE_TEMPLATE_FORM_UUID" <<'PY'
import sqlite3
import sys
poll_db, quote_form_uuid = sys.argv[1], sys.argv[2]
conn = sqlite3.connect(poll_db)
conn.row_factory = sqlite3.Row
rows = conn.execute(
"""
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'
order by coalesce(r.timestamp, q.discovered_at) asc, q.form_response_uuid asc
""",
(quote_form_uuid,),
).fetchall()
for row in rows:
print(row["form_response_uuid"])
PY
)
if [[ ${#FORM_RESPONSE_UUIDS[@]} -eq 0 ]]; then
echo "No unapplied Quote Template responses found."
echo "Finished: $(date --iso-8601=seconds)"
exit 0
fi
printf 'Found %d unapplied Quote Template response(s):\n' "${#FORM_RESPONSE_UUIDS[@]}"
printf ' - %s\n' "${FORM_RESPONSE_UUIDS[@]}"
echo
if [[ "$DRY_RUN" == "1" ]]; then
echo "== Dry-run preview only; no ServiceM8 writes =="
APPLY_ARGS=(--pretty)
else
echo "== Applying to ServiceM8 =="
APPLY_ARGS=(--apply --pretty)
fi
if [[ "$FORCE" == "1" ]]; then
APPLY_ARGS+=(--force)
fi
for uuid in "${FORM_RESPONSE_UUIDS[@]}"; do
echo
if [[ "$DRY_RUN" == "1" ]]; then
echo "-- Dry-run form_response_uuid=$uuid --"
else
echo "-- Applying form_response_uuid=$uuid --"
fi
"$APPLY_SCRIPT" --uuid "$uuid" "${APPLY_ARGS[@]}"
done
echo
echo "Finished: $(date --iso-8601=seconds)"
echo "Log: $LOG_FILE"