From dacb7ef4e0deccc771b254d6478ab62ffbe3b05e Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 24 Jun 2025 08:38:35 +0200 Subject: [PATCH 1/8] Add `xlvask_parser_h_nger` helper class for handling specific XLVask product parsing --- .../xlvask/helpers/xlvask_parser_h_nger.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 services/nginx/app/modules/xlvask/helpers/xlvask_parser_h_nger.php diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_h_nger.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_h_nger.php new file mode 100644 index 00000000..bd5b8f2c --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_h_nger.php @@ -0,0 +1,28 @@ +setup('Hænger'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + // For some reason, in XLVask the primary product is stored as vask_h_nger in some cases, + // This is a workaround to handle that case. (Since the functionality is exactly the same as // xlvask_parser_stor_bil) + return (new xlvask_parser_stor_bil())->parseProduct(...func_get_args()); + } +} \ No newline at end of file From c0ff18761d30562798a4870ce30784d852c96c80 Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 24 Jun 2025 09:16:03 +0200 Subject: [PATCH 2/8] Add `useDynamicPricing` property and `getUnitPriceExVat` method in XLVask helpers. Refactor parsing logic to use integer type casting for price and count. --- .../xlvask/helpers/xlvask_product_parser.php | 1 + .../xlvask/helpers/xlvask_product_parser_t.php | 15 ++++++++++++++- .../modules/xlvask/helpers/xlvask_wash_item.php | 8 +++++++- services/nginx/app/objects/orders_o.php | 4 ++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php index 1ff101c7..4109ad6a 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php @@ -8,6 +8,7 @@ abstract class xlvask_product_parser { use xlvask_product_parser_t; + /** * The setup method initializes the Original Product Name. * This is required for the parser to function correctly. diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php index c77b8d62..199f0dde 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php @@ -8,6 +8,20 @@ use objects\products_o; trait xlvask_product_parser_t { protected string $OriginalProductName; + /** + * This property indicates whether the parser is dynamic. + * When set to true, the parser will handle dynamic pricing. + * This should be set to true if the pricing of the product can change based on certain conditions, + * such as the amount of usage, time of day, or other factors. + * When this is set to true, the parser should implement logic to handle dynamic pricing. + * @example + * The "flowmeter" product parser uses this to calculate the price based on the amount of water used. + * * @note This is used to determine whether the parser should calculate a dynamic price or a static price. + * @see xlvask_product_parser_t::calculateDynamicPrice() + * When this is set to false, the parser should return a static price. (amount x price per unit) + * @var bool $useDynamicPricing + */ + protected bool $useDynamicPricing = false; /** @@ -40,5 +54,4 @@ trait xlvask_product_parser_t */ abstract protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o; - } \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php b/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php index 3d53e6af..02503b36 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php @@ -229,7 +229,7 @@ class xlvask_wash_item extends xlvask_helper // Check if the parameter is empty or matches the default values return $param === null || $param === '' || $param === $this->default_string || $param === $this->default_int || $param === $this->default_bool || $param === $this->default_int_nullable || $param === $this->default_string_nullable || $param === $this->default_bool_nullable; } - + /** * Get the product associated with this wash item. @@ -352,4 +352,10 @@ class xlvask_wash_item extends xlvask_helper // If either PriceIncVat or Count is not numeric, return 0 return 0.0; } + + public function getUnitPriceExVat(): float + { + // Calculate the unit price excluding VAT + return $this->getPriceExVat() / (float)$this->Count; + } } \ No newline at end of file diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index d56f73f7..7ec806e6 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -754,8 +754,8 @@ class orders_o extends db '', '', //$washItem->OriginalProductName, 2285, - $washItem->getPriceExVat(), - $washItem->Count, + (int)$washItem->getUnitPriceExVat(), + (int)$washItem->Count, $firstItemId === null ? null : $firstItemId, // Set the first item as the parent item (if applicable) ); // Set the first item ID for the next item to link to (provided this is the first item) From b65b1346bbc3742faabc3fa988664da2e92c089b Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 24 Jun 2025 09:39:20 +0200 Subject: [PATCH 3/8] =?UTF-8?q?Update=20XLVask=20helpers:=20modify=20parse?= =?UTF-8?q?r=20setup=20string,=20add=20'H=C3=A6nger'=20to=20usage=20log,?= =?UTF-8?q?=20and=20clean=20up=20whitespace.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/modules/xlvask/helpers/xlvask_parser_vask_udf_rt.php | 2 +- services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_vask_udf_rt.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_vask_udf_rt.php index 405bfa5c..8c7e2ce1 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_vask_udf_rt.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_vask_udf_rt.php @@ -11,7 +11,7 @@ class xlvask_parser_vask_udf_rt extends xlvask_product_parser public function __construct() { - $this->setup('vask_udf_rt'); + $this->setup('Vask udført'); } diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php b/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php index e3a6489a..97ec4c5d 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php @@ -312,6 +312,7 @@ class xlvask_usage_log extends xlvask_helper 'Stor bil', 'Lille bil', 'Vask udført', + 'Hænger', ]; usort($generatedItems, function ($a, $b) use ($primaryItems) { // Check if the item is a primary item @@ -362,7 +363,7 @@ class xlvask_usage_log extends xlvask_helper // Check if the parameter is empty or matches the default values return $param === null || $param === '' || $param === $this->default_string || $param === $this->default_int || $param === $this->default_bool || $param === $this->default_int_nullable || $param === $this->default_string_nullable || $param === $this->default_bool_nullable; } - + /** * Get the formatted date of the wash start time. From c16d8729cbff53256d6019fb9e114c35b8bdf5d6 Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 24 Jun 2025 09:48:10 +0200 Subject: [PATCH 4/8] Add `xlvask_parser_h_nger` to XLVask helpers imports for extended parsing functionalities --- services/nginx/app/modules/xlvask/xlvask_helpers.php | 1 + 1 file changed, 1 insertion(+) diff --git a/services/nginx/app/modules/xlvask/xlvask_helpers.php b/services/nginx/app/modules/xlvask/xlvask_helpers.php index e876dfd3..95cca407 100644 --- a/services/nginx/app/modules/xlvask/xlvask_helpers.php +++ b/services/nginx/app/modules/xlvask/xlvask_helpers.php @@ -49,6 +49,7 @@ require_once WD . '/modules/xlvask/helpers/xlvask_parser_specials_be_sider.php'; require_once WD . '/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_p_tag.php'; require_once WD . '/modules/xlvask/helpers/xlvask_parser_ht_osc_sider.php'; require_once WD . '/modules/xlvask/helpers/xlvask_parser_kun_b_rster_ved_hytten.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_h_nger.php'; use helpers\xlvask_cache; use helpers\xlvask_create_customer; From 2b3ebfe357a9854db0b7b02accab3dec835dbd34 Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 24 Jun 2025 10:40:52 +0200 Subject: [PATCH 5/8] Add `xlvask_parser_h_nger` to XLVask helpers imports for extended parsing functionalities --- .../limble/classes/limble_endpoints.php | 32 +++++++++---- .../limble/helpers/limble_object_helper.php | 8 ++++ .../helpers/limble_pagination_helper.php | 24 ++++++++++ .../limble/helpers/limble_task_comment.php | 42 +++++++++++++++++ .../helpers/limble_task_instruction.php | 47 +++++++++++++++++++ .../helpers/limble_tasks_pagination.php | 14 ++++++ .../helpers/limble_webhook_payload_task.php | 20 ++++---- .../limble/interfaces/limble_endpoints_i.php | 14 +++++- .../app/modules/limble/limble_helpers.php | 5 +- .../nginx/app/routes/moduleLimbleRoute.php | 13 ++--- 10 files changed, 190 insertions(+), 29 deletions(-) create mode 100644 services/nginx/app/modules/limble/helpers/limble_object_helper.php create mode 100644 services/nginx/app/modules/limble/helpers/limble_pagination_helper.php create mode 100644 services/nginx/app/modules/limble/helpers/limble_task_comment.php create mode 100644 services/nginx/app/modules/limble/helpers/limble_task_instruction.php create mode 100644 services/nginx/app/modules/limble/helpers/limble_tasks_pagination.php diff --git a/services/nginx/app/modules/limble/classes/limble_endpoints.php b/services/nginx/app/modules/limble/classes/limble_endpoints.php index 6db12c46..d8efbeaa 100644 --- a/services/nginx/app/modules/limble/classes/limble_endpoints.php +++ b/services/nginx/app/modules/limble/classes/limble_endpoints.php @@ -5,6 +5,7 @@ require_once WD . '/modules/limble/interfaces/limble_endpoints_i.php'; require_once WD . '/modules/limble/classes/limble_request.php'; use Exception; +use limble\helpers\limble_tasks_pagination; use limble_endpoints_i; abstract class limble_endpoints extends limble_request implements limble_endpoints_i @@ -14,14 +15,11 @@ abstract class limble_endpoints extends limble_request implements limble_endpoin * @inheritDoc * @throws Exception If the module is not enabled */ - public function listTasks(): array + public function listTasks(limble_tasks_pagination $pagination): array { $this->requireModuleEnabled(); $url = $this->config->api_url . '/tasks'; - $params = [ -// 'status' => 'open', -// 'limit' => 100, - ]; + $params = [...$pagination->getParams()]; return $this->sendRequest( $url, 'GET', @@ -51,6 +49,22 @@ abstract class limble_endpoints extends limble_request implements limble_endpoin ); } + /** + * @inheritDoc + * @throws Exception If the module is not enabled + */ + public function getTaskInstructions(int $taskId): array + { + $this->requireModuleEnabled(); + $url = $this->config->api_url . '/tasks/' . $taskId . '/instructions'; + return $this->sendRequest( + $url, + 'GET', + [], + [self::getAuthHeader(), 'Content-Type: application/json'] + ); + } + /** * @inheritDoc * @throws Exception If the module is not enabled @@ -59,12 +73,14 @@ abstract class limble_endpoints extends limble_request implements limble_endpoin { $this->requireModuleEnabled(); $url = $this->config->api_url . '/tasks'; + // Set the taskId as a pagination parameter + $pagination = new limble_tasks_pagination(); + $pagination->tasks = $taskId; + $pagination->limit = 1; // Limit to 1 task return $this->sendRequest( $url, 'GET', - [ - 'cursor' => $taskId, - ], + [...$pagination->getParams()], [self::getAuthHeader(), 'Content-Type: application/json'] ); } diff --git a/services/nginx/app/modules/limble/helpers/limble_object_helper.php b/services/nginx/app/modules/limble/helpers/limble_object_helper.php new file mode 100644 index 00000000..73563bdc --- /dev/null +++ b/services/nginx/app/modules/limble/helpers/limble_object_helper.php @@ -0,0 +1,8 @@ +slack->send_message('Initializing Limble Webhook Payload Task', 'Limble Webhook Task Initialization'); $this->taskID = (int)($payload['taskID'] ?? 0); $this->status = (string)($payload['status'] ?? ''); $this->category = (string)($payload['category'] ?? ''); $this->user = (string)($payload['user'] ?? ''); - $this->taskObject = new limble_task(((new limble())->getTask($payload['taskID'])) ?: []); - $this->slack->send_message('Task has been initialized with ID: ' . $this->taskID, 'Limble Webhook Task Initialized'); + $this->taskObject = new limble_task(((new limble())->getTask($payload['taskID']))[0] ?? []); $this->notifyWebhook(); } protected function notifyWebhook(): void { + // Generate the message to notify the webhook + $message = ""; + $message .= "Task ID: " . $this->taskID . "\n"; + $message .= "Name: " . $this->taskObject->name . "\n"; + $message .= "Description: " . $this->taskObject->description . "\n"; + $message .= "User: " . $this->user . "\n"; + $message .= "Status: " . $this->status . "\n"; + $message .= "Category: " . $this->category . "\n"; // Notify the webhook with the task object - $this->slack->send_message(' - Task ID: ' . $this->taskID . ' - Status: ' . $this->status . ' - Category: ' . $this->category . ' - User: ' . $this->user, - 'Limble Webhook Notification' - ); + $this->slack->send_message($message, 'Limble Webhook Task Notification'); } /** diff --git a/services/nginx/app/modules/limble/interfaces/limble_endpoints_i.php b/services/nginx/app/modules/limble/interfaces/limble_endpoints_i.php index 49cb8873..47676144 100644 --- a/services/nginx/app/modules/limble/interfaces/limble_endpoints_i.php +++ b/services/nginx/app/modules/limble/interfaces/limble_endpoints_i.php @@ -1,14 +1,18 @@ post('/modules/limble/webhook/task', function () { global $response; $slack = new slack(); - $slack->send_message('Limble Webhook Task Triggered', 'Limble Webhook'); //TODO: Add authentication of some sort here //self::requirePermission('modules_limble_webhooks_task'); // Check if the module is enabled @@ -32,14 +32,7 @@ class moduleLimbleRoute if (json_last_error() !== JSON_ERROR_NONE) { $response->error('Invalid JSON payload', 400); } - $slack->send_message('TaskId: ' . ($payload['taskID'] ?? 'Not provided'), 'Limble Webhook Task ID'); - $slack->send_message('Payload: ' . json_encode($payload), 'Limble Webhook Task Payload'); $task = new limble_webhook_payload_task($payload); - //$task = new $limble->helpers->limble_webhook_payload_task($payload); - $slack->send_message('Limble Webhook Task Received: ' . $task->taskID, 'Limble Webhook Task'); - $slack->send_message('Task Name: ' . $task->taskObject->name, 'Limble Webhook Task Name'); -// $slack->send_message('Task ID: ' . $payload['task_id'], 'Limble Webhook Task ID'); - // Response $response->success('Debug', 200); }, @@ -56,8 +49,10 @@ class moduleLimbleRoute // Check if the module is enabled $limble = new limble(); $limble->requireModuleEnabled(); + $pagination = new limble_tasks_pagination(); + //$pagination->tasks = 2; // Get the tasks - $tasks = $limble->getTasks()->formatTasks($limble->listTasks()); + $tasks = $limble->getTasks()->formatTasks($limble->listTasks($pagination)); // Response $response->success($tasks, 200); }, From 0e90c80ee14a5931fb330addc8198e61b4d66305 Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Thu, 26 Jun 2025 07:59:46 +0200 Subject: [PATCH 6/8] Uncomment vehicle cache retrieval in `xlvask_vehicles` helper. --- services/nginx/app/modules/xlvask/helpers/xlvask_vehicles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_vehicles.php b/services/nginx/app/modules/xlvask/helpers/xlvask_vehicles.php index 102736d0..05813bed 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_vehicles.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_vehicles.php @@ -73,7 +73,7 @@ class xlvask_vehicles extends xlvask_helper $xlvask = new xlvask(); $xlvask_cache = $xlvask->getCache(); if ($xlvask_cache->isVehicleCached($registrationNumber)) { - //return $xlvask_cache->getVehicleCache($registrationNumber); + return $xlvask_cache->getVehicleCache($registrationNumber); } else if ($onlyCache) { return null; // Return null if onlyCache is true and vehicle is not cached } From 5b88ef9dc3e141cac378f57ac5e125a4bfa0d8d4 Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Thu, 26 Jun 2025 08:55:58 +0200 Subject: [PATCH 7/8] Refactor order and order item management: add cache invalidation, improve object change tracking, and enhance `asArray` conversion with caching and expiration support. --- .../objects/collected_order_invoices_o.php | 31 ++++++- services/nginx/app/objects/order_items_o.php | 49 ++++++++-- services/nginx/app/objects/orders_o.php | 93 ++++++++++++------- services/nginx/app/routes/ordersRoute.php | 5 +- services/nginx/app/traits/db_object_t.php | 43 ++++++++- 5 files changed, 172 insertions(+), 49 deletions(-) diff --git a/services/nginx/app/objects/collected_order_invoices_o.php b/services/nginx/app/objects/collected_order_invoices_o.php index 291031be..70f88757 100644 --- a/services/nginx/app/objects/collected_order_invoices_o.php +++ b/services/nginx/app/objects/collected_order_invoices_o.php @@ -90,10 +90,19 @@ class collected_order_invoices_o extends db } /** + * Get the invoice collection as an array * @throws ApiErrorException If the payment method is Stripe and the request fails + * @throws Exception If the request was not successful */ public function asArray(): array { + // Require the invoice collection to be selected + self::requireSelected(); + // Check if the object is cached + $cached = self::getCached('asArray', $this->id); + if ($cached !== null) { + return (array)$cached; + } $tmp = [ 'id' => (int)$this->id, 'customer_number' => (int)$this->customer_number->value(), @@ -130,6 +139,9 @@ class collected_order_invoices_o extends db ]); $tmp['stripe'] = $stripe_details; } + // Cache the result + self::cache('asArray', $tmp, $this->id); + self::setCachedExpiration('asArray', self::$asArrayCacheExpiration, $this->id); return $tmp; } @@ -141,6 +153,8 @@ class collected_order_invoices_o extends db */ public function getOrders(bool $count = false): array|int { + // Require the invoice collection to be selected + self::requireSelected(); $orders = new orders_o(); $order_ids = $orders->getFieldsWhere( [ @@ -542,7 +556,8 @@ class collected_order_invoices_o extends db public function objectChanged(): void { - //TODO: Add cache invalidation + // Invalidate the cache, so the next time the object is requested, it will be fetched from the database + self::deleteCached('asArray', $this->id); } /** @@ -609,6 +624,8 @@ class collected_order_invoices_o extends db foreach ( $orders as $order ) { self::addInvoiceToDraft($order['id'], true, $draft_id, $currency); } + // Object changed + self::objectChanged(); return $this; } @@ -837,6 +854,8 @@ class collected_order_invoices_o extends db // Set the order to the new invoice collection $order_object->invoice_collection_id->set($tmp->id); } + // Invalidate the cache for the invoice collection + $this->objectChanged(); } /** @@ -912,6 +931,7 @@ class collected_order_invoices_o extends db $transaction->invoice_collection_id->set($this->id); $transaction->created_at->set(self::getFirstDayOfMonth($this->created_at->value())); $transaction->objectChanged(); + $this->objectChanged(); // Add the vehicle subscriptions to the transaction $order_items_o = new order_items_o(); foreach ( $vehicle_array as $vehicle ) { @@ -974,6 +994,7 @@ class collected_order_invoices_o extends db $order_item_object->requireSelected(); $order_item_object->include_in_invoice->set(0); $order_item_object->price->set((int)self::getWashSubscriptionPrice((new products_o())->select((int)$order_item['product_id'])->price->value()) / 2); + $order_item_object->objectChanged(); } // Check if the order item product id is equal any of the vehicle subscription addons product id if (isset($vehicle_regs[$order['reg_1']]['addons'][$order_item['product_id']])) { @@ -999,10 +1020,13 @@ class collected_order_invoices_o extends db $order_item_object->requireSelected(); $order_item_object->include_in_invoice->set(0); $order_item_object->price->set($tmp_subscription_price); + $order_item_object->objectChanged(); } } + $order_object->objectChanged(); } } + $this->objectChanged(); //print_r($vehicle_array); } @@ -1034,6 +1058,8 @@ class collected_order_invoices_o extends db $order->requireSelected(); $order->delete(); } + // Clear the cache for the invoice collection + $this->objectChanged(); } private static function getFirstDayOfMonth(string $timestamp): string @@ -1137,7 +1163,10 @@ class collected_order_invoices_o extends db $order_item_object->requireSelected(); $order_item_object->include_in_invoice->set(0); $order_item_object->price->set(0); + $order_item_object->objectChanged(); } } + // Invalidate the cache for the invoice collection + $this->objectChanged(); } } \ No newline at end of file diff --git a/services/nginx/app/objects/order_items_o.php b/services/nginx/app/objects/order_items_o.php index 6db043a6..3cf3c746 100644 --- a/services/nginx/app/objects/order_items_o.php +++ b/services/nginx/app/objects/order_items_o.php @@ -4,6 +4,7 @@ namespace objects; use classes\db; use classes\object_property; +use Exception; use traits\db_object_t; class order_items_o extends db @@ -62,11 +63,6 @@ class order_items_o extends db $this->setTable('order_items'); } - public function objectChanged(): void - { - // No need to invalidate the cache, since the order_items object is not cached - } - public function getOrderItemById(int $id): order_items_o { global $db; @@ -113,11 +109,37 @@ class order_items_o extends db if ($related_item_id) { $this->related_item_id->set($related_item_id); } - } catch (\Exception $e) { + // Inform the order object that a new item has been added + $this->getOrder()->objectChanged(); + } catch (Exception $e) { $response->error($e->getMessage()); } } + /** + * @throws Exception + */ + public function objectChanged(): void + { + // This method is called when the object is changed, to inform the order object + // that an item has been added, edited or removed. + // This is used to update the order total and other related properties. + $order = $this->getOrder(); + $order->objectChanged(); + } + + /** + * Get the order object associated with this order item + * @return orders_o The order object associated with this order item + * @throws Exception If the order item is not selected + * @throws Exception If no order is selected + */ + public function getOrder(): orders_o + { + self::requireSelected(); + return (new orders_o())->select($this->id); + } + public function edit(int $id, int $order_id, int $product_id, string $reference, string $notes, int $cashier_id, int $price, int $quantity): void { global $db, $response; @@ -132,7 +154,9 @@ class order_items_o extends db // Set the values of the object properties $this->getObjectProperties(); - } catch (\Exception $e) { + // Inform the order object that an item has been edited + $this->objectChanged(); + } catch (Exception $e) { $response->error($e->getMessage()); } } @@ -173,13 +197,17 @@ class order_items_o extends db if ($notes) { $this->notes->set($notes); } + $this->objectChanged(); - } catch (\Exception $e) { + } catch (Exception $e) { $response->error($e->getMessage()); } } + /** + * @throws Exception + */ public function removeOrderItem(int $id): void { // TODO: Implement delete() method instead @@ -187,6 +215,8 @@ class order_items_o extends db $this->id = $id; $sql = "DELETE FROM $this->table WHERE id = $this->id or related_item_id = $this->id"; $db->query($sql); + // Inform the order object that an item has been removed + $this->getOrder()->objectChanged(); } public function getItemAsArray(): array @@ -236,7 +266,8 @@ class order_items_o extends db $db->query($sql); // Set the values of the object properties $this->getObjectProperties(); - } catch (\Exception $e) { + $this->objectChanged(); + } catch (Exception $e) { $response->error($e->getMessage()); } } diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index 7ec806e6..9f5c7d82 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -34,6 +34,7 @@ class orders_o extends db public object_property $wash_id; // The XL Vask Wash ID, if any public object_property $lane; // The lane used for the order, if any + public function structure(): void { $this->setTable('orders'); @@ -116,9 +117,49 @@ class orders_o extends db { // Set the deleted_at property to the current timestamp $this->deleted_at->set(date('Y-m-d H:i:s')); + $this->objectChanged(); // Save the object } + /** + * @throws Exception If the order is not selected + * This function is called when the order object is changed. + */ + public function objectChanged(): void + { + self::requireSelected(); + // Reset the cached object + self::deleteCached('asArray', $this->id); + // Inform the order collection that the order has changed + $this->getOrderCollection()->objectChanged(); + } + + /** + * Get the order collection for the order + * @return collected_order_invoices_o The order collection + * @throws Exception If the order is not selected + */ + public function getOrderCollection(): collected_order_invoices_o + { + self::requireSelected(); + $order_collection = new collected_order_invoices_o(); + $order_collection->select($this->invoice_collection_id->value()); + if (!$order_collection->exists()) { + throw new Exception('Order collection not found'); + } + return $order_collection; + } + + public function exists(): bool + { + // Check if the id is greater than 0, and that the deleted_at property is null + if ($this->id > 0) { + $this->getObjectProperties(); + return $this->deleted_at->value() === null; + } + return false; + } + public function restore(): void { // Set the deleted_at property to null @@ -192,16 +233,6 @@ class orders_o extends db $this->{$data['field']}->set($data['value']); } - public function exists(): bool - { - // Check if the id is greater than 0, and that the deleted_at property is null - if ($this->id > 0) { - $this->getObjectProperties(); - return $this->deleted_at->value() === null; - } - return false; - } - /** * Mark the order as completed * @throws Exception If the order is not selected @@ -223,11 +254,6 @@ class orders_o extends db $this->objectChanged(); } - public function objectChanged(): void - { - // Since the orders object is not cached, there is no need to invalidate the cache - } - /** * Get the order by invoice id * @param int $invoiceId @@ -427,22 +453,6 @@ class orders_o extends db return $count; } - /** - * Get the order collection for the order - * @return collected_order_invoices_o The order collection - * @throws Exception If the order is not selected - */ - public function getOrderCollection(): collected_order_invoices_o - { - self::requireSelected(); - $order_collection = new collected_order_invoices_o(); - $order_collection->select($this->invoice_collection_id->value()); - if (!$order_collection->exists()) { - throw new Exception('Order collection not found'); - } - return $order_collection; - } - /** * Get the wash subscription transactions for a customer * @throws Exception If something goes wrong @@ -473,9 +483,22 @@ class orders_o extends db ); } - public function asArray(): array + /** + * @return array The order as an array + * @throws Exception + * Convert the order object to an array + */ + public function asArray(bool $skipCache = false): array { - return [ + self::requireSelected(); + if (!$skipCache) { + // Check if the object is cached + $cached = self::getCached('asArray', $this->id); + if ($cached) { + return (array)$cached; + } + } + $tmp = [ 'id' => $this->id, 'customer_id' => (int)$this->customer_id->value(), 'cashier_id' => (int)$this->cashier_id->value(), @@ -495,6 +518,10 @@ class orders_o extends db 'lane' => $this->lane->value(), 'closed_at' => (int)$this->invoice_collection_id->value() ? (new collected_order_invoices_o())->select((int)$this->invoice_collection_id->value())->closed_at->value() : null, ]; + // Cache the object + self::cache('asArray', $tmp, $this->id); + self::setCachedExpiration('asArray', self::$asArrayCacheExpiration, $this->id); + return $tmp; } public function getNetAmount(): float diff --git a/services/nginx/app/routes/ordersRoute.php b/services/nginx/app/routes/ordersRoute.php index 3e737e33..f9a97d01 100644 --- a/services/nginx/app/routes/ordersRoute.php +++ b/services/nginx/app/routes/ordersRoute.php @@ -41,7 +41,7 @@ class ordersRoute // Log the incident (new logs_o())->add('orders', 'global', 1, $user->id, 'LIST_ORDERS', 'Successfully listed orders'); // Create economic_module_orders object - $economic_module_orders = new economic_module_orders(); + //$economic_module_orders = new economic_module_orders(); $orders = new orders_o(); if (!$restrict_only_own) { $department_ids = $user->getGroup()->getDepartments(); @@ -216,7 +216,8 @@ class ordersRoute if (isset($data['created_at'])) { $order->created_at->set($data['created_at']); } - // If the department ID is set, validate it + // Void any cached key for the order + $order->objectChanged(); // Log the incident (new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'EDIT_ORDER', 'Successfully updated an order (ID: ' . $data['id'] . ')'); // Return a success message diff --git a/services/nginx/app/traits/db_object_t.php b/services/nginx/app/traits/db_object_t.php index a71a31fa..f8185b4d 100644 --- a/services/nginx/app/traits/db_object_t.php +++ b/services/nginx/app/traits/db_object_t.php @@ -44,10 +44,11 @@ use objects\users_o; trait db_object_t { - public int $id; // The id of the object in the database - private string $table; // The table of the objects in the database (e.g. users) - private array $searchableFields = []; // The fields to search in the database (e.g. ['name', 'email']). If empty, all fields will be searched - private array $whereClauses = []; // The where clauses to add to the pagination query + public static int $asArrayCacheExpiration = 600; // The id of the object in the database + public int $id; // The table of the objects in the database (e.g. users) + private string $table; // The fields to search in the database (e.g. ['name', 'email']). If empty, all fields will be searched + private array $searchableFields = []; // The where clauses to add to the pagination query + private array $whereClauses = []; // The cache expiration time for the asArray function, in seconds. This is used to cache the result of the asArray function to improve performance. Default is 10 minutes (600 seconds). public function __construct() { @@ -744,6 +745,40 @@ trait db_object_t redis->set($this->table . '_' . $objectId . '_' . $key, $data); } + /** + * Set cached object expiration time + * @param string $key The key to set the cached object expiration time + * @param int $seconds The number of seconds to set the cached object expiration time + * @param null $objectId + * @return void + */ + public function setCachedExpiration(string $key, int $seconds, $objectId = null): void + { + // If the object id is not set, use the object id + if (!$objectId) { + $objectId = $this->id; + } + // Set the expiration time for the cached data + redis->expire($this->table . '_' . $objectId . '_' . $key, $seconds); + } + + /** + * Get a cached object key (redis key) + * @param string $key The key to get the cached object + * @return string The cached object key + * @throws Exception If the object is not selected, it throws an exception + */ + public function getCachedKey(string $key, $objectId = null): string + { + self::requireSelected(); + // If the object id is not set, use the object id + if (!$objectId) { + $objectId = $this->id; + } + // Return the cached data key + return $this->table . '_' . $objectId . '_' . $key; + } + /** * Get cached object * @param string $key The key to get the cached object From 1bcd15fa2f0783b0d8bfef13a0caf4379a7803ab Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Thu, 26 Jun 2025 12:33:38 +0200 Subject: [PATCH 8/8] Enhance `customer_vehicles_o`: add `asArray` caching with expiration, implement cache invalidation in object changes, and improve vehicle update flow with cache management. --- .../nginx/app/objects/customer_vehicles_o.php | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/services/nginx/app/objects/customer_vehicles_o.php b/services/nginx/app/objects/customer_vehicles_o.php index 43ad9905..d692819d 100644 --- a/services/nginx/app/objects/customer_vehicles_o.php +++ b/services/nginx/app/objects/customer_vehicles_o.php @@ -43,6 +43,11 @@ class customer_vehicles_o extends db public function asArray(): array { self::requireSelected(); + // Check if the vehicle is cached + $cached = $this->getCached('asArray', $this->id); + if ($cached) { + return (array)$cached; + } $wash_subscription = (bool)$this->wash_subscription->value(); $addons = self::transformObjectsToArray( self::getAddons() @@ -53,7 +58,7 @@ class customer_vehicles_o extends db $last_order_id = self::getLastOrderId(); $customer_number = (int)$this->customer_id->value(); $xlvask = $this->hasXLVask() ? $this->getXLVask() : null; - return [ + $tmp = [ 'id' => (int)$this->id, 'user_id' => (int)(new users_o())->getUserIdFromEconomic((int)$customer_number), 'customer_id' => (int)$this->customer_id->value(), @@ -74,6 +79,10 @@ class customer_vehicles_o extends db return $vehicle_type->toArray(); }, $this->getVehicleTypes()), ]; + // Cache the result + $this->cache('asArray', $tmp, $this->id); + $this->setCachedExpiration('asArray', self::$asArrayCacheExpiration, $this->id); + return $tmp; } /** @@ -237,7 +246,8 @@ class customer_vehicles_o extends db public function objectChanged(): void { - // Since the customer_vehicles object is not cached, there is no need to invalidate the cache + // Clear the cache for the object + $this->deleteCached('asArray', $this->id); } public function structure(): void @@ -245,6 +255,11 @@ class customer_vehicles_o extends db $this->setTable('customer_vehicles'); } + public function delete() + { + + } + public function getObjectProperties(): void { $this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'int', true); @@ -345,6 +360,7 @@ class customer_vehicles_o extends db $xlvask->updateVehicle($xlvask_vehicle); // Cache the updated vehicle $xlvask->getCache()->setVehicleCache($xlvask_vehicle->registrationNumber, $xlvask_vehicle); + $this->objectChanged(); } /** @@ -393,6 +409,7 @@ class customer_vehicles_o extends db /** @var xlvask_vehicles $vehicles */ $xlvask_vehicle = $vehicles::getVehicleByRegistrationNumber((string)$this->reg->value()); $xlvask->getCache()->setVehicleCache($xlvask_vehicle->registrationNumber, $xlvask_vehicle); + $this->objectChanged(); return $xlvask_vehicle; } @@ -429,5 +446,6 @@ class customer_vehicles_o extends db $xlvask->updateVehicle($xlvask_vehicle); // Cache the updated vehicle $xlvask->getCache()->setVehicleCache($xlvask_vehicle->registrationNumber, $xlvask_vehicle); + $this->objectChanged(); } } \ No newline at end of file