- Add Redis mget method and cache management improvements

- Update cache expiration times for `economicCustomerName` and `isBooked` objects
- Introduce `getCachedForMultipleObjects` for batch cache retrieval
- Optimize `isBooked` with optional caching and update to store results
- Implement `getCustomerNames` in `users_o` with caching for bulk name retrieval
- Refactor customer transaction handling in `InvoicingPeriodRoute` for efficiency
- Filter orders excluded from invoicing in `collected_order_invoices_o`
This commit is contained in:
Jeppe Bundgaard
2025-11-26 10:01:24 +01:00
parent 7e9c456074
commit ecd44a455a
6 changed files with 168 additions and 54 deletions
+6
View File
@@ -392,4 +392,10 @@ class redis implements redis_i
return 'temporary_cache_' . uniqid();
}
public function mget(array $array_map): array
{
// Get multiple keys from Redis
return $this->redis->mget($array_map);
}
}
@@ -918,6 +918,13 @@ class collected_order_invoices_o extends db
self::removeVehicleSubscriptionsTransactions();
// Get the orders in the invoice collection
$orders = self::getOrders();
// Remove any orders exempted from invoicing
$orders = array_filter($orders, function ($order) {
$order_object = new orders_o();
$order_object->select((int)$order['id']);
$order_object->requireSelected();
return $order_object->isIncludedInInvoicing();
});
// Get the customers vehicle subscriptions
$vehicles_o = new customer_vehicles_o();
$vehicle_ids = $vehicles_o->getFieldsWhere(
+15 -2
View File
@@ -759,16 +759,29 @@ class orders_o extends db
/**
* @throws Exception
*/
public function isBooked(): bool
public function isBooked(bool $useCache = false): bool
{
$this->requireSelected();
// Check if we should use the cached value
if ($useCache) {
$cached = self::getCached('isBooked', $this->id);
if ($cached !== null) {
return (bool)$cached;
}
}
// Check the invoice collection has been booked
if ((int)$this->invoice_collection_id->value() > 0) {
$invoice_collection = (new collected_order_invoices_o())->select((int)$this->invoice_collection_id->value());
self::requireSelected();
return $invoice_collection->isBooked();
$isBooked = $invoice_collection->isBooked();
// Cache the result
self::cache('isBooked', $isBooked, $this->id);
self::setCachedExpiration('isBooked', self::$isBookedCacheExpiration, $this->id);
return $isBooked;
} else {
// If there is no invoice collection, the order is not booked
self::cache('isBooked', false, $this->id);
self::setCachedExpiration('isBooked', self::$isBookedCacheExpiration, $this->id);
return false;
}
}
+61
View File
@@ -5,6 +5,7 @@ namespace objects;
use classes\db;
use classes\language_packs;
use classes\object_property;
use classes\redis;
use classes\response;
use classes\xlvask;
use customers\economic_customer_mo;
@@ -42,6 +43,7 @@ class users_o extends db
public object_property $wash_certificate_email; // Optional
protected array $wash_subscription_transactions;
public function structure(): void
{
$this->setTable('users');
@@ -401,6 +403,9 @@ class users_o extends db
return $array;
}
/**
* @throws Exception
*/
public function getCustomerName(int $customer_number): string|null
{
// Get the customer from the customer object
@@ -1400,4 +1405,60 @@ class users_o extends db
$this->objectChanged();
}
/**
* @param int[] $customer_numbers
* @return array<string, int> Map of customer number to customer name
*/
public function getCustomerNames(array $customer_numbers): array
{
global $db;
$customer_numbers = array_map('intval', $customer_numbers);
// Look in the cache first
$customer_numbers_to_fetch = [];
$customer_names_cached = self::getCachedForMultipleObjects('economic_customer_name', $customer_numbers);
// Loop through the customer numbers and check if they are cached
$customer_names = array_map(function ($cached_name) {
return $cached_name ? json_decode($cached_name)->name : null;
}, array_values($customer_names_cached));
// Set the names for the cached customer numbers [ "customer_number" => "customer_name" ]
$customer_names = array_combine(
array_map('strval', $customer_numbers),
$customer_names
);
// Find the customer numbers that are not cached
foreach ( $customer_names as $customer_number => $customer_name ) {
if ($customer_name === null) {
$customer_numbers_to_fetch[] = (int)$customer_number;
}
}
// Fetch the remaining customer names from E-conomic
if (count($customer_numbers_to_fetch) > 0) {
foreach ( $customer_numbers_to_fetch as $customer_number ) {
// Get the customer name from the external source
try {
// Try to get the economic customer data cached in the user
$tmp_user = new users_o();
$tmp_user->getUserByCustomerNumber($customer_number);
$cached_name = $tmp_user->getCached('economic_customer');
// If not cached, fetch from E-conomic
if (!$cached_name) {
$tmp_user->getCustomerEcocomicData($customer_number);
$cached_name = $tmp_user->getCached('economic_customer');
}
if ($cached_name) {
$customer_names[(string)$customer_number] = $cached_name->name;
}
// Cache the name
$this->cache('economic_customer_name', $cached_name, $customer_number);
$this->setCachedExpiration('economic_customer_name', self::$economicCustomerNameCacheExpiration, $customer_number);
} catch ( Exception $e ) {
// Ignore exceptions
$customer_names[(string)$customer_number] = 'Unable to fetch name';
}
}
}
// Return the customer names
return $customer_names;
}
}
@@ -640,21 +640,24 @@ class InvoicingPeriodRoute
*/
$customer_numbers = [];
$tmp = [];
foreach ( $customers as $customer ) {
$customer_number = (int)$customer->customer_number->value();
if (empty($customer_number)) {
// Skip if the customer number is empty
continue;
self::debugGetTime(function () use ($customers, &$customer_numbers) {
// Process the customer numbers to ensure they are unique
foreach ( $customers as $customer ) {
$customer_number = (int)$customer->customer_number->value();
if (empty($customer_number)) {
// Skip if the customer number is empty
continue;
}
// Check if the customer number is already in the array
// This ensures that we only process each customer number once
// We use (int)$customer_number to ensure that the customer number is an integer
if (isset($customer_numbers[$customer_number])) {
continue;
}
// Add the customer number to the array
$customer_numbers[$customer_number] = $customer->id;
}
// Check if the customer number is already in the array
// This ensures that we only process each customer number once
// We use (int)$customer_number to ensure that the customer number is an integer
if (isset($customer_numbers[$customer_number])) {
continue;
}
// Add the customer number to the array
$customer_numbers[$customer_number] = $customer->id;
}
}, 'process_customer_numbers');
// Get the transactions for the customers in the specified date range
/**
* @example
@@ -666,48 +669,58 @@ class InvoicingPeriodRoute
* ]
* @var $customer_number_transactions
*/
$customer_number_transactions = (new orders_o())->getTransactionsForCustomersInDateRange(array_keys($customer_numbers), $dateFrom, $dateTo);
self::debugGetTime(function () use ($customer_numbers, $dateFrom, $dateTo, &$customer_number_transactions) {
$customer_number_transactions = (new orders_o())->getTransactionsForCustomersInDateRange(array_keys($customer_numbers), $dateFrom, $dateTo);
}, 'get_transactions_for_customers_in_date_range');
// Calculate the total amount for each transaction, to minimize the number of queries
$transaction_ids = [];
foreach ( $customer_number_transactions as $customer_number => $transactions ) {
foreach ( $transactions as $transaction ) {
if ($transaction instanceof orders_o) {
$transaction_ids[] = $transaction->id;
self::debugGetTime(function () use ($customer_number_transactions) {
// Get all transaction ids
$transaction_ids = [];
foreach ( $customer_number_transactions as $customer_number => $transactions ) {
foreach ( $transactions as $transaction ) {
if ($transaction instanceof orders_o) {
$transaction_ids[] = $transaction->id;
}
}
}
}
// Get the total amount for each transaction
$transaction_totals = (new orders_o())->getNetAmountForOrders($transaction_ids);
// Add the total amount to each transaction
foreach ( $customer_number_transactions as $customer_number => $transactions ) {
foreach ( $transactions as $transaction ) {
if ($transaction instanceof orders_o) {
// Set the total amount for the transaction
$transaction->setTemporaryNetAmount($transaction_totals[$transaction->id] ?? 0);
// Get the total amount for each transaction
$transaction_totals = (new orders_o())->getNetAmountForOrders($transaction_ids);
// Add the total amount to each transaction
foreach ( $customer_number_transactions as $customer_number => $transactions ) {
foreach ( $transactions as $transaction ) {
if ($transaction instanceof orders_o) {
// Set the total amount for the transaction
$transaction->setTemporaryNetAmount($transaction_totals[$transaction->id] ?? 0);
}
}
}
}
}, 'calculate_transaction_totals');
// Get the customer names from the cache
$customer_names = (new \objects\users_o())->getCustomerNames(array_keys($customer_numbers));
// Process the customer numbers to ensure they are unique
foreach ( $customer_numbers as $customer_number => $user_id ) {
if (empty($customer_number)) {
// Skip if the customer number is empty
continue;
self::debugGetTime(function () use ($customer_numbers, $customer_number_transactions, &$tmp, $customer_names) {
foreach ( $customer_numbers as $customer_number => $user_id ) {
if (empty($customer_number)) {
// Skip if the customer number is empty
continue;
}
// Check if the customer number is already in the array
if (isset($tmp[$customer_number])) {
// If the customer number is already in the array, skip it
continue;
}
// If the customer number is not in the array, add it
// Construct the customer object with transactions
$tmp[] = self::constructCustomerObject(
(int)$customer_number,
$customer_names[(int)$customer_number] ?? 'Unknown Customer',
//(new \objects\users_o())->getCustomerName((int)$customer_number),
$customer_number_transactions[(int)$customer_number] ?? [],
false,
(int)$user_id,
);
}
// Check if the customer number is already in the array
if (isset($tmp[$customer_number])) {
// If the customer number is already in the array, skip it
continue;
}
// If the customer number is not in the array, add it
// Construct the customer object with transactions
$tmp[] = self::constructCustomerObject(
(int)$customer_number,
(new \objects\users_o())->getCustomerName((int)$customer_number),
$customer_number_transactions[(int)$customer_number] ?? [],
false,
(int)$user_id,
);
}
}, 'construct_customer_objects');
return $tmp;
}
@@ -749,7 +762,7 @@ class InvoicingPeriodRoute
'id' => $transaction->id,
'date' => $transaction->created_at->value(),
'amount' => $transaction->temporary_net_amount, // Use the temporary net amount set earlier
'booked' => $transaction->isBooked(),
'booked' => $transaction->isBooked(true),
'excluded' => !$transaction->isIncludedInInvoicing()
];
}
+16 -2
View File
@@ -49,8 +49,9 @@ use objects\users_o;
trait db_object_t
{
public static int $economicCustomerNameCacheExpiration = 86400; // The economic customer name cache expiration time, in seconds. Default is 1 day (86400 seconds).
public static int $asArrayCacheExpiration = 600; // The id of the object in the database
public static int $isBookedCacheExpiration = 60; // The booked / not booked cache expiration time, in seconds. Default is 1 minute (60 seconds).
public static int $isBookedCacheExpiration = 300; // The booked / not booked cache expiration time, in seconds. Default is 1 minute (60 seconds).
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
@@ -914,6 +915,19 @@ trait db_object_t
redis->set($this->table . '_' . $objectId . '_' . $key, $data);
}
/**
* Get cached values for multiple objects
* @param string $key The key to get the cached object
* @param array $objectIds The object ids to get the cached objects for
* @return array The cached object keys
*/
public function getCachedForMultipleObjects(string $key, array $objectIds): array
{
return redis->mget(array_map(function($objectId) use ($key) {
return $this->table . '_' . $objectId . '_' . $key;
}, $objectIds));
}
/**
* Set cached object expiration time
* @param string $key The key to set the cached object expiration time
@@ -939,9 +953,9 @@ trait db_object_t
*/
public function getCachedKey(string $key, $objectId = null): string
{
self::requireSelected();
// If the object id is not set, use the object id
if (!$objectId) {
self::requireSelected();
$objectId = $this->id;
}
// Return the cached data key