From f5ba9dd891c9d387aeaa4873011993189650b642 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Wed, 23 Jul 2025 09:31:43 +0200 Subject: [PATCH] Add product discount calculation and update handling for dynamic usage logs - Introduced `getProductDiscountPercentage` method to calculate accurate discounts for users across products, categories, and global settings. - Adjusted `importUsageLogs` to refine date modifier parameter and enhance `usage log` checks. - Enhanced `orders_o` to include product discount logic with stricter type casting for consistency. - Added `updateFieldsWhere` method for selective database updates based on specific conditions. - Updated `customers_o` to handle updates for existing customers while adding new ones. --- services/nginx/app/objects/orders_o.php | 4 +- services/nginx/app/objects/users_o.php | 28 +++++++++++ .../nginx/app/objects/xlvask_customers_o.php | 15 +++++- .../nginx/app/objects/xlvask_usage_logs_o.php | 10 ++-- services/nginx/app/traits/db_object_t.php | 47 +++++++++++++++++++ 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index c373bd06..fc0a90a8 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -1152,7 +1152,7 @@ class orders_o extends db } $order_item->getObjectProperties(); $order_item->order_id->set($this->id); // Set the order ID to the simulated order ID - $order_item->product_id->set($product->id); // Set the product ID to the product ID from the wash item + $order_item->product_id->set((int)$product->id); // Set the product ID to the product ID from the wash item $order_item->reference->set(''); // Get the product price based on the department $product_price = (int)$product->getDepartmentPrice((int)$this->department_id->value()); // Get the department price for the product @@ -1161,7 +1161,7 @@ class orders_o extends db if (!$user->exists()) { throw new Exception('No user found matching the customer number in the usage log'); } - $product_price_discount_percentage = (int)$user->getCustomPrice($product->id, false); // Get the custom price discount percentage for the product + $product_price_discount_percentage = (int)$user->getProductDiscountPercentage((int)$order_item->product_id->value()); // Get the custom price discount percentage for the product // Apply the discount percentage to the product price $product_price = (int)($product_price * (1 - ($product_price_discount_percentage / 100))); // Apply the discount percentage to the product price $order_item->notes->set(null); // Set notes for the simulated order item diff --git a/services/nginx/app/objects/users_o.php b/services/nginx/app/objects/users_o.php index 2f77c4c1..c5fd82f4 100644 --- a/services/nginx/app/objects/users_o.php +++ b/services/nginx/app/objects/users_o.php @@ -699,6 +699,34 @@ class users_o extends db return $discount; } + /** + * Get the final price of the product for the user + * This includes: + * - Global discount (If the product allows category inheritance of discounts) + * - Category discount (If the product allows category inheritance of discounts) + * - Product discount (If the product has a custom price) + */ + public function getProductDiscountPercentage(int $product_id): int|null + { + self::requireSelected(); + // Get the product by ID + $product = (new products_o())->select((int)$product_id); + $doesProductAllowCategoryDiscount = (bool)$product->apply_category_discount->value(); + $economic_discount_percentage = $this->getEconomicCustomerDiscountPercentage(); + $discount_percentage = $this->getCustomPrice($product_id); + // If the product allows category inheritance of discounts, get the category discounts + if ($doesProductAllowCategoryDiscount) { + // If the category discount is higher than the product discount, use the category discount + if ($economic_discount_percentage > $discount_percentage) { + $discount_percentage = $economic_discount_percentage; + } + } + // If the discount percentage is null, return 0 + return $discount_percentage === null ? 0 : (int)$discount_percentage; + } + + + /** * Get all custom prices (DISCOUNT) for the user * @return array The custom prices diff --git a/services/nginx/app/objects/xlvask_customers_o.php b/services/nginx/app/objects/xlvask_customers_o.php index 853367c7..ef8845b2 100644 --- a/services/nginx/app/objects/xlvask_customers_o.php +++ b/services/nginx/app/objects/xlvask_customers_o.php @@ -106,8 +106,11 @@ class xlvask_customers_o extends db $new_customers = array_filter($customers, function ($customer) use ($known_customerIds) { return !in_array($customer->customerId, $known_customerIds); }); + /** The already known customers */ + $known_customers = array_filter($customers, function ($customer) use ($known_customerIds) { + return in_array($customer->customerId, $known_customerIds); + }); unset($known_customerIds); - unset($customers); /** Adding the new customers */ foreach ($new_customers as $customer) { if (!$customer->isValid()) { @@ -118,6 +121,16 @@ class xlvask_customers_o extends db $this->add($customer->toArray()); } unset($new_customers); + /** Updating the already known customers */ + foreach ($known_customers as $customer) { + if (!$customer->isValid()) { + echo $customer->formattedDetails(); + throw new Exception('A customer from XL Vask is not valid, have the structure changed?'); + } + // Update the existing customer + $this->updateFieldsWhere($customer->toArray(), ['customerId' => $customer->customerId],1); + unset($customer); // Free up memory + } } /** diff --git a/services/nginx/app/objects/xlvask_usage_logs_o.php b/services/nginx/app/objects/xlvask_usage_logs_o.php index 3713e720..6d1c6d49 100644 --- a/services/nginx/app/objects/xlvask_usage_logs_o.php +++ b/services/nginx/app/objects/xlvask_usage_logs_o.php @@ -86,11 +86,11 @@ class xlvask_usage_logs_o extends db /** * Import the usage logs from XL Vask - * @param string $dateTimeModifier A date time modifier to use for the import, defaults to '-1 day' + * @param string $dateTimeModifier A date time modifier to use for the import, defaults to '-7 days' * @throws Exception If the objects were not successfully added. * @returns void */ - public function importUsageLogs(string $dateTimeModifier = '-7 day'): void + public function importUsageLogs(string $dateTimeModifier = '-7 days'): void { if (!empty($this->id)) { throw new Exception('To prevent issues, having a selected object is not allowed.'); @@ -101,7 +101,11 @@ class xlvask_usage_logs_o extends db /** @var string[] $known_usage_logIds The XL Vask usage logIds currently known */ $known_usage_logIds = array_map(function ($log) { return $log['WashId']; - }, self::getFields(['WashId'])); + }, + self::getFieldsWhere( + ['WashId' => array_column($usage_logs, 'WashId')], + ['WashId'] + )); /** The XL Vask usage logs without a matching WashId in the database */ $new_usage_logs = array_filter($usage_logs, function ($log) use ($known_usage_logIds) { return !in_array($log->WashId, $known_usage_logIds); diff --git a/services/nginx/app/traits/db_object_t.php b/services/nginx/app/traits/db_object_t.php index b91ccbe7..1bb631d9 100644 --- a/services/nginx/app/traits/db_object_t.php +++ b/services/nginx/app/traits/db_object_t.php @@ -5,6 +5,7 @@ namespace traits; use classes\db; use classes\response; use Exception; +use mysqli_result; use objects\bookings_new_o; use objects\bookings_o; use objects\branding_o; @@ -122,6 +123,52 @@ trait db_object_t return $db->fetch_all($result); } + /** + * Update fields in the database where the conditions are met + * @param array $fieldsAndValues The fields and values to update (e.g. ['name' => 'John Doe', 'email' => 'test@email.com']) + * @param array $conditions The conditions to meet for the update (e.g. ['id' => 1, 'status' => 'active']) + * @param int $limit The limit of rows to update, default is 0 (0 means no limit, update all rows that match the conditions) + * @return mysqli_result|bool Returns the result of the update query, or false on failure + */ + public function updateFieldsWhere(array $fieldsAndValues, array $conditions, int $limit = 1): mysqli_result|bool + { + global /** @var db $db */ + $db; + $table = $this->table; + $set = []; + foreach ( $fieldsAndValues as $field => $value ) { + // Escape the value to prevent SQL injection + if (is_null($value)) { + $set[] = "$field = NULL"; + continue; + } + $value = $db->escape_string($value); + $set[] = "$field = '$value'"; + } + $set = implode(', ', $set); + + // Build the WHERE clause + $where = []; + foreach ( $conditions as $field => $value ) { + // Escape the value to prevent SQL injection + if (is_null($value)) { + $where[] = "$field IS NULL"; + } else { + $value = $db->escape_string($value); + $where[] = "$field = '$value'"; + } + } + $where = implode(' AND ', $where); + // If the limit is set, add it to the query + if ($limit > 0) { + $where .= " LIMIT $limit"; + } + + // Execute the update query + $sql = "UPDATE $table SET $set WHERE $where"; + return $db->query($sql); + } + /** * Set the searchable fields * @param array $fields The fields to search in the database (e.g. ['name', 'email'])