#!/usr/bin/env bash # STEP053 - Porting Presenze 215.6 # # Correzione mirata delle stampe Silvelox. # # Causa confermata: # - il report legge ORD/STR/ASS/OVR/BT da ax_new_time_day; # - ax_new_time_day e' incoerente; # - ax_new_time_causali contiene invece i valori giornalieri corretti. # # Questa patch NON modifica il database. # Prima della creazione di dataReportTable.json ricostruisce esclusivamente # i campi riepilogativi di time_day dalle causali giornaliere: # # ORD -> ordinarie # EXT -> straordinarie # ABSE -> assenze # MAGG -> maggiorazioni # OVER -> overturno # BANK -> bancaore # # Le timbrature, i giorni, le relazioni e gli altri dati restano invariati. # # Server atteso: 215.6 / ax-accessi set -Eeuo pipefail umask 077 STEP_NAME="STEP053_2156_SILVELOX_PRINT_REBUILD_DAY_TOTALS_FROM_CAUSALI_PATCH_VERIFIED_20260731" EXPECTED_HOST="${EXPECTED_HOST:-ax-accessi}" EXPECTED_IP_FRAGMENT="${EXPECTED_IP_FRAGMENT:-192.168.215.6}" TARGET_ROOT="${TARGET_ROOT:-/var/www/html/presenze.silvelox}" TARGET_FILE="${TARGET_FILE:-$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_stampe/ctrl/controllerCtrl.php}" HELPER_FILE="${HELPER_FILE:-$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_stampe/ctrl/helper.php}" EXPECTED_SHA256="${EXPECTED_SHA256:-0b76faa7032361fe86eadd7da211d3d4338f665096795b1514ef4f105987a09e}" ALLOW_TEST="${ALLOW_TEST:-0}" SKIP_RELOAD="${SKIP_RELOAD:-0}" FAIL_AFTER_PATCH="${FAIL_AFTER_PATCH:-0}" STAMP="$(date +%Y%m%d_%H%M%S)" OUTPUT_ZIP="$PWD/${STEP_NAME}_${STAMP}.zip" WORKDIR="$(mktemp -d "$PWD/.${STEP_NAME}.XXXXXX")" REPORT="$WORKDIR/${STEP_NAME}_REPORT.txt" BACKUP="$WORKDIR/controllerCtrl.php.before" AFTER="$WORKDIR/controllerCtrl.php.after" PATCHER="$WORKDIR/patch.php" TEST_DIR="$WORKDIR/unit" TEST_CONTROLLER="$TEST_DIR/controllerCtrl.class.php" TEST_HELPER="$TEST_DIR/helper.php" TEST_COMMON="$TEST_DIR/docroot/ax_common/a000_ax_include_controller.php" UNIT_TEST="$TEST_DIR/unit_test.php" UNIT_OUTPUT="$WORKDIR/unit_test_output.txt" CHANGED=0 cleanup() { rm -rf -- "$WORKDIR" } reload_runtime() { if [[ "$SKIP_RELOAD" == "1" ]]; then return 0 fi if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^php5\.6-fpm\.service'; then systemctl reload php5.6-fpm elif command -v service >/dev/null 2>&1; then service php5.6-fpm reload fi apache2ctl configtest apache2ctl graceful } rollback() { if [[ "$CHANGED" == "1" && -f "$BACKUP" ]]; then echo "ROLLBACK: ripristino controller ax_stampe originale." >&2 cp -a -- "$BACKUP" "$TARGET_FILE" reload_runtime >/dev/null 2>&1 || true CHANGED=0 fi } on_error() { local rc=$? local line="${1:-?}" trap - ERR rollback printf 'ERRORE linea %s, codice %s\n' "$line" "$rc" >&2 exit "$rc" } on_interrupt() { trap - ERR rollback exit 130 } trap cleanup EXIT trap 'on_error "$LINENO"' ERR trap on_interrupt INT TERM section() { printf '\n\n================================================================\n%s\n================================================================\n' "$1" >> "$REPORT" } HOST_NOW="$(hostname 2>/dev/null || true)" IPS_NOW="$(hostname -I 2>/dev/null || true)" { echo "$STEP_NAME" echo "DATA: $(date --iso-8601=seconds 2>/dev/null || date)" echo "SERVER: $HOST_NOW" echo "IP: $IPS_NOW" echo "PORTALE: $TARGET_ROOT" echo "CONTROLLER: $TARGET_FILE" echo "MODIFICA DATABASE: NO" echo "SCOPO: ricostruire time_day dalle causali prima del JSON" } > "$REPORT" if [[ "$ALLOW_TEST" != "1" ]]; then if [[ "$HOST_NOW" != "$EXPECTED_HOST" && "$IPS_NOW" != *"$EXPECTED_IP_FRAGMENT"* ]]; then echo "ERRORE: server errato; atteso 215.6 / ax-accessi." >&2 exit 10 fi fi for required in "$TARGET_FILE" "$HELPER_FILE"; do [[ -f "$required" ]] || { echo "ERRORE: file richiesto non trovato: $required" >&2 exit 11 } done PHP_BIN="$(command -v php5.6 || command -v php || true)" [[ -n "$PHP_BIN" ]] || { echo "ERRORE: PHP CLI non disponibile." >&2 exit 12 } CURRENT_HASH="$(sha256sum "$TARGET_FILE" | awk '{print $1}')" PATCH_ALREADY_PRESENT=0 if grep -Fq \ 'private function correggiTimeDayDaCausali(' \ "$TARGET_FILE"; then PATCH_ALREADY_PRESENT=1 elif [[ "$ALLOW_TEST" != "1" && "$CURRENT_HASH" != "$EXPECTED_SHA256" ]]; then echo "ERRORE: controller diverso da quello auditato nello STEP052." >&2 echo "HASH ATTESO: $EXPECTED_SHA256" >&2 echo "HASH REALE: $CURRENT_HASH" >&2 exit 13 fi section "Pre-check" { echo "HASH_INIZIALE=$CURRENT_HASH" echo "PATCH_ALREADY_PRESENT=$PATCH_ALREADY_PRESENT" "$PHP_BIN" -l "$TARGET_FILE" "$PHP_BIN" -l "$HELPER_FILE" [[ "$(grep -F -c 'private function makeTables($parm)' "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "\$table['time_day'] = \$tmpDay['out'];" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "\$fncCausDay = \$this->getCausLines(" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "\$table['cauys_day'] = \$fncCausDay->table;" "$TARGET_FILE")" -eq 1 ]] stat -c '%A %a %U:%G %s %y %n' "$TARGET_FILE" } >> "$REPORT" 2>&1 cp -a -- "$TARGET_FILE" "$BACKUP" cat > "$PATCHER" <<'PHP' 'ordinarie', 'EXT' => 'straordinarie', 'ABSE' => 'assenze', 'MAGG' => 'maggiorazioni', 'OVER' => 'overturno', 'BANK' => 'bancaore' ); $totals = array(); if (is_array($causaliDay)) { foreach ($causaliDay as $rawRow) { $row = (array) $rawRow; if ( !isset($row['userId']) || !isset($row['dayCaus']) ) { continue; } $type = isset($row['causType']) ? strtoupper(trim($row['causType'])) : ''; if ( isset($row['causGest']) && $row['causGest'] === 'la_magg' ) { $type = 'MAGG'; } if (!isset($columnByType[$type])) { continue; } $day = new DateTime($row['dayCaus']); $key = $row['userId'] . '_' . $day->format('Y_m_d'); $value = isset($row['causTime']) ? (float) $row['causTime'] : 0.0; if (!isset($totals[$key][$type])) { $totals[$key][$type] = 0.0; } $totals[$key][$type] += $value; } } $bankExcluded = ( is_array($excludeType) && in_array('bancaore', $excludeType, true) ); if (!is_array($timeDay)) { return $timeDay; } foreach ($timeDay as $index => $rawRow) { $row = (array) $rawRow; $key = ''; if (isset($row['priKeyDay'])) { $key = $row['priKeyDay']; } elseif ( isset($row['userId']) && isset($row['day']) ) { $day = new DateTime($row['day']); $key = $row['userId'] . '_' . $day->format('Y_m_d'); } foreach ($columnByType as $type => $column) { $value = isset($totals[$key][$type]) ? $totals[$key][$type] : 0.0; if ($type === 'BANK' && $bankExcluded) { $value = 0.0; } $row[$column] = number_format( $value, 2, '.', '' ); } $bankForTotal = $bankExcluded ? 0.0 : (float) $row['bancaore']; $row['totLav'] = number_format( (float) $row['ordinarie'] + (float) $row['straordinarie'] + $bankForTotal, 2, '.', '' ); $timeDay[$index] = $row; } return $timeDay; } PHPMETHOD; $method = convertEol($method, $eol); $data = str_replace( $insertMarker, $method . $insertMarker, $data ); $changed = true; } $call = <<<'PHPCALL' $table['time_day'] = $this->correggiTimeDayDaCausali( $table['time_day'], $fncCausDay->table, $excludeType ); PHPCALL; $call = convertEol($call, $eol); $callMarker = " \$xc = 0;"; if (strpos($data, "correggiTimeDayDaCausali(\n") === false && strpos($data, "correggiTimeDayDaCausali(\r\n") === false) { throw new Exception("Metodo non presente dopo inserimento"); } $callCount = substr_count( $data, "\$table['time_day'] = \$this->correggiTimeDayDaCausali(" ); if ($callCount === 0) { if (substr_count($data, $callMarker) !== 1) { throw new Exception( "Punto inserimento chiamata non trovato o ambiguo" ); } $data = str_replace( $callMarker, $call . $callMarker, $data ); $changed = true; } elseif ($callCount !== 1) { throw new Exception( "Chiamate correggiTimeDayDaCausali inattese: " . $callCount ); } if ($changed) { if (file_put_contents($file, $data) === false) { fwrite(STDERR, "Impossibile scrivere il controller\n"); exit(4); } echo "PATCH_APPLICATA\n"; } else { echo "PATCH_GIA_PRESENTE\n"; } PHP section "Applicazione patch" CHANGED=1 if PATCH_RESULT="$("$PHP_BIN" "$PATCHER" "$TARGET_FILE" 2>&1)"; then : else rc=$? echo "$PATCH_RESULT" >&2 exit "$rc" fi echo "$PATCH_RESULT" | tee -a "$REPORT" if [[ "$FAIL_AFTER_PATCH" == "1" ]]; then echo "ERRORE DI TEST DOPO PATCH" >&2 false fi section "Verifica statica dopo patch" { "$PHP_BIN" -l "$TARGET_FILE" [[ "$(grep -F -c 'private function correggiTimeDayDaCausali(' "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "\$table['time_day'] = \$this->correggiTimeDayDaCausali(" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "'ORD' => 'ordinarie'" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "'EXT' => 'straordinarie'" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "'ABSE' => 'assenze'" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "'MAGG' => 'maggiorazioni'" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "'OVER' => 'overturno'" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "'BANK' => 'bancaore'" "$TARGET_FILE")" -eq 1 ]] [[ "$(grep -F -c "\$fncCausDay->table" "$TARGET_FILE")" -ge 2 ]] echo "PHP lint: OK" echo "Metodo unico: OK" echo "Chiamata unica nel flusso makeTables: OK" echo "Mappatura ORD/EXT/ABSE/MAGG/OVER/BANK: OK" } >> "$REPORT" 2>&1 cp -a -- "$TARGET_FILE" "$AFTER" section "Unit test sul controller reale patchato" mkdir -p \ "$TEST_DIR" \ "$(dirname "$TEST_COMMON")" cp -a -- "$HELPER_FILE" "$TEST_HELPER" # Mantiene la classe reale ed elimina soltanto il bootstrap finale. awk ' /^\$ax_controller = new ax_controller\(\);/ {exit} {print} ' "$TARGET_FILE" > "$TEST_CONTROLLER" cat > "$TEST_COMMON" <<'PHP' "$UNIT_TEST" <<'PHP' setAccessible(true); $timeDay = array( array( 'userId' => '223', 'day' => '2026-07-02', 'priKeyDay' => '223_2026_07_02', 'ordinarie' => '0.00', 'straordinarie' => '0.00', 'assenze' => '8.00', 'maggiorazioni' => '0.00', 'overturno' => '0.00', 'bancaore' => '0.00', 'totLav' => '0.00', 'dayName' => 'Gi' ), array( 'userId' => '223', 'day' => '2026-07-03', 'priKeyDay' => '223_2026_07_03', 'ordinarie' => '8.00', 'straordinarie' => '0.00', 'assenze' => '0.00', 'maggiorazioni' => '0.00', 'overturno' => '0.00', 'bancaore' => '0.00', 'totLav' => '8.00', 'dayName' => 'Ve' ), array( 'userId' => '223', 'day' => '2026-07-04', 'priKeyDay' => '223_2026_07_04', 'ordinarie' => '0.00', 'straordinarie' => '0.00', 'assenze' => '0.00', 'maggiorazioni' => '0.00', 'overturno' => '0.00', 'bancaore' => '0.00', 'totLav' => '0.00', 'dayName' => 'Sa' ) ); $causali = array( array( 'userId' => '223', 'dayCaus' => '2026-07-02', 'causType' => 'ORD', 'causGest' => '', 'causTime' => '8.00' ), array( 'userId' => '223', 'dayCaus' => '2026-07-03', 'causType' => 'ORD', 'causGest' => '', 'causTime' => '6.00' ), array( 'userId' => '223', 'dayCaus' => '2026-07-03', 'causType' => 'ABSE', 'causGest' => '', 'causTime' => '2.00' ), array( 'userId' => '223', 'dayCaus' => '2026-07-03', 'causType' => 'ORD', 'causGest' => 'la_magg', 'causTime' => '1.50' ), array( 'userId' => '223', 'dayCaus' => '2026-07-03', 'causType' => 'BANK', 'causGest' => '', 'causTime' => '1.00' ) ); $result = $method->invoke( $controller, $timeDay, $causali, null ); assertSameValue('8.00', $result[0]['ordinarie'], '02 ORD'); assertSameValue('0.00', $result[0]['assenze'], '02 ABSE'); assertSameValue('8.00', $result[0]['totLav'], '02 TOT'); assertSameValue('6.00', $result[1]['ordinarie'], '03 ORD'); assertSameValue('2.00', $result[1]['assenze'], '03 ABSE'); assertSameValue('1.50', $result[1]['maggiorazioni'], '03 MAGG'); assertSameValue('1.00', $result[1]['bancaore'], '03 BANK'); assertSameValue('7.00', $result[1]['totLav'], '03 TOT'); assertSameValue('Ve', $result[1]['dayName'], 'Campo esistente preservato'); assertSameValue('0.00', $result[2]['ordinarie'], '04 ORD'); assertSameValue('0.00', $result[2]['assenze'], '04 ABSE'); $excluded = $method->invoke( $controller, $timeDay, $causali, array('bancaore') ); assertSameValue('0.00', $excluded[1]['bancaore'], 'BANK esclusa'); assertSameValue('6.00', $excluded[1]['totLav'], 'TOT senza BANK'); echo "UNIT_TEST_OK\n"; PHP if "$PHP_BIN" \ "$UNIT_TEST" \ "$TEST_DIR/docroot" \ "$TEST_CONTROLLER" \ > "$UNIT_OUTPUT" 2>&1; then : else rc=$? cat "$UNIT_OUTPUT" >&2 exit "$rc" fi grep -Fxq "UNIT_TEST_OK" "$UNIT_OUTPUT" cat "$UNIT_OUTPUT" >> "$REPORT" section "Reload runtime" reload_runtime >> "$REPORT" 2>&1 section "Esito" { echo "PATCH=$PATCH_RESULT" echo "HASH_PRIMA=$CURRENT_HASH" echo "HASH_DOPO=$(sha256sum "$TARGET_FILE" | awk '{print $1}')" echo "DATABASE MODIFICATO: NO" echo "CONTROLLER MODIFICATO: SI" echo "UNIT TEST CONTROLLER REALE: OK" echo "ORD DA CAUSALI: OK" echo "ABSE DA CAUSALI: OK" echo "MAGG DA causGest=la_magg: OK" echo "BANK ESCLUSA QUANDO RICHIESTO: OK" echo "CAMPI ESISTENTI PRESERVATI: OK" echo "ESITO: COMPLETATO" } >> "$REPORT" ( cd "$WORKDIR" zip -q -9 "$OUTPUT_ZIP" \ "$(basename "$REPORT")" \ "$(basename "$BACKUP")" \ "$(basename "$AFTER")" \ "$(basename "$UNIT_OUTPUT")" ) zip -T "$OUTPUT_ZIP" >/dev/null CHANGED=0 echo "STEP053 COMPLETATO" echo "ZIP: $OUTPUT_ZIP"