#!/usr/bin/env bash set -Eeuo pipefail umask 022 # STEP016 - SERVER ATTESO: 215.6 / ax-accessi # # Crea le timbrature DEMO per il giorno odierno in: # presenze_distribuite.terminal_clockings # # Metodo: # - individua l'ultimo giorno DEMO completo precedente a oggi; # - richiede esattamente 25 badge, 4 timbrature per badge, 2 ingressi e 2 uscite; # - clona esclusivamente orari e attributi su oggi; # - non modifica né cancella dati storici; # - rifiuta l'esecuzione se oggi esistono già timbrature per quei badge; # - marca tutte le nuove righe in dbgInfo; # - in caso di verifica fallita rimuove automaticamente solo le righe inserite. # # terminal_clockings è MyISAM: il rollback è selettivo tramite dbgInfo. # Lascia nella cartella di lancio soltanto lo ZIP finale. START_DIR="$PWD" STAMP="$(date '+%Y%m%d_%H%M%S')" TARGET_DATE="${STEP_TARGET_DATE:-$(date '+%F')}" BASE="STEP016_2156_DISTRIBUITE_TERMINAL_CLOCKINGS_TODAY_DEMO_PATCH_TEST_COMMIT_ZIP_ONLY_${STAMP}" WORKDIR="${START_DIR}/.${BASE}_work_$$" OUTZIP="${START_DIR}/${BASE}.zip" LOGFILE="${WORKDIR}/run.log" REPORT="${WORKDIR}/report.txt" INSERT_SQL="${WORKDIR}/insert_today_demo.sql" ROLLBACK_SQL="${WORKDIR}/rollback_today_demo.sql" MARKER="${BASE}_${TARGET_DATE}" PACKAGED=0 INSERT_DONE=0 ROLLBACK_DONE=0 FAILURES=0 WARNINGS=0 EXPECTED_HOST="${STEP_EXPECTED_HOST:-ax-accessi}" EXPECTED_IP="${STEP_EXPECTED_IP:-192.168.215.6}" SKIP_SERVER_CHECK="${STEP_SKIP_SERVER_CHECK:-0}" TARGET_DB="${STEP_TARGET_DB:-presenze_distribuite}" EXPECTED_BADGES=25 EXPECTED_ROWS_PER_BADGE=4 EXPECTED_ROWS=100 EXPECTED_SENSE_ROWS=50 mkdir -p "$WORKDIR" : >"$LOGFILE" : >"$REPORT" log() { printf '%s\n' "$*" | tee -a "$LOGFILE"; } report() { printf '%s\n' "$*" >>"$REPORT"; } fail() { FAILURES=$((FAILURES + 1)); log "ERRORE: $*"; report "ERRORE: $*"; } warn() { WARNINGS=$((WARNINGS + 1)); log "ATTENZIONE: $*"; report "ATTENZIONE: $*"; } package_results() { local rc="${1:-0}" local zip_rc=0 [[ "$PACKAGED" -eq 1 ]] && return 0 PACKAGED=1 report "" report "Esito finale: errori=${FAILURES}, avvisi=${WARNINGS}, insert=${INSERT_DONE}, rollback=${ROLLBACK_DONE}, exit_code=${rc}" rm -f -- "$OUTZIP" if ! command -v zip >/dev/null 2>&1; then rm -rf -- "$WORKDIR" printf '%s\n' "ERRORE: comando zip non disponibile" >&2 return 127 fi (cd "$WORKDIR" && zip -qr "$OUTZIP" .) || zip_rc=$? if [[ "$zip_rc" -eq 0 ]]; then zip -T "$OUTZIP" >>"$LOGFILE" 2>&1 || zip_rc=$? fi rm -rf -- "$WORKDIR" if [[ "$zip_rc" -ne 0 ]]; then rm -f -- "$OUTZIP" printf '%s\n' "ERRORE: creazione/verifica ZIP fallita (rc=${zip_rc})" >&2 return "$zip_rc" fi printf '%s\n' "ZIP CREATO E VERIFICATO: $OUTZIP" } on_exit() { local rc=$? trap - EXIT INT TERM HUP package_results "$rc" || rc=$? exit "$rc" } trap on_exit EXIT trap 'exit 130' INT trap 'exit 143' TERM HUP log "$BASE" log "SERVER ATTESO: 215.6 / ax-accessi" log "DATABASE: ${TARGET_DB}.terminal_clockings" log "DATA TARGET: $TARGET_DATE" log "MODALITA': PATCH + TEST + COMMIT" log "Avvio: $(date --iso-8601=seconds)" ACTUAL_HOST="$(hostname -s 2>/dev/null || hostname)" ACTUAL_IPS="$(hostname -I 2>/dev/null | xargs || true)" { echo "STEP: $BASE" echo "Server atteso: 215.6 / ax-accessi" echo "Database: ${TARGET_DB}.terminal_clockings" echo "Data target: $TARGET_DATE" echo "Modalita': PATCH + TEST + COMMIT" echo "Data esecuzione: $(date --iso-8601=seconds)" echo "Hostname: $ACTUAL_HOST" echo "IP: $ACTUAL_IPS" echo "Marcatore: $MARKER" echo } >>"$REPORT" for cmd in grep awk wc sort zip hostname xargs tr date; do command -v "$cmd" >/dev/null 2>&1 || fail "comando richiesto non disponibile: $cmd" done [[ "$TARGET_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] \ || fail "data target non valida: $TARGET_DATE" [[ "$FAILURES" -eq 0 ]] || exit 20 if [[ "$SKIP_SERVER_CHECK" != "1" ]]; then [[ "$ACTUAL_HOST" == "$EXPECTED_HOST" ]] \ || fail "hostname ${ACTUAL_HOST}; previsto ${EXPECTED_HOST}" if [[ -n "$EXPECTED_IP" ]] && ! grep -qw -- "$EXPECTED_IP" <<<"$ACTUAL_IPS"; then fail "IP ${EXPECTED_IP} non presente tra: ${ACTUAL_IPS}" fi fi [[ "$FAILURES" -eq 0 ]] || exit 21 MYSQL_BIN="${STEP_MYSQL_BIN:-}" if [[ -z "$MYSQL_BIN" ]]; then if command -v mysql >/dev/null 2>&1; then MYSQL_BIN="$(command -v mysql)" elif command -v mariadb >/dev/null 2>&1; then MYSQL_BIN="$(command -v mariadb)" else fail "client mysql/mariadb non trovato" exit 22 fi fi MYSQL=("$MYSQL_BIN" --batch --raw --default-character-set=utf8mb4) MYSQL_SCALAR=("$MYSQL_BIN" --batch --raw --skip-column-names --default-character-set=utf8mb4) if ! "${MYSQL_SCALAR[@]}" -e 'SELECT 1' >/dev/null 2>&1; then fail "accesso MySQL locale non riuscito" exit 23 fi sql_scalar() { "${MYSQL_SCALAR[@]}" -e "$1" 2>>"$LOGFILE" } rollback_inserted() { local remaining log "Rollback automatico delle sole righe inserite dallo STEP016" if "${MYSQL[@]}" --show-warnings <"$ROLLBACK_SQL" >>"$LOGFILE" 2>&1; then remaining="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE dbgInfo='${MARKER}';")" if [[ "$remaining" == "0" ]]; then ROLLBACK_DONE=1 report "Rollback automatico completato." return 0 fi fi fail "rollback automatico fallito o incompleto" return 1 } log "[1/7] Controlli schema" DB_EXISTS="$(sql_scalar " SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='${TARGET_DB}';")" TABLE_EXISTS="$(sql_scalar " SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='${TARGET_DB}' AND TABLE_NAME='terminal_clockings';")" ENGINE="$(sql_scalar " SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA='${TARGET_DB}' AND TABLE_NAME='terminal_clockings';")" [[ "$DB_EXISTS" == "1" ]] || fail "database assente: $TARGET_DB" [[ "$TABLE_EXISTS" == "1" ]] || fail "tabella assente: ${TARGET_DB}.terminal_clockings" [[ "$ENGINE" == "MyISAM" ]] || warn "engine terminal_clockings=${ENGINE}; atteso MyISAM" for column in \ Id Sense Type BadgeNumber Reason DateAndTime TerminalId \ codeControll originalData infoData masterLock dbgInfo do EXISTS="$(sql_scalar " SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='${TARGET_DB}' AND TABLE_NAME='terminal_clockings' AND COLUMN_NAME='${column}';")" [[ "$EXISTS" == "1" ]] || fail "colonna terminal_clockings.${column} assente" done [[ "$FAILURES" -eq 0 ]] || exit 24 log "[2/7] Individuazione ultimo giorno DEMO completo" SOURCE_DATE="$(sql_scalar " SELECT DATE_FORMAT(MAX(DATE(DateAndTime)),'%Y-%m-%d') FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)<'${TARGET_DATE}';")" [[ "$SOURCE_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] \ || fail "giorno sorgente non individuato" SOURCE_ROWS="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}';")" SOURCE_BADGES="$(sql_scalar " SELECT COUNT(DISTINCT BadgeNumber) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}';")" SOURCE_SENSE0="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}' AND Sense='0';")" SOURCE_SENSE1="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}' AND Sense='1';")" BAD_SOURCE_BADGES="$(sql_scalar " SELECT COUNT(*) FROM ( SELECT BadgeNumber FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}' GROUP BY BadgeNumber HAVING COUNT(*)<>${EXPECTED_ROWS_PER_BADGE} OR SUM(Sense='0')<>2 OR SUM(Sense='1')<>2 ) x;")" SOURCE_DUPLICATES="$(sql_scalar " SELECT COUNT(*) FROM ( SELECT BadgeNumber,DateAndTime FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}' GROUP BY BadgeNumber,DateAndTime HAVING COUNT(*)>1 ) x;")" TARGET_ROWS="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE DATE(DateAndTime)='${TARGET_DATE}' AND BadgeNumber IN ( SELECT DISTINCT BadgeNumber FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}' );")" PREVIOUS_MARKER_ROWS="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE dbgInfo='${MARKER}';")" { printf 'controllo\tatteso\trilevato\n' printf 'source_date\tprecedente_a_%s\t%s\n' "$TARGET_DATE" "$SOURCE_DATE" printf 'source_rows\t%s\t%s\n' "$EXPECTED_ROWS" "$SOURCE_ROWS" printf 'source_badges\t%s\t%s\n' "$EXPECTED_BADGES" "$SOURCE_BADGES" printf 'source_sense_0\t%s\t%s\n' "$EXPECTED_SENSE_ROWS" "$SOURCE_SENSE0" printf 'source_sense_1\t%s\t%s\n' "$EXPECTED_SENSE_ROWS" "$SOURCE_SENSE1" printf 'bad_source_badges\t0\t%s\n' "$BAD_SOURCE_BADGES" printf 'source_duplicates\t0\t%s\n' "$SOURCE_DUPLICATES" printf 'target_rows_for_source_badges\t0\t%s\n' "$TARGET_ROWS" printf 'previous_marker_rows\t0\t%s\n' "$PREVIOUS_MARKER_ROWS" } >"${WORKDIR}/01_PRECHECKS.tsv" [[ "$SOURCE_DATE" < "$TARGET_DATE" ]] \ || fail "data sorgente ${SOURCE_DATE} non precedente alla data target ${TARGET_DATE}" [[ "$SOURCE_ROWS" == "$EXPECTED_ROWS" ]] \ || fail "righe del giorno sorgente ${SOURCE_DATE}: ${SOURCE_ROWS}; attese ${EXPECTED_ROWS}" [[ "$SOURCE_BADGES" == "$EXPECTED_BADGES" ]] \ || fail "badge del giorno sorgente: ${SOURCE_BADGES}; attesi ${EXPECTED_BADGES}" [[ "$SOURCE_SENSE0" == "$EXPECTED_SENSE_ROWS" ]] \ || fail "ingressi sorgente: ${SOURCE_SENSE0}; attesi ${EXPECTED_SENSE_ROWS}" [[ "$SOURCE_SENSE1" == "$EXPECTED_SENSE_ROWS" ]] \ || fail "uscite sorgente: ${SOURCE_SENSE1}; attese ${EXPECTED_SENSE_ROWS}" [[ "$BAD_SOURCE_BADGES" == "0" ]] \ || fail "badge sorgente non completi o sbilanciati: $BAD_SOURCE_BADGES" [[ "$SOURCE_DUPLICATES" == "0" ]] \ || fail "duplicati nel giorno sorgente: $SOURCE_DUPLICATES" [[ "$TARGET_ROWS" == "0" ]] \ || fail "per la data ${TARGET_DATE} esistono già ${TARGET_ROWS} timbrature sui badge DEMO" [[ "$PREVIOUS_MARKER_ROWS" == "0" ]] \ || fail "marcatore STEP016 già presente" [[ "$FAILURES" -eq 0 ]] || exit 25 log "[3/7] Esportazione modello sorgente" "${MYSQL[@]}" -e " SELECT BadgeNumber, Sense, Type, Reason, DateAndTime, TIME(DateAndTime) AS orario, TerminalId, codeControll, infoData, masterLock FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${SOURCE_DATE}' ORDER BY BadgeNumber,DateAndTime;" \ >"${WORKDIR}/02_SOURCE_DAY.tsv" 2>>"$LOGFILE" cat >"$ROLLBACK_SQL" <"$INSERT_SQL" <>"$LOGFILE" 2>&1; then fail "inserimento SQL fallito" rollback_inserted || true exit 26 fi INSERT_DONE=1 log "[6/7] Verifica completa dopo l'inserimento" INSERTED_ROWS="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE dbgInfo='${MARKER}';")" TODAY_ROWS="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${TARGET_DATE}';")" TODAY_BADGES="$(sql_scalar " SELECT COUNT(DISTINCT BadgeNumber) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${TARGET_DATE}';")" TODAY_SENSE0="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${TARGET_DATE}' AND Sense='0';")" TODAY_SENSE1="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${TARGET_DATE}' AND Sense='1';")" BAD_TODAY_BADGES="$(sql_scalar " SELECT COUNT(*) FROM ( SELECT BadgeNumber FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${TARGET_DATE}' GROUP BY BadgeNumber HAVING COUNT(*)<>${EXPECTED_ROWS_PER_BADGE} OR SUM(Sense='0')<>2 OR SUM(Sense='1')<>2 ) x;")" TODAY_DUPLICATES="$(sql_scalar " SELECT COUNT(*) FROM ( SELECT BadgeNumber,DateAndTime FROM \`${TARGET_DB}\`.terminal_clockings WHERE DATE(DateAndTime)='${TARGET_DATE}' GROUP BY BadgeNumber,DateAndTime HAVING COUNT(*)>1 ) x;")" SOURCE_ROWS_MISSING_TODAY="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings s WHERE s.infoData='DEMO' AND DATE(s.DateAndTime)='${SOURCE_DATE}' AND NOT EXISTS ( SELECT 1 FROM \`${TARGET_DB}\`.terminal_clockings t WHERE t.infoData='DEMO' AND DATE(t.DateAndTime)='${TARGET_DATE}' AND t.BadgeNumber=s.BadgeNumber AND t.Sense=s.Sense AND t.Type=s.Type AND t.Reason=s.Reason AND TIME(t.DateAndTime)=TIME(s.DateAndTime) AND t.TerminalId=s.TerminalId );")" BAD_INSERTED_ROWS="$(sql_scalar " SELECT COUNT(*) FROM \`${TARGET_DB}\`.terminal_clockings WHERE dbgInfo='${MARKER}' AND ( DATE(DateAndTime)<>'${TARGET_DATE}' OR originalData<>DateAndTime OR infoData<>'DEMO' );")" { printf 'controllo\tatteso\trilevato\n' printf 'inserted_rows\t%s\t%s\n' "$EXPECTED_ROWS" "$INSERTED_ROWS" printf 'today_demo_rows\t%s\t%s\n' "$EXPECTED_ROWS" "$TODAY_ROWS" printf 'today_badges\t%s\t%s\n' "$EXPECTED_BADGES" "$TODAY_BADGES" printf 'today_sense_0\t%s\t%s\n' "$EXPECTED_SENSE_ROWS" "$TODAY_SENSE0" printf 'today_sense_1\t%s\t%s\n' "$EXPECTED_SENSE_ROWS" "$TODAY_SENSE1" printf 'bad_today_badges\t0\t%s\n' "$BAD_TODAY_BADGES" printf 'today_duplicates\t0\t%s\n' "$TODAY_DUPLICATES" printf 'source_rows_missing_today\t0\t%s\n' "$SOURCE_ROWS_MISSING_TODAY" printf 'bad_inserted_rows\t0\t%s\n' "$BAD_INSERTED_ROWS" } >"${WORKDIR}/03_POSTCHECKS.tsv" VERIFY_OK=1 [[ "$INSERTED_ROWS" == "$EXPECTED_ROWS" ]] || VERIFY_OK=0 [[ "$TODAY_ROWS" == "$EXPECTED_ROWS" ]] || VERIFY_OK=0 [[ "$TODAY_BADGES" == "$EXPECTED_BADGES" ]] || VERIFY_OK=0 [[ "$TODAY_SENSE0" == "$EXPECTED_SENSE_ROWS" ]] || VERIFY_OK=0 [[ "$TODAY_SENSE1" == "$EXPECTED_SENSE_ROWS" ]] || VERIFY_OK=0 [[ "$BAD_TODAY_BADGES" == "0" ]] || VERIFY_OK=0 [[ "$TODAY_DUPLICATES" == "0" ]] || VERIFY_OK=0 [[ "$SOURCE_ROWS_MISSING_TODAY" == "0" ]] || VERIFY_OK=0 [[ "$BAD_INSERTED_ROWS" == "0" ]] || VERIFY_OK=0 if [[ "$VERIFY_OK" -ne 1 ]]; then fail "verifica finale incoerente" rollback_inserted || true exit 27 fi "${MYSQL[@]}" -e " SELECT * FROM \`${TARGET_DB}\`.terminal_clockings WHERE infoData='DEMO' AND DATE(DateAndTime)='${TARGET_DATE}' ORDER BY BadgeNumber,DateAndTime;" \ >"${WORKDIR}/04_TODAY_ROWS.tsv" 2>>"$LOGFILE" log "[7/7] Esito" { echo "PATCH COMPLETATA" echo "Server: 215.6 / ax-accessi" echo "Tabella: ${TARGET_DB}.terminal_clockings" echo "Giorno sorgente clonato: $SOURCE_DATE" echo "Giorno DEMO creato: $TARGET_DATE" echo "Timbrature inserite: $INSERTED_ROWS" echo "Badge: $TODAY_BADGES" echo "Ingressi: $TODAY_SENSE0" echo "Uscite: $TODAY_SENSE1" echo "Badge incompleti o sbilanciati: $BAD_TODAY_BADGES" echo "Duplicati: $TODAY_DUPLICATES" echo "Rollback automatico: non necessario" } >"${WORKDIR}/99_SUMMARY.txt" report "Creato il giorno DEMO ${TARGET_DATE} clonando il modello completo del ${SOURCE_DATE}." report "Inserite ${INSERTED_ROWS} timbrature su ${TODAY_BADGES} badge." report "Ingressi=${TODAY_SENSE0}, uscite=${TODAY_SENSE1}, duplicati=${TODAY_DUPLICATES}." log "PATCH COMPLETATA E VERIFICATA"