From 8a1cbe5468962cc2876849c8dd1ecec059619655 Mon Sep 17 00:00:00 2001 From: "jan.zhanal" Date: Fri, 13 Mar 2026 15:45:53 +0100 Subject: [PATCH 01/16] ORIS entries: complete logic to push all entries to ORIS --- _SQL/zmeny.sql.php | 3 +- _SQL/zmeny_3.4.5.658.sql.php | 23 +++ adm_oris_sync.php | 43 +++++ connectors.php | 39 ++-- lib/OrisIntegrationService.php | 193 ++++++++++++++++++++ nav.inc.php | 1 + oris_sync.log | 148 +++++++++++++++ oris_sync_daemon.php | 321 +++++++++++++++++++++++++++++++++ race_edit.php | 5 + race_edit_exc.php | 19 +- race_new.php | 1 + race_new_exc.php | 3 +- race_regs_1_exc.php | 29 ++- race_regs_all_exc.php | 31 +++- 14 files changed, 816 insertions(+), 43 deletions(-) create mode 100644 _SQL/zmeny_3.4.5.658.sql.php create mode 100644 adm_oris_sync.php create mode 100644 lib/OrisIntegrationService.php create mode 100644 oris_sync.log create mode 100644 oris_sync_daemon.php diff --git a/_SQL/zmeny.sql.php b/_SQL/zmeny.sql.php index edafc49..d5dba19 100644 --- a/_SQL/zmeny.sql.php +++ b/_SQL/zmeny.sql.php @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/adm_oris_sync.php b/adm_oris_sync.php new file mode 100644 index 0000000..5881268 --- /dev/null +++ b/adm_oris_sync.php @@ -0,0 +1,43 @@ + + + + + ORIS Sync Daemon Manual Trigger + + + +

Manual ORIS Sync Execution

+

Executing daemon...

+
+getMessage() . "\n"; +} +$output = ob_get_clean(); + +// Display standard output +echo htmlspecialchars($output); +?> +
+
+ Return to System + + \ No newline at end of file diff --git a/connectors.php b/connectors.php index c0ffd9a..82638e9 100644 --- a/connectors.php +++ b/connectors.php @@ -27,11 +27,12 @@ class RaceInfo { public $etap; public $poznamka; public $vicedenni; - public $oddil; - public $modify_flag; - public $kategorie; - public $startovne; - public $cancelled; + public $oddil; + public $modify_flag; + public $kategorie; + public $startovne; + public $cancelled; + public $oris_entry_start; // Constructor to initialize the object with key-value pairs public function __construct($data) { @@ -57,12 +58,13 @@ public function __construct($data) { $this->poznamka = $data['poznamka'] ?? null; $this->vicedenni = $data['vicedenni'] ?? null; $this->oddil = $data['oddil'] ?? null; - $this->modify_flag = $data['modify_flag'] ?? null; - $this->kategorie = $data['kategorie'] ?? null; - $this->startovne = $data['startovne'] ?? null; - $this->cancelled = $data['cancelled'] ?? null; - } -} + $this->modify_flag = $data['modify_flag'] ?? null; + $this->kategorie = $data['kategorie'] ?? null; + $this->startovne = $data['startovne'] ?? null; + $this->cancelled = $data['cancelled'] ?? null; + $this->oris_entry_start = $data['oris_entry_start'] ?? null; + } +} class RacePayement { public int $raceId; @@ -273,13 +275,14 @@ public function getRaceInfo($raceId) : RaceInfo { 'koeficient2' => $raceData['EntryKoef3'], 'etap' => $raceData['Stages'], // 'poznamka' => $poznamka, - 'vicedenni' => ($raceData['Stages']>1?1:0), - 'oddil' => $oddily, - 'modify_flag' => 0, - 'kategorie' => implode(';', array_keys ( $classFees ) ), - 'startovne' => $classFees, - 'cancelled' => (!empty($raceData['Cancelled']) || !empty($raceData['Canceled']) || !empty($raceData['cancelled']) || !empty($raceData['canceled'])) ? 1 : 0 - ]); + 'vicedenni' => ($raceData['Stages']>1?1:0), + 'oddil' => $oddily, + 'modify_flag' => 0, + 'kategorie' => implode(';', array_keys ( $classFees ) ), + 'startovne' => $classFees, + 'cancelled' => (!empty($raceData['Cancelled']) || !empty($raceData['Canceled']) || !empty($raceData['cancelled']) || !empty($raceData['canceled'])) ? 1 : 0, + 'oris_entry_start' => !empty($raceData['EntryStart']) ? $raceData['EntryStart'] : null + ]); } else { return null; // Return null if race not found or error } diff --git a/lib/OrisIntegrationService.php b/lib/OrisIntegrationService.php new file mode 100644 index 0000000..6bced1f --- /dev/null +++ b/lib/OrisIntegrationService.php @@ -0,0 +1,193 @@ +clubKey = $clubKey; + } + + /** + * Executes the HTTP POST request to ORIS API + */ + private function executeRequest($method, $params) { + $params['method'] = $method; + $params['format'] = 'json'; + $params['clubkey'] = $this->clubKey; + + $postData = http_build_query($params); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->apiUrl); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + // Ensure SSL verification + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + + if(curl_errno($ch)){ + $error = curl_error($ch); + curl_close($ch); + return ['status' => 'error', 'message' => 'cURL Error: ' . $error, 'request' => $postData]; + } + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $decoded = json_decode($response, true); + + if ($httpCode >= 200 && $httpCode < 300 && isset($decoded['Status']) && $decoded['Status'] === 'OK') { + return ['status' => 'success', 'data' => $decoded['Data'] ?? $decoded, 'request' => $postData]; + } else { + $apiErrorMsg = ''; + if (is_array($decoded) && isset($decoded['Status'])) { + $apiErrorMsg = " ORIS Status: " . $decoded['Status']; + // Some endpoints might return error details in Data or a specific Error field + if (isset($decoded['Data']) && is_string($decoded['Data'])) { + $apiErrorMsg .= " - " . $decoded['Data']; + } + } + return [ + 'status' => 'error', + 'message' => 'API Error or HTTP ' . $httpCode . $apiErrorMsg, + 'payload' => $response, + 'request' => $postData + ]; + } + } + + public function createEntry($clubuser, $classId, $si, $rentSi, $note = '') { + $params = [ + 'clubuser' => $clubuser, + 'class' => $classId + ]; + + if ($si) { + $params['si'] = $si; + } + if ($rentSi) { + $params['rent_si'] = 1; + } + if ($note) { + $params['note'] = $note; + } + + return $this->executeRequest('createEntry', $params); + } + + public function updateEntry($entryId, $clubuser, $classId, $si, $rentSi, $note = '') { + $params = [ + 'entryid' => $entryId, + 'clubuser' => $clubuser, + 'class' => $classId + ]; + + if ($si) { + $params['si'] = $si; + } + if ($rentSi) { + $params['rent_si'] = 1; + } + if ($note) { + $params['note'] = $note; + } + + return $this->executeRequest('updateEntry', $params); + } + + public function deleteEntry($entryId) { + $params = [ + 'entryid' => $entryId + ]; + return $this->executeRequest('deleteEntry', $params); + } + + public function getUser($rgnum) { + $params = [ + 'method' => 'getUser', + 'format' => 'json', + 'rgnum' => $rgnum + ]; + + $ch = curl_init(); + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); + } + + public function getClubUsers($userId) { + $params = [ + 'method' => 'getClubUsers', + 'format' => 'json', + 'user' => $userId + ]; + + $ch = curl_init(); + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); + } + + public function getEventEntries($eventId) { + $params = [ + 'method' => 'getEventEntries', + 'format' => 'json', + 'eventid' => $eventId + ]; + + $ch = curl_init(); + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); + } + + public function getEvent($eventId) { + $params = [ + 'method' => 'getEvent', + 'format' => 'json', + 'id' => $eventId + ]; + + $ch = curl_init(); + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); + } +} +?> \ No newline at end of file diff --git a/nav.inc.php b/nav.inc.php index 98f1c8f..bc1bf00 100644 --- a/nav.inc.php +++ b/nav.inc.php @@ -100,6 +100,7 @@ function DrawMenuGroupHeader($name) DrawMenuGroupHeader('Menu přihlašovatele'); DrawMenuItem('Přihlášky na závody',_REGISTRATOR_GROUP_ID_,1); DrawMenuItem('Editace závodů',_REGISTRATOR_GROUP_ID_,4); + DrawMenuItemStatic('Synchronizuj přihlášky s ORISem', 'adm_oris_sync.php'); } if(IsLoggedManager()) { diff --git a/oris_sync.log b/oris_sync.log new file mode 100644 index 0000000..a9beebf --- /dev/null +++ b/oris_sync.log @@ -0,0 +1,148 @@ +[2026-03-12 21:53:42] Processing create for entry ID 125 (user: 26, race: 48) +[2026-03-12 21:53:42] - Skipped: Race 48 is not imported from ORIS (no oris_comp_id). Marking LOCAL_ONLY. +[2026-03-12 21:56:25] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 21:56:25] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 +[2026-03-12 21:56:25] - Failed: Action create encountered an error: API Error or HTTP 200 +[2026-03-12 21:56:57] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 21:56:57] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 +[2026-03-12 21:56:57] - Failed: Action create encountered an error: API Error or HTTP 200 +[2026-03-12 21:59:24] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 21:59:24] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 +[2026-03-12 21:59:24] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné +[2026-03-12 22:01:44] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:01:44] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 +[2026-03-12 22:01:44] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné +[2026-03-12 22:01:58] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:01:58] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 +[2026-03-12 22:01:58] - Raw POST Data: clubuser=8721&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:01:58] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné +[2026-03-12 22:02:59] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:02:59] - Sending create request to ORIS API. clubuser: ZBM8721, class: H21, si: 0 +[2026-03-12 22:02:59] - Raw POST Data: clubuser=ZBM8721&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:02:59] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné +[2026-03-12 22:05:58] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:05:58] - Sending create request to ORIS API. clubuser: ZBM8721, class: H21, si: 0 +[2026-03-12 22:05:58] - Raw POST Data: clubuser=ZBM8721&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:05:58] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné +[2026-03-12 22:06:30] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:06:30] - Sending create request to ORIS API. clubuser: 7000, class: H21, si: 0 +[2026-03-12 22:06:31] - Raw POST Data: clubuser=7000&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:06:31] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Neplatná kategorie +[2026-03-12 22:11:24] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:11:25] - Warning: Could not resolve ORIS Class ID for category 'H21' +[2026-03-12 22:11:25] - Sending create request to ORIS API. clubuser: 7000, class: H21 (mapped from H21), si: 0 +[2026-03-12 22:11:25] - Raw POST Data: clubuser=7000&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:11:25] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Neplatná kategorie +[2026-03-12 22:12:14] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:12:14] - Warning: Could not resolve ORIS Class ID for category 'H21' +[2026-03-12 22:12:14] - Sending create request to ORIS API. clubuser: 7000, class: H21 (mapped from H21), si: 0 +[2026-03-12 22:12:14] - Raw POST Data: clubuser=7000&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:12:14] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Neplatná kategorie +[2026-03-12 22:15:33] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:15:34] - Sending create request to ORIS API. clubuser: 7000, class: 217130 (mapped from H21), si: 0 +[2026-03-12 22:15:34] - Raw POST Data: clubuser=7000&class=217130&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:15:34] - Success: Action create completed. ORIS Entry ID: +[2026-03-12 22:24:40] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:24:41] - Sending create request to ORIS API. clubuser: 7323, class: 217130 (mapped from H21), si: 0 +[2026-03-12 22:24:41] - Raw POST Data: clubuser=7323&class=217130&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:24:41] - Success: Action create completed. ORIS Entry ID: +[2026-03-12 22:42:43] Processing create for entry ID 138 (user: 39, race: 52) +[2026-03-12 22:42:43] - Sending create request to ORIS API. clubuser: 7323, class: 217130 (mapped from H21), si: 0 +[2026-03-12 22:42:43] - Raw POST Data: clubuser=7323&class=217130&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-12 22:42:43] - Success: Action create completed. ORIS Entry ID: +[2026-03-13 13:41:09] Processing update for entry ID 139 (user: 39, race: 53) +[2026-03-13 13:41:10] - Sending update request to ORIS API. clubuser: 7323, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 13:41:10] - Failed: Action update encountered an error: Cannot update: No ORIS Entry ID +[2026-03-13 13:45:25] Processing create for entry ID 139 (user: 39, race: 53) +[2026-03-13 13:45:25] - Sending create request to ORIS API. clubuser: 7323, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 13:45:25] - Raw POST Data: clubuser=7323&class=211624&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 13:45:25] - Success: Action create completed. ORIS Entry ID: +[2026-03-13 13:50:40] Processing delete for entry ID 139 (user: 39, race: 53) +[2026-03-13 13:50:41] - Sending delete request to ORIS API. clubuser: 7323, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 13:50:41] - Success: Action delete completed. ORIS Entry ID: +[2026-03-13 13:53:30] Processing create for entry ID 140 (user: 11, race: 53) +[2026-03-13 13:53:30] - Sending create request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 +[2026-03-13 13:53:31] - Raw POST Data: clubuser=7315&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 13:53:31] - Success: Action create completed. ORIS Entry ID: +[2026-03-13 13:53:31] Processing create for entry ID 141 (user: 39, race: 53) +[2026-03-13 13:53:31] - Sending create request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 13:53:31] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 13:53:31] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen +[2026-03-13 13:54:19] Processing delete for entry ID 140 (user: 11, race: 53) +[2026-03-13 13:54:19] - Sending delete request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 +[2026-03-13 13:54:19] - Success: Action delete completed. ORIS Entry ID: +[2026-03-13 13:54:19] Processing delete for entry ID 141 (user: 39, race: 53) +[2026-03-13 13:54:20] - Sending delete request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 13:54:20] - Success: Action delete completed. ORIS Entry ID: +[2026-03-13 14:04:36] Processing update for entry ID 142 (user: 11, race: 53) +[2026-03-13 14:04:36] - Sending update request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:04:36] - Warning: Missing ORIS Entry ID for update. Falling back to create. +[2026-03-13 14:04:37] - Raw POST Data: clubuser=7315&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:04:37] - Failed: Action update encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen +[2026-03-13 14:04:37] Processing update for entry ID 143 (user: 39, race: 53) +[2026-03-13 14:04:37] - Sending update request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:04:37] - Warning: Missing ORIS Entry ID for update. Falling back to create. +[2026-03-13 14:04:37] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:04:37] - Failed: Action update encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen +[2026-03-13 14:06:59] Processing create for entry ID 142 (user: 11, race: 53) +[2026-03-13 14:06:59] - Sending create request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:07:00] - Raw POST Data: clubuser=7315&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:07:00] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen +[2026-03-13 14:07:00] Processing create for entry ID 143 (user: 39, race: 53) +[2026-03-13 14:07:00] - Sending create request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:07:00] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:07:00] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen +[2026-03-13 14:08:30] Processing update for entry ID 142 (user: 11, race: 53) +[2026-03-13 14:08:30] - Sending update request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:08:30] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. +[2026-03-13 14:08:31] - Found ORIS Entry ID 3170927 for clubuser 7315 +[2026-03-13 14:08:31] - Raw POST Data: entryid=3170927&clubuser=7315&class=211626&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:08:31] - Success: Action update completed. ORIS Entry ID: +[2026-03-13 14:08:31] Processing update for entry ID 143 (user: 39, race: 53) +[2026-03-13 14:08:31] - Sending update request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:08:31] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. +[2026-03-13 14:08:31] - Found ORIS Entry ID 3170917 for clubuser 7323 +[2026-03-13 14:08:32] - Raw POST Data: entryid=3170917&clubuser=7323&class=211626&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:08:32] - Success: Action update completed. ORIS Entry ID: +[2026-03-13 14:10:12] Processing update for entry ID 142 (user: 11, race: 53) +[2026-03-13 14:10:13] - Sending update request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 14:10:13] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. +[2026-03-13 14:10:13] - Found ORIS Entry ID 3170927 for clubuser 7315 +[2026-03-13 14:10:13] - Raw POST Data: entryid=3170927&clubuser=7315&class=211624&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:10:13] - Success: Action update completed. ORIS Entry ID: 3170927 +[2026-03-13 14:10:13] Processing update for entry ID 143 (user: 39, race: 53) +[2026-03-13 14:10:14] - Warning: Could not resolve ORIS Class ID for category 'H21' +[2026-03-13 14:10:14] - Sending update request to ORIS API. clubuser: 7323, class: H21 (mapped from H21), si: 0 +[2026-03-13 14:10:14] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. +[2026-03-13 14:10:14] - Found ORIS Entry ID 3170917 for clubuser 7323 +[2026-03-13 14:10:14] - Raw POST Data: entryid=3170917&clubuser=7323&class=H21&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:10:14] - Success: Action update completed. ORIS Entry ID: 3170917 +[2026-03-13 14:39:31] Processing delete for entry ID 142 (user: 11, race: 53) +[2026-03-13 14:39:32] - Sending delete request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 14:39:32] - Raw POST Data: entryid=3170927&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:39:32] - Success: Action delete completed. ORIS Entry ID: 3170927 +[2026-03-13 14:39:32] Processing delete for entry ID 143 (user: 39, race: 53) +[2026-03-13 14:39:32] - Warning: Could not resolve ORIS Class ID for category 'H21' +[2026-03-13 14:39:32] - Sending delete request to ORIS API. clubuser: 7323, class: H21 (mapped from H21), si: 0 +[2026-03-13 14:39:33] - Raw POST Data: entryid=3170917&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:39:33] - Success: Action delete completed. ORIS Entry ID: 3170917 +[2026-03-13 14:40:37] Processing create for entry ID 144 (user: 11, race: 53) +[2026-03-13 14:40:38] - Sending create request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 14:40:39] - Raw POST Data: clubuser=7315&class=211624&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:40:39] - Success: Action create completed. ORIS Entry ID: +[2026-03-13 14:40:39] Processing create for entry ID 145 (user: 39, race: 53) +[2026-03-13 14:40:40] - Sending create request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:40:40] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:40:40] - Success: Action create completed. ORIS Entry ID: +[2026-03-13 14:44:01] Processing delete for entry ID 144 (user: 11, race: 53) +[2026-03-13 14:44:02] - Sending delete request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 +[2026-03-13 14:44:02] - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS. +[2026-03-13 14:44:02] - Found ORIS Entry ID 3170940 for clubuser 7315 +[2026-03-13 14:44:02] - Raw POST Data: entryid=3170940&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:44:02] - Success: Action delete completed. ORIS Entry ID: +[2026-03-13 14:44:02] Processing delete for entry ID 145 (user: 39, race: 53) +[2026-03-13 14:44:03] - Sending delete request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 +[2026-03-13 14:44:03] - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS. +[2026-03-13 14:44:03] - Found ORIS Entry ID 3170941 for clubuser 7323 +[2026-03-13 14:44:03] - Raw POST Data: entryid=3170941&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 +[2026-03-13 14:44:03] - Success: Action delete completed. ORIS Entry ID: diff --git a/oris_sync_daemon.php b/oris_sync_daemon.php new file mode 100644 index 0000000..0b39e9a --- /dev/null +++ b/oris_sync_daemon.php @@ -0,0 +1,321 @@ + $now) { + $diff = $entryStartTime - $now; + if ($diff <= 65) { // 65 seconds buffer for cron running every minute + logMessage("Race opening in $diff seconds (at $entryStartStr). Sleeping before processing..."); + sleep($diff); + } else { + // Race is not open yet and more than a minute away, skip for now + logMessage("Skipping entry ID {$row['id']} - Race opens at $entryStartStr (in $diff seconds)."); + continue; + } + } + } + + $action = ''; + if ($row['sync_status'] === 'PENDING_CREATE') { + $action = 'create'; + } elseif ($row['sync_status'] === 'PENDING_UPDATE') { + $action = 'update'; + } elseif ($row['sync_status'] === 'PENDING_DELETE') { + $action = 'delete'; + } elseif ($row['sync_status'] === 'FAILED_RETRY') { + // We will just let it be an 'update' if there's an ID, or 'create_or_update' + // if there's no ID so processEntry can check ORIS first. + $action = empty($row['oris_entry_id']) ? 'create' : 'update'; + // Wait, instead of setting it to 'create' which assumes it doesn't exist, + // if we set it to 'update', the new logic in processEntry will fetch from ORIS. + // If it's not found on ORIS, it falls back to 'create' anyway! + $action = 'update'; + } + + if (!empty($action)) { + processEntry($row, $action, $service); + } +} + +function logMessage($msg) { + $date = date('Y-m-d H:i:s'); + $line = "[$date] $msg\n"; + // Output to console when run via CLI + echo $line; + // Log to file in the same directory + file_put_contents(__DIR__ . '/oris_sync.log', $line, FILE_APPEND); +} + +function processEntry($row, $action, $service) { + global $g_oris_club_key, $g_shortcut; + + $id = $row['id']; + $userId = $row['id_user']; + $raceId = $row['id_zavod']; + + logMessage("Processing $action for entry ID $id (user: $userId, race: $raceId)"); + + // Get ORIS user ID + $userQuery = "SELECT * FROM `" . TBL_USER . "` WHERE `id` = " . (int)$userId; + $userRes = query_db($userQuery); + $userRow = mysqli_fetch_assoc($userRes); + + $clubuser = null; + $rgnum = null; + if ($userRow && isset($userRow['reg'])) { + // Reg number usually only contains the digits, so we prepend the club shortcut (e.g. ZBM) + $rgnum = $userRow['reg']; + // Ensure the club prefix is attached if it isn't already + if (!empty($rgnum) && !preg_match('/^[A-Z]{3}/', $rgnum)) { + $rgnum = $g_shortcut . str_pad($rgnum, 4, '0', STR_PAD_LEFT); + } + + // Use the Oris API to get the internal user ID required for createEntry + if (!empty($rgnum)) { + $userApiRes = $service->getUser($rgnum); + if (isset($userApiRes['Status']) && $userApiRes['Status'] === 'OK' && isset($userApiRes['Data']['ID'])) { + $globalUserId = $userApiRes['Data']['ID']; + + // Now get the club user ID + $clubUsersRes = $service->getClubUsers($globalUserId); + if (isset($clubUsersRes['Status']) && $clubUsersRes['Status'] === 'OK' && isset($clubUsersRes['Data'])) { + // It might be a single object or an array of objects + $clubUsers = is_array($clubUsersRes['Data']) ? $clubUsersRes['Data'] : []; + if (isset($clubUsers['ID'])) { + $clubUsers = [$clubUsers]; + } + + foreach ($clubUsers as $cu) { + if (isset($cu['ID'])) { + $clubuser = $cu['ID']; + break; + } + } + } + + if (empty($clubuser)) { + logMessage(" - Warning: Could not resolve ORIS Club User ID for user ID $globalUserId"); + } + } else { + logMessage(" - Warning: Could not resolve ORIS User ID for rgnum $rgnum"); + } + } + } + + // Get Race ORIS ID + $raceQuery = "SELECT * FROM `" . TBL_RACE . "` WHERE `id` = " . (int)$raceId; + $raceRes = query_db($raceQuery); + $raceRow = mysqli_fetch_assoc($raceRes); + + // Ensure the race was imported from ORIS + $comp = $raceRow['ext_id'] ?? null; + if (empty($comp)) { + logMessage(" - Skipped: Race $raceId is not imported from ORIS (no ext_id). Marking LOCAL_ONLY."); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` SET `sync_status` = 'LOCAL_ONLY' WHERE `id` = " . (int)$id; + query_db($updateQuery); + return; + } + + // Ignore relays and sprint relays + // S is typically for štafety (relays) in local DB + if ($raceRow && isset($raceRow['typ0']) && $raceRow['typ0'] === 'S') { + logMessage(" - Skipped: Race $raceId is a relay (typ0 = S). Marking LOCAL_ONLY."); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` SET `sync_status` = 'LOCAL_ONLY' WHERE `id` = " . (int)$id; + query_db($updateQuery); + return; + } + + // Fetch Classes from ORIS Event to map the string category (e.g. H21) to the ORIS class ID + $classId = null; + $katName = $row['kat']; + if (!empty($comp)) { + $eventRes = $service->getEvent($comp); + if (isset($eventRes['Status']) && $eventRes['Status'] === 'OK' && isset($eventRes['Data']['Classes'])) { + $classes = is_array($eventRes['Data']['Classes']) ? $eventRes['Data']['Classes'] : []; + + foreach ($classes as $cls) { + if (!is_array($cls)) continue; + $clsName = $cls['Name'] ?? ''; + // Some categories might have different spacing or case + if (trim($clsName) === trim($katName)) { + $classId = $cls['ID'] ?? null; + break; + } + } + } + } + + if (empty($classId)) { + logMessage(" - Warning: Could not resolve ORIS Class ID for category '$katName'"); + // Fallback to sending the string, which will likely be rejected by ORIS but preserves behavior + $classId = $katName; + } + + $si = $row['si_chip']; + $rentSi = $row['rent_si'] ?? 0; // assuming rent_si is added or mapped + $note = $row['pozn'] ?? ''; + + $response = []; + logMessage(" - Sending $action request to ORIS API. clubuser: $clubuser, class: $classId (mapped from $katName), si: $si"); + + if ($action === 'create') { + // Check if it already exists before creating to avoid "Již přihlášen" + $existingEntryId = null; + if (!empty($comp) && !empty($clubuser)) { + $entriesRes = $service->getEventEntries($comp); + if (isset($entriesRes['Status']) && $entriesRes['Status'] === 'OK' && isset($entriesRes['Data'])) { + $entries = is_array($entriesRes['Data']) ? $entriesRes['Data'] : []; + foreach ($entries as $entry) { + if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { + $existingEntryId = $entry['ID']; + logMessage(" - Found existing ORIS Entry ID $existingEntryId for clubuser $clubuser during create. Switching to update."); + break; + } + } + } + } + + if (!empty($existingEntryId)) { + $response = $service->updateEntry($existingEntryId, $clubuser, $classId, $si, $rentSi, $note); + // Also update the action string so log messages below make sense + $action = 'update'; + } else { + $response = $service->createEntry($clubuser, $classId, $si, $rentSi, $note); + } + } elseif ($action === 'update') { + $entryIdToUpdate = $row['oris_entry_id'] ?? null; + + if (empty($entryIdToUpdate) && !empty($comp) && !empty($clubuser)) { + logMessage(" - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS."); + $entriesRes = $service->getEventEntries($comp); + if (isset($entriesRes['Status']) && $entriesRes['Status'] === 'OK' && isset($entriesRes['Data'])) { + $entries = is_array($entriesRes['Data']) ? $entriesRes['Data'] : []; + foreach ($entries as $entry) { + if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { + $entryIdToUpdate = $entry['ID']; + logMessage(" - Found ORIS Entry ID $entryIdToUpdate for clubuser $clubuser"); + break; + } + } + } + } + + if (empty($entryIdToUpdate)) { + // It was never created on ORIS side successfully, so let's fall back to create instead of failing + logMessage(" - Warning: Missing ORIS Entry ID for update. Falling back to create."); + $response = $service->createEntry($clubuser, $classId, $si, $rentSi, $note); + } else { + $response = $service->updateEntry($entryIdToUpdate, $clubuser, $classId, $si, $rentSi, $note); + } + } elseif ($action === 'delete') { + $entryIdToDelete = $row['oris_entry_id'] ?? null; + + // If we don't have the entry ID locally, we must fetch it from ORIS first + if (empty($entryIdToDelete) && !empty($comp) && !empty($clubuser)) { + logMessage(" - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS."); + $entriesRes = $service->getEventEntries($comp); + if (isset($entriesRes['Status']) && $entriesRes['Status'] === 'OK' && isset($entriesRes['Data'])) { + $entries = is_array($entriesRes['Data']) ? $entriesRes['Data'] : []; + foreach ($entries as $entry) { + if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { + $entryIdToDelete = $entry['ID']; + logMessage(" - Found ORIS Entry ID $entryIdToDelete for clubuser $clubuser"); + break; + } + } + } + } + + if (empty($entryIdToDelete)) { + // Already doesn't exist remotely or could not be found + logMessage(" - Warning: Could not find ORIS Entry ID to delete. Assuming already deleted."); + $response = ['status' => 'success', 'data' => []]; + } else { + $response = $service->deleteEntry($entryIdToDelete); + } + } + + // Log the exact raw payload sent to ORIS + if (isset($response['request'])) { + logMessage(" - Raw POST Data: " . $response['request']); + } + + if ($response['status'] === 'success') { + // 'create' usually returns ID in $response['data']['ID'] + // 'update' might not return the ID, so we use the one we just found/used + $entryId = $response['data']['ID'] ?? null; + if (empty($entryId)) { + if ($action === 'update' && !empty($entryIdToUpdate)) { + $entryId = $entryIdToUpdate; + } elseif ($action === 'update' && !empty($existingEntryId)) { // from the create fallback block + $entryId = $existingEntryId; + } else { + $entryId = $row['oris_entry_id']; + } + } + + logMessage(" - Success: Action $action completed. ORIS Entry ID: $entryId"); + + if ($action === 'delete') { + // Remove the row locally after successful delete + $updateQuery = "DELETE FROM `" . TBL_ZAVXUS . "` WHERE `id` = " . (int)$id; + query_db($updateQuery); + } else { + // Update the row + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` + SET `sync_status` = 'SYNCED', + `oris_entry_id` = " . ($entryId ? (int)$entryId : "NULL") . ", + `sync_timestamp` = NOW(), + `sync_error_payload` = NULL + WHERE `id` = " . (int)$id; + query_db($updateQuery); + } + } else { + $errMsg = $response['message'] ?? 'Unknown error'; + logMessage(" - Failed: Action $action encountered an error: $errMsg"); + + $errorPayload = correct_sql_string(json_encode($response)); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` + SET `sync_status` = 'FAILED_RETRY', + `sync_error_payload` = '$errorPayload' + WHERE `id` = " . (int)$id; + query_db($updateQuery); + } +} +?> \ No newline at end of file diff --git a/race_edit.php b/race_edit.php index b2da97d..7357742 100644 --- a/race_edit.php +++ b/race_edit.php @@ -374,6 +374,11 @@ function toggleButtonState() { +oris_entry_start !== null) { + echo ''; +} +?> + 0) { + query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id'"); + } + } if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); - if ($result !== false && mysqli_affected_rows($db_conn) > 0) { - query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id'"); - } } else { // update @@ -107,7 +115,12 @@ $pozn2=correct_sql_string($pozn2); $termin=correct_sql_string($termin); - $result=query_db("UPDATE ".TBL_ZAVXUS." SET kat='$kateg', pozn='$pozn', pozn_in='$pozn2', termin='$termin', transport = '$transport', sedadel = ".$sedadel.", ubytovani = '$ubytovani' WHERE id_zavod = '$id' AND id_user = '$user_id'") + $sync_status_update = ""; + if (!empty($zaznam_z['ext_id']) && $zaznam['sync_status'] !== 'PENDING_CREATE') { + $sync_status_update = ", sync_status='PENDING_UPDATE'"; + } + + $result=query_db("UPDATE ".TBL_ZAVXUS." SET kat='$kateg', pozn='$pozn', pozn_in='$pozn2', termin='$termin', transport = '$transport', sedadel = ".$sedadel.", ubytovani = '$ubytovani'".$sync_status_update." WHERE id_zavod = '$id' AND id_user = '$user_id'") or die("Chyba při provádění dotazu do databáze."); if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); @@ -122,8 +135,10 @@ $pozn=correct_sql_string($pozn); $pozn2=correct_sql_string($pozn2); $termin=correct_sql_string($termin); + + $sync_status = !empty($zaznam_z['ext_id']) ? 'PENDING_CREATE' : 'LOCAL_ONLY'; - $result=query_db("INSERT INTO ".TBL_ZAVXUS." (id_user, id_zavod, kat, pozn, pozn_in,termin,transport,sedadel,ubytovani) VALUES ('$user_id','$id','$kateg', '$pozn', '$pozn2','$termin','$transport',".$sedadel.",'$ubytovani')") + $result=query_db("INSERT INTO ".TBL_ZAVXUS." (id_user, id_zavod, kat, pozn, pozn_in,termin,transport,sedadel,ubytovani,sync_status) VALUES ('$user_id','$id','$kateg', '$pozn', '$pozn2','$termin','$transport',".$sedadel.",'$ubytovani','$sync_status')") or die("Chyba při provádění dotazu do databáze."); if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); diff --git a/race_regs_all_exc.php b/race_regs_all_exc.php index 61d0eaa..1455a8e 100644 --- a/race_regs_all_exc.php +++ b/race_regs_all_exc.php @@ -30,7 +30,7 @@ $sub_query = (IsLoggedRegistrator() || IsLoggedManager()) ? '' : ' AND '.TBL_USER.'.chief_id = '.$usr->user_id.' OR '.TBL_USER.'.id = '.$usr->user_id; -$query = 'SELECT '.TBL_USER.'.id, kat, termin FROM '.TBL_USER.' LEFT JOIN '.TBL_ZAVXUS.' ON '.TBL_USER.'.id = '.TBL_ZAVXUS.'.id_user AND '.TBL_ZAVXUS.'.id_zavod='.$id.' WHERE '.TBL_USER.'.hidden = 0'.$sub_query; +$query = 'SELECT '.TBL_USER.'.id, kat, termin, sync_status FROM '.TBL_USER.' LEFT JOIN '.TBL_ZAVXUS.' ON '.TBL_USER.'.id = '.TBL_ZAVXUS.'.id_user AND '.TBL_ZAVXUS.'.id_zavod='.$id.' WHERE '.TBL_USER.'.hidden = 0'.$sub_query; @$vysledek=query_db($query); @@ -83,13 +83,21 @@ if ($kat == "") { // del // echo "DEL"; - $result=query_db("DELETE FROM ".TBL_ZAVXUS." WHERE id_zavod = '$id' AND id_user = '$user'") - or die("Chyba při provádění dotazu do databáze."); + $has_ext_id = !empty($zaznam_z['ext_id']); + $is_pending_create = ($zaznamZ['sync_status'] === 'PENDING_CREATE'); + + if ($has_ext_id && !$is_pending_create) { + $result=query_db("UPDATE ".TBL_ZAVXUS." SET sync_status='PENDING_DELETE' WHERE id_zavod = '$id' AND id_user = '$user'") + or die("Chyba při provádění dotazu do databáze."); + } else { + $result=query_db("DELETE FROM ".TBL_ZAVXUS." WHERE id_zavod = '$id' AND id_user = '$user'") + or die("Chyba při provádění dotazu do databáze."); + if ($result !== false && mysqli_affected_rows($db_conn) > 0) { + query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id'"); + } + } if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); - if ($result !== false && mysqli_affected_rows($db_conn) > 0) { - query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id'"); - } } else { // update @@ -99,7 +107,12 @@ $poz2=correct_sql_string($poz2); $cterm=correct_sql_string($cterm); - $result=query_db("UPDATE ".TBL_ZAVXUS." SET kat='$kat', pozn='$poz', pozn_in='$poz2', termin='$cterm', transport=$trans, sedadel=$sedl, ubytovani=$ubyt WHERE id_zavod = '$id' AND id_user = '$user'") + $sync_status_update = ""; + if (!empty($zaznam_z['ext_id']) && $zaznamZ['sync_status'] !== 'PENDING_CREATE') { + $sync_status_update = ", sync_status='PENDING_UPDATE'"; + } + + $result=query_db("UPDATE ".TBL_ZAVXUS." SET kat='$kat', pozn='$poz', pozn_in='$poz2', termin='$cterm', transport=$trans, sedadel=$sedl, ubytovani=$ubyt".$sync_status_update." WHERE id_zavod = '$id' AND id_user = '$user'") or die("Chyba při provádění dotazu do databáze."); if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); @@ -115,7 +128,9 @@ $poz2=correct_sql_string($poz2); $cterm=correct_sql_string($cterm); - $result=query_db("INSERT INTO ".TBL_ZAVXUS." (id_user, id_zavod, kat, pozn, pozn_in, termin, transport, sedadel,ubytovani) VALUES ('$user','$id','$kat','$poz','$poz2','$cterm',$trans,$sedl,$ubyt)") + $sync_status = !empty($zaznam_z['ext_id']) ? 'PENDING_CREATE' : 'LOCAL_ONLY'; + + $result=query_db("INSERT INTO ".TBL_ZAVXUS." (id_user, id_zavod, kat, pozn, pozn_in, termin, transport, sedadel,ubytovani, sync_status) VALUES ('$user','$id','$kat','$poz','$poz2','$cterm',$trans,$sedl,$ubyt,'$sync_status')") or die("Chyba při provádění dotazu do databáze."); if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); From a63e16bfba8b3b843810f4133566b0a9471c6062 Mon Sep 17 00:00:00 2001 From: "jan.zhanal" Date: Fri, 13 Mar 2026 15:49:24 +0100 Subject: [PATCH 02/16] ORIS entries: complete logic to push all entries to ORIS --- oris_sync_daemon.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oris_sync_daemon.php b/oris_sync_daemon.php index 0b39e9a..aad999a 100644 --- a/oris_sync_daemon.php +++ b/oris_sync_daemon.php @@ -19,7 +19,7 @@ global $g_oris_club_key; if (empty($g_oris_club_key)) { - die("Error: ORIS club key is not configured."); + die("Error: ORIS club key neni nastaven."); } $service = new OrisIntegrationService($g_oris_club_key); @@ -41,11 +41,11 @@ if ($entryStartTime > $now) { $diff = $entryStartTime - $now; if ($diff <= 65) { // 65 seconds buffer for cron running every minute - logMessage("Race opening in $diff seconds (at $entryStartStr). Sleeping before processing..."); + logMessage("Prihlasky se oteviraji za $diff seconds (at $entryStartStr). Jdu sapt pred pokracovanim..."); sleep($diff); } else { // Race is not open yet and more than a minute away, skip for now - logMessage("Skipping entry ID {$row['id']} - Race opens at $entryStartStr (in $diff seconds)."); + logMessage("Preskakuju prihlasku ID {$row['id']} - Zavod otevira prihlasky v $entryStartStr (za $diff s)."); continue; } } From 5017f641faa5a5b67b7916e1f6b01ef4239f2058 Mon Sep 17 00:00:00 2001 From: "jan.zhanal" Date: Fri, 13 Mar 2026 23:18:21 +0100 Subject: [PATCH 03/16] ORIS entries: fine tuning, refactor 2 connector files --- ads_oris.inc.php | 11 +- connectors.php | 784 ++++++++++++++++----------------- lib/OrisIntegrationService.php | 45 +- oris_sync.log | 148 ------- oris_sync_daemon.php | 3 +- 5 files changed, 432 insertions(+), 559 deletions(-) delete mode 100644 oris_sync.log diff --git a/ads_oris.inc.php b/ads_oris.inc.php index 8b672aa..dedc30f 100644 --- a/ads_oris.inc.php +++ b/ads_oris.inc.php @@ -51,17 +51,18 @@ function startsWith( $haystack, $needle ) { return substr( $haystack, 0, $length ) === $needle; } -$json = file_get_contents('https://oris.orientacnisporty.cz/API/?format=json&method=getRegistration&sport=1&year='.$ORIS_year); -$obj = json_decode($json); +require_once './lib/OrisIntegrationService.php'; +$service = new OrisIntegrationService(null); +$obj = $service->getRegistration(1, $ORIS_year); $arr_oris = array(); -foreach ($obj->Data as $key=>$value) +foreach ($obj['Data'] as $key=>$value) { $user = new User(); - $user->create($value->UserID, $value->FirstName, $value->LastName, $value->RegNo, $value->SI, $value->ClubID); + $user->create($value['UserID'], $value['FirstName'], $value['LastName'], $value['RegNo'], $value['SI'], $value['ClubID']); - $reg = $value->RegNo; + $reg = $value['RegNo']; if (startsWith($reg, $g_shortcut)) { $arr_oris["user"] [$reg]= $user; diff --git a/connectors.php b/connectors.php index 82638e9..3fbd6ba 100644 --- a/connectors.php +++ b/connectors.php @@ -1,404 +1,380 @@ -ext_id = $data['ext_id'] ?? null; - $this->datum = $data['datum'] ?? null; - $this->datum2 = $data['datum2'] ?? null; - $this->nazev = $data['nazev'] ?? null; - $this->misto = $data['misto'] ?? null; - $this->oblasti = $data['oblasti'] ?? null; - $this->typ = $data['typ'] ?? null; - $this->zebricek2 = $data['zebricek2'] ?? null; - $this->ranking = $data['ranking'] ?? null; - $this->odkaz = $data['odkaz'] ?? null; - $this->prihlasky = $data['prihlasky'] ?? null; - $this->prihlasky1 = $data['prihlasky1'] ?? null; - $this->prihlasky2 = $data['prihlasky2'] ?? null; - $this->prihlasky3 = $data['prihlasky3'] ?? null; - $this->prihlasky4 = $data['prihlasky4'] ?? null; - $this->prihlasky5 = $data['prihlasky5'] ?? null; - $this->koeficient1 = $data['koeficient1'] ?? null; - $this->koeficient2 = $data['koeficient2'] ?? null; - $this->etap = $data['etap'] ?? null; - $this->poznamka = $data['poznamka'] ?? null; - $this->vicedenni = $data['vicedenni'] ?? null; - $this->oddil = $data['oddil'] ?? null; - $this->modify_flag = $data['modify_flag'] ?? null; - $this->kategorie = $data['kategorie'] ?? null; - $this->startovne = $data['startovne'] ?? null; - $this->cancelled = $data['cancelled'] ?? null; - $this->oris_entry_start = $data['oris_entry_start'] ?? null; - } -} - -class RacePayement { - public int $raceId; - public RaceOverview $overview; - /** @var array RegNo => User */ - public array $participants = []; - - public function __construct(int $raceId) { - $this->raceId = $raceId; - $this->overview = new RaceOverview(); - } - - public function addPatricipant(RaceParticipant $user): void { - $this->participants[$user->regNo] = $user; - } - - public function addCategory(string $name, int $feeTier, int $fee ): void { - $this->overview->addCategory($name,$feeTier,$fee); - } - - public function addService(string $name, int $fee, int $count ): void { - $this->overview->addService( $name, $fee, $count ); - } -} - -class RaceOverview { - /** @var array> Category => [EntryStop => Fee] */ - public array $categories = []; - - /** @var array> ServiceName => [ Fee => Count ] */ - public array $services = []; - - /** @var array feeTier => exist */ - public array $feeTiers = []; - - public function addCategory(string $name, int $feeTier, int $fee ): void { - - if (!isset($this->categories[$name])) { - $this->categories[$name] = []; - } - $this->categories[$name][$feeTier] = $fee; - - // Store only unique sorted feeTier values - $this->feeTiers[$feeTier] = true; - } - - public function addService(string $name, int $fee, int $count ): void { - if ( isset ( $this->services[$name][$fee] ) ) { - $this->services[$name][$fee] += $count; - } else { - $this->services[$name][$fee] = $count; - } - } -} - -class RaceParticipant { - public string $regNo; - public string $classDesc; - public string $name; - public bool $rentSI; - public string $licence; - public $fee; - public int $feeTier; - - public function __construct(string $regNo, string $classDesc, string $name, bool $rentSI, string|null $licence, int $fee, int $feeTier) { - $this->regNo = $regNo; - $this->classDesc = $classDesc; - $this->name = $name; - $this->rentSI = $rentSI; - $this->licence = $licence ?? ''; - $this->fee = $fee; - $this->feeTier = $feeTier; - } -} - -interface ConnectorInterface { - public function getSystemName(): string; - public function getRaceURL(string $id): string; - public function getRaceInfo(string $id) : ?RaceInfo; - public function getRacesList($fromDate, $toDate); - public function getRacePayement(string $id) : ?RacePayement; -} - -class OrisCZConnector implements ConnectorInterface { - private $sourceUrl = 'https://oris.orientacnisporty.cz/'; - private $apiUrl; - - public function __construct() { - $this->apiUrl = $this->sourceUrl . 'API/'; - } - - // Method to get the system name - public function getSystemName(): string { - return "Oris"; - } - - // RaceInfo URL - public function getRaceURL($raceId) : string { - return $this->sourceUrl . 'Zavod?id=' . $raceId; - } - - private function mapLevelToZebricek2($levelId) { - $map = [ - 1 => 17, // MCR - 3 => 6, // ZB - 4 => 8, // OZ - 7 => 33, // CPS - 8 => 1, // CP - 11 => 24, // OM - 17 => 17 // VET - ]; - return $map[$levelId] ?? 0x0080; // Default to 0x80 if not found - } - - private function mapSport($sportId) { - //sport ID from ORIS : 1=OB, 2=LOB, 3=MTBO, 4=TRAIL - $map = [ - 1 => 1, // OB - 2 => 4, // LOB - 3 => 2, // MTBO - 4 => 8, // TRAIL - ]; - return $map[$sportId] ?? 1; // Default is OB - } - - private function getClubs(&$raceData) { - - $oddily = []; - if (isset($raceData['Org1']['Abbr'])) { - $oddily[] = $raceData['Org1']['Abbr']; - } - if (isset($raceData['Org2']['Abbr'])) { - $oddily[] = $raceData['Org2']['Abbr']; - } - return implode('+', $oddily); - } - - // Method to get race date based on race ID or provided response - public function getRaceDate($raceId, $response = null) { - // If no response provided, fetch it from API - if ($response === null) { - $url = $this->apiUrl . '?format=json&method=getEvent&id=' . $raceId; - $response = $this->makeRequest($url); - } - - if ($response && isset($response['Status']) && $response['Status'] === "OK") { - $raceData = $response['Data']; - return String2DateDMY(formatDate($raceData['Date'])); - } - - return ''; // Return empty string if race not found or error - } - - // Method to get detailed race information based on race ID - public function getRaceInfo($raceId) : RaceInfo { - $url = $this->apiUrl . '?format=json&method=getEvent&id=' . $raceId; - - $response = $this->makeRequest($url); - - if ($response && $response['Status'] == "OK") { - $raceData = $response['Data']; - - $classFees = []; - if (isset($raceData['Classes'])) { - foreach ($raceData['Classes'] as $class) { - if (isset($class['Name'])) { - $name = $class['Name']; - $fee = $class['Fee'] ?? null; // default to null if missing - $classFees[$name] = $fee; - } - } - } - - ksort ( $classFees ); - - $oddily = $this->getClubs($raceData); - $oblasti = []; - if (isset($raceData['Regions'])) { - foreach ($raceData['Regions'] as $tag => $region) { - $oblasti[] = $region['ID']; - } - } - - // Get last Stage date if multistage event - $date2 = ($raceData['Stages'] > 1) ? $this->getRaceDate($raceData['Stage'.$raceData['Stages']], $response) : 0; - // Use associative array to pass data to constructor - return new RaceInfo([ - 'ext_id' => $raceData['ID'], - 'datum' => String2DateDMY(formatDate($raceData['Date'])), - 'datum2' => $date2, - 'nazev' => $raceData['Name'], - 'misto' => $raceData['Place'], -// 'category' => $raceData['Category'], - //typ0 => Typ akce - 'oblasti' => $oblasti, - 'typ0' => 'Z', - 'typ' => $this->mapSport($raceData['Sport']['ID']), - 'zebricek2' => $this->mapLevelToZebricek2($raceData['Level']['ID']), - 'ranking' => $raceData['Ranking'], - 'odkaz' => $this->getRaceURL($raceData['ID']), - 'prihlasky' => strtotime($raceData['EntryDate1']), - 'prihlasky1' => strtotime($raceData['EntryDate2']), - 'prihlasky2' => strtotime($raceData['EntryDate3']), -// 'prihlasky3' => '', -// 'prihlasky4' => '', -// 'prihlasky5' => '', - 'koeficient1' => $raceData['EntryKoef2'], - 'koeficient2' => $raceData['EntryKoef3'], - 'etap' => $raceData['Stages'], -// 'poznamka' => $poznamka, - 'vicedenni' => ($raceData['Stages']>1?1:0), - 'oddil' => $oddily, - 'modify_flag' => 0, - 'kategorie' => implode(';', array_keys ( $classFees ) ), - 'startovne' => $classFees, - 'cancelled' => (!empty($raceData['Cancelled']) || !empty($raceData['Canceled']) || !empty($raceData['cancelled']) || !empty($raceData['canceled'])) ? 1 : 0, - 'oris_entry_start' => !empty($raceData['EntryStart']) ? $raceData['EntryStart'] : null - ]); - } else { - return null; // Return null if race not found or error - } - } - - // Helper method to make HTTP requests - private function makeRequest($url) { - $response = file_get_contents($url); - - // Decode JSON response - return json_decode($response, true); - } - - private function makeRequestCurl($url) { - $ch = curl_init($url); - - // Set curl options - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $response = curl_exec($ch); - - // Check for errors - if (curl_errno($ch)) { - echo 'Curl error: ' . curl_error($ch); - return null; - } - - curl_close($ch); - - // Decode JSON response - return json_decode($response, true); - } - - function getRacesList($fromDate, $toDate) { - $url = $this->apiUrl.'?format=json&method=getEventList&all=1&datefrom='.$fromDate.'&dateto='.$toDate; -// echo($url.'
'); - $response = $this->makeRequest($url); - - if ($response && $response['Status'] == "OK") { - $racesData = $response['Data']; - $rows = array(); - foreach($racesData as $oneRace) { - $oddily = $this->getClubs($oneRace); - - $row = array(); - $row[] = $oneRace['ID']; - $row[] = $oneRace['Date']; - $row[] = $oneRace['Name']; - $row[] = $oddily; - $rows[] = $row; - } - return $rows; - } else { - return null; // Return null if race not found or error - } - } - - // Method to get detailed race information based on race ID - public function getRacePayement($raceId) : ?RacePayement { - - global $g_external_is_club_id; - - if ( !IsSet ($g_external_is_club_id) || $g_external_is_club_id === '' ) return null; - - $url = $this->apiUrl . '?format=json&method=getEventEntries&clubid=' . $g_external_is_club_id . '&eventid=' . $raceId; - - $response = $this->makeRequest($url); - $racePayement = null; - - if ($response && $response['Status'] == "OK") { - $racePayement = new RacePayement($raceId); - - foreach ($response['Data'] as $entry) { - if (isset($entry['Fee'])) { - if (isset($entry['RegNo']) ) { - $racePayement->addPatricipant( - new RaceParticipant($entry['RegNo'], $entry['ClassDesc'], $entry['Name'], - $entry['RentSI'], $entry['Licence'], (int)$entry['Fee'], $entry['EntryStop'])); - } - if (isset($entry['ClassDesc'])&&isset($entry['ClassDesc'])) { - $racePayement->addCategory($entry['ClassDesc'], $entry['EntryStop'], (int)$entry['Fee']); - } - } - } - } - - $url = $this->apiUrl . '?format=json&method=getEventServiceEntries&clubid=' . $g_external_is_club_id . '&eventid=' . $raceId; - - $response = $this->makeRequest($url); - - if ($response && $response['Status'] == "OK") { - if ( $racePayement === null ) $racePayement = new RacePayement($raceId); - foreach ($response['Data'] as $entry) { - if ( isset ( $entry['Service'] ) ) { - $racePayement->addService($entry['Service']['NameCZ'] ?? 'Name?', $entry['Service']['UnitPrice'] , $entry['Quantity'] ); - } else { - if ( isset ( $entry['Quantity'] ) && isset ( $entry['TotalFee'] ) ) { - $racePayement->addService('Name?', $entry['TotalFee'] / $entry['Quantity'], $entry['Quantity'] ); - } - } - } - } - - //print_r ($racePayement); - - return $racePayement; - } -} - -class ConnectorFactory { - public static function create(): ?ConnectorInterface { - global $g_external_is_connector; - - if ( $g_external_is_connector && class_exists( $g_external_is_connector)) { - return new $g_external_is_connector(); - } - - return null; // Return null explicitly if no valid connector is found - } -} +ext_id = $data['ext_id'] ?? null; + $this->datum = $data['datum'] ?? null; + $this->datum2 = $data['datum2'] ?? null; + $this->nazev = $data['nazev'] ?? null; + $this->misto = $data['misto'] ?? null; + $this->oblasti = $data['oblasti'] ?? null; + $this->typ = $data['typ'] ?? null; + $this->zebricek2 = $data['zebricek2'] ?? null; + $this->ranking = $data['ranking'] ?? null; + $this->odkaz = $data['odkaz'] ?? null; + $this->prihlasky = $data['prihlasky'] ?? null; + $this->prihlasky1 = $data['prihlasky1'] ?? null; + $this->prihlasky2 = $data['prihlasky2'] ?? null; + $this->prihlasky3 = $data['prihlasky3'] ?? null; + $this->prihlasky4 = $data['prihlasky4'] ?? null; + $this->prihlasky5 = $data['prihlasky5'] ?? null; + $this->koeficient1 = $data['koeficient1'] ?? null; + $this->koeficient2 = $data['koeficient2'] ?? null; + $this->etap = $data['etap'] ?? null; + $this->poznamka = $data['poznamka'] ?? null; + $this->vicedenni = $data['vicedenni'] ?? null; + $this->oddil = $data['oddil'] ?? null; + $this->modify_flag = $data['modify_flag'] ?? null; + $this->kategorie = $data['kategorie'] ?? null; + $this->startovne = $data['startovne'] ?? null; + $this->cancelled = $data['cancelled'] ?? null; + $this->oris_entry_start = $data['oris_entry_start'] ?? null; + } +} + +class RacePayement { + public int $raceId; + public RaceOverview $overview; + /** @var array RegNo => User */ + public array $participants = []; + + public function __construct(int $raceId) { + $this->raceId = $raceId; + $this->overview = new RaceOverview(); + } + + public function addPatricipant(RaceParticipant $user): void { + $this->participants[$user->regNo] = $user; + } + + public function addCategory(string $name, int $feeTier, int $fee ): void { + $this->overview->addCategory($name,$feeTier,$fee); + } + + public function addService(string $name, int $fee, int $count ): void { + $this->overview->addService( $name, $fee, $count ); + } +} + +class RaceOverview { + /** @var array> Category => [EntryStop => Fee] */ + public array $categories = []; + + /** @var array> ServiceName => [ Fee => Count ] */ + public array $services = []; + + /** @var array feeTier => exist */ + public array $feeTiers = []; + + public function addCategory(string $name, int $feeTier, int $fee ): void { + + if (!isset($this->categories[$name])) { + $this->categories[$name] = []; + } + $this->categories[$name][$feeTier] = $fee; + + // Store only unique sorted feeTier values + $this->feeTiers[$feeTier] = true; + } + + public function addService(string $name, int $fee, int $count ): void { + if ( isset ( $this->services[$name][$fee] ) ) { + $this->services[$name][$fee] += $count; + } else { + $this->services[$name][$fee] = $count; + } + } +} + +class RaceParticipant { + public string $regNo; + public string $classDesc; + public string $name; + public bool $rentSI; + public string $licence; + public $fee; + public int $feeTier; + + public function __construct(string $regNo, string $classDesc, string $name, bool $rentSI, string|null $licence, int $fee, int $feeTier) { + $this->regNo = $regNo; + $this->classDesc = $classDesc; + $this->name = $name; + $this->rentSI = $rentSI; + $this->licence = $licence ?? ''; + $this->fee = $fee; + $this->feeTier = $feeTier; + } +} + +interface ConnectorInterface { + public function getSystemName(): string; + public function getRaceURL(string $id): string; + public function getRaceInfo(string $id) : ?RaceInfo; + public function getRacesList($fromDate, $toDate); + public function getRacePayement(string $id) : ?RacePayement; +} + +class OrisCZConnector implements ConnectorInterface { + private $sourceUrl = 'https://oris.ceskyorientak.cz/'; + private $apiUrl; + private $service; + + public function __construct() { + $this->apiUrl = $this->sourceUrl . 'API/'; + $this->service = new OrisIntegrationService(null); + } + + // Method to get the system name + public function getSystemName(): string { + return "Oris"; + } + + // RaceInfo URL + public function getRaceURL($raceId) : string { + return $this->sourceUrl . 'Zavod?id=' . $raceId; + } + + private function mapLevelToZebricek2($levelId) { + $map = [ + 1 => 17, // MCR + 3 => 6, // ZB + 4 => 8, // OZ + 7 => 33, // CPS + 8 => 1, // CP + 11 => 24, // OM + 17 => 17 // VET + ]; + return $map[$levelId] ?? 0x0080; // Default to 0x80 if not found + } + + private function mapSport($sportId) { + //sport ID from ORIS : 1=OB, 2=LOB, 3=MTBO, 4=TRAIL + $map = [ + 1 => 1, // OB + 2 => 4, // LOB + 3 => 2, // MTBO + 4 => 8, // TRAIL + ]; + return $map[$sportId] ?? 1; // Default is OB + } + + private function getClubs(&$raceData) { + + $oddily = []; + if (isset($raceData['Org1']['Abbr'])) { + $oddily[] = $raceData['Org1']['Abbr']; + } + if (isset($raceData['Org2']['Abbr'])) { + $oddily[] = $raceData['Org2']['Abbr']; + } + return implode('+', $oddily); + } + + // Method to get race date based on race ID + public function getRaceDate($raceId) { + $response = $this->service->getEvent($raceId); + + if ($response && isset($response['Status']) && $response['Status'] == "OK") { + $raceData = $response['Data']; + return String2DateDMY(formatDate($raceData['Date'])); + } + + return ''; // Return empty string if race not found or error + } + + // Method to get detailed race information based on race ID + public function getRaceInfo($raceId) : ?RaceInfo { + $response = $this->service->getEvent($raceId); + + if ($response && isset($response['Status']) && $response['Status'] == "OK") { + $raceData = $response['Data']; + + $classFees = []; + if (isset($raceData['Classes'])) { + foreach ($raceData['Classes'] as $class) { + if (isset($class['Name'])) { + $name = $class['Name']; + $fee = $class['Fee'] ?? null; // default to null if missing + $classFees[$name] = $fee; + } + } + } + + ksort ( $classFees ); + + $oddily = $this->getClubs($raceData); + $oblasti = []; + if (isset($raceData['Regions'])) { + foreach ($raceData['Regions'] as $tag => $region) { + $oblasti[] = $region['ID']; + } + } + + // Get last Stage date if multistage event + $date2 = ($raceData['Stages'] > 1) ? $this->getRaceDate($raceData['Stage'.$raceData['Stages']]) : 0; + // Use associative array to pass data to constructor + return new RaceInfo([ + 'ext_id' => $raceData['ID'], + 'datum' => String2DateDMY(formatDate($raceData['Date'])), + 'datum2' => $date2, + 'nazev' => $raceData['Name'], + 'misto' => $raceData['Place'], +// 'category' => $raceData['Category'], + //typ0 => Typ akce + 'oblasti' => $oblasti, + 'typ0' => 'Z', + 'typ' => $this->mapSport($raceData['Sport']['ID']), + 'zebricek2' => $this->mapLevelToZebricek2($raceData['Level']['ID']), + 'ranking' => $raceData['Ranking'], + 'odkaz' => $this->getRaceURL($raceData['ID']), + 'prihlasky' => strtotime($raceData['EntryDate1']), + 'prihlasky1' => strtotime($raceData['EntryDate2']), + 'prihlasky2' => strtotime($raceData['EntryDate3']), +// 'prihlasky3' => '', +// 'prihlasky4' => '', +// 'prihlasky5' => '', + 'koeficient1' => $raceData['EntryKoef2'], + 'koeficient2' => $raceData['EntryKoef3'], + 'etap' => $raceData['Stages'], +// 'poznamka' => $poznamka, + 'vicedenni' => ($raceData['Stages']>1?1:0), + 'oddil' => $oddily, + 'modify_flag' => 0, + 'kategorie' => implode(';', array_keys ( $classFees ) ), + 'startovne' => $classFees, + 'cancelled' => (!empty($raceData['Cancelled']) || !empty($raceData['Canceled']) || !empty($raceData['cancelled']) || !empty($raceData['canceled'])) ? 1 : 0, + 'oris_entry_start' => !empty($raceData['EntryStart']) ? $raceData['EntryStart'] : null + ]); + } else { + return null; // Return null if race not found or error + } + } + + // Helper method to make HTTP requests (used by getRacePayement) + private function makeRequest($url) { + $response = file_get_contents($url); + + // Decode JSON response + return json_decode($response, true); + } + + function getRacesList($fromDate, $toDate) { + $response = $this->service->getEventList($fromDate, $toDate, 1); + + if ($response && isset($response['Status']) && $response['Status'] == "OK") { + $racesData = $response['Data']; + $rows = array(); + foreach($racesData as $oneRace) { + $oddily = $this->getClubs($oneRace); + + $row = array(); + $row[] = $oneRace['ID']; + $row[] = $oneRace['Date']; + $row[] = $oneRace['Name']; + $row[] = $oddily; + $rows[] = $row; + } + return $rows; + } else { + return null; // Return null if race not found or error + } + } + + // Method to get race payment details + public function getRacePayement($raceId) : ?RacePayement { + + global $g_external_is_club_id; + + if ( !IsSet ($g_external_is_club_id) || $g_external_is_club_id === '' ) return null; + + $url = $this->apiUrl . '?format=json&method=getEventEntries&clubid=' . $g_external_is_club_id . '&eventid=' . $raceId; + + $response = $this->makeRequest($url); + $racePayement = null; + + if ($response && $response['Status'] == "OK") { + $racePayement = new RacePayement($raceId); + + foreach ($response['Data'] as $entry) { + if (isset($entry['Fee'])) { + if (isset($entry['RegNo']) ) { + $racePayement->addPatricipant( + new RaceParticipant($entry['RegNo'], $entry['ClassDesc'], $entry['Name'], + $entry['RentSI'], $entry['Licence'], (int)$entry['Fee'], $entry['EntryStop'])); + } + if (isset($entry['ClassDesc'])&&isset($entry['ClassDesc'])) { + $racePayement->addCategory($entry['ClassDesc'], $entry['EntryStop'], (int)$entry['Fee']); + } + } + } + } + + $url = $this->apiUrl . '?format=json&method=getEventServiceEntries&clubid=' . $g_external_is_club_id . '&eventid=' . $raceId; + + $response = $this->makeRequest($url); + + if ($response && $response['Status'] == "OK") { + if ( $racePayement === null ) $racePayement = new RacePayement($raceId); + foreach ($response['Data'] as $entry) { + if ( isset ( $entry['Service'] ) ) { + $racePayement->addService($entry['Service']['NameCZ'] ?? 'Name?', $entry['Service']['UnitPrice'] , $entry['Quantity'] ); + } else { + if ( isset ( $entry['Quantity'] ) && isset ( $entry['TotalFee'] ) ) { + $racePayement->addService('Name?', $entry['TotalFee'] / $entry['Quantity'], $entry['Quantity'] ); + } + } + } + } + + return $racePayement; + } +} + +class ConnectorFactory { + public static function create(): ?ConnectorInterface { + global $g_external_is_connector; + + if ( $g_external_is_connector && class_exists( $g_external_is_connector)) { + return new $g_external_is_connector(); + } + + return null; // Return null explicitly if no valid connector is found + } +} diff --git a/lib/OrisIntegrationService.php b/lib/OrisIntegrationService.php index 6bced1f..cf7c6c5 100644 --- a/lib/OrisIntegrationService.php +++ b/lib/OrisIntegrationService.php @@ -189,5 +189,48 @@ public function getEvent($eventId) { return json_decode($response, true); } + + public function getEventList($fromDate, $toDate, $all = 1) { + $params = [ + 'method' => 'getEventList', + 'format' => 'json', + 'all' => $all, + 'datefrom' => $fromDate, + 'dateto' => $toDate + ]; + + $ch = curl_init(); + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); + } + + public function getRegistration($sport, $year) { + $params = [ + 'method' => 'getRegistration', + 'format' => 'json', + 'sport' => $sport, + 'year' => $year + ]; + + $ch = curl_init(); + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); + } } -?> \ No newline at end of file +?> diff --git a/oris_sync.log b/oris_sync.log deleted file mode 100644 index a9beebf..0000000 --- a/oris_sync.log +++ /dev/null @@ -1,148 +0,0 @@ -[2026-03-12 21:53:42] Processing create for entry ID 125 (user: 26, race: 48) -[2026-03-12 21:53:42] - Skipped: Race 48 is not imported from ORIS (no oris_comp_id). Marking LOCAL_ONLY. -[2026-03-12 21:56:25] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 21:56:25] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 -[2026-03-12 21:56:25] - Failed: Action create encountered an error: API Error or HTTP 200 -[2026-03-12 21:56:57] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 21:56:57] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 -[2026-03-12 21:56:57] - Failed: Action create encountered an error: API Error or HTTP 200 -[2026-03-12 21:59:24] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 21:59:24] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 -[2026-03-12 21:59:24] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné -[2026-03-12 22:01:44] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:01:44] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 -[2026-03-12 22:01:44] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné -[2026-03-12 22:01:58] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:01:58] - Sending create request to ORIS API. clubuser: 8721, class: H21, si: 0 -[2026-03-12 22:01:58] - Raw POST Data: clubuser=8721&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:01:58] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné -[2026-03-12 22:02:59] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:02:59] - Sending create request to ORIS API. clubuser: ZBM8721, class: H21, si: 0 -[2026-03-12 22:02:59] - Raw POST Data: clubuser=ZBM8721&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:02:59] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné -[2026-03-12 22:05:58] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:05:58] - Sending create request to ORIS API. clubuser: ZBM8721, class: H21, si: 0 -[2026-03-12 22:05:58] - Raw POST Data: clubuser=ZBM8721&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:05:58] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Reg.č. není platné -[2026-03-12 22:06:30] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:06:30] - Sending create request to ORIS API. clubuser: 7000, class: H21, si: 0 -[2026-03-12 22:06:31] - Raw POST Data: clubuser=7000&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:06:31] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Neplatná kategorie -[2026-03-12 22:11:24] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:11:25] - Warning: Could not resolve ORIS Class ID for category 'H21' -[2026-03-12 22:11:25] - Sending create request to ORIS API. clubuser: 7000, class: H21 (mapped from H21), si: 0 -[2026-03-12 22:11:25] - Raw POST Data: clubuser=7000&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:11:25] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Neplatná kategorie -[2026-03-12 22:12:14] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:12:14] - Warning: Could not resolve ORIS Class ID for category 'H21' -[2026-03-12 22:12:14] - Sending create request to ORIS API. clubuser: 7000, class: H21 (mapped from H21), si: 0 -[2026-03-12 22:12:14] - Raw POST Data: clubuser=7000&class=H21&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:12:14] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Neplatná kategorie -[2026-03-12 22:15:33] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:15:34] - Sending create request to ORIS API. clubuser: 7000, class: 217130 (mapped from H21), si: 0 -[2026-03-12 22:15:34] - Raw POST Data: clubuser=7000&class=217130&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:15:34] - Success: Action create completed. ORIS Entry ID: -[2026-03-12 22:24:40] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:24:41] - Sending create request to ORIS API. clubuser: 7323, class: 217130 (mapped from H21), si: 0 -[2026-03-12 22:24:41] - Raw POST Data: clubuser=7323&class=217130&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:24:41] - Success: Action create completed. ORIS Entry ID: -[2026-03-12 22:42:43] Processing create for entry ID 138 (user: 39, race: 52) -[2026-03-12 22:42:43] - Sending create request to ORIS API. clubuser: 7323, class: 217130 (mapped from H21), si: 0 -[2026-03-12 22:42:43] - Raw POST Data: clubuser=7323&class=217130&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-12 22:42:43] - Success: Action create completed. ORIS Entry ID: -[2026-03-13 13:41:09] Processing update for entry ID 139 (user: 39, race: 53) -[2026-03-13 13:41:10] - Sending update request to ORIS API. clubuser: 7323, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 13:41:10] - Failed: Action update encountered an error: Cannot update: No ORIS Entry ID -[2026-03-13 13:45:25] Processing create for entry ID 139 (user: 39, race: 53) -[2026-03-13 13:45:25] - Sending create request to ORIS API. clubuser: 7323, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 13:45:25] - Raw POST Data: clubuser=7323&class=211624&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 13:45:25] - Success: Action create completed. ORIS Entry ID: -[2026-03-13 13:50:40] Processing delete for entry ID 139 (user: 39, race: 53) -[2026-03-13 13:50:41] - Sending delete request to ORIS API. clubuser: 7323, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 13:50:41] - Success: Action delete completed. ORIS Entry ID: -[2026-03-13 13:53:30] Processing create for entry ID 140 (user: 11, race: 53) -[2026-03-13 13:53:30] - Sending create request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 -[2026-03-13 13:53:31] - Raw POST Data: clubuser=7315&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 13:53:31] - Success: Action create completed. ORIS Entry ID: -[2026-03-13 13:53:31] Processing create for entry ID 141 (user: 39, race: 53) -[2026-03-13 13:53:31] - Sending create request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 13:53:31] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 13:53:31] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen -[2026-03-13 13:54:19] Processing delete for entry ID 140 (user: 11, race: 53) -[2026-03-13 13:54:19] - Sending delete request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 -[2026-03-13 13:54:19] - Success: Action delete completed. ORIS Entry ID: -[2026-03-13 13:54:19] Processing delete for entry ID 141 (user: 39, race: 53) -[2026-03-13 13:54:20] - Sending delete request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 13:54:20] - Success: Action delete completed. ORIS Entry ID: -[2026-03-13 14:04:36] Processing update for entry ID 142 (user: 11, race: 53) -[2026-03-13 14:04:36] - Sending update request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:04:36] - Warning: Missing ORIS Entry ID for update. Falling back to create. -[2026-03-13 14:04:37] - Raw POST Data: clubuser=7315&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:04:37] - Failed: Action update encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen -[2026-03-13 14:04:37] Processing update for entry ID 143 (user: 39, race: 53) -[2026-03-13 14:04:37] - Sending update request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:04:37] - Warning: Missing ORIS Entry ID for update. Falling back to create. -[2026-03-13 14:04:37] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:04:37] - Failed: Action update encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen -[2026-03-13 14:06:59] Processing create for entry ID 142 (user: 11, race: 53) -[2026-03-13 14:06:59] - Sending create request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:07:00] - Raw POST Data: clubuser=7315&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:07:00] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen -[2026-03-13 14:07:00] Processing create for entry ID 143 (user: 39, race: 53) -[2026-03-13 14:07:00] - Sending create request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:07:00] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:07:00] - Failed: Action create encountered an error: API Error or HTTP 200 ORIS Status: Již přihlášen -[2026-03-13 14:08:30] Processing update for entry ID 142 (user: 11, race: 53) -[2026-03-13 14:08:30] - Sending update request to ORIS API. clubuser: 7315, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:08:30] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. -[2026-03-13 14:08:31] - Found ORIS Entry ID 3170927 for clubuser 7315 -[2026-03-13 14:08:31] - Raw POST Data: entryid=3170927&clubuser=7315&class=211626&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:08:31] - Success: Action update completed. ORIS Entry ID: -[2026-03-13 14:08:31] Processing update for entry ID 143 (user: 39, race: 53) -[2026-03-13 14:08:31] - Sending update request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:08:31] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. -[2026-03-13 14:08:31] - Found ORIS Entry ID 3170917 for clubuser 7323 -[2026-03-13 14:08:32] - Raw POST Data: entryid=3170917&clubuser=7323&class=211626&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:08:32] - Success: Action update completed. ORIS Entry ID: -[2026-03-13 14:10:12] Processing update for entry ID 142 (user: 11, race: 53) -[2026-03-13 14:10:13] - Sending update request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 14:10:13] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. -[2026-03-13 14:10:13] - Found ORIS Entry ID 3170927 for clubuser 7315 -[2026-03-13 14:10:13] - Raw POST Data: entryid=3170927&clubuser=7315&class=211624&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:10:13] - Success: Action update completed. ORIS Entry ID: 3170927 -[2026-03-13 14:10:13] Processing update for entry ID 143 (user: 39, race: 53) -[2026-03-13 14:10:14] - Warning: Could not resolve ORIS Class ID for category 'H21' -[2026-03-13 14:10:14] - Sending update request to ORIS API. clubuser: 7323, class: H21 (mapped from H21), si: 0 -[2026-03-13 14:10:14] - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS. -[2026-03-13 14:10:14] - Found ORIS Entry ID 3170917 for clubuser 7323 -[2026-03-13 14:10:14] - Raw POST Data: entryid=3170917&clubuser=7323&class=H21&method=updateEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:10:14] - Success: Action update completed. ORIS Entry ID: 3170917 -[2026-03-13 14:39:31] Processing delete for entry ID 142 (user: 11, race: 53) -[2026-03-13 14:39:32] - Sending delete request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 14:39:32] - Raw POST Data: entryid=3170927&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:39:32] - Success: Action delete completed. ORIS Entry ID: 3170927 -[2026-03-13 14:39:32] Processing delete for entry ID 143 (user: 39, race: 53) -[2026-03-13 14:39:32] - Warning: Could not resolve ORIS Class ID for category 'H21' -[2026-03-13 14:39:32] - Sending delete request to ORIS API. clubuser: 7323, class: H21 (mapped from H21), si: 0 -[2026-03-13 14:39:33] - Raw POST Data: entryid=3170917&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:39:33] - Success: Action delete completed. ORIS Entry ID: 3170917 -[2026-03-13 14:40:37] Processing create for entry ID 144 (user: 11, race: 53) -[2026-03-13 14:40:38] - Sending create request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 14:40:39] - Raw POST Data: clubuser=7315&class=211624&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:40:39] - Success: Action create completed. ORIS Entry ID: -[2026-03-13 14:40:39] Processing create for entry ID 145 (user: 39, race: 53) -[2026-03-13 14:40:40] - Sending create request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:40:40] - Raw POST Data: clubuser=7323&class=211626&method=createEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:40:40] - Success: Action create completed. ORIS Entry ID: -[2026-03-13 14:44:01] Processing delete for entry ID 144 (user: 11, race: 53) -[2026-03-13 14:44:02] - Sending delete request to ORIS API. clubuser: 7315, class: 211624 (mapped from H21C), si: 0 -[2026-03-13 14:44:02] - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS. -[2026-03-13 14:44:02] - Found ORIS Entry ID 3170940 for clubuser 7315 -[2026-03-13 14:44:02] - Raw POST Data: entryid=3170940&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:44:02] - Success: Action delete completed. ORIS Entry ID: -[2026-03-13 14:44:02] Processing delete for entry ID 145 (user: 39, race: 53) -[2026-03-13 14:44:03] - Sending delete request to ORIS API. clubuser: 7323, class: 211626 (mapped from H35), si: 0 -[2026-03-13 14:44:03] - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS. -[2026-03-13 14:44:03] - Found ORIS Entry ID 3170941 for clubuser 7323 -[2026-03-13 14:44:03] - Raw POST Data: entryid=3170941&method=deleteEntry&format=json&clubkey=16704c0a8cc4bb025db487f49411e808 -[2026-03-13 14:44:03] - Success: Action delete completed. ORIS Entry ID: diff --git a/oris_sync_daemon.php b/oris_sync_daemon.php index aad999a..5e751db 100644 --- a/oris_sync_daemon.php +++ b/oris_sync_daemon.php @@ -29,6 +29,7 @@ FROM `" . TBL_ZAVXUS . "` zx LEFT JOIN `" . TBL_RACE . "` r ON zx.id_zavod = r.id WHERE zx.sync_status IN ('PENDING_CREATE', 'PENDING_UPDATE', 'PENDING_DELETE', 'FAILED_RETRY') + AND (r.datum >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) OR r.datum IS NULL) ORDER BY ISNULL(r.oris_entry_start), r.oris_entry_start ASC "; $res = query_db($query); @@ -79,7 +80,7 @@ function logMessage($msg) { // Output to console when run via CLI echo $line; // Log to file in the same directory - file_put_contents(__DIR__ . '/oris_sync.log', $line, FILE_APPEND); + file_put_contents(__DIR__ . '/logs/oris_sync.log', $line, FILE_APPEND); } function processEntry($row, $action, $service) { From 47998c2b462b20d172b7ea2eb6505ef87c7e11d5 Mon Sep 17 00:00:00 2001 From: "jan.zhanal" Date: Tue, 17 Mar 2026 21:50:40 +0100 Subject: [PATCH 04/16] refactor connectors --- connectors.php | 143 +++++----------------- lib/OrisDTOs.php | 99 ++++++++++++++++ lib/OrisExceptions.php | 26 ++++ lib/OrisIntegrationService.php | 211 +++++++-------------------------- 4 files changed, 203 insertions(+), 276 deletions(-) create mode 100644 lib/OrisDTOs.php create mode 100644 lib/OrisExceptions.php diff --git a/connectors.php b/connectors.php index 3fbd6ba..0691a55 100644 --- a/connectors.php +++ b/connectors.php @@ -7,68 +7,6 @@ require_once __DIR__ . '/lib/OrisIntegrationService.php'; -// Define a class to represent the race data -class RaceInfo { - public $ext_id; - public $datum; - public $datum2; - public $nazev; - public $misto; - public $oblasti; - public $typ; - public $zebricek2; - public $ranking; - public $odkaz; - public $prihlasky; - public $prihlasky1; - public $prihlasky2; - public $prihlasky3; - public $prihlasky4; - public $prihlasky5; - public $koeficient1; - public $koeficient2; - public $etap; - public $poznamka; - public $vicedenni; - public $oddil; - public $modify_flag; - public $kategorie; - public $startovne; - public $cancelled; - public $oris_entry_start; - - // Constructor to initialize the object with key-value pairs - public function __construct($data) { - $this->ext_id = $data['ext_id'] ?? null; - $this->datum = $data['datum'] ?? null; - $this->datum2 = $data['datum2'] ?? null; - $this->nazev = $data['nazev'] ?? null; - $this->misto = $data['misto'] ?? null; - $this->oblasti = $data['oblasti'] ?? null; - $this->typ = $data['typ'] ?? null; - $this->zebricek2 = $data['zebricek2'] ?? null; - $this->ranking = $data['ranking'] ?? null; - $this->odkaz = $data['odkaz'] ?? null; - $this->prihlasky = $data['prihlasky'] ?? null; - $this->prihlasky1 = $data['prihlasky1'] ?? null; - $this->prihlasky2 = $data['prihlasky2'] ?? null; - $this->prihlasky3 = $data['prihlasky3'] ?? null; - $this->prihlasky4 = $data['prihlasky4'] ?? null; - $this->prihlasky5 = $data['prihlasky5'] ?? null; - $this->koeficient1 = $data['koeficient1'] ?? null; - $this->koeficient2 = $data['koeficient2'] ?? null; - $this->etap = $data['etap'] ?? null; - $this->poznamka = $data['poznamka'] ?? null; - $this->vicedenni = $data['vicedenni'] ?? null; - $this->oddil = $data['oddil'] ?? null; - $this->modify_flag = $data['modify_flag'] ?? null; - $this->kategorie = $data['kategorie'] ?? null; - $this->startovne = $data['startovne'] ?? null; - $this->cancelled = $data['cancelled'] ?? null; - $this->oris_entry_start = $data['oris_entry_start'] ?? null; - } -} - class RacePayement { public int $raceId; public RaceOverview $overview; @@ -146,7 +84,7 @@ public function __construct(string $regNo, string $classDesc, string $name, bool interface ConnectorInterface { public function getSystemName(): string; public function getRaceURL(string $id): string; - public function getRaceInfo(string $id) : ?RaceInfo; + public function getRaceInfo(string $id); public function getRacesList($fromDate, $toDate); public function getRacePayement(string $id) : ?RacePayement; } @@ -161,12 +99,10 @@ public function __construct() { $this->service = new OrisIntegrationService(null); } - // Method to get the system name public function getSystemName(): string { return "Oris"; } - // RaceInfo URL public function getRaceURL($raceId) : string { return $this->sourceUrl . 'Zavod?id=' . $raceId; } @@ -185,7 +121,6 @@ private function mapLevelToZebricek2($levelId) { } private function mapSport($sportId) { - //sport ID from ORIS : 1=OB, 2=LOB, 3=MTBO, 4=TRAIL $map = [ 1 => 1, // OB 2 => 4, // LOB @@ -196,7 +131,6 @@ private function mapSport($sportId) { } private function getClubs(&$raceData) { - $oddily = []; if (isset($raceData['Org1']['Abbr'])) { $oddily[] = $raceData['Org1']['Abbr']; @@ -207,37 +141,34 @@ private function getClubs(&$raceData) { return implode('+', $oddily); } - // Method to get race date based on race ID public function getRaceDate($raceId) { - $response = $this->service->getEvent($raceId); - - if ($response && isset($response['Status']) && $response['Status'] == "OK") { - $raceData = $response['Data']; - return String2DateDMY(formatDate($raceData['Date'])); + try { + $raceData = $this->service->getEvent($raceId); + if (isset($raceData['Date'])) { + return String2DateDMY(formatDate($raceData['Date'])); + } + } catch (OrisException $e) { + // fallback } - - return ''; // Return empty string if race not found or error + return ''; } - // Method to get detailed race information based on race ID - public function getRaceInfo($raceId) : ?RaceInfo { - $response = $this->service->getEvent($raceId); - - if ($response && isset($response['Status']) && $response['Status'] == "OK") { - $raceData = $response['Data']; + public function getRaceInfo($raceId) { + try { + $raceData = $this->service->getEvent($raceId); $classFees = []; if (isset($raceData['Classes'])) { foreach ($raceData['Classes'] as $class) { if (isset($class['Name'])) { $name = $class['Name']; - $fee = $class['Fee'] ?? null; // default to null if missing + $fee = $class['Fee'] ?? null; $classFees[$name] = $fee; } } } - ksort ( $classFees ); + ksort($classFees); $oddily = $this->getClubs($raceData); $oblasti = []; @@ -247,17 +178,14 @@ public function getRaceInfo($raceId) : ?RaceInfo { } } - // Get last Stage date if multistage event $date2 = ($raceData['Stages'] > 1) ? $this->getRaceDate($raceData['Stage'.$raceData['Stages']]) : 0; - // Use associative array to pass data to constructor - return new RaceInfo([ + + return new RaceDTO([ 'ext_id' => $raceData['ID'], 'datum' => String2DateDMY(formatDate($raceData['Date'])), 'datum2' => $date2, 'nazev' => $raceData['Name'], 'misto' => $raceData['Place'], -// 'category' => $raceData['Category'], - //typ0 => Typ akce 'oblasti' => $oblasti, 'typ0' => 'Z', 'typ' => $this->mapSport($raceData['Sport']['ID']), @@ -267,39 +195,25 @@ public function getRaceInfo($raceId) : ?RaceInfo { 'prihlasky' => strtotime($raceData['EntryDate1']), 'prihlasky1' => strtotime($raceData['EntryDate2']), 'prihlasky2' => strtotime($raceData['EntryDate3']), -// 'prihlasky3' => '', -// 'prihlasky4' => '', -// 'prihlasky5' => '', 'koeficient1' => $raceData['EntryKoef2'], 'koeficient2' => $raceData['EntryKoef3'], 'etap' => $raceData['Stages'], -// 'poznamka' => $poznamka, 'vicedenni' => ($raceData['Stages']>1?1:0), 'oddil' => $oddily, 'modify_flag' => 0, - 'kategorie' => implode(';', array_keys ( $classFees ) ), + 'kategorie' => implode(';', array_keys($classFees)), 'startovne' => $classFees, 'cancelled' => (!empty($raceData['Cancelled']) || !empty($raceData['Canceled']) || !empty($raceData['cancelled']) || !empty($raceData['canceled'])) ? 1 : 0, 'oris_entry_start' => !empty($raceData['EntryStart']) ? $raceData['EntryStart'] : null - ]); - } else { - return null; // Return null if race not found or error + ]); + } catch (OrisException $e) { + return null; } } - // Helper method to make HTTP requests (used by getRacePayement) - private function makeRequest($url) { - $response = file_get_contents($url); - - // Decode JSON response - return json_decode($response, true); - } - function getRacesList($fromDate, $toDate) { - $response = $this->service->getEventList($fromDate, $toDate, 1); - - if ($response && isset($response['Status']) && $response['Status'] == "OK") { - $racesData = $response['Data']; + try { + $racesData = $this->service->getEventList($fromDate, $toDate, 1); $rows = array(); foreach($racesData as $oneRace) { $oddily = $this->getClubs($oneRace); @@ -312,12 +226,19 @@ function getRacesList($fromDate, $toDate) { $rows[] = $row; } return $rows; - } else { - return null; // Return null if race not found or error + } catch (OrisException $e) { + return null; } } - // Method to get race payment details + // Helper method to make HTTP requests (used by getRacePayement) + private function makeRequest($url) { + $response = file_get_contents($url); + + // Decode JSON response + return json_decode($response, true); + } + public function getRacePayement($raceId) : ?RacePayement { global $g_external_is_club_id; diff --git a/lib/OrisDTOs.php b/lib/OrisDTOs.php new file mode 100644 index 0000000..287d001 --- /dev/null +++ b/lib/OrisDTOs.php @@ -0,0 +1,99 @@ +ext_id = $data['ext_id'] ?? null; + $this->datum = $data['datum'] ?? null; + $this->datum2 = $data['datum2'] ?? null; + $this->nazev = $data['nazev'] ?? null; + $this->misto = $data['misto'] ?? null; + $this->oblasti = $data['oblasti'] ?? null; + $this->typ = $data['typ'] ?? null; + $this->zebricek2 = $data['zebricek2'] ?? null; + $this->ranking = $data['ranking'] ?? null; + $this->odkaz = $data['odkaz'] ?? null; + $this->prihlasky = $data['prihlasky'] ?? null; + $this->prihlasky1 = $data['prihlasky1'] ?? null; + $this->prihlasky2 = $data['prihlasky2'] ?? null; + $this->prihlasky3 = $data['prihlasky3'] ?? null; + $this->prihlasky4 = $data['prihlasky4'] ?? null; + $this->prihlasky5 = $data['prihlasky5'] ?? null; + $this->koeficient1 = $data['koeficient1'] ?? null; + $this->koeficient2 = $data['koeficient2'] ?? null; + $this->etap = $data['etap'] ?? null; + $this->poznamka = $data['poznamka'] ?? null; + $this->vicedenni = $data['vicedenni'] ?? null; + $this->oddil = $data['oddil'] ?? null; + $this->modify_flag = $data['modify_flag'] ?? null; + $this->kategorie = $data['kategorie'] ?? null; + $this->startovne = $data['startovne'] ?? null; + $this->cancelled = $data['cancelled'] ?? null; + $this->oris_entry_start = $data['oris_entry_start'] ?? null; + $this->typ0 = $data['typ0'] ?? null; + } +} + +class OrisEntryRequestDTO { + public ?string $clubuser; + public ?string $classId; + public ?string $si; + public int $rentSi; + public ?string $note; + public ?string $entryId; + + public function __construct( + ?string $clubuser, + ?string $classId, + ?string $si, + bool $rentSi = false, + ?string $note = null, + ?string $entryId = null + ) { + $this->clubuser = $clubuser; + $this->classId = $classId; + $this->si = $si; + $this->rentSi = $rentSi ? 1 : 0; + $this->note = $note; + $this->entryId = $entryId; + } + + public function toArray(): array { + $data = []; + if ($this->clubuser !== null) $data['clubuser'] = $this->clubuser; + if ($this->classId !== null) $data['class'] = $this->classId; + if ($this->si !== null && $this->si !== '') $data['si'] = $this->si; + if ($this->rentSi) $data['rent_si'] = 1; + if ($this->note !== null && $this->note !== '') $data['note'] = $this->note; + if ($this->entryId !== null) $data['entryid'] = $this->entryId; + return $data; + } +} diff --git a/lib/OrisExceptions.php b/lib/OrisExceptions.php new file mode 100644 index 0000000..4cda17f --- /dev/null +++ b/lib/OrisExceptions.php @@ -0,0 +1,26 @@ +apiStatus = $apiStatus; + $this->apiData = $apiData; + } + + public function getApiStatus() { + return $this->apiStatus; + } + + public function getApiData() { + return $this->apiData; + } +} + +class OrisValidationException extends OrisException {} diff --git a/lib/OrisIntegrationService.php b/lib/OrisIntegrationService.php index cf7c6c5..65bf619 100644 --- a/lib/OrisIntegrationService.php +++ b/lib/OrisIntegrationService.php @@ -1,33 +1,44 @@ clubKey = $clubKey; } /** - * Executes the HTTP POST request to ORIS API + * Internal generic HTTP request method. */ - private function executeRequest($method, $params) { + private function makeRequest($method, $params = [], $isPost = false) { $params['method'] = $method; $params['format'] = 'json'; - $params['clubkey'] = $this->clubKey; + if ($this->clubKey) { + $params['clubkey'] = $this->clubKey; + } + + $ch = curl_init(); - $postData = http_build_query($params); + if ($isPost) { + $postData = http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $this->apiUrl); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + } else { + $url = $this->apiUrl . '?' . http_build_query($params); + curl_setopt($ch, CURLOPT_URL, $url); + } - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $this->apiUrl); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - // Ensure SSL verification curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); @@ -36,7 +47,7 @@ private function executeRequest($method, $params) { if(curl_errno($ch)){ $error = curl_error($ch); curl_close($ch); - return ['status' => 'error', 'message' => 'cURL Error: ' . $error, 'request' => $postData]; + throw new OrisNetworkException('cURL Error: ' . $error); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); @@ -45,192 +56,62 @@ private function executeRequest($method, $params) { $decoded = json_decode($response, true); if ($httpCode >= 200 && $httpCode < 300 && isset($decoded['Status']) && $decoded['Status'] === 'OK') { - return ['status' => 'success', 'data' => $decoded['Data'] ?? $decoded, 'request' => $postData]; + return $decoded['Data'] ?? $decoded; } else { - $apiErrorMsg = ''; - if (is_array($decoded) && isset($decoded['Status'])) { - $apiErrorMsg = " ORIS Status: " . $decoded['Status']; - // Some endpoints might return error details in Data or a specific Error field - if (isset($decoded['Data']) && is_string($decoded['Data'])) { - $apiErrorMsg .= " - " . $decoded['Data']; - } + $apiStatus = $decoded['Status'] ?? 'Unknown'; + $apiData = $decoded['Data'] ?? null; + $msg = "API Error or HTTP {$httpCode}. Status: {$apiStatus}"; + if (is_string($apiData)) { + $msg .= " - " . $apiData; } - return [ - 'status' => 'error', - 'message' => 'API Error or HTTP ' . $httpCode . $apiErrorMsg, - 'payload' => $response, - 'request' => $postData - ]; + throw new OrisApiException($msg, $apiStatus, $apiData); } } - public function createEntry($clubuser, $classId, $si, $rentSi, $note = '') { - $params = [ - 'clubuser' => $clubuser, - 'class' => $classId - ]; - - if ($si) { - $params['si'] = $si; - } - if ($rentSi) { - $params['rent_si'] = 1; - } - if ($note) { - $params['note'] = $note; - } + // --- Write/Mutating Operations (Phase C) --- - return $this->executeRequest('createEntry', $params); + public function createEntry(OrisEntryRequestDTO $dto) { + return $this->makeRequest('createEntry', $dto->toArray(), true); } - public function updateEntry($entryId, $clubuser, $classId, $si, $rentSi, $note = '') { - $params = [ - 'entryid' => $entryId, - 'clubuser' => $clubuser, - 'class' => $classId - ]; - - if ($si) { - $params['si'] = $si; - } - if ($rentSi) { - $params['rent_si'] = 1; - } - if ($note) { - $params['note'] = $note; - } - - return $this->executeRequest('updateEntry', $params); + public function updateEntry(OrisEntryRequestDTO $dto) { + return $this->makeRequest('updateEntry', $dto->toArray(), true); } public function deleteEntry($entryId) { - $params = [ - 'entryid' => $entryId - ]; - return $this->executeRequest('deleteEntry', $params); + return $this->makeRequest('deleteEntry', ['entryid' => $entryId], true); } + // --- Read-Only and Protected Read Endpoints (Phase A & B) --- + public function getUser($rgnum) { - $params = [ - 'method' => 'getUser', - 'format' => 'json', - 'rgnum' => $rgnum - ]; - - $ch = curl_init(); - $url = $this->apiUrl . '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - - $response = curl_exec($ch); - curl_close($ch); - - return json_decode($response, true); + return $this->makeRequest('getUser', ['rgnum' => $rgnum]); } public function getClubUsers($userId) { - $params = [ - 'method' => 'getClubUsers', - 'format' => 'json', - 'user' => $userId - ]; - - $ch = curl_init(); - $url = $this->apiUrl . '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - - $response = curl_exec($ch); - curl_close($ch); - - return json_decode($response, true); + return $this->makeRequest('getClubUsers', ['user' => $userId]); } public function getEventEntries($eventId) { - $params = [ - 'method' => 'getEventEntries', - 'format' => 'json', - 'eventid' => $eventId - ]; - - $ch = curl_init(); - $url = $this->apiUrl . '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - - $response = curl_exec($ch); - curl_close($ch); - - return json_decode($response, true); + return $this->makeRequest('getEventEntries', ['eventid' => $eventId]); } public function getEvent($eventId) { - $params = [ - 'method' => 'getEvent', - 'format' => 'json', - 'id' => $eventId - ]; - - $ch = curl_init(); - $url = $this->apiUrl . '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - - $response = curl_exec($ch); - curl_close($ch); - - return json_decode($response, true); + return $this->makeRequest('getEvent', ['id' => $eventId]); } public function getEventList($fromDate, $toDate, $all = 1) { - $params = [ - 'method' => 'getEventList', - 'format' => 'json', + return $this->makeRequest('getEventList', [ 'all' => $all, 'datefrom' => $fromDate, 'dateto' => $toDate - ]; - - $ch = curl_init(); - $url = $this->apiUrl . '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - - $response = curl_exec($ch); - curl_close($ch); - - return json_decode($response, true); + ]); } public function getRegistration($sport, $year) { - $params = [ - 'method' => 'getRegistration', - 'format' => 'json', + return $this->makeRequest('getRegistration', [ 'sport' => $sport, 'year' => $year - ]; - - $ch = curl_init(); - $url = $this->apiUrl . '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - - $response = curl_exec($ch); - curl_close($ch); - - return json_decode($response, true); + ]); } } -?> From 8dc0fd818c692095d44022375a2fd24c2f555b0c Mon Sep 17 00:00:00 2001 From: "jan.zhanal" Date: Mon, 23 Mar 2026 00:41:25 +0100 Subject: [PATCH 05/16] ORIS: immediate entry, fix PR comments, --- _SQL/zmeny_3.4.5.658.sql.php | 5 +- adm_oris_sync.php | 1 + ads_oris.inc.php | 20 +- ads_oris_si_sync.php | 4 +- lib/OrisDTOs.php | 6 +- lib/OrisIntegrationService.php | 3 + lib/oris_sync.inc.php | 370 +++++++++++++++++++++++++++++++++ nav.inc.php | 1 + oris_sync_daemon.php | 274 +----------------------- race_reg_chip_exc.php | 46 +++- race_regs_1_exc.php | 77 ++++++- race_regs_all_exc.php | 105 ++++++++-- rg_oris_sync_log.php | 33 +++ us_race_regoff_exc.php | 42 +++- us_race_regon_exc.php | 82 +++++++- 15 files changed, 762 insertions(+), 307 deletions(-) create mode 100644 lib/oris_sync.inc.php create mode 100644 rg_oris_sync_log.php diff --git a/_SQL/zmeny_3.4.5.658.sql.php b/_SQL/zmeny_3.4.5.658.sql.php index ec7084d..3dbf46a 100644 --- a/_SQL/zmeny_3.4.5.658.sql.php +++ b/_SQL/zmeny_3.4.5.658.sql.php @@ -12,9 +12,10 @@ //############################################################################ $sql[0] = "ALTER TABLE `".TBL_ZAVXUS."` ADD COLUMN `oris_entry_id` INT UNSIGNED NULL, - ADD COLUMN `sync_status` VARCHAR(32) NOT NULL DEFAULT 'LOCAL_ONLY', + ADD COLUMN `sync_status` ENUM('LOCAL_ONLY', 'SYNCED', 'PENDING_CREATE', 'PENDING_UPDATE', 'PENDING_DELETE', 'FAILED_CREATE', 'FAILED_UPDATE', 'FAILED_DELETE') NOT NULL DEFAULT 'LOCAL_ONLY', ADD COLUMN `sync_timestamp` DATETIME NULL, - ADD COLUMN `sync_error_payload` TEXT NULL"; + ADD COLUMN `sync_error_payload` TEXT NULL, + ADD INDEX `sync_status` (`sync_status`)"; $sql[1] = "ALTER TABLE `".TBL_RACE."` ADD COLUMN `oris_entry_start` DATETIME NULL"; diff --git a/adm_oris_sync.php b/adm_oris_sync.php index 5881268..7cbf612 100644 --- a/adm_oris_sync.php +++ b/adm_oris_sync.php @@ -26,6 +26,7 @@ $value) -{ - $user = new User(); - $user->create($value['UserID'], $value['FirstName'], $value['LastName'], $value['RegNo'], $value['SI'], $value['ClubID']); - - $reg = $value['RegNo']; - if (startsWith($reg, $g_shortcut)) +if (is_array($obj) || is_object($obj)) { + foreach ($obj as $key=>$value) { - $arr_oris["user"] [$reg]= $user; - $arr_oris["members"][$reg] = 0; + $user = new User(); + $user->create($value['UserID'], $value['FirstName'], $value['LastName'], $value['RegNo'], $value['SI'], $value['ClubID']); + + $reg = $value['RegNo']; + if (startsWith($reg, $g_shortcut)) + { + $arr_oris["user"] [$reg]= $user; + $arr_oris["members"][$reg] = 0; + } } } //konec nahrani dat z orisu diff --git a/ads_oris_si_sync.php b/ads_oris_si_sync.php index c5b7173..b1a5d52 100644 --- a/ads_oris_si_sync.php +++ b/ads_oris_si_sync.php @@ -30,7 +30,6 @@ 'format' => 'json', 'method' => 'editPerson', 'userid' => $oris_id, - 'si' => $zaznam['si_chip'], 'clubkey' => $g_oris_club_key, 'firstname' => $zaznam['jmeno'], 'lastname' => $zaznam['prijmeni'], @@ -40,6 +39,9 @@ 'zip' => $zaznam['psc'], 'country' => (!empty($zaznam['narodnost']) ? $zaznam['narodnost'] : 'CZ') ); + if (!empty($zaznam['si_chip']) && $zaznam['si_chip'] != 0 && $zaznam['si_chip'] !== '0') { + $params['si'] = $zaznam['si_chip']; + } $url = "https://oris.ceskyorientak.cz/API/?" . http_build_query($params); diff --git a/lib/OrisDTOs.php b/lib/OrisDTOs.php index 287d001..bf93586 100644 --- a/lib/OrisDTOs.php +++ b/lib/OrisDTOs.php @@ -90,8 +90,10 @@ public function toArray(): array { $data = []; if ($this->clubuser !== null) $data['clubuser'] = $this->clubuser; if ($this->classId !== null) $data['class'] = $this->classId; - if ($this->si !== null && $this->si !== '') $data['si'] = $this->si; - if ($this->rentSi) $data['rent_si'] = 1; + if ($this->si !== null && $this->si !== '' && $this->si !== '0' && $this->si !== 0) { + $data['si'] = $this->si; + } + $data['rent_si'] = $this->rentSi ? 1 : 0; if ($this->note !== null && $this->note !== '') $data['note'] = $this->note; if ($this->entryId !== null) $data['entryid'] = $this->entryId; return $data; diff --git a/lib/OrisIntegrationService.php b/lib/OrisIntegrationService.php index 65bf619..9e379c3 100644 --- a/lib/OrisIntegrationService.php +++ b/lib/OrisIntegrationService.php @@ -61,6 +61,9 @@ private function makeRequest($method, $params = [], $isPost = false) { $apiStatus = $decoded['Status'] ?? 'Unknown'; $apiData = $decoded['Data'] ?? null; $msg = "API Error or HTTP {$httpCode}. Status: {$apiStatus}"; + if ($isPost) { + $msg .= "\nPOST Data sent: " . print_r($params, true); + } if (is_string($apiData)) { $msg .= " - " . $apiData; } diff --git a/lib/oris_sync.inc.php b/lib/oris_sync.inc.php new file mode 100644 index 0000000..44a64d5 --- /dev/null +++ b/lib/oris_sync.inc.php @@ -0,0 +1,370 @@ +getUser($rgnum); + if (is_array($userApiRes) && isset($userApiRes['ID'])) { + $globalUserId = $userApiRes['ID']; + + // Now get the club user ID + try { + $clubUsersRes = $service->getClubUsers($globalUserId); + if (is_array($clubUsersRes)) { + $clubUsers = $clubUsersRes; + // Check if it's associative array representing a single user directly + if (isset($clubUsers['ID'])) { + $clubUsers = [$clubUsers]; + } + + // We must find the correct clubuser entry for the current club + // First, try to fetch current club key from config + global $g_external_is_club_id; + $clubId = $g_external_is_club_id ?? null; + + foreach ($clubUsers as $cu) { + if (isset($cu['ID'])) { + // If club ID is known, prefer that match + if ($clubId && isset($cu['ClubID']) && $cu['ClubID'] == $clubId) { + $clubuser = $cu['ID']; + break; + } + // Otherwise, just take the first one or valid one + if (empty($clubuser)) { + $clubuser = $cu['ID']; + } + } + } + } + } catch (Exception $e) { + $errMsg = $e->getMessage(); + logMessage(" - Warning: getClubUsers failed for user ID $globalUserId: $errMsg"); + if ($e instanceof OrisNetworkException || ($e instanceof OrisApiException && strpos($errMsg, 'API Error or HTTP 5') !== false)) { + logMessage(" - Network error getting club users. Skipping to keep PENDING."); + return 'queued'; + } + } + + if (empty($clubuser)) { + logMessage(" - Warning: Could not resolve ORIS Club User ID for user ID $globalUserId"); + } + } else { + logMessage(" - Warning: Could not resolve ORIS User ID for rgnum $rgnum"); + } + } catch (Exception $e) { + $errMsg = $e->getMessage(); + logMessage(" - Warning: getUser failed for rgnum $rgnum: $errMsg"); + if ($e instanceof OrisNetworkException || ($e instanceof OrisApiException && strpos($errMsg, 'API Error or HTTP 5') !== false)) { + logMessage(" - Network error getting user. Skipping to keep PENDING."); + return 'queued'; + } + } + } + } + } + + // Get Race ORIS ID + $raceQuery = "SELECT * FROM `" . TBL_RACE . "` WHERE `id` = " . (int)$raceId; + $raceRes = query_db($raceQuery); + $raceRow = mysqli_fetch_assoc($raceRes); + + // Ensure the race was imported from ORIS + $comp = $raceRow['ext_id'] ?? null; + if (empty($comp)) { + logMessage(" - Skipped: Race $raceId is not imported from ORIS (no ext_id). Marking LOCAL_ONLY."); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` SET `sync_status` = 'LOCAL_ONLY' WHERE `id` = " . (int)$id; + query_db($updateQuery); + return; + } + + // Ignore relays and sprint relays + // S is typically for štafety (relays) in local DB + if ($raceRow && isset($raceRow['typ0']) && $raceRow['typ0'] === 'S') { + logMessage(" - Skipped: Race $raceId is a relay (typ0 = S). Marking LOCAL_ONLY."); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` SET `sync_status` = 'LOCAL_ONLY' WHERE `id` = " . (int)$id; + query_db($updateQuery); + return; + } + + // Fetch Classes from ORIS Event to map the string category (e.g. H21) to the ORIS class ID + $classId = null; + $katName = $row['kat']; + if (!empty($comp)) { + try { + $eventRes = $service->getEvent($comp); + if (is_array($eventRes) && isset($eventRes['Classes'])) { + $classes = is_array($eventRes['Classes']) ? $eventRes['Classes'] : []; + + foreach ($classes as $cls) { + if (!is_array($cls)) continue; + $clsName = $cls['Name'] ?? ''; + // Some categories might have different spacing or case + if (trim($clsName) === trim($katName)) { + $classId = $cls['ID'] ?? null; + break; + } + } + } + } catch (Exception $e) { + $errMsg = $e->getMessage(); + logMessage(" - Warning: getEvent failed for event $comp: $errMsg"); + if ($e instanceof OrisNetworkException || ($e instanceof OrisApiException && strpos($errMsg, 'API Error or HTTP 5') !== false)) { + logMessage(" - Network error getting event. Skipping to keep PENDING."); + return 'queued'; + } + } + } + + if (empty($classId)) { + logMessage(" - Warning: Could not resolve ORIS Class ID for category '$katName'"); + $errorPayload = correct_sql_string(json_encode(['status' => 'error', 'message' => "Nelze spárovat kategorii '$katName' s ORISem."])); + $failedStatus = 'FAILED_' . strtoupper($action); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` + SET `sync_status` = '$failedStatus', + `sync_error_payload` = '$errorPayload' + WHERE `id` = " . (int)$id; + query_db($updateQuery); + return false; + } + + $si = $row['si_chip']; + if (empty($si) && !empty($userRow['si_chip'])) { + $si = $userRow['si_chip']; + } + $rentSi = $row['rent_si'] ?? 0; // assuming rent_si is added or mapped + $note = $row['pozn'] ?? ''; + + $response = []; + logMessage(" - Sending $action request to ORIS API. clubuser: $clubuser, class: $classId (mapped from $katName), si: $si"); + + if (empty($clubuser)) { + logMessage(" - Failed: Cannot perform action '$action' because ORIS Club User ID (clubuser) could not be resolved."); + $errorPayload = correct_sql_string(json_encode(['status' => 'error', 'message' => 'Chybí ORIS ID uživatele v klubu (clubuser).'])); + $failedStatus = 'FAILED_' . strtoupper($action); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` + SET `sync_status` = '$failedStatus', + `sync_error_payload` = '$errorPayload' + WHERE `id` = " . (int)$id; + query_db($updateQuery); + return false; + } + + try { + if ($action === 'create') { + // Check if it already exists before creating to avoid "Již přihlášen" + $existingEntryId = null; + if (!empty($comp) && !empty($clubuser)) { + try { + $entriesRes = $service->getEventEntries($comp); + if (is_array($entriesRes)) { + $entries = $entriesRes; + foreach ($entries as $entry) { + if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { + $existingEntryId = $entry['ID']; + logMessage(" - Found existing ORIS Entry ID $existingEntryId for clubuser $clubuser during create. Switching to update."); + break; + } + } + } + } catch (Exception $e) { + logMessage(" - Warning: getEventEntries failed: " . $e->getMessage()); + } + } + + if (!empty($existingEntryId)) { + $updateDto = new OrisEntryRequestDTO($clubuser, $classId, $si, (bool)$rentSi, $note, $existingEntryId); + $response = $service->updateEntry($updateDto); + // Also update the action string so log messages below make sense + $action = 'update'; + } else { + $createDto = new OrisEntryRequestDTO($clubuser, $classId, $si, (bool)$rentSi, $note); + $response = $service->createEntry($createDto); + } + } elseif ($action === 'update') { + $entryIdToUpdate = $row['oris_entry_id'] ?? null; + + if (empty($entryIdToUpdate) && !empty($comp) && !empty($clubuser)) { + logMessage(" - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS."); + try { + $entriesRes = $service->getEventEntries($comp); + if (is_array($entriesRes)) { + $entries = $entriesRes; + foreach ($entries as $entry) { + if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { + $entryIdToUpdate = $entry['ID']; + logMessage(" - Found ORIS Entry ID $entryIdToUpdate for clubuser $clubuser"); + break; + } + } + } + } catch (Exception $e) { + logMessage(" - Warning: getEventEntries failed: " . $e->getMessage()); + } + } + + if (empty($entryIdToUpdate)) { + // It was never created on ORIS side successfully, so let's fall back to create instead of failing + logMessage(" - Warning: Missing ORIS Entry ID for update. Falling back to create."); + $createDto = new OrisEntryRequestDTO($clubuser, $classId, $si, (bool)$rentSi, $note); + $response = $service->createEntry($createDto); + } else { + $updateDto = new OrisEntryRequestDTO($clubuser, $classId, $si, (bool)$rentSi, $note, $entryIdToUpdate); + $response = $service->updateEntry($updateDto); + } + } elseif ($action === 'delete') { + $entryIdToDelete = $row['oris_entry_id'] ?? null; + + // If we don't have the entry ID locally, we must fetch it from ORIS first + if (empty($entryIdToDelete) && !empty($comp) && !empty($clubuser)) { + logMessage(" - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS."); + try { + $entriesRes = $service->getEventEntries($comp); + if (is_array($entriesRes)) { + $entries = $entriesRes; + foreach ($entries as $entry) { + if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { + $entryIdToDelete = $entry['ID']; + logMessage(" - Found ORIS Entry ID $entryIdToDelete for clubuser $clubuser"); + break; + } + } + } + } catch (Exception $e) { + logMessage(" - Warning: getEventEntries failed: " . $e->getMessage()); + } + } + + if (empty($entryIdToDelete)) { + // Already doesn't exist remotely or could not be found + logMessage(" - Warning: Could not find ORIS Entry ID to delete. Assuming already deleted."); + $response = ['ID' => null]; + } else { + $response = $service->deleteEntry($entryIdToDelete); + } + } + + // Log the exact raw payload sent to ORIS if available (custom debug info) + if (isset($response['request'])) { + logMessage(" - Raw POST Data: " . $response['request']); + } + + // Action succeeded since no exception was thrown + // 'create' usually returns ID in $response['ID'] + // 'update' might not return the ID, so we use the one we just found/used + $entryId = $response['ID'] ?? null; + if (empty($entryId)) { + if ($action === 'update' && !empty($entryIdToUpdate)) { + $entryId = $entryIdToUpdate; + } elseif ($action === 'update' && !empty($existingEntryId)) { // from the create fallback block + $entryId = $existingEntryId; + } else { + $entryId = $row['oris_entry_id']; + } + } + + logMessage(" - Success: Action $action completed. ORIS Entry ID: " . ($entryId ?: "N/A")); + + if ($action === 'delete') { + // Remove the row locally after successful delete + $updateQuery = "DELETE FROM `" . TBL_ZAVXUS . "` WHERE `id` = " . (int)$id; + query_db($updateQuery); + } else { + // Update the row + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` + SET `sync_status` = 'SYNCED', + `oris_entry_id` = " . ($entryId ? (int)$entryId : "NULL") . ", + `sync_timestamp` = NOW(), + `sync_error_payload` = NULL + WHERE `id` = " . (int)$id; + query_db($updateQuery); + } + return true; + + } catch (Exception $e) { + $errMsg = $e->getMessage(); + logMessage(" - Failed: Action $action encountered an error: $errMsg"); + + if ($e instanceof OrisNetworkException || ($e instanceof OrisApiException && strpos($errMsg, 'API Error or HTTP 5') !== false)) { + // It's a temporary network error or 5xx server error, keep it as PENDING and retry later + logMessage(" - Will keep entry in PENDING status for retry later because it is a network error."); + return 'queued'; + } + + $errorData = [ + 'status' => 'error', + 'message' => $errMsg, + 'api_status' => ($e instanceof OrisApiException) ? $e->getApiStatus() : 'Exception' + ]; + $errorPayload = correct_sql_string(json_encode($errorData)); + $failedStatus = 'FAILED_' . strtoupper($action); + $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` + SET `sync_status` = '$failedStatus', + `sync_error_payload` = '$errorPayload' + WHERE `id` = " . (int)$id; + query_db($updateQuery); + return false; + } +} + +function getOrisSyncError($id) { + $q = query_db("SELECT sync_error_payload FROM `" . TBL_ZAVXUS . "` WHERE `id` = " . (int)$id); + if ($q && $r = mysqli_fetch_assoc($q)) { + if (!empty($r['sync_error_payload'])) { + $err = json_decode($r['sync_error_payload'], true); + return $err['message'] ?? 'Neznámá chyba'; + } + } + return 'Neznámá chyba'; +} +?> diff --git a/nav.inc.php b/nav.inc.php index bc1bf00..8b70556 100644 --- a/nav.inc.php +++ b/nav.inc.php @@ -101,6 +101,7 @@ function DrawMenuGroupHeader($name) DrawMenuItem('Přihlášky na závody',_REGISTRATOR_GROUP_ID_,1); DrawMenuItem('Editace závodů',_REGISTRATOR_GROUP_ID_,4); DrawMenuItemStatic('Synchronizuj přihlášky s ORISem', 'adm_oris_sync.php'); + DrawMenuItemStatic('ORIS Sync Log', 'rg_oris_sync_log.php'); } if(IsLoggedManager()) { diff --git a/oris_sync_daemon.php b/oris_sync_daemon.php index 5e751db..1552854 100644 --- a/oris_sync_daemon.php +++ b/oris_sync_daemon.php @@ -8,6 +8,7 @@ require_once("common.inc.php"); require_once("lib/OrisIntegrationService.php"); require_once("oris_user.class.php"); +require_once("lib/oris_sync.inc.php"); // Establish database connection if not already connected if (!isset($GLOBALS['db_conn']) || !$GLOBALS['db_conn']) { @@ -24,14 +25,15 @@ $service = new OrisIntegrationService($g_oris_club_key); -$query = " - SELECT zx.*, r.oris_entry_start + $query = " + SELECT zx.*, r.oris_entry_start, u.si_chip as default_si_chip FROM `" . TBL_ZAVXUS . "` zx - LEFT JOIN `" . TBL_RACE . "` r ON zx.id_zavod = r.id - WHERE zx.sync_status IN ('PENDING_CREATE', 'PENDING_UPDATE', 'PENDING_DELETE', 'FAILED_RETRY') + LEFT JOIN `" . TBL_RACE . "` r ON zx.id_zavod = r.id + LEFT JOIN `" . TBL_USER . "` u ON zx.id_user = u.id + WHERE zx.sync_status IN ('PENDING_CREATE', 'PENDING_UPDATE', 'PENDING_DELETE', 'FAILED_CREATE', 'FAILED_UPDATE', 'FAILED_DELETE') AND (r.datum >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) OR r.datum IS NULL) ORDER BY ISNULL(r.oris_entry_start), r.oris_entry_start ASC -"; + "; $res = query_db($query); while ($row = mysqli_fetch_assoc($res)) { @@ -53,270 +55,16 @@ } $action = ''; - if ($row['sync_status'] === 'PENDING_CREATE') { + if ($row['sync_status'] === 'PENDING_CREATE' || $row['sync_status'] === 'FAILED_CREATE') { $action = 'create'; - } elseif ($row['sync_status'] === 'PENDING_UPDATE') { + } elseif ($row['sync_status'] === 'PENDING_UPDATE' || $row['sync_status'] === 'FAILED_UPDATE') { $action = 'update'; - } elseif ($row['sync_status'] === 'PENDING_DELETE') { + } elseif ($row['sync_status'] === 'PENDING_DELETE' || $row['sync_status'] === 'FAILED_DELETE') { $action = 'delete'; - } elseif ($row['sync_status'] === 'FAILED_RETRY') { - // We will just let it be an 'update' if there's an ID, or 'create_or_update' - // if there's no ID so processEntry can check ORIS first. - $action = empty($row['oris_entry_id']) ? 'create' : 'update'; - // Wait, instead of setting it to 'create' which assumes it doesn't exist, - // if we set it to 'update', the new logic in processEntry will fetch from ORIS. - // If it's not found on ORIS, it falls back to 'create' anyway! - $action = 'update'; } if (!empty($action)) { processEntry($row, $action, $service); } } - -function logMessage($msg) { - $date = date('Y-m-d H:i:s'); - $line = "[$date] $msg\n"; - // Output to console when run via CLI - echo $line; - // Log to file in the same directory - file_put_contents(__DIR__ . '/logs/oris_sync.log', $line, FILE_APPEND); -} - -function processEntry($row, $action, $service) { - global $g_oris_club_key, $g_shortcut; - - $id = $row['id']; - $userId = $row['id_user']; - $raceId = $row['id_zavod']; - - logMessage("Processing $action for entry ID $id (user: $userId, race: $raceId)"); - - // Get ORIS user ID - $userQuery = "SELECT * FROM `" . TBL_USER . "` WHERE `id` = " . (int)$userId; - $userRes = query_db($userQuery); - $userRow = mysqli_fetch_assoc($userRes); - - $clubuser = null; - $rgnum = null; - if ($userRow && isset($userRow['reg'])) { - // Reg number usually only contains the digits, so we prepend the club shortcut (e.g. ZBM) - $rgnum = $userRow['reg']; - // Ensure the club prefix is attached if it isn't already - if (!empty($rgnum) && !preg_match('/^[A-Z]{3}/', $rgnum)) { - $rgnum = $g_shortcut . str_pad($rgnum, 4, '0', STR_PAD_LEFT); - } - - // Use the Oris API to get the internal user ID required for createEntry - if (!empty($rgnum)) { - $userApiRes = $service->getUser($rgnum); - if (isset($userApiRes['Status']) && $userApiRes['Status'] === 'OK' && isset($userApiRes['Data']['ID'])) { - $globalUserId = $userApiRes['Data']['ID']; - - // Now get the club user ID - $clubUsersRes = $service->getClubUsers($globalUserId); - if (isset($clubUsersRes['Status']) && $clubUsersRes['Status'] === 'OK' && isset($clubUsersRes['Data'])) { - // It might be a single object or an array of objects - $clubUsers = is_array($clubUsersRes['Data']) ? $clubUsersRes['Data'] : []; - if (isset($clubUsers['ID'])) { - $clubUsers = [$clubUsers]; - } - - foreach ($clubUsers as $cu) { - if (isset($cu['ID'])) { - $clubuser = $cu['ID']; - break; - } - } - } - - if (empty($clubuser)) { - logMessage(" - Warning: Could not resolve ORIS Club User ID for user ID $globalUserId"); - } - } else { - logMessage(" - Warning: Could not resolve ORIS User ID for rgnum $rgnum"); - } - } - } - - // Get Race ORIS ID - $raceQuery = "SELECT * FROM `" . TBL_RACE . "` WHERE `id` = " . (int)$raceId; - $raceRes = query_db($raceQuery); - $raceRow = mysqli_fetch_assoc($raceRes); - - // Ensure the race was imported from ORIS - $comp = $raceRow['ext_id'] ?? null; - if (empty($comp)) { - logMessage(" - Skipped: Race $raceId is not imported from ORIS (no ext_id). Marking LOCAL_ONLY."); - $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` SET `sync_status` = 'LOCAL_ONLY' WHERE `id` = " . (int)$id; - query_db($updateQuery); - return; - } - - // Ignore relays and sprint relays - // S is typically for štafety (relays) in local DB - if ($raceRow && isset($raceRow['typ0']) && $raceRow['typ0'] === 'S') { - logMessage(" - Skipped: Race $raceId is a relay (typ0 = S). Marking LOCAL_ONLY."); - $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` SET `sync_status` = 'LOCAL_ONLY' WHERE `id` = " . (int)$id; - query_db($updateQuery); - return; - } - - // Fetch Classes from ORIS Event to map the string category (e.g. H21) to the ORIS class ID - $classId = null; - $katName = $row['kat']; - if (!empty($comp)) { - $eventRes = $service->getEvent($comp); - if (isset($eventRes['Status']) && $eventRes['Status'] === 'OK' && isset($eventRes['Data']['Classes'])) { - $classes = is_array($eventRes['Data']['Classes']) ? $eventRes['Data']['Classes'] : []; - - foreach ($classes as $cls) { - if (!is_array($cls)) continue; - $clsName = $cls['Name'] ?? ''; - // Some categories might have different spacing or case - if (trim($clsName) === trim($katName)) { - $classId = $cls['ID'] ?? null; - break; - } - } - } - } - - if (empty($classId)) { - logMessage(" - Warning: Could not resolve ORIS Class ID for category '$katName'"); - // Fallback to sending the string, which will likely be rejected by ORIS but preserves behavior - $classId = $katName; - } - - $si = $row['si_chip']; - $rentSi = $row['rent_si'] ?? 0; // assuming rent_si is added or mapped - $note = $row['pozn'] ?? ''; - - $response = []; - logMessage(" - Sending $action request to ORIS API. clubuser: $clubuser, class: $classId (mapped from $katName), si: $si"); - - if ($action === 'create') { - // Check if it already exists before creating to avoid "Již přihlášen" - $existingEntryId = null; - if (!empty($comp) && !empty($clubuser)) { - $entriesRes = $service->getEventEntries($comp); - if (isset($entriesRes['Status']) && $entriesRes['Status'] === 'OK' && isset($entriesRes['Data'])) { - $entries = is_array($entriesRes['Data']) ? $entriesRes['Data'] : []; - foreach ($entries as $entry) { - if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { - $existingEntryId = $entry['ID']; - logMessage(" - Found existing ORIS Entry ID $existingEntryId for clubuser $clubuser during create. Switching to update."); - break; - } - } - } - } - - if (!empty($existingEntryId)) { - $response = $service->updateEntry($existingEntryId, $clubuser, $classId, $si, $rentSi, $note); - // Also update the action string so log messages below make sense - $action = 'update'; - } else { - $response = $service->createEntry($clubuser, $classId, $si, $rentSi, $note); - } - } elseif ($action === 'update') { - $entryIdToUpdate = $row['oris_entry_id'] ?? null; - - if (empty($entryIdToUpdate) && !empty($comp) && !empty($clubuser)) { - logMessage(" - Warning: Missing ORIS Entry ID for update. Attempting to fetch from ORIS."); - $entriesRes = $service->getEventEntries($comp); - if (isset($entriesRes['Status']) && $entriesRes['Status'] === 'OK' && isset($entriesRes['Data'])) { - $entries = is_array($entriesRes['Data']) ? $entriesRes['Data'] : []; - foreach ($entries as $entry) { - if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { - $entryIdToUpdate = $entry['ID']; - logMessage(" - Found ORIS Entry ID $entryIdToUpdate for clubuser $clubuser"); - break; - } - } - } - } - - if (empty($entryIdToUpdate)) { - // It was never created on ORIS side successfully, so let's fall back to create instead of failing - logMessage(" - Warning: Missing ORIS Entry ID for update. Falling back to create."); - $response = $service->createEntry($clubuser, $classId, $si, $rentSi, $note); - } else { - $response = $service->updateEntry($entryIdToUpdate, $clubuser, $classId, $si, $rentSi, $note); - } - } elseif ($action === 'delete') { - $entryIdToDelete = $row['oris_entry_id'] ?? null; - - // If we don't have the entry ID locally, we must fetch it from ORIS first - if (empty($entryIdToDelete) && !empty($comp) && !empty($clubuser)) { - logMessage(" - Warning: Missing ORIS Entry ID for delete. Attempting to fetch from ORIS."); - $entriesRes = $service->getEventEntries($comp); - if (isset($entriesRes['Status']) && $entriesRes['Status'] === 'OK' && isset($entriesRes['Data'])) { - $entries = is_array($entriesRes['Data']) ? $entriesRes['Data'] : []; - foreach ($entries as $entry) { - if (isset($entry['ClubUserID']) && $entry['ClubUserID'] == $clubuser) { - $entryIdToDelete = $entry['ID']; - logMessage(" - Found ORIS Entry ID $entryIdToDelete for clubuser $clubuser"); - break; - } - } - } - } - - if (empty($entryIdToDelete)) { - // Already doesn't exist remotely or could not be found - logMessage(" - Warning: Could not find ORIS Entry ID to delete. Assuming already deleted."); - $response = ['status' => 'success', 'data' => []]; - } else { - $response = $service->deleteEntry($entryIdToDelete); - } - } - - // Log the exact raw payload sent to ORIS - if (isset($response['request'])) { - logMessage(" - Raw POST Data: " . $response['request']); - } - - if ($response['status'] === 'success') { - // 'create' usually returns ID in $response['data']['ID'] - // 'update' might not return the ID, so we use the one we just found/used - $entryId = $response['data']['ID'] ?? null; - if (empty($entryId)) { - if ($action === 'update' && !empty($entryIdToUpdate)) { - $entryId = $entryIdToUpdate; - } elseif ($action === 'update' && !empty($existingEntryId)) { // from the create fallback block - $entryId = $existingEntryId; - } else { - $entryId = $row['oris_entry_id']; - } - } - - logMessage(" - Success: Action $action completed. ORIS Entry ID: $entryId"); - - if ($action === 'delete') { - // Remove the row locally after successful delete - $updateQuery = "DELETE FROM `" . TBL_ZAVXUS . "` WHERE `id` = " . (int)$id; - query_db($updateQuery); - } else { - // Update the row - $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` - SET `sync_status` = 'SYNCED', - `oris_entry_id` = " . ($entryId ? (int)$entryId : "NULL") . ", - `sync_timestamp` = NOW(), - `sync_error_payload` = NULL - WHERE `id` = " . (int)$id; - query_db($updateQuery); - } - } else { - $errMsg = $response['message'] ?? 'Unknown error'; - logMessage(" - Failed: Action $action encountered an error: $errMsg"); - - $errorPayload = correct_sql_string(json_encode($response)); - $updateQuery = "UPDATE `" . TBL_ZAVXUS . "` - SET `sync_status` = 'FAILED_RETRY', - `sync_error_payload` = '$errorPayload' - WHERE `id` = " . (int)$id; - query_db($updateQuery); - } -} -?> \ No newline at end of file +?> diff --git a/race_reg_chip_exc.php b/race_reg_chip_exc.php index 42faf99..7af011e 100644 --- a/race_reg_chip_exc.php +++ b/race_reg_chip_exc.php @@ -5,6 +5,9 @@ require_once ("./connect.inc.php"); require_once ("./sess.inc.php"); +require_once ("./lib/oris_sync.inc.php"); +require_once ("./common.inc.php"); +require_once ("./common_race.inc.php"); if (!IsLoggedRegistrator()) { @@ -16,7 +19,12 @@ db_Connect(); -$query = 'SELECT z.id_user, u.si_chip FROM '.TBL_ZAVXUS.' as z, '.TBL_USER.' as u WHERE z.id_user = u.id AND z.id_zavod='.$id_zav.' AND u.hidden = 0'; +@$vysledek_z=query_db("SELECT ext_id FROM ".TBL_RACE." WHERE id=$id_zav"); +$zaznam_z = mysqli_fetch_array($vysledek_z); +$has_ext_id = !empty($zaznam_z['ext_id']); +$sync_queue = []; + +$query = 'SELECT z.id as z_id, z.id_user, z.sync_status, z.si_chip as z_chip, u.si_chip FROM '.TBL_ZAVXUS.' as z, '.TBL_USER.' as u WHERE z.id_user = u.id AND z.id_zavod='.$id_zav.' AND u.hidden = 0'; @$vysledek=query_db($query); @@ -28,12 +36,39 @@ if (IsSet($chip[$user])) { $si_chip = (int)$chip[$user]; - if ($si_chip != $zaznam['si_chip']) + $old_chip = (int)($zaznam['z_chip'] ? $zaznam['z_chip'] : $zaznam['si_chip']); + if ($si_chip != $old_chip) { - $result=query_db('UPDATE '.TBL_ZAVXUS.' SET `si_chip`= '.$si_chip.' WHERE `id_zavod` = '.$id_zav.' AND `id_user` = '.$user) + $sync_status_update = ""; + if ($has_ext_id && $zaznam['sync_status'] !== 'PENDING_CREATE') { + $sync_status_update = ", `sync_status`='PENDING_UPDATE'"; + } + $result=query_db('UPDATE '.TBL_ZAVXUS.' SET `si_chip`= '.$si_chip.$sync_status_update.' WHERE `id_zavod` = '.$id_zav.' AND `id_user` = '.$user) or die("Chyba při provádění dotazu do databáze."); if ($result == FALSE) die ("Nepodařilo se změnit přihlášku člena."); + + if ($has_ext_id) { + $action = ($zaznam['sync_status'] === 'PENDING_CREATE') ? 'create' : 'update'; + $sync_queue[] = ['id' => $zaznam['z_id'], 'action' => $action]; + } + } + } + } +} + +$sync_errors = []; +if ($has_ext_id && count($sync_queue) > 0) { + global $g_oris_club_key; + if (!empty($g_oris_club_key)) { + $service = new OrisIntegrationService($g_oris_club_key); + foreach ($sync_queue as $sq) { + $rowQuery = query_db("SELECT * FROM `" . TBL_ZAVXUS . "` WHERE `id` = " . (int)$sq['id']); + if ($rowQuery && $syncRow = mysqli_fetch_assoc($rowQuery)) { + $syncRes = processEntry($syncRow, $sq['action'], $service); + if ($syncRes !== true && $syncRes !== 'queued') { + $sync_errors[] = "Záznam " . $sq['id'] . ": " . getOrisSyncError($sq['id']); + } } } } @@ -41,6 +76,11 @@ ?> + +
+ Chyby při synchronizaci s ORIS:

+ ', array_map('htmlspecialchars', $sync_errors)); ?> +

+ Zpět na přehled +
+ diff --git a/rg_oris_sync_log.php b/rg_oris_sync_log.php new file mode 100644 index 0000000..56e9189 --- /dev/null +++ b/rg_oris_sync_log.php @@ -0,0 +1,33 @@ +'; +if (file_exists($logFile)) { + $content = file_get_contents($logFile); + if ($content !== false) { + echo nl2br(htmlspecialchars($content)); + } else { + echo "Error reading log file."; + } +} else { + echo "Log file does not exist yet."; +} +echo ''; + +echo ''; + +HTML_Footer(); +?> \ No newline at end of file diff --git a/us_race_regoff_exc.php b/us_race_regoff_exc.php index bd409c1..315dc84 100644 --- a/us_race_regoff_exc.php +++ b/us_race_regoff_exc.php @@ -5,6 +5,7 @@ require_once ("./connect.inc.php"); require_once ("./sess.inc.php"); +require_once ("./lib/oris_sync.inc.php"); if (!IsLogged()) { @@ -26,15 +27,50 @@ if (!$entry_lock) { - @$vysledek=query_db("DELETE FROM ".TBL_ZAVXUS." WHERE id_zavod = '$id_zav' AND id_user = '$id_us'"); - if ($vysledek !== false && mysqli_affected_rows($db_conn) > 0) { - query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id_zav'"); + $vysledek_z=query_db("SELECT ext_id FROM ".TBL_RACE." WHERE id='$id_zav'"); + $zaznam_z = mysqli_fetch_array($vysledek_z); + $has_ext_id = !empty($zaznam_z['ext_id']); + + $vysledek_zx=query_db("SELECT id, sync_status FROM ".TBL_ZAVXUS." WHERE id_zavod='$id_zav' AND id_user='$id_us'"); + $zaznam_zx = mysqli_fetch_array($vysledek_zx); + + $sync_error_msg = null; + if ($zaznam_zx) { + $zx_id = $zaznam_zx['id']; + $sync_status = $zaznam_zx['sync_status']; + + if ($has_ext_id && $sync_status !== 'PENDING_CREATE') { + $vysledek = query_db("UPDATE ".TBL_ZAVXUS." SET sync_status='PENDING_DELETE' WHERE id = '$zx_id'"); + if ($vysledek !== false) { + global $g_oris_club_key; + if (!empty($g_oris_club_key)) { + $service = new OrisIntegrationService($g_oris_club_key); + $rowQuery = query_db("SELECT * FROM `" . TBL_ZAVXUS . "` WHERE `id` = '$zx_id'"); + if ($rowQuery && $syncRow = mysqli_fetch_assoc($rowQuery)) { + $syncRes = processEntry($syncRow, 'delete', $service); + if ($syncRes === true || $syncRes === 'queued') { + query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id_zav'"); + } else { + $sync_error_msg = getOrisSyncError($zx_id); + } + } + } + } + } else { + @$vysledek=query_db("DELETE FROM ".TBL_ZAVXUS." WHERE id = '$zx_id'"); + if ($vysledek !== false && mysqli_affected_rows($db_conn) > 0) { + query_db("UPDATE ".TBL_RACE." SET prihlasenych = GREATEST(0, prihlasenych - 1) WHERE id = '$id_zav'"); + } + } } } ?> - -
-
-
- -user_id; + $id_zavod = $race_id; + $datum = null; + if ($payment == "pay") + { + foreach ($_REQUEST as $var => $user_id) + { + if (!preg_match('/^userid(.+)$/', $var, $matches)) { + continue; + } + + $i = $matches[1]; + $payment_id = $_REQUEST["paymentid".$i] ?? null; + $amount = $_REQUEST["am".$i] ?? ''; + $note = $_REQUEST["nt".$i] ?? ''; + + if ($amount != "") + { + if ($payment_id) + { + updatePayment($editor_id, $payment_id, $id_zavod, $amount, $note); + } else { + createPayment($editor_id, $user_id, $amount, $note, $datum, $id_zavod); + } + } + + $var = "cat".$i; + if (array_key_exists($var, $_REQUEST)) { + $cat = $_REQUEST[$var]; + $user = new Member($user_id); + $user->updateCategoryOnRace($race_id, $cat); + } + } + } + } catch (Exception $e) { + $_SESSION['flash_message'] = ob_get_clean(); // stops buffering and stores content + $_SESSION['flash_message_type'] = 'error'; + header("Location: ?race_id=" . $race_id . "&status=error&msg=".urlencode($e->getMessage())); + exit; + } + + // capture all buffer content + $_SESSION['flash_message'] = ob_get_clean(); // stops buffering and stores content + $_SESSION['flash_message_type'] = 'success'; + + //Redirect to the same page (GET) → clears POST data + header("Location: ?race_id=" . $race_id . "&status=ok"); + exit; +} + +ob_end_flush(); // at the very start + +if (!isset($head_addons)) $head_addons = ''; +$head_addons .="\t".''."\n"; +require_once ("./header.inc.php"); // header obsahuje uvod html a konci +require_once ("./common.inc.php"); +require_once ("./common_user.inc.php"); +require_once ("./ctable.inc.php"); +DrawPageTitle('Finance závodu'); + +if ( isset($msg) ) { + echo '
Chyba: '.htmlspecialchars($msg).'
'; +} + + +if (!empty($_SESSION['flash_message'])) { + echo $_SESSION['flash_message']; // display buffered output + unset($_SESSION['flash_message']); +} +?> +
+ + +
+
+
+
+ diff --git a/tests/playwright/workflows/oris-mockup-race-flow.spec.js b/tests/playwright/workflows/oris-mockup-race-flow.spec.js index b4c3995..95ffa24 100644 --- a/tests/playwright/workflows/oris-mockup-race-flow.spec.js +++ b/tests/playwright/workflows/oris-mockup-race-flow.spec.js @@ -26,6 +26,11 @@ const { const { createWorkflowRun, } = require('../helpers/workflow-runtime'); +const { + expectFinanceRowValues, + financeRow, + openRaceFinancePopup, +} = require('../helpers/race-finance'); const ORIS_MOCKUP_RACE_WORKFLOW = { name: 'Oris Mockup Race Workflow', @@ -290,4 +295,49 @@ test.describe(ORIS_MOCKUP_RACE_WORKFLOW.name, () => { || entry.RegNo === ORIS_MOCKUP_RACE_WORKFLOW.memberRegNo ))).toBeUndefined(); }); + + test('accountant can create and update a payment for another racer', async ({ page }) => { + await loginAs(page, 'accountant'); + + const financePopup = await openRaceFinancePopup(page, state.race.id); + + try { + const otherRacersForm = financePopup.locator('form', { + has: financePopup.locator('h3', { hasText: 'Ostatní závodníci' }), + }); + const firstMemberRow = otherRacersForm.locator('tr', { + has: financePopup.locator('a.adr_name'), + }).first(); + const memberName = (await firstMemberRow.locator('td').nth(1).innerText()).trim(); + + await firstMemberRow.locator('input[data-col="amount"]').fill('222'); + await firstMemberRow.locator('input[data-col="note"]').fill('Test nová platba'); + + await Promise.all([ + financePopup.waitForURL(new RegExp(`race_finance_view\\.php\\?race_id=${state.race.id}.*status=ok`)), + otherRacersForm.locator('input[type="submit"][value="Vytvořit nové platby"]').click(), + ]); + + const createdPayment = financeRow(financePopup, memberName); + await expectFinanceRowValues(createdPayment, { + amount: '222', + note: 'Test nová platba', + }); + + await createdPayment.amount.fill('234'); + await createdPayment.note.fill('Test změna platby'); + + await Promise.all([ + financePopup.waitForURL(new RegExp(`race_finance_view\\.php\\?race_id=${state.race.id}.*status=ok`)), + financePopup.locator('input[type="submit"][value="Změnit platby"]').click(), + ]); + + await expectFinanceRowValues(financeRow(financePopup, memberName), { + amount: '234', + note: 'Test změna platby', + }); + } finally { + await financePopup.close(); + } + }); }); From b747f19a1071dbba7fec47a28a6bffdec6c68ceb Mon Sep 17 00:00:00 2001 From: arnost00 Date: Sun, 19 Jul 2026 15:02:19 +0200 Subject: [PATCH 16/16] Version bump --- version.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.inc.php b/version.inc.php index f53d33b..0390aa2 100644 --- a/version.inc.php +++ b/version.inc.php @@ -6,7 +6,7 @@ function GetCodeVersion() { - return "v3.5.0.671"; + return "v3.5.0.672"; } function GetCopyright()