From 0b18433fdf0fddd1d7e27fcacf4ec9ba396ee8e5 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 15:07:41 +0200 Subject: [PATCH] Add unit tests for EconomicCustomerModel parsing and validation --- .../nginx/app/build/logs/api-server.err.log | 716 ++++++++++++++++++ services/nginx/app/config.php | 2 +- .../customers/economic_customer_mo.php | 44 +- .../objects/collected_order_invoices_o.php | 15 +- services/nginx/app/objects/users_o.php | 32 +- .../tests/Support/Api/ApiSchemaBootstrap.php | 11 + .../nginx/app/tests/Support/Api/ApiServer.php | 61 +- .../app/tests/Support/Api/ApiTestRuntime.php | 7 +- .../EconomicCustomerModelParsingTest.php | 47 ++ services/nginx/app/traits/minio_t.php | 102 ++- .../app/traits/module_config_variable_t.php | 59 +- 11 files changed, 1070 insertions(+), 26 deletions(-) create mode 100644 services/nginx/app/tests/Unit/Users/EconomicCustomerModelParsingTest.php diff --git a/services/nginx/app/build/logs/api-server.err.log b/services/nginx/app/build/logs/api-server.err.log index 569fad1f..de37266e 100644 --- a/services/nginx/app/build/logs/api-server.err.log +++ b/services/nginx/app/build/logs/api-server.err.log @@ -9849,3 +9849,719 @@ [Tue Apr 21 10:21:42 2026] 127.0.0.1:40134 Closing [Tue Apr 21 10:21:42 2026] 127.0.0.1:60180 Accepted [Tue Apr 21 10:21:46 2026] 127.0.0.1:60180 Closing +[Tue Apr 21 12:19:03 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:03 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:26 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:26 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:27 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:28 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:28 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:29 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:29 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:29 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:34 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:34 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:36 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:36 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:38 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:38 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:40 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:40 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:50 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:50 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:50 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:51 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:51 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:51 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:52 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:52 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:53 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:53 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:54 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:54 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:54 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:21:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:21 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:21 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:22 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:22 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:22 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:25 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:25 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:23:25 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:03 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:04 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:05 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:05 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:05 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:08 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:25:08 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:28:04 2026] PHP 8.2.30 Development Server (http://127.0.0.1:18083) started +[Tue Apr 21 12:28:04 2026] 127.0.0.1:36832 Accepted +[Tue Apr 21 12:28:06 2026] 127.0.0.1:36832 Closing +[Tue Apr 21 12:28:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:28:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:29:18 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use) +[Tue Apr 21 12:29:38 2026] PHP 8.2.30 Development Server (http://127.0.0.1:18084) started +[Tue Apr 21 12:29:39 2026] 127.0.0.1:49800 Accepted +[Tue Apr 21 12:29:41 2026] 127.0.0.1:49800 Closing +[Tue Apr 21 12:31:10 2026] PHP 8.2.30 Development Server (http://127.0.0.1:33655) started +[Tue Apr 21 12:31:10 2026] 127.0.0.1:37066 Accepted +[Tue Apr 21 12:31:09 2026] 127.0.0.1:37066 Closing +[Tue Apr 21 12:31:09 2026] 127.0.0.1:37070 Accepted +[Tue Apr 21 12:31:10 2026] 127.0.0.1:37070 Closing +[Tue Apr 21 12:31:30 2026] PHP 8.2.30 Development Server (http://127.0.0.1:33509) started +[Tue Apr 21 12:31:30 2026] 127.0.0.1:57784 Accepted +[Tue Apr 21 12:31:31 2026] 127.0.0.1:57784 Closing +[Tue Apr 21 12:31:31 2026] 127.0.0.1:57788 Accepted +[Tue Apr 21 12:31:32 2026] 127.0.0.1:57788 Closing +[Tue Apr 21 12:31:33 2026] 127.0.0.1:59232 Accepted +[Tue Apr 21 12:31:33 2026] 127.0.0.1:59232 Closing +[Tue Apr 21 12:31:33 2026] 127.0.0.1:59246 Accepted +[Tue Apr 21 12:31:34 2026] 127.0.0.1:59246 Closing +[Tue Apr 21 12:31:34 2026] 127.0.0.1:59250 Accepted +[Tue Apr 21 12:31:35 2026] 127.0.0.1:59250 Closing +[Tue Apr 21 12:31:35 2026] 127.0.0.1:59260 Accepted +[Tue Apr 21 12:31:36 2026] 127.0.0.1:59260 Closing +[Tue Apr 21 12:31:36 2026] 127.0.0.1:59266 Accepted +[Tue Apr 21 12:31:37 2026] 127.0.0.1:59266 Closing +[Tue Apr 21 12:31:37 2026] 127.0.0.1:59274 Accepted +[Tue Apr 21 12:31:37 2026] 127.0.0.1:59274 Closing +[Tue Apr 21 12:31:37 2026] 127.0.0.1:59282 Accepted +[Tue Apr 21 12:31:38 2026] 127.0.0.1:59282 Closing +[Tue Apr 21 12:31:38 2026] 127.0.0.1:59292 Accepted +[Tue Apr 21 12:31:36 2026] 127.0.0.1:59292 Closing +[Tue Apr 21 12:31:36 2026] 127.0.0.1:59298 Accepted +[Tue Apr 21 12:31:36 2026] 127.0.0.1:59298 Closing +[Tue Apr 21 12:31:36 2026] 127.0.0.1:59314 Accepted +[Tue Apr 21 12:31:37 2026] 127.0.0.1:59314 Closing +[Tue Apr 21 12:31:37 2026] 127.0.0.1:59316 Accepted +[Tue Apr 21 12:31:38 2026] 127.0.0.1:59316 Closing +[Tue Apr 21 12:32:49 2026] PHP 8.2.30 Development Server (http://127.0.0.1:40003) started +[Tue Apr 21 12:32:50 2026] 127.0.0.1:56156 Accepted +[Tue Apr 21 12:32:51 2026] 127.0.0.1:56156 Closing +[Tue Apr 21 12:32:51 2026] 127.0.0.1:56158 Accepted +[Tue Apr 21 12:32:51 2026] 127.0.0.1:56158 Closing +[Tue Apr 21 12:32:52 2026] 127.0.0.1:56174 Accepted +[Tue Apr 21 12:32:52 2026] 127.0.0.1:56174 Closing +[Tue Apr 21 12:32:52 2026] 127.0.0.1:56178 Accepted +[Tue Apr 21 12:32:53 2026] 127.0.0.1:56178 Closing +[Tue Apr 21 12:32:53 2026] 127.0.0.1:56192 Accepted +[Tue Apr 21 12:32:53 2026] 127.0.0.1:56192 Closing +[Tue Apr 21 12:32:54 2026] 127.0.0.1:56196 Accepted +[Tue Apr 21 12:32:54 2026] 127.0.0.1:56196 Closing +[Tue Apr 21 12:32:55 2026] 127.0.0.1:59960 Accepted +[Tue Apr 21 12:32:55 2026] 127.0.0.1:59960 Closing +[Tue Apr 21 12:32:55 2026] 127.0.0.1:59964 Accepted +[Tue Apr 21 12:32:56 2026] 127.0.0.1:59964 Closing +[Tue Apr 21 12:32:56 2026] 127.0.0.1:59972 Accepted +[Tue Apr 21 12:32:56 2026] 127.0.0.1:59972 Closing +[Tue Apr 21 12:32:56 2026] 127.0.0.1:59982 Accepted +[Tue Apr 21 12:32:56 2026] 127.0.0.1:59982 Closing +[Tue Apr 21 12:32:56 2026] 127.0.0.1:59984 Accepted +[Tue Apr 21 12:32:57 2026] 127.0.0.1:59984 Closing +[Tue Apr 21 12:32:57 2026] 127.0.0.1:59994 Accepted +[Tue Apr 21 12:32:57 2026] 127.0.0.1:59994 Closing +[Tue Apr 21 12:32:57 2026] 127.0.0.1:60002 Accepted +[Tue Apr 21 12:32:58 2026] 127.0.0.1:60002 Closing +[Tue Apr 21 12:34:41 2026] PHP 8.2.30 Development Server (http://127.0.0.1:37999) started +[Tue Apr 21 12:34:41 2026] 127.0.0.1:42608 Accepted +[Tue Apr 21 12:34:42 2026] 127.0.0.1:42608 Closing +[Tue Apr 21 12:34:42 2026] 127.0.0.1:42610 Accepted +[Tue Apr 21 12:34:43 2026] 127.0.0.1:42610 Closing +[Tue Apr 21 12:34:43 2026] 127.0.0.1:42626 Accepted +[Tue Apr 21 12:34:44 2026] 127.0.0.1:42626 Closing +[Tue Apr 21 12:34:44 2026] 127.0.0.1:59522 Accepted +[Tue Apr 21 12:34:44 2026] 127.0.0.1:59522 Closing +[Tue Apr 21 12:34:44 2026] 127.0.0.1:59532 Accepted +[Tue Apr 21 12:34:45 2026] 127.0.0.1:59532 Closing +[Tue Apr 21 12:34:45 2026] 127.0.0.1:59544 Accepted +[Tue Apr 21 12:34:46 2026] 127.0.0.1:59544 Closing +[Tue Apr 21 12:34:46 2026] 127.0.0.1:59554 Accepted +[Tue Apr 21 12:34:47 2026] 127.0.0.1:59554 Closing +[Tue Apr 21 12:34:47 2026] 127.0.0.1:59568 Accepted +[Tue Apr 21 12:34:48 2026] 127.0.0.1:59568 Closing +[Tue Apr 21 12:34:48 2026] 127.0.0.1:59582 Accepted +[Tue Apr 21 12:34:49 2026] 127.0.0.1:59582 Closing +[Tue Apr 21 12:34:49 2026] 127.0.0.1:59594 Accepted +[Tue Apr 21 12:34:47 2026] 127.0.0.1:59594 Closing +[Tue Apr 21 12:34:47 2026] 127.0.0.1:59602 Accepted +[Tue Apr 21 12:34:48 2026] 127.0.0.1:59602 Closing +[Tue Apr 21 12:34:48 2026] 127.0.0.1:59608 Accepted +[Tue Apr 21 12:34:48 2026] 127.0.0.1:59608 Closing +[Tue Apr 21 12:34:48 2026] 127.0.0.1:59622 Accepted +[Tue Apr 21 12:34:49 2026] 127.0.0.1:59622 Closing +[Tue Apr 21 12:38:02 2026] PHP 8.2.30 Development Server (http://127.0.0.1:35141) started +[Tue Apr 21 12:38:02 2026] 127.0.0.1:56092 Accepted +[Tue Apr 21 12:38:03 2026] 127.0.0.1:56092 Closing +[Tue Apr 21 12:38:03 2026] 127.0.0.1:56094 Accepted +[Tue Apr 21 12:38:04 2026] 127.0.0.1:56094 Closing +[Tue Apr 21 12:38:05 2026] 127.0.0.1:56108 Accepted +[Tue Apr 21 12:38:05 2026] 127.0.0.1:56108 Closing +[Tue Apr 21 12:38:05 2026] 127.0.0.1:56118 Accepted +[Tue Apr 21 12:38:06 2026] 127.0.0.1:56118 Closing +[Tue Apr 21 12:38:06 2026] 127.0.0.1:56134 Accepted +[Tue Apr 21 12:38:06 2026] 127.0.0.1:56134 Closing +[Tue Apr 21 12:38:07 2026] 127.0.0.1:56136 Accepted +[Tue Apr 21 12:38:07 2026] 127.0.0.1:56136 Closing +[Tue Apr 21 12:38:07 2026] 127.0.0.1:56144 Accepted +[Tue Apr 21 12:38:07 2026] 127.0.0.1:56144 Closing +[Tue Apr 21 12:38:07 2026] 127.0.0.1:56156 Accepted +[Tue Apr 21 12:38:08 2026] 127.0.0.1:56156 Closing +[Tue Apr 21 12:38:08 2026] 127.0.0.1:56166 Accepted +[Tue Apr 21 12:38:08 2026] 127.0.0.1:56166 Closing +[Tue Apr 21 12:38:08 2026] 127.0.0.1:56172 Accepted +[Tue Apr 21 12:38:09 2026] 127.0.0.1:56172 Closing +[Tue Apr 21 12:38:09 2026] 127.0.0.1:56186 Accepted +[Tue Apr 21 12:38:09 2026] 127.0.0.1:56186 Closing +[Tue Apr 21 12:38:09 2026] 127.0.0.1:56188 Accepted +[Tue Apr 21 12:38:09 2026] 127.0.0.1:56188 Closing +[Tue Apr 21 12:38:09 2026] 127.0.0.1:56196 Accepted +[Tue Apr 21 12:38:10 2026] 127.0.0.1:56196 Closing +[Tue Apr 21 12:39:00 2026] PHP 8.2.30 Development Server (http://127.0.0.1:40709) started +[Tue Apr 21 12:39:00 2026] 127.0.0.1:52258 Accepted +[Tue Apr 21 12:39:01 2026] 127.0.0.1:52258 Closing +[Tue Apr 21 12:39:01 2026] 127.0.0.1:52268 Accepted +[Tue Apr 21 12:39:01 2026] 127.0.0.1:52268 Closing +[Tue Apr 21 12:39:01 2026] 127.0.0.1:52270 Accepted +[Tue Apr 21 12:39:01 2026] 127.0.0.1:52270 Closing +[Tue Apr 21 12:39:01 2026] 127.0.0.1:52286 Accepted +[Tue Apr 21 12:39:02 2026] 127.0.0.1:52286 Closing +[Tue Apr 21 12:39:02 2026] 127.0.0.1:52290 Accepted +[Tue Apr 21 12:39:02 2026] 127.0.0.1:52290 Closing +[Tue Apr 21 12:39:02 2026] 127.0.0.1:52298 Accepted +[Tue Apr 21 12:39:03 2026] 127.0.0.1:52298 Closing +[Tue Apr 21 12:39:03 2026] 127.0.0.1:52300 Accepted +[Tue Apr 21 12:39:03 2026] 127.0.0.1:52300 Closing +[Tue Apr 21 12:39:03 2026] 127.0.0.1:52312 Accepted +[Tue Apr 21 12:39:03 2026] 127.0.0.1:52312 Closing +[Tue Apr 21 12:39:03 2026] 127.0.0.1:52324 Accepted +[Tue Apr 21 12:39:04 2026] 127.0.0.1:52324 Closing +[Tue Apr 21 12:39:04 2026] 127.0.0.1:52326 Accepted +[Tue Apr 21 12:39:04 2026] 127.0.0.1:52326 Closing +[Tue Apr 21 12:39:04 2026] 127.0.0.1:52328 Accepted +[Tue Apr 21 12:39:05 2026] 127.0.0.1:52328 Closing +[Tue Apr 21 12:39:05 2026] 127.0.0.1:52330 Accepted +[Tue Apr 21 12:39:06 2026] 127.0.0.1:52330 Closing +[Tue Apr 21 12:39:06 2026] 127.0.0.1:52346 Accepted +[Tue Apr 21 12:39:06 2026] 127.0.0.1:52346 Closing +[Tue Apr 21 12:39:06 2026] 127.0.0.1:49132 Accepted +[Tue Apr 21 12:39:07 2026] 127.0.0.1:49132 Closing +[Tue Apr 21 12:39:07 2026] 127.0.0.1:49142 Accepted +[Tue Apr 21 12:39:07 2026] 127.0.0.1:49142 Closing +[Tue Apr 21 12:39:07 2026] 127.0.0.1:49150 Accepted +[Tue Apr 21 12:39:08 2026] 127.0.0.1:49150 Closing +[Tue Apr 21 12:39:08 2026] 127.0.0.1:49160 Accepted +[Tue Apr 21 12:39:08 2026] 127.0.0.1:49160 Closing +[Tue Apr 21 12:39:08 2026] 127.0.0.1:49176 Accepted +[Tue Apr 21 12:39:09 2026] 127.0.0.1:49176 Closing +[Tue Apr 21 12:39:09 2026] 127.0.0.1:49182 Accepted +[Tue Apr 21 12:39:10 2026] 127.0.0.1:49182 Closing +[Tue Apr 21 12:39:10 2026] 127.0.0.1:49194 Accepted +[Tue Apr 21 12:39:11 2026] 127.0.0.1:49194 Closing +[Tue Apr 21 12:39:11 2026] 127.0.0.1:49196 Accepted +[Tue Apr 21 12:39:11 2026] 127.0.0.1:49196 Closing +[Tue Apr 21 12:39:11 2026] 127.0.0.1:49210 Accepted +[Tue Apr 21 12:39:12 2026] 127.0.0.1:49210 Closing +[Tue Apr 21 12:39:12 2026] 127.0.0.1:49216 Accepted +[Tue Apr 21 12:39:13 2026] 127.0.0.1:49216 Closing +[Tue Apr 21 12:39:13 2026] 127.0.0.1:49232 Accepted +[Tue Apr 21 12:39:13 2026] 127.0.0.1:49232 Closing +[Tue Apr 21 12:39:13 2026] 127.0.0.1:49234 Accepted +[Tue Apr 21 12:39:14 2026] 127.0.0.1:49234 Closing +[Tue Apr 21 12:39:14 2026] 127.0.0.1:49238 Accepted +[Tue Apr 21 12:39:14 2026] 127.0.0.1:49238 Closing +[Tue Apr 21 12:39:14 2026] 127.0.0.1:49252 Accepted +[Tue Apr 21 12:39:15 2026] 127.0.0.1:49252 Closing +[Tue Apr 21 12:39:15 2026] 127.0.0.1:49262 Accepted +[Tue Apr 21 12:39:15 2026] 127.0.0.1:49262 Closing +[Tue Apr 21 12:39:15 2026] 127.0.0.1:49266 Accepted +[Tue Apr 21 12:39:16 2026] 127.0.0.1:49266 Closing +[Tue Apr 21 12:39:16 2026] 127.0.0.1:49278 Accepted +[Tue Apr 21 12:39:16 2026] 127.0.0.1:49278 Closing +[Tue Apr 21 12:39:16 2026] 127.0.0.1:49280 Accepted +[Tue Apr 21 12:39:16 2026] 127.0.0.1:49280 Closing +[Tue Apr 21 12:39:17 2026] 127.0.0.1:44144 Accepted +[Tue Apr 21 12:39:17 2026] 127.0.0.1:44144 Closing +[Tue Apr 21 12:39:17 2026] 127.0.0.1:44160 Accepted +[Tue Apr 21 12:39:18 2026] 127.0.0.1:44160 Closing +[Tue Apr 21 12:39:18 2026] 127.0.0.1:44168 Accepted +[Tue Apr 21 12:39:18 2026] 127.0.0.1:44168 Closing +[Tue Apr 21 12:39:18 2026] 127.0.0.1:44178 Accepted +[Tue Apr 21 12:39:19 2026] 127.0.0.1:44178 Closing +[Tue Apr 21 12:39:19 2026] 127.0.0.1:44182 Accepted +[Tue Apr 21 12:39:20 2026] 127.0.0.1:44182 Closing +[Tue Apr 21 12:39:20 2026] 127.0.0.1:44190 Accepted +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44190 Closing +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44206 Accepted +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44206 Closing +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44214 Accepted +[Tue Apr 21 12:39:22 2026] 127.0.0.1:44214 Closing +[Tue Apr 21 12:39:22 2026] 127.0.0.1:44220 Accepted +[Tue Apr 21 12:39:20 2026] 127.0.0.1:44220 Closing +[Tue Apr 21 12:39:20 2026] 127.0.0.1:44234 Accepted +[Tue Apr 21 12:39:20 2026] 127.0.0.1:44234 Closing +[Tue Apr 21 12:39:20 2026] 127.0.0.1:44244 Accepted +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44244 Closing +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44260 Accepted +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44260 Closing +[Tue Apr 21 12:39:21 2026] 127.0.0.1:44272 Accepted +[Tue Apr 21 12:39:23 2026] 127.0.0.1:44272 Closing +[Tue Apr 21 12:39:23 2026] 127.0.0.1:44280 Accepted +[Tue Apr 21 12:39:24 2026] 127.0.0.1:44280 Closing +[Tue Apr 21 12:39:24 2026] 127.0.0.1:34634 Accepted +[Tue Apr 21 12:39:24 2026] 127.0.0.1:34634 Closing +[Tue Apr 21 12:39:24 2026] 127.0.0.1:34644 Accepted +[Tue Apr 21 12:39:25 2026] 127.0.0.1:34644 Closing +[Tue Apr 21 12:39:25 2026] 127.0.0.1:34658 Accepted +[Tue Apr 21 12:39:25 2026] 127.0.0.1:34658 Closing +[Tue Apr 21 12:39:25 2026] 127.0.0.1:34674 Accepted +[Tue Apr 21 12:39:26 2026] 127.0.0.1:34674 Closing +[Tue Apr 21 12:39:26 2026] 127.0.0.1:34688 Accepted +[Tue Apr 21 12:39:26 2026] 127.0.0.1:34688 Closing +[Tue Apr 21 12:39:26 2026] 127.0.0.1:34696 Accepted +[Tue Apr 21 12:39:27 2026] 127.0.0.1:34696 Closing +[Tue Apr 21 12:39:27 2026] 127.0.0.1:34702 Accepted +[Tue Apr 21 12:39:28 2026] 127.0.0.1:34702 Closing +[Tue Apr 21 12:39:28 2026] 127.0.0.1:34712 Accepted +[Tue Apr 21 12:39:29 2026] 127.0.0.1:34712 Closing +[Tue Apr 21 12:39:29 2026] 127.0.0.1:34726 Accepted +[Tue Apr 21 12:39:32 2026] 127.0.0.1:34726 Closing +[Tue Apr 21 12:39:32 2026] 127.0.0.1:34742 Accepted +[Tue Apr 21 12:39:34 2026] 127.0.0.1:34742 Closing +[Tue Apr 21 12:39:34 2026] 127.0.0.1:43282 Accepted +[Tue Apr 21 12:39:35 2026] 127.0.0.1:43282 Closing +[Tue Apr 21 12:39:35 2026] 127.0.0.1:43286 Accepted +[Tue Apr 21 12:39:36 2026] 127.0.0.1:43286 Closing +[Tue Apr 21 12:39:36 2026] 127.0.0.1:43290 Accepted +[Tue Apr 21 12:39:36 2026] 127.0.0.1:43290 Closing +[Tue Apr 21 12:39:36 2026] 127.0.0.1:43302 Accepted +[Tue Apr 21 12:39:37 2026] 127.0.0.1:43302 Closing +[Tue Apr 21 12:39:37 2026] 127.0.0.1:43310 Accepted +[Tue Apr 21 12:39:38 2026] 127.0.0.1:43310 Closing +[Tue Apr 21 12:39:38 2026] 127.0.0.1:43312 Accepted +[Tue Apr 21 12:39:39 2026] 127.0.0.1:43312 Closing +[Tue Apr 21 12:39:39 2026] 127.0.0.1:43322 Accepted +[Tue Apr 21 12:39:40 2026] 127.0.0.1:43322 Closing +[Tue Apr 21 12:39:40 2026] 127.0.0.1:43334 Accepted +[Tue Apr 21 12:39:40 2026] 127.0.0.1:43334 Closing +[Tue Apr 21 12:39:40 2026] 127.0.0.1:43336 Accepted +[Tue Apr 21 12:39:41 2026] 127.0.0.1:43336 Closing +[Tue Apr 21 12:39:41 2026] 127.0.0.1:43340 Accepted +[Tue Apr 21 12:39:41 2026] 127.0.0.1:43340 Closing +[Tue Apr 21 12:39:41 2026] 127.0.0.1:43346 Accepted +[Tue Apr 21 12:39:41 2026] 127.0.0.1:43346 Closing +[Tue Apr 21 12:39:41 2026] 127.0.0.1:43362 Accepted +[Tue Apr 21 12:39:42 2026] 127.0.0.1:43362 Closing +[Tue Apr 21 12:39:42 2026] 127.0.0.1:43364 Accepted +[Tue Apr 21 12:39:42 2026] 127.0.0.1:43364 Closing +[Tue Apr 21 12:39:42 2026] 127.0.0.1:43370 Accepted +[Tue Apr 21 12:39:42 2026] 127.0.0.1:43370 Closing +[Tue Apr 21 12:39:42 2026] 127.0.0.1:43386 Accepted +[Tue Apr 21 12:39:43 2026] 127.0.0.1:43386 Closing +[Tue Apr 21 12:39:43 2026] 127.0.0.1:43392 Accepted +[Tue Apr 21 12:39:43 2026] 127.0.0.1:43392 Closing +[Tue Apr 21 12:39:43 2026] 127.0.0.1:43404 Accepted +[Tue Apr 21 12:39:44 2026] 127.0.0.1:43404 Closing +[Tue Apr 21 12:39:44 2026] 127.0.0.1:41558 Accepted +[Tue Apr 21 12:39:44 2026] 127.0.0.1:41558 Closing +[Tue Apr 21 12:39:45 2026] 127.0.0.1:41564 Accepted +[Tue Apr 21 12:39:45 2026] 127.0.0.1:41564 Closing +[Tue Apr 21 12:39:45 2026] 127.0.0.1:41566 Accepted +[Tue Apr 21 12:39:46 2026] 127.0.0.1:41566 Closing +[Tue Apr 21 12:39:46 2026] 127.0.0.1:41570 Accepted +[Tue Apr 21 12:39:47 2026] 127.0.0.1:41570 Closing +[Tue Apr 21 12:39:47 2026] 127.0.0.1:41584 Accepted +[Tue Apr 21 12:39:47 2026] 127.0.0.1:41584 Closing +[Tue Apr 21 12:39:48 2026] 127.0.0.1:41586 Accepted +[Tue Apr 21 12:39:49 2026] 127.0.0.1:41586 Closing +[Tue Apr 21 12:39:49 2026] 127.0.0.1:41602 Accepted +[Tue Apr 21 12:39:49 2026] 127.0.0.1:41602 Closing +[Tue Apr 21 12:48:25 2026] PHP 8.2.30 Development Server (http://127.0.0.1:37693) started +[Tue Apr 21 12:48:26 2026] 127.0.0.1:56166 Accepted +[Tue Apr 21 12:48:26 2026] 127.0.0.1:56166 Closing +[Tue Apr 21 12:48:26 2026] 127.0.0.1:56182 Accepted +[Tue Apr 21 12:48:27 2026] 127.0.0.1:56182 Closing +[Tue Apr 21 12:48:27 2026] 127.0.0.1:56186 Accepted +[Tue Apr 21 12:48:29 2026] 127.0.0.1:56186 Closing +[Tue Apr 21 12:48:30 2026] 127.0.0.1:52486 Accepted +[Tue Apr 21 12:48:31 2026] 127.0.0.1:52486 Closing +[Tue Apr 21 12:48:31 2026] 127.0.0.1:52498 Accepted +[Tue Apr 21 12:48:32 2026] 127.0.0.1:52498 Closing +[Tue Apr 21 12:48:32 2026] 127.0.0.1:52508 Accepted +[Tue Apr 21 12:48:33 2026] 127.0.0.1:52508 Closing +[Tue Apr 21 12:48:33 2026] 127.0.0.1:52518 Accepted +[Tue Apr 21 12:48:34 2026] 127.0.0.1:52518 Closing +[Tue Apr 21 12:48:34 2026] 127.0.0.1:52532 Accepted +[Tue Apr 21 12:48:36 2026] 127.0.0.1:52532 Closing +[Tue Apr 21 12:48:36 2026] 127.0.0.1:52548 Accepted +[Tue Apr 21 12:48:37 2026] 127.0.0.1:52548 Closing +[Tue Apr 21 12:48:37 2026] 127.0.0.1:52560 Accepted +[Tue Apr 21 12:48:38 2026] 127.0.0.1:52560 Closing +[Tue Apr 21 12:48:38 2026] 127.0.0.1:52576 Accepted +[Tue Apr 21 12:48:39 2026] 127.0.0.1:52576 Closing +[Tue Apr 21 12:48:39 2026] 127.0.0.1:57582 Accepted +[Tue Apr 21 12:48:40 2026] 127.0.0.1:57582 Closing +[Tue Apr 21 12:48:40 2026] 127.0.0.1:57598 Accepted +[Tue Apr 21 12:48:41 2026] 127.0.0.1:57598 Closing +[Tue Apr 21 12:48:41 2026] 127.0.0.1:57614 Accepted +[Tue Apr 21 12:48:42 2026] 127.0.0.1:57614 Closing +[Tue Apr 21 12:48:42 2026] 127.0.0.1:57624 Accepted +[Tue Apr 21 12:48:43 2026] 127.0.0.1:57624 Closing +[Tue Apr 21 12:48:43 2026] 127.0.0.1:57628 Accepted +[Tue Apr 21 12:48:46 2026] 127.0.0.1:57628 Closing +[Tue Apr 21 12:48:46 2026] 127.0.0.1:57634 Accepted +[Tue Apr 21 12:48:46 2026] 127.0.0.1:57634 Closing +[Tue Apr 21 12:48:46 2026] 127.0.0.1:57646 Accepted +[Tue Apr 21 12:48:48 2026] 127.0.0.1:57646 Closing +[Tue Apr 21 12:49:51 2026] PHP 8.2.30 Development Server (http://127.0.0.1:41511) started +[Tue Apr 21 12:49:51 2026] 127.0.0.1:60470 Accepted +[Tue Apr 21 12:49:53 2026] 127.0.0.1:60470 Closing +[Tue Apr 21 12:49:53 2026] 127.0.0.1:60472 Accepted +[Tue Apr 21 12:49:53 2026] 127.0.0.1:60472 Closing +[Tue Apr 21 12:49:54 2026] 127.0.0.1:60480 Accepted +[Tue Apr 21 12:49:57 2026] 127.0.0.1:60480 Closing +[Tue Apr 21 12:49:57 2026] 127.0.0.1:60484 Accepted +[Tue Apr 21 12:49:59 2026] 127.0.0.1:60484 Closing +[Tue Apr 21 12:49:59 2026] 127.0.0.1:60494 Accepted +[Tue Apr 21 12:50:00 2026] 127.0.0.1:60494 Closing +[Tue Apr 21 12:50:00 2026] 127.0.0.1:60504 Accepted +[Tue Apr 21 12:50:02 2026] 127.0.0.1:60504 Closing +[Tue Apr 21 12:50:02 2026] 127.0.0.1:50656 Accepted +[Tue Apr 21 12:50:03 2026] 127.0.0.1:50656 Closing +[Tue Apr 21 12:50:04 2026] 127.0.0.1:50670 Accepted +[Tue Apr 21 12:50:05 2026] 127.0.0.1:50670 Closing +[Tue Apr 21 12:50:05 2026] 127.0.0.1:50676 Accepted +[Tue Apr 21 12:50:07 2026] 127.0.0.1:50676 Closing +[Tue Apr 21 12:50:07 2026] 127.0.0.1:50678 Accepted +[Tue Apr 21 12:50:09 2026] 127.0.0.1:50678 Closing +[Tue Apr 21 12:50:09 2026] 127.0.0.1:50686 Accepted +[Tue Apr 21 12:50:10 2026] 127.0.0.1:50686 Closing +[Tue Apr 21 12:50:10 2026] 127.0.0.1:50694 Accepted +[Tue Apr 21 12:50:11 2026] 127.0.0.1:50694 Closing +[Tue Apr 21 12:51:35 2026] PHP 8.2.30 Development Server (http://127.0.0.1:44745) started +[Tue Apr 21 12:51:35 2026] 127.0.0.1:56336 Accepted +[Tue Apr 21 12:51:36 2026] 127.0.0.1:56336 Closing +[Tue Apr 21 12:51:36 2026] 127.0.0.1:56342 Accepted +[Tue Apr 21 12:51:37 2026] 127.0.0.1:56342 Closing +[Tue Apr 21 12:51:37 2026] 127.0.0.1:56344 Accepted +[Tue Apr 21 12:51:37 2026] 127.0.0.1:56344 Closing +[Tue Apr 21 12:51:39 2026] 127.0.0.1:56354 Accepted +[Tue Apr 21 12:51:41 2026] 127.0.0.1:56354 Closing +[Tue Apr 21 12:51:41 2026] 127.0.0.1:37198 Accepted +[Tue Apr 21 12:51:42 2026] 127.0.0.1:37198 Closing +[Tue Apr 21 12:51:42 2026] 127.0.0.1:37212 Accepted +[Tue Apr 21 12:51:44 2026] 127.0.0.1:37212 Closing +[Tue Apr 21 12:51:44 2026] 127.0.0.1:37228 Accepted +[Tue Apr 21 12:51:46 2026] 127.0.0.1:37228 Closing +[Tue Apr 21 12:51:46 2026] 127.0.0.1:37236 Accepted +[Tue Apr 21 12:51:47 2026] 127.0.0.1:37236 Closing +[Tue Apr 21 12:51:47 2026] 127.0.0.1:37244 Accepted +[Tue Apr 21 12:51:49 2026] 127.0.0.1:37244 Closing +[Tue Apr 21 12:51:49 2026] 127.0.0.1:37252 Accepted +[Tue Apr 21 12:51:50 2026] 127.0.0.1:37252 Closing +[Tue Apr 21 12:51:50 2026] 127.0.0.1:52198 Accepted +[Tue Apr 21 12:51:52 2026] 127.0.0.1:52198 Closing +[Tue Apr 21 12:51:52 2026] 127.0.0.1:52208 Accepted +[Tue Apr 21 12:51:53 2026] 127.0.0.1:52208 Closing +[Tue Apr 21 12:52:37 2026] PHP 8.2.30 Development Server (http://127.0.0.1:45395) started +[Tue Apr 21 12:52:37 2026] 127.0.0.1:45928 Accepted +[Tue Apr 21 12:52:39 2026] 127.0.0.1:45928 Closing +[Tue Apr 21 12:52:39 2026] 127.0.0.1:45940 Accepted +[Tue Apr 21 12:52:40 2026] 127.0.0.1:45940 Closing +[Tue Apr 21 12:52:40 2026] 127.0.0.1:45956 Accepted +[Tue Apr 21 12:52:41 2026] 127.0.0.1:45956 Closing +[Tue Apr 21 12:52:41 2026] 127.0.0.1:45968 Accepted +[Tue Apr 21 12:52:42 2026] 127.0.0.1:45968 Closing +[Tue Apr 21 12:52:42 2026] 127.0.0.1:45980 Accepted +[Tue Apr 21 12:52:42 2026] 127.0.0.1:45980 Closing +[Tue Apr 21 12:52:43 2026] 127.0.0.1:45996 Accepted +[Tue Apr 21 12:52:44 2026] 127.0.0.1:45996 Closing +[Tue Apr 21 12:52:44 2026] 127.0.0.1:46000 Accepted +[Tue Apr 21 12:52:45 2026] 127.0.0.1:46000 Closing +[Tue Apr 21 12:52:45 2026] 127.0.0.1:37320 Accepted +[Tue Apr 21 12:52:45 2026] 127.0.0.1:37320 Closing +[Tue Apr 21 12:52:45 2026] 127.0.0.1:37334 Accepted +[Tue Apr 21 12:52:46 2026] 127.0.0.1:37334 Closing +[Tue Apr 21 12:52:46 2026] 127.0.0.1:37346 Accepted +[Tue Apr 21 12:52:47 2026] 127.0.0.1:37346 Closing +[Tue Apr 21 12:52:47 2026] 127.0.0.1:37360 Accepted +[Tue Apr 21 12:52:47 2026] 127.0.0.1:37360 Closing +[Tue Apr 21 12:52:47 2026] 127.0.0.1:37376 Accepted +[Tue Apr 21 12:52:48 2026] 127.0.0.1:37376 Closing +[Tue Apr 21 12:52:48 2026] 127.0.0.1:37378 Accepted +[Tue Apr 21 12:52:48 2026] 127.0.0.1:37378 Closing +[Tue Apr 21 12:52:48 2026] 127.0.0.1:37390 Accepted +[Tue Apr 21 12:52:49 2026] 127.0.0.1:37390 Closing +[Tue Apr 21 12:52:49 2026] 127.0.0.1:37406 Accepted +[Tue Apr 21 12:52:50 2026] 127.0.0.1:37406 Closing +[Tue Apr 21 12:52:50 2026] 127.0.0.1:37416 Accepted +[Tue Apr 21 12:52:50 2026] 127.0.0.1:37416 Closing +[Tue Apr 21 12:52:51 2026] 127.0.0.1:37428 Accepted +[Tue Apr 21 12:52:51 2026] 127.0.0.1:37428 Closing +[Tue Apr 21 12:52:51 2026] 127.0.0.1:37440 Accepted +[Tue Apr 21 12:52:52 2026] 127.0.0.1:37440 Closing +[Tue Apr 21 12:52:52 2026] 127.0.0.1:37450 Accepted +[Tue Apr 21 12:52:53 2026] 127.0.0.1:37450 Closing +[Tue Apr 21 12:52:53 2026] 127.0.0.1:37452 Accepted +[Tue Apr 21 12:52:54 2026] 127.0.0.1:37452 Closing +[Tue Apr 21 12:52:54 2026] 127.0.0.1:37458 Accepted +[Tue Apr 21 12:52:54 2026] 127.0.0.1:37458 Closing +[Tue Apr 21 12:52:54 2026] 127.0.0.1:37466 Accepted +[Tue Apr 21 12:52:55 2026] 127.0.0.1:37466 Closing +[Tue Apr 21 12:52:55 2026] 127.0.0.1:44262 Accepted +[Tue Apr 21 12:52:55 2026] 127.0.0.1:44262 Closing +[Tue Apr 21 12:52:55 2026] 127.0.0.1:44268 Accepted +[Tue Apr 21 12:52:56 2026] 127.0.0.1:44268 Closing +[Tue Apr 21 12:52:56 2026] 127.0.0.1:44284 Accepted +[Tue Apr 21 12:52:56 2026] 127.0.0.1:44284 Closing +[Tue Apr 21 12:52:56 2026] 127.0.0.1:44290 Accepted +[Tue Apr 21 12:52:57 2026] 127.0.0.1:44290 Closing +[Tue Apr 21 12:52:57 2026] 127.0.0.1:44296 Accepted +[Tue Apr 21 12:52:58 2026] 127.0.0.1:44296 Closing +[Tue Apr 21 12:52:58 2026] 127.0.0.1:44312 Accepted +[Tue Apr 21 12:52:58 2026] 127.0.0.1:44312 Closing +[Tue Apr 21 12:52:58 2026] 127.0.0.1:44328 Accepted +[Tue Apr 21 12:52:59 2026] 127.0.0.1:44328 Closing +[Tue Apr 21 12:52:59 2026] 127.0.0.1:44332 Accepted +[Tue Apr 21 12:53:00 2026] 127.0.0.1:44332 Closing +[Tue Apr 21 12:53:00 2026] 127.0.0.1:44334 Accepted +[Tue Apr 21 12:52:58 2026] 127.0.0.1:44334 Closing +[Tue Apr 21 12:52:58 2026] 127.0.0.1:44346 Accepted +[Tue Apr 21 12:52:59 2026] 127.0.0.1:44346 Closing +[Tue Apr 21 12:52:59 2026] 127.0.0.1:44360 Accepted +[Tue Apr 21 12:53:00 2026] 127.0.0.1:44360 Closing +[Tue Apr 21 12:53:00 2026] 127.0.0.1:44372 Accepted +[Tue Apr 21 12:53:00 2026] 127.0.0.1:44372 Closing +[Tue Apr 21 12:53:00 2026] 127.0.0.1:44384 Accepted +[Tue Apr 21 12:53:01 2026] 127.0.0.1:44384 Closing +[Tue Apr 21 12:53:01 2026] 127.0.0.1:44400 Accepted +[Tue Apr 21 12:53:02 2026] 127.0.0.1:44400 Closing +[Tue Apr 21 12:53:02 2026] 127.0.0.1:60944 Accepted +[Tue Apr 21 12:53:03 2026] 127.0.0.1:60944 Closing +[Tue Apr 21 12:53:03 2026] 127.0.0.1:60958 Accepted +[Tue Apr 21 12:53:04 2026] 127.0.0.1:60958 Closing +[Tue Apr 21 12:53:04 2026] 127.0.0.1:60974 Accepted +[Tue Apr 21 12:53:04 2026] 127.0.0.1:60974 Closing +[Tue Apr 21 12:53:05 2026] 127.0.0.1:60982 Accepted +[Tue Apr 21 12:53:05 2026] 127.0.0.1:60982 Closing +[Tue Apr 21 12:53:06 2026] 127.0.0.1:60984 Accepted +[Tue Apr 21 12:53:06 2026] 127.0.0.1:60984 Closing +[Tue Apr 21 12:53:07 2026] 127.0.0.1:60986 Accepted +[Tue Apr 21 12:53:07 2026] 127.0.0.1:60986 Closing +[Tue Apr 21 12:53:08 2026] 127.0.0.1:60990 Accepted +[Tue Apr 21 12:53:08 2026] 127.0.0.1:60990 Closing +[Tue Apr 21 12:53:09 2026] 127.0.0.1:32768 Accepted +[Tue Apr 21 12:53:11 2026] 127.0.0.1:32768 Closing +[Tue Apr 21 12:53:12 2026] 127.0.0.1:32772 Accepted +[Tue Apr 21 12:53:12 2026] 127.0.0.1:32772 Closing +[Tue Apr 21 12:53:12 2026] 127.0.0.1:60592 Accepted +[Tue Apr 21 12:53:13 2026] 127.0.0.1:60592 Closing +[Tue Apr 21 12:53:13 2026] 127.0.0.1:60600 Accepted +[Tue Apr 21 12:53:14 2026] 127.0.0.1:60600 Closing +[Tue Apr 21 12:53:14 2026] 127.0.0.1:60610 Accepted +[Tue Apr 21 12:53:15 2026] 127.0.0.1:60610 Closing +[Tue Apr 21 12:53:15 2026] 127.0.0.1:60626 Accepted +[Tue Apr 21 12:53:16 2026] 127.0.0.1:60626 Closing +[Tue Apr 21 12:53:16 2026] 127.0.0.1:60628 Accepted +[Tue Apr 21 12:53:17 2026] 127.0.0.1:60628 Closing +[Tue Apr 21 12:53:17 2026] 127.0.0.1:60634 Accepted +[Tue Apr 21 12:53:17 2026] 127.0.0.1:60634 Closing +[Tue Apr 21 12:53:18 2026] 127.0.0.1:60638 Accepted +[Tue Apr 21 12:53:18 2026] 127.0.0.1:60638 Closing +[Tue Apr 21 12:53:19 2026] 127.0.0.1:60640 Accepted +[Tue Apr 21 12:53:19 2026] 127.0.0.1:60640 Closing +[Tue Apr 21 12:53:19 2026] 127.0.0.1:60642 Accepted +[Tue Apr 21 12:53:20 2026] 127.0.0.1:60642 Closing +[Tue Apr 21 12:53:20 2026] 127.0.0.1:60658 Accepted +[Tue Apr 21 12:53:21 2026] 127.0.0.1:60658 Closing +[Tue Apr 21 12:53:21 2026] 127.0.0.1:60674 Accepted +[Tue Apr 21 12:53:24 2026] 127.0.0.1:60674 Closing +[Tue Apr 21 12:53:24 2026] 127.0.0.1:57922 Accepted +[Tue Apr 21 12:53:25 2026] 127.0.0.1:57922 Closing +[Tue Apr 21 12:53:25 2026] 127.0.0.1:57928 Accepted +[Tue Apr 21 12:53:27 2026] 127.0.0.1:57928 Closing +[Tue Apr 21 12:53:27 2026] 127.0.0.1:57942 Accepted +[Tue Apr 21 12:53:26 2026] 127.0.0.1:57942 Closing +[Tue Apr 21 12:53:26 2026] 127.0.0.1:57946 Accepted +[Tue Apr 21 12:53:27 2026] 127.0.0.1:57946 Closing +[Tue Apr 21 12:53:27 2026] 127.0.0.1:57954 Accepted +[Tue Apr 21 12:53:27 2026] 127.0.0.1:57954 Closing +[Tue Apr 21 12:53:27 2026] 127.0.0.1:57960 Accepted +[Tue Apr 21 12:53:28 2026] 127.0.0.1:57960 Closing +[Tue Apr 21 12:53:28 2026] 127.0.0.1:57972 Accepted +[Tue Apr 21 12:53:29 2026] 127.0.0.1:57972 Closing +[Tue Apr 21 12:53:29 2026] 127.0.0.1:57986 Accepted +[Tue Apr 21 12:53:30 2026] 127.0.0.1:57986 Closing +[Tue Apr 21 12:53:30 2026] 127.0.0.1:41580 Accepted +[Tue Apr 21 12:53:31 2026] 127.0.0.1:41580 Closing +[Tue Apr 21 12:53:31 2026] 127.0.0.1:41588 Accepted +[Tue Apr 21 12:53:31 2026] 127.0.0.1:41588 Closing +[Tue Apr 21 12:53:31 2026] 127.0.0.1:41602 Accepted +[Tue Apr 21 12:53:33 2026] 127.0.0.1:41602 Closing +[Tue Apr 21 12:53:33 2026] 127.0.0.1:41608 Accepted +[Tue Apr 21 12:53:34 2026] 127.0.0.1:41608 Closing +[Tue Apr 21 12:53:34 2026] 127.0.0.1:41624 Accepted +[Tue Apr 21 12:53:34 2026] 127.0.0.1:41624 Closing +[Tue Apr 21 12:53:34 2026] 127.0.0.1:41634 Accepted +[Tue Apr 21 12:53:35 2026] 127.0.0.1:41634 Closing +[Tue Apr 21 12:53:35 2026] 127.0.0.1:41638 Accepted +[Tue Apr 21 12:53:36 2026] 127.0.0.1:41638 Closing +[Tue Apr 21 12:53:36 2026] 127.0.0.1:41644 Accepted +[Tue Apr 21 12:53:36 2026] 127.0.0.1:41644 Closing +[Tue Apr 21 12:53:36 2026] 127.0.0.1:41650 Accepted +[Tue Apr 21 12:53:37 2026] 127.0.0.1:41650 Closing +[Tue Apr 21 12:53:37 2026] 127.0.0.1:41652 Accepted +[Tue Apr 21 12:53:37 2026] 127.0.0.1:41652 Closing +[Tue Apr 21 12:53:38 2026] 127.0.0.1:41654 Accepted +[Tue Apr 21 12:53:38 2026] 127.0.0.1:41654 Closing +[Tue Apr 21 12:53:38 2026] 127.0.0.1:41660 Accepted +[Tue Apr 21 12:53:38 2026] 127.0.0.1:41660 Closing +[Tue Apr 21 12:53:38 2026] 127.0.0.1:41664 Accepted +[Tue Apr 21 12:53:39 2026] 127.0.0.1:41664 Closing +[Tue Apr 21 12:53:39 2026] 127.0.0.1:36412 Accepted +[Tue Apr 21 12:53:40 2026] 127.0.0.1:36412 Closing +[Tue Apr 21 12:53:40 2026] 127.0.0.1:36420 Accepted +[Tue Apr 21 12:53:41 2026] 127.0.0.1:36420 Closing +[Tue Apr 21 12:53:41 2026] 127.0.0.1:36426 Accepted +[Tue Apr 21 12:53:42 2026] 127.0.0.1:36426 Closing +[Tue Apr 21 12:53:42 2026] 127.0.0.1:36442 Accepted +[Tue Apr 21 12:53:44 2026] 127.0.0.1:36442 Closing +[Tue Apr 21 12:53:44 2026] 127.0.0.1:36450 Accepted +[Tue Apr 21 12:53:47 2026] 127.0.0.1:36450 Closing +[Tue Apr 21 12:53:49 2026] 127.0.0.1:47160 Accepted +[Tue Apr 21 12:53:52 2026] 127.0.0.1:47160 Closing +[Tue Apr 21 12:53:52 2026] 127.0.0.1:47174 Accepted +[Tue Apr 21 12:53:54 2026] 127.0.0.1:47174 Closing +[Tue Apr 21 12:53:54 2026] 127.0.0.1:47178 Accepted +[Tue Apr 21 12:53:53 2026] 127.0.0.1:47178 Closing +[Tue Apr 21 12:53:53 2026] 127.0.0.1:47194 Accepted +[Tue Apr 21 12:53:55 2026] 127.0.0.1:47194 Closing +[Tue Apr 21 12:53:55 2026] 127.0.0.1:47204 Accepted +[Tue Apr 21 12:53:57 2026] 127.0.0.1:47204 Closing +[Tue Apr 21 12:53:57 2026] 127.0.0.1:46990 Accepted +[Tue Apr 21 12:53:59 2026] 127.0.0.1:46990 Closing +[Tue Apr 21 12:53:59 2026] 127.0.0.1:46994 Accepted +[Tue Apr 21 12:54:01 2026] 127.0.0.1:46994 Closing +[Tue Apr 21 12:54:01 2026] 127.0.0.1:46996 Accepted +[Tue Apr 21 12:54:03 2026] 127.0.0.1:46996 Closing +[Tue Apr 21 12:54:03 2026] 127.0.0.1:47008 Accepted +[Tue Apr 21 12:54:04 2026] 127.0.0.1:47008 Closing +[Tue Apr 21 12:54:04 2026] 127.0.0.1:47012 Accepted +[Tue Apr 21 12:54:05 2026] 127.0.0.1:47012 Closing +[Tue Apr 21 12:54:05 2026] 127.0.0.1:47026 Accepted +[Tue Apr 21 12:54:06 2026] 127.0.0.1:47026 Closing diff --git a/services/nginx/app/config.php b/services/nginx/app/config.php index 521d8f4a..be5988ad 100644 --- a/services/nginx/app/config.php +++ b/services/nginx/app/config.php @@ -2,7 +2,7 @@ $runtimeEnvironment = getenv(); if (is_array($runtimeEnvironment) && $runtimeEnvironment !== []) { - $_ENV = array_merge($runtimeEnvironment, $_ENV ?? []); + $_ENV = array_merge($_ENV ?? [], $runtimeEnvironment); } /** diff --git a/services/nginx/app/modules/economic/customers/economic_customer_mo.php b/services/nginx/app/modules/economic/customers/economic_customer_mo.php index 4f3b0735..c77c6cef 100644 --- a/services/nginx/app/modules/economic/customers/economic_customer_mo.php +++ b/services/nginx/app/modules/economic/customers/economic_customer_mo.php @@ -2,7 +2,6 @@ namespace customers; -use classes\response; use objects\users_o; class economic_customer_mo @@ -33,11 +32,14 @@ class economic_customer_mo } - public function parseCustomer($customer): static + public function parseCustomer(mixed $customer): static { - global /** @var response $response */ - $response; - $this->customer_number = $customer->customerNumber; + $customer_number = $this->extractCustomerNumber($customer); + if ($customer_number === null) { + return $this; + } + + $this->customer_number = $customer_number; $this->name = ($customer->name ?? null); $this->address = ($customer->address ?? null); $this->city = ($customer->city ?? null); @@ -48,6 +50,33 @@ class economic_customer_mo $this->currency = ($customer->currency ?? null); $this->country = ($customer->country ?? null); $this->barred = (isset($customer->barred) ? (bool)$customer->barred : false); + $this->cacheCustomerForImportedUser(); + // Add the customer to the debug log (IF DEBUG IS ENABLED) + //$response->add_debug_list('economic_customer', $this->customer_number, $customer); + return $this; + } + + protected function extractCustomerNumber(mixed $customer): ?int + { + if (!is_object($customer)) { + return null; + } + + $customer_number = $customer->customerNumber ?? $customer->customer_number ?? null; + if (!is_int($customer_number) && !is_float($customer_number) && !is_string($customer_number)) { + return null; + } + + $customer_number = (int)$customer_number; + if ($customer_number <= 0) { + return null; + } + + return $customer_number; + } + + protected function cacheCustomerForImportedUser(): void + { $users_o = new users_o(); $user = $users_o->getUserByCustomerNumber($this->customer_number); @@ -55,9 +84,6 @@ class economic_customer_mo if ($user->exists()) { $user->cache('economic_customer', $this); } - // Add the customer to the debug log (IF DEBUG IS ENABLED) - //$response->add_debug_list('economic_customer', $this->customer_number, $customer); - return $this; } public function asArray(): array @@ -80,4 +106,4 @@ class economic_customer_mo 'barred' => $this->barred, ]; } -} \ No newline at end of file +} diff --git a/services/nginx/app/objects/collected_order_invoices_o.php b/services/nginx/app/objects/collected_order_invoices_o.php index f9f88707..1411b6fe 100644 --- a/services/nginx/app/objects/collected_order_invoices_o.php +++ b/services/nginx/app/objects/collected_order_invoices_o.php @@ -546,9 +546,6 @@ class collected_order_invoices_o extends db $db; // Sanitize the input $customer_number = $db->escape_string($customer_number); - if (!empty($name)) { - $name = $db->escape_string($name); - } if (!empty($notes)) { $notes = $db->escape_string($notes); } @@ -564,10 +561,20 @@ class collected_order_invoices_o extends db } // Require the customer number to be of a valid customer self::requireValidCustomer($customer_number); + $resolved_name = is_string($name) ? trim($name) : ''; + if ($resolved_name === '') { + $customer = (new users_o())->getUserByCustomerNumber((int)$customer_number); + $customer->requireSelected(); + $resolved_name = trim((string)$customer->display_name->value()); + } + if ($resolved_name === '') { + $resolved_name = 'Invoice collection ' . (string)$customer_number; + } + $resolved_name = $db->escape_string($resolved_name); // Add the object $tmp_id = self::add_object([ 'customer_number' => (int)$customer_number, - 'name' => $name, + 'name' => $resolved_name, 'notes' => $notes, ...(!empty($closed_at) ? ['closed_at' => (string)$closed_at] : []), ]); diff --git a/services/nginx/app/objects/users_o.php b/services/nginx/app/objects/users_o.php index d710c382..265f88a9 100644 --- a/services/nginx/app/objects/users_o.php +++ b/services/nginx/app/objects/users_o.php @@ -477,16 +477,27 @@ class users_o extends db // Create a temporary user object $tmp_user = new users_o(); $tmp_user->getUserByCustomerNumber($customer_number); + $fallbackName = null; + if ($tmp_user->exists()) { + $displayName = $tmp_user->display_name->value(); + if (is_string($displayName) && trim($displayName) !== '') { + $fallbackName = $displayName; + } + } // Get the customer name (Check cache first) $cached = $tmp_user->getCached('economic_customer'); if (!$cached) { - $tmp_user->getCustomerEcocomicData($customer_number); + try { + $tmp_user->getCustomerEcocomicData($customer_number); + } catch (Exception) { + return $fallbackName; + } $cached = $tmp_user->getCached('economic_customer'); } - if ($cached) { + if ($cached && isset($cached->name) && is_string($cached->name) && trim($cached->name) !== '') { return $cached->name; } - return null; + return $fallbackName; } public function getCustomerEcocomicData(int $customer_number = null): users_o @@ -502,7 +513,20 @@ class users_o extends db return $this; } - $this->economic_customer = (new economic_customer_mo())->getCustomerByCustomerNumber($customer_number); + $cachedCustomer = $this->getCached('economic_customer'); + if (is_object($cachedCustomer)) { + $cachedCustomerNumber = (int)($cachedCustomer->customerNumber ?? $cachedCustomer->customer_number ?? 0); + if ($cachedCustomerNumber === $customer_number) { + $this->economic_customer = (new economic_customer_mo())->parseCustomer($cachedCustomer); + return $this; + } + } + + try { + $this->economic_customer = (new economic_customer_mo())->getCustomerByCustomerNumber($customer_number); + } catch (Exception) { + $this->economic_customer = new economic_customer_mo(); + } return $this; } diff --git a/services/nginx/app/tests/Support/Api/ApiSchemaBootstrap.php b/services/nginx/app/tests/Support/Api/ApiSchemaBootstrap.php index 9d845c95..92f9d758 100644 --- a/services/nginx/app/tests/Support/Api/ApiSchemaBootstrap.php +++ b/services/nginx/app/tests/Support/Api/ApiSchemaBootstrap.php @@ -414,6 +414,17 @@ CREATE TABLE IF NOT EXISTS `stripe_module_orders` ( `updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SQL, + 'stripe_module_customers' => <<<'SQL' +CREATE TABLE IF NOT EXISTS `stripe_module_customers` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `email` VARCHAR(255) NOT NULL, + `customer_id` VARCHAR(255) NOT NULL, + `created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_stripe_module_customers_email` (`email`), + KEY `idx_stripe_module_customers_customer_id` (`customer_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci SQL, 'stripe_payment_intents' => <<<'SQL' CREATE TABLE IF NOT EXISTS `stripe_payment_intents` ( diff --git a/services/nginx/app/tests/Support/Api/ApiServer.php b/services/nginx/app/tests/Support/Api/ApiServer.php index 6ebad9fb..b2d409ec 100644 --- a/services/nginx/app/tests/Support/Api/ApiServer.php +++ b/services/nginx/app/tests/Support/Api/ApiServer.php @@ -33,6 +33,7 @@ final class ApiServer escapeshellarg((string)(PHP_BINARY ?: 'php')), escapeshellarg($this->host . ':' . $this->port), ); + $environment = $this->inheritEnvironment(); $descriptorSpec = [ 0 => ['pipe', 'r'], @@ -40,7 +41,7 @@ final class ApiServer 2 => ['file', $this->stderrLog, 'a'], ]; - $this->process = proc_open($command, $descriptorSpec, $pipes, $this->workingDirectory); + $this->process = proc_open($command, $descriptorSpec, $pipes, $this->workingDirectory, $environment); if (!is_resource($this->process)) { throw new RuntimeException('Unable to start the PHP test server.'); } @@ -88,4 +89,62 @@ final class ApiServer return $message; } + + public static function findAvailablePort(string $host): int + { + $socket = @stream_socket_server('tcp://' . $host . ':0', $errorCode, $errorMessage); + if ($socket === false) { + throw new RuntimeException( + sprintf('Unable to reserve an API test port on %s: %s (%d)', $host, $errorMessage, $errorCode) + ); + } + + $address = stream_socket_get_name($socket, false); + fclose($socket); + + if (!is_string($address) || $address === '') { + throw new RuntimeException('Unable to determine the reserved API test port.'); + } + + $lastSeparator = strrpos($address, ':'); + if ($lastSeparator === false) { + throw new RuntimeException('Unable to parse the reserved API test port.'); + } + + $port = (int)substr($address, $lastSeparator + 1); + if ($port <= 0) { + throw new RuntimeException('Unable to parse a valid API test port.'); + } + + return $port; + } + + /** + * @return array + */ + private function inheritEnvironment(): array + { + $environment = []; + + $sources = [getenv(), $_ENV ?? [], $_SERVER ?? []]; + foreach ($sources as $source) { + if (!is_array($source)) { + continue; + } + + foreach ($source as $key => $value) { + if (!is_string($key) || $key === '') { + continue; + } + + if (!is_scalar($value) && $value !== null) { + continue; + } + + $environment[$key] = $value === null ? '' : (string)$value; + } + } + + return $environment; + } } diff --git a/services/nginx/app/tests/Support/Api/ApiTestRuntime.php b/services/nginx/app/tests/Support/Api/ApiTestRuntime.php index d7c4c09e..1cc04847 100644 --- a/services/nginx/app/tests/Support/Api/ApiTestRuntime.php +++ b/services/nginx/app/tests/Support/Api/ApiTestRuntime.php @@ -25,6 +25,7 @@ final class ApiTestRuntime private bool $shutdownRegistered = false; private string $baseUrl = self::DEFAULT_BASE_URL; private bool $usesExternalBaseUrl = false; + private ?int $internalServerPort = null; public static function instance(): self { @@ -58,7 +59,6 @@ final class ApiTestRuntime $this->bootstrapSchemaIfRequested(); $this->cleanup = new ApiCleanup(); $this->fixtures = new ApiFixtures($this->db(), $this->redis(), $this->cleanup); - $this->ensureServerIsRunning(); } public function endTest(): void @@ -137,6 +137,7 @@ final class ApiTestRuntime $this->bootstrapped = false; $this->schemaBootstrapped = false; + $this->internalServerPort = null; } private function bootstrapEnvironment(): void @@ -212,7 +213,9 @@ final class ApiTestRuntime $url = parse_url($this->baseUrl); $host = (string)($url['host'] ?? '127.0.0.1'); - $port = (int)($url['port'] ?? 18080); + $port = $this->internalServerPort ?? ApiServer::findAvailablePort($host); + $this->internalServerPort = $port; + $this->baseUrl = sprintf('http://%s:%d', $host, $port); $this->server = new ApiServer(app_path(), $host, $port); $this->server->start(); diff --git a/services/nginx/app/tests/Unit/Users/EconomicCustomerModelParsingTest.php b/services/nginx/app/tests/Unit/Users/EconomicCustomerModelParsingTest.php new file mode 100644 index 00000000..3b5a0587 --- /dev/null +++ b/services/nginx/app/tests/Unit/Users/EconomicCustomerModelParsingTest.php @@ -0,0 +1,47 @@ +parseCustomer((object)[ + 'customer_number' => '42331123', + 'name' => 'Truckwash ApS', + 'email' => 'jb@truckwash.dk', + 'currency' => 'DKK', + 'country' => 'DK', + 'barred' => true, + ]); + + expect($model->asArray())->toBe([ + 'customerNumber' => 42331123, + 'name' => 'Truckwash ApS', + 'address' => null, + 'city' => null, + 'zip' => null, + 'corporateIdentificationNumber' => null, + 'email' => 'jb@truckwash.dk', + 'mobilePhone' => null, + 'currency' => 'DKK', + 'country' => 'DK', + 'barred' => true, + ]); +}); + +it('leaves the economic customer model empty when no valid customer number is present', function (): void { + $model = (new EconomicCustomerModelParsingProbe())->parseCustomer((object)[ + 'name' => 'Missing Number', + ]); + + expect($model->asArray())->toBe([]); +}); diff --git a/services/nginx/app/traits/minio_t.php b/services/nginx/app/traits/minio_t.php index 7ae78bc0..c9197c5d 100644 --- a/services/nginx/app/traits/minio_t.php +++ b/services/nginx/app/traits/minio_t.php @@ -24,6 +24,10 @@ trait minio_t */ public function listFiles(): array { + if ($this->shouldUseLocalTestStorage()) { + return $this->listLocalTestObjects(); + } + $objects = self::getS3Client()->listObjects([ 'Bucket' => self::getBucket() ]); @@ -123,6 +127,10 @@ trait minio_t */ public function createObject(string $key, string $body): bool { + if ($this->shouldUseLocalTestStorage()) { + return file_put_contents($this->getLocalTestObjectPath($key), $body) !== false; + } + $result = self::getS3Client()->putObject([ 'Bucket' => self::getBucket(), 'Key' => $key, @@ -138,6 +146,10 @@ trait minio_t */ public function getObjectUrl(string $key): string { + if ($this->shouldUseLocalTestStorage()) { + return $this->getLocalTestObjectPath($key); + } + return self::getS3Client()->getObjectUrl(self::getBucket(), $key); } @@ -153,6 +165,10 @@ trait minio_t */ public function getPresignedUrl(string $key, ?int $expires = null, bool $isUpload = false): string { + if ($this->shouldUseLocalTestStorage()) { + return $this->getLocalTestObjectPath($key); + } + $commandAction = $isUpload ? 'PutObject' : 'GetObject'; $command = self::getS3Client()->getCommand($commandAction, [ 'Bucket' => self::getBucket(), @@ -171,6 +187,10 @@ trait minio_t */ public function uploadFile(string $key, string $file): bool { + if ($this->shouldUseLocalTestStorage()) { + return copy($file, $this->getLocalTestObjectPath($key)); + } + $result = self::getS3Client()->putObject([ 'Bucket' => self::getBucket(), 'Key' => $key, @@ -186,6 +206,10 @@ trait minio_t */ public function doesObjectExist(string $key): bool { + if ($this->shouldUseLocalTestStorage()) { + return file_exists($this->getLocalTestObjectPath($key)); + } + return self::getS3Client()->doesObjectExist(self::getBucket(), $key); } @@ -305,4 +329,80 @@ trait minio_t // If no match, return a default extension (e.g., 'jpg') return 'jpg'; } -} \ No newline at end of file + + private function shouldUseLocalTestStorage(): bool + { + if (getenv('RUN_API_TESTS') !== '1') { + return false; + } + + return trim((string)$this->getEndpoint()) === '' + || trim((string)$this->getAccessKey()) === '' + || trim((string)$this->getSecretKey()) === ''; + } + + /** + * @return array + */ + private function listLocalTestObjects(): array + { + $directory = $this->getLocalTestStorageDirectory(); + $objects = []; + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS) + ); + + foreach ($iterator as $fileInfo) { + if (!$fileInfo->isFile()) { + continue; + } + + $pathname = $fileInfo->getPathname(); + $relativePath = substr($pathname, strlen($directory) + 1); + $objects[] = [ + 'Key' => str_replace('\\', '/', $relativePath), + ]; + } + + return $objects; + } + + private function getLocalTestStorageDirectory(): string + { + $bucket = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $this->getBucket()) ?: 'default'; + $directory = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) + . DIRECTORY_SEPARATOR + . 'truckwash-test-object-store' + . DIRECTORY_SEPARATOR + . $bucket; + + if (!is_dir($directory)) { + mkdir($directory, 0777, true); + } + + return $directory; + } + + private function getLocalTestObjectPath(string $key): string + { + $normalizedKey = str_replace('\\', '/', $key); + $normalizedKey = preg_replace('#(^|/)\\.\\.(?=/|$)#', '', $normalizedKey) ?? $normalizedKey; + $normalizedKey = ltrim($normalizedKey, '/'); + + if ($normalizedKey === '') { + $normalizedKey = 'object'; + } + + $path = $this->getLocalTestStorageDirectory() + . DIRECTORY_SEPARATOR + . str_replace('/', DIRECTORY_SEPARATOR, $normalizedKey); + $directory = dirname($path); + + if (!is_dir($directory)) { + mkdir($directory, 0777, true); + } + + return $path; + } +} diff --git a/services/nginx/app/traits/module_config_variable_t.php b/services/nginx/app/traits/module_config_variable_t.php index 740dbaec..72ef8fca 100644 --- a/services/nginx/app/traits/module_config_variable_t.php +++ b/services/nginx/app/traits/module_config_variable_t.php @@ -71,7 +71,7 @@ trait module_config_variable */ static function isVariableSet(string $module, string $variable): bool { - global $db; + $db = self::getModuleConfigDatabase(); $module = $db->escape_string($module); $variable = $db->escape_string($variable); $sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'"; @@ -87,7 +87,7 @@ trait module_config_variable */ function insertVariableValue(string $module, string $variable, mixed $value): void { - global $db; + $db = self::getModuleConfigDatabase(); $module = $db->escape_string($module); $variable = $db->escape_string($variable); $value = $db->escape_string((string)$value); @@ -126,7 +126,7 @@ trait module_config_variable */ function getVariableValue(): mixed { - global $db; + $db = self::getModuleConfigDatabase(); $module = $db->escape_string($this->module_name); $variable = $db->escape_string($this->config_variable); $sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'"; @@ -242,7 +242,7 @@ trait module_config_variable */ static function updateVariableValue(string $module, string $variable, mixed $value): void { - global $db; + $db = self::getModuleConfigDatabase(); $module = $db->escape_string($module); $variable = $db->escape_string($variable); $value = $db->escape_string((string)$value); @@ -251,6 +251,57 @@ trait module_config_variable system_search_cache::markDirtyTable('module_config'); } + private static function getModuleConfigDatabase(): \classes\db + { + global $db, $CONFIG_DB; + + if ($db instanceof \classes\db) { + return $db; + } + + if (!is_array($CONFIG_DB) || $CONFIG_DB === []) { + $CONFIG_DB = self::readDatabaseConfigFromEnvironment(); + } + + if (!is_array($CONFIG_DB) || $CONFIG_DB === []) { + throw new Exception('Database configuration is not available'); + } + + $db = new \classes\db($CONFIG_DB); + $db->connect(); + + return $db; + } + + private static function readDatabaseConfigFromEnvironment(): array + { + $readEnv = static function (string $key, string $default = ''): string { + $value = $_ENV[$key] ?? getenv($key); + if ($value === false || $value === null) { + return $default; + } + + return trim((string)$value); + }; + + $host = $readEnv('CONFIG_DB_HOST'); + $user = $readEnv('CONFIG_DB_USER'); + $database = $readEnv('CONFIG_DB_DATABASE'); + + if ($host === '' || $user === '' || $database === '') { + return []; + } + + return [ + 'host' => $host, + 'user' => $user, + 'password' => $readEnv('CONFIG_DB_PASSWORD'), + 'database' => $database, + 'port' => (int)($readEnv('CONFIG_DB_PORT', '3306') ?: '3306'), + 'ssl_mode' => $readEnv('CONFIG_DB_SSL_MODE', 'DISABLED') ?: 'DISABLED', + ]; + } + /** * Get the config variable as a JSON string * @return string