From ea717c8abf7d102a2d014582cc885348ef34bb4d Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 3 May 2026 01:46:41 +0000 Subject: [PATCH] Autosave: 20260503-014641 --- db/migrations/20260216_add_credit_limit.sql | 4 +- db/migrations/20260502_full_schema_sync.php | 4 + ...20260503_ensure_entity_balance_columns.php | 65 ++++++++++++++ debug.log | 1 + index.php | 20 ++++- lib/LicenseService.php | 85 ++++++++++++++++++- post_debug.log | 1 + 7 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 db/migrations/20260503_ensure_entity_balance_columns.php diff --git a/db/migrations/20260216_add_credit_limit.sql b/db/migrations/20260216_add_credit_limit.sql index 541ed76..5e1732f 100644 --- a/db/migrations/20260216_add_credit_limit.sql +++ b/db/migrations/20260216_add_credit_limit.sql @@ -1 +1,3 @@ -ALTER TABLE customers ADD COLUMN credit_limit DECIMAL(15,3) DEFAULT 0.000 AFTER balance; +-- Ensure legacy customer installs have the monetary columns before later migrations modify them. +ALTER TABLE customers ADD COLUMN IF NOT EXISTS balance DECIMAL(15,3) DEFAULT 0.000; +ALTER TABLE customers ADD COLUMN IF NOT EXISTS credit_limit DECIMAL(15,3) DEFAULT 0.000; diff --git a/db/migrations/20260502_full_schema_sync.php b/db/migrations/20260502_full_schema_sync.php index e29e67e..1206a7b 100644 --- a/db/migrations/20260502_full_schema_sync.php +++ b/db/migrations/20260502_full_schema_sync.php @@ -76,9 +76,13 @@ if (!function_exists('full_schema_sync_20260502_run')) { ], 'customers' => [ 'outlet_id' => 'INT(11) DEFAULT NULL', + 'balance' => 'DECIMAL(15,3) DEFAULT 0.000', + 'credit_limit' => 'DECIMAL(15,3) DEFAULT 0.000', ], 'suppliers' => [ 'outlet_id' => 'INT(11) DEFAULT 1', + 'balance' => 'DECIMAL(15,3) DEFAULT NULL', + 'credit_limit' => 'DECIMAL(15,3) DEFAULT NULL', ], 'stock_categories' => [ 'outlet_id' => 'INT(11) DEFAULT 1', diff --git a/db/migrations/20260503_ensure_entity_balance_columns.php b/db/migrations/20260503_ensure_entity_balance_columns.php new file mode 100644 index 0000000..d906c8a --- /dev/null +++ b/db/migrations/20260503_ensure_entity_balance_columns.php @@ -0,0 +1,65 @@ + [ + 'balance' => 'DECIMAL(15,3) DEFAULT 0.000', + 'credit_limit' => 'DECIMAL(15,3) DEFAULT 0.000', + ], + 'suppliers' => [ + 'balance' => 'DECIMAL(15,3) DEFAULT NULL', + 'credit_limit' => 'DECIMAL(15,3) DEFAULT NULL', + ], + ]; + + foreach ($definitions as $table => $columns) { + if (!ensure_entity_balance_columns_20260503_table_exists($pdo, $table)) { + continue; + } + + foreach ($columns as $column => $definition) { + if (ensure_entity_balance_columns_20260503_column_exists($pdo, $table, $column)) { + continue; + } + + $statement = sprintf( + 'ALTER TABLE `%s` ADD COLUMN `%s` %s', + str_replace('`', '', $table), + str_replace('`', '', $column), + $definition + ); + + $pdo->exec($statement); + } + } + } + + function ensure_entity_balance_columns_20260503_table_exists(PDO $pdo, string $table): bool + { + $stmt = $pdo->prepare( + 'SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1' + ); + $stmt->execute([$table]); + + return (bool) $stmt->fetchColumn(); + } + + function ensure_entity_balance_columns_20260503_column_exists(PDO $pdo, string $table, string $column): bool + { + $stmt = $pdo->prepare( + 'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1' + ); + $stmt->execute([$table, $column]); + + return (bool) $stmt->fetchColumn(); + } +} + +ensure_entity_balance_columns_20260503_run(); diff --git a/debug.log b/debug.log index f0c1e6a..06a519f 100644 --- a/debug.log +++ b/debug.log @@ -135,3 +135,4 @@ 2026-05-02 17:32:11 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true} 2026-05-02 17:32:54 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true} 2026-05-02 18:41:44 - Items case hit +2026-05-03 01:36:24 - Items case hit diff --git a/index.php b/index.php index 3a1e161..a6e3cf7 100644 --- a/index.php +++ b/index.php @@ -4257,6 +4257,7 @@ switch ($page) { $data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll(); break; case 'expenses': + $data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll(); $where = ["1=1"]; $params = []; if (!empty($_GET['category_id'])) { @@ -8612,14 +8613,25 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; +
Expenses List
-
+ + + +
@@ -8627,7 +8639,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; @@ -8698,7 +8710,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
@@ -8752,7 +8764,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; diff --git a/lib/LicenseService.php b/lib/LicenseService.php index a6c2b73..5f3615c 100644 --- a/lib/LicenseService.php +++ b/lib/LicenseService.php @@ -18,14 +18,14 @@ class LicenseService { return rtrim((string)$url, '/'); } - private static function detectLocalApiUrl() { + private static function getCurrentRequestBaseParts() { if (PHP_SAPI === 'cli') { - return ''; + return ['', '', '']; } $host = trim((string)($_SERVER['HTTP_HOST'] ?? '')); if ($host === '') { - return ''; + return ['', '', '']; } $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') @@ -38,6 +38,78 @@ class LicenseService { $baseDir = ''; } + return [$scheme, $host, $baseDir]; + } + + private static function buildLocalApiCandidates() { + [$scheme, $host, $baseDir] = self::getCurrentRequestBaseParts(); + if ($host === '') { + return []; + } + + $prefixes = []; + if ($baseDir !== '') { + $prefixes[] = $baseDir; + } + $prefixes[] = ''; + + $suffixes = ['/central_license_manager', '/key', '/keys']; + $candidates = []; + + foreach ($prefixes as $prefix) { + foreach ($suffixes as $suffix) { + $candidate = self::normalizeApiBaseUrl($scheme . '://' . $host . $prefix . $suffix); + if ($candidate !== '') { + $candidates[$candidate] = true; + } + } + } + + return array_keys($candidates); + } + + private static function isLocalHostUrl($url) { + $host = strtolower((string)(parse_url((string)$url, PHP_URL_HOST) ?: '')); + return in_array($host, ['localhost', '127.0.0.1', '::1'], true); + } + + private static function urlLooksLikeHealthyLicenseApi($baseUrl) { + $baseUrl = self::normalizeApiBaseUrl($baseUrl); + if ($baseUrl === '') { + return false; + } + + $url = rtrim($baseUrl, '/') . '/index.php?action=health'; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 3); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + $resp = curl_exec($ch); + $http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($resp === false || $http_code < 200 || $http_code >= 300) { + return false; + } + + $data = json_decode($resp, true); + return is_array($data) + && !empty($data['success']) + && stripos((string)($data['manager'] ?? ''), 'license') !== false; + } + + private static function detectLocalApiUrl() { + foreach (self::buildLocalApiCandidates() as $candidate) { + if (self::urlLooksLikeHealthyLicenseApi($candidate)) { + return $candidate; + } + } + + [$scheme, $host, $baseDir] = self::getCurrentRequestBaseParts(); + if ($host === '') { + return ''; + } + return self::normalizeApiBaseUrl($scheme . '://' . $host . $baseDir . '/central_license_manager'); } @@ -48,7 +120,12 @@ class LicenseService { $configured = self::normalizeApiBaseUrl(getenv('LICENSE_API_URL') ?: ''); if ($configured !== '') { - self::$remote_api_url = $configured; + if (self::isLocalHostUrl($configured) && !self::urlLooksLikeHealthyLicenseApi($configured)) { + $detectedLocalUrl = self::detectLocalApiUrl(); + self::$remote_api_url = $detectedLocalUrl !== '' ? $detectedLocalUrl : $configured; + } else { + self::$remote_api_url = $configured; + } return self::$remote_api_url; } diff --git a/post_debug.log b/post_debug.log index 8565461..24bd48b 100644 --- a/post_debug.log +++ b/post_debug.log @@ -186,3 +186,4 @@ 2026-05-02 17:32:54 - POST: {"action":"translate","text":"onion","target":"ar"} 2026-05-02 18:40:57 - POST: {"name":"\u0645\u062d\u0627\u0633\u0628 1","add_cash_register":""} 2026-05-02 18:41:32 - POST: {"id":"1","name":"\u0627\u0644\u0641\u0631\u0639 \u0627\u0644\u0631\u0626\u064a\u0633\u064a","phone":"","address":"Head Office","status":"active","edit_outlet":""} +2026-05-02 19:20:30 - POST: {"license_key":"BACC-F7B0-A44F-2AC2","activate":"1"}