Add caching methods for invoice period flags and cron jobs for warming caches

- Introduced methods for caching, retrieving, and clearing manual and automatic invoice period flags, as well as order item rows, using the Redis interface.
- Implemented `warmManualFlagsCache` and `warmAutomaticFlagsForPeriod` methods in `invoice_period_flag_service` to enhance performance by loading flags and order items into cache.
- Added new cron jobs `WarmInvoicePeriodManualFlagsCron` and `WarmInvoicePeriodAutomaticFlagsCron` to regularly update cached data for improved access speeds.
This commit is contained in:
Jeppe Bundgaard
2026-05-12 14:04:51 +02:00
parent c8aba05bc1
commit d9813a3fe2
4 changed files with 300 additions and 21 deletions
@@ -25,6 +25,7 @@ class invoice_period_flag_service
private array $economicCustomerDiscountCache = [];
private array $userDisplayNameCache = [];
private array $orderItemsPreviewCache = [];
private ?array $manualFlagsInstanceCache = null;
public function __construct()
{
@@ -186,6 +187,7 @@ class invoice_period_flag_service
*/
public function applyFlagsToPeriodTypes(array $types, string $dateFrom, string $dateTo, ?array $onlyCustomerNumbers = null): array
{
global $response;
$context = $this->buildPeriodContext($types, $dateFrom, $dateTo, $onlyCustomerNumbers);
$manualFlags = $this->getManualFlagsForPeriod($context, $dateFrom, $dateTo, $onlyCustomerNumbers);
$automaticFlags = $this->getAutomaticFlagsForPeriod($dateFrom, $dateTo, $onlyCustomerNumbers);
@@ -317,6 +319,30 @@ class invoice_period_flag_service
return $this->formatStoredFlag($row);
}
public function warmManualFlagsCache(): void
{
global $db;
$result = $db->query(
"SELECT * FROM invoice_period_flags
WHERE source = '" . self::SOURCE_MANUAL . "'
AND status = '" . self::STATUS_ACTIVE . "'
ORDER BY created_at ASC, id ASC"
);
$flags = [];
if ($result) {
while ($row = $result->fetch_assoc()) {
$flags[] = $this->formatStoredFlag($row);
}
}
try {
(new redis())->cache_invoice_period_manual_flags($flags);
} catch (Throwable) {
}
}
private function formatStoredFlag(array $row): array
{
$context = [];
@@ -353,6 +379,23 @@ class invoice_period_flag_service
];
}
private function getCachedManualFlags(): array
{
try {
$flags = (new redis())->get_invoice_period_manual_flags();
} catch (Throwable) {
return [];
}
if (!is_array($flags)) {
return [];
}
return array_values(array_filter($flags, static function ($flag): bool {
return is_array($flag);
}));
}
private function buildPeriodContext(array $types, string $dateFrom, string $dateTo, ?array $onlyCustomerNumbers): array
{
$customerNumbers = [];
@@ -398,16 +441,11 @@ class invoice_period_flag_service
private function getManualFlagsForPeriod(array $context, string $dateFrom, string $dateTo, ?array $onlyCustomerNumbers): array
{
global $db;
$result = $db->query(
"SELECT * FROM invoice_period_flags
WHERE source = '" . self::SOURCE_MANUAL . "'
AND status = '" . self::STATUS_ACTIVE . "'
ORDER BY created_at ASC, id ASC"
);
if (!$result || $result->num_rows === 0) {
if ($this->manualFlagsInstanceCache === null) {
$this->manualFlagsInstanceCache = $this->getCachedManualFlags();
}
$cachedFlags = $this->manualFlagsInstanceCache;
if (empty($cachedFlags)) {
return [];
}
@@ -424,7 +462,7 @@ class invoice_period_flag_service
}
$flags = [];
while ($row = $result->fetch_assoc()) {
foreach ($cachedFlags as $row) {
$targetType = (string)$row['target_type'];
$targetId = (int)$row['target_id'];
$customerNumber = null;
@@ -464,7 +502,7 @@ class invoice_period_flag_service
continue;
}
$flag = $this->formatStoredFlag($row);
$flag = $row;
$flag['customer_number'] = $customerNumber;
$flag['message'] = (string)$flag['reason'];
$flags[] = $flag;
@@ -475,16 +513,43 @@ class invoice_period_flag_service
private function getAutomaticFlagsForPeriod(string $dateFrom, string $dateTo, ?array $onlyCustomerNumbers): array
{
$rows = $this->getPeriodOrderItemRows($dateFrom, $dateTo, $onlyCustomerNumbers);
$attributes = $this->getCustomerAttributes($onlyCustomerNumbers);
try {
$flags = (new redis())->get_invoice_period_automatic_flags($dateFrom, $dateTo);
} catch (Throwable) {
return [];
}
return array_merge(
if (!is_array($flags)) {
return [];
}
if ($onlyCustomerNumbers === null) {
return $flags;
}
$allowed = array_fill_keys(array_map('intval', $onlyCustomerNumbers), true);
return array_values(array_filter($flags, static function (array $flag) use ($allowed): bool {
return isset($allowed[(int)($flag['customer_number'] ?? 0)]);
}));
}
public function warmAutomaticFlagsForPeriod(string $dateFrom, string $dateTo): void
{
$rows = $this->getPeriodOrderItemRows($dateFrom, $dateTo, null);
$attributes = $this->getCustomerAttributes(null);
$flags = array_merge(
$this->detectCustomerRuleViolations($rows, $attributes),
$this->detectPriceMismatches($rows),
$this->detectAbnormalQuantities($rows, $dateFrom, $dateTo),
$this->detectVehicleTypeMismatches($rows, $dateFrom),
$this->detectMissingXlVaskLinks($dateFrom, $dateTo, $onlyCustomerNumbers)
$this->detectMissingXlVaskLinks($dateFrom, $dateTo, null)
);
try {
(new redis())->cache_invoice_period_automatic_flags($dateFrom, $dateTo, $flags);
} catch (Throwable) {
}
}
private function filterSuppressedAutomaticFlags(array $flags): array
@@ -523,12 +588,44 @@ class invoice_period_flag_service
}
private function getPeriodOrderItemRows(string $dateFrom, string $dateTo, ?array $onlyCustomerNumbers): array
{
try {
$rows = (new redis())->get_invoice_period_order_item_rows($dateFrom, $dateTo);
} catch (Throwable) {
return [];
}
if (!is_array($rows)) {
return [];
}
$this->seedOrderItemsPreviewCacheFromRows($rows);
if ($onlyCustomerNumbers === null) {
return $rows;
}
$allowed = array_fill_keys(array_map('intval', $onlyCustomerNumbers), true);
return array_values(array_filter($rows, static function (array $row) use ($allowed): bool {
return isset($allowed[(int)($row['customer_number'] ?? 0)]);
}));
}
public function warmOrderItemRowsForPeriod(string $dateFrom, string $dateTo): void
{
$rows = $this->fetchOrderItemRowsFromDb($dateFrom, $dateTo);
try {
(new redis())->cache_invoice_period_order_item_rows($dateFrom, $dateTo, $rows);
} catch (Throwable) {
}
}
private function fetchOrderItemRowsFromDb(string $dateFrom, string $dateTo): array
{
global $db;
$customerFilter = $this->customerFilterSql('o.customer_id', $onlyCustomerNumbers);
$dateFrom = $db->escape_string($dateFrom);
$dateTo = $db->escape_string($dateTo);
$escapedDateFrom = $db->escape_string($dateFrom);
$escapedDateTo = $db->escape_string($dateTo);
$sql = "
SELECT
@@ -593,9 +690,8 @@ class invoice_period_flag_service
) category_discount
ON category_discount.customer_number = o.customer_id
AND category_discount.product_or_category_id = p.category
WHERE o.created_at BETWEEN '{$dateFrom}' AND '{$dateTo}'
WHERE o.created_at BETWEEN '{$escapedDateFrom}' AND '{$escapedDateTo}'
AND o.deleted_at IS NULL
{$customerFilter}
ORDER BY o.customer_id, o.id, oi.id";
$result = $db->query($sql);
+83
View File
@@ -364,6 +364,89 @@ class redis implements redis_i
return $this;
}
/**
* @inheritDoc
*/
public function cache_invoice_period_manual_flags(array $flags): self
{
$this->set_array('invoice_period_manual_flags', $flags);
return $this;
}
/**
* @inheritDoc
*/
public function get_invoice_period_manual_flags(): array|null
{
return $this->get_array('invoice_period_manual_flags');
}
/**
* @inheritDoc
*/
public function clear_invoice_period_manual_flags(): self
{
$this->delete('invoice_period_manual_flags');
return $this;
}
private function invoicePeriodCacheKey(string $prefix, string $dateFrom, string $dateTo): string
{
return $prefix . ':' . $dateFrom . ':' . $dateTo;
}
/**
* @inheritDoc
*/
public function cache_invoice_period_automatic_flags(string $dateFrom, string $dateTo, array $flags): self
{
$this->set_array($this->invoicePeriodCacheKey('invoice_period_automatic_flags', $dateFrom, $dateTo), $flags);
return $this;
}
/**
* @inheritDoc
*/
public function get_invoice_period_automatic_flags(string $dateFrom, string $dateTo): array|null
{
return $this->get_array($this->invoicePeriodCacheKey('invoice_period_automatic_flags', $dateFrom, $dateTo));
}
/**
* @inheritDoc
*/
public function clear_invoice_period_automatic_flags(string $dateFrom, string $dateTo): self
{
$this->delete($this->invoicePeriodCacheKey('invoice_period_automatic_flags', $dateFrom, $dateTo));
return $this;
}
/**
* @inheritDoc
*/
public function cache_invoice_period_order_item_rows(string $dateFrom, string $dateTo, array $rows): self
{
$this->set_array($this->invoicePeriodCacheKey('invoice_period_order_item_rows', $dateFrom, $dateTo), $rows);
return $this;
}
/**
* @inheritDoc
*/
public function get_invoice_period_order_item_rows(string $dateFrom, string $dateTo): array|null
{
return $this->get_array($this->invoicePeriodCacheKey('invoice_period_order_item_rows', $dateFrom, $dateTo));
}
/**
* @inheritDoc
*/
public function clear_invoice_period_order_item_rows(string $dateFrom, string $dateTo): self
{
$this->delete($this->invoicePeriodCacheKey('invoice_period_order_item_rows', $dateFrom, $dateTo));
return $this;
}
/**
* @inheritDoc
*/
+31
View File
@@ -4,6 +4,7 @@
use classes\backup_store;
use classes\economic;
use classes\economic_transfer_queue;
use classes\invoice_period_flag_service;
use classes\system_search_cache;
use classes\system_search_document_index;
use classes\system_search_economic_customer_index;
@@ -135,8 +136,38 @@ $cron_tasks = [
'next_run' => 0,
'function' => 'PruneSystemSessionActivityCron',
],
'WarmInvoicePeriodManualFlagsCron' => [
'interval' => 300, // 5 minutes
'last_run' => 0,
'next_run' => 0,
'function' => 'WarmInvoicePeriodManualFlagsCron',
],
'WarmInvoicePeriodAutomaticFlagsCron' => [
'interval' => 300, // 5 minutes
'last_run' => 0,
'next_run' => 0,
'function' => 'WarmInvoicePeriodAutomaticFlagsCron',
],
];
function WarmInvoicePeriodManualFlagsCron(): void
{
(new invoice_period_flag_service())->warmManualFlagsCache();
}
function WarmInvoicePeriodAutomaticFlagsCron(): void
{
$service = new invoice_period_flag_service();
$now = new DateTime();
$previousMonth = new DateTime('first day of previous month');
foreach ([$now, $previousMonth] as $date) {
$dateFrom = $date->format('Y-m-01');
$dateTo = $date->format('Y-m-t');
$service->warmOrderItemRowsForPeriod($dateFrom, $dateTo);
$service->warmAutomaticFlagsForPeriod($dateFrom, $dateTo);
}
}
function checkUnfulfilledBookings(): void
{
// This is deactivated for now, as it is not wanted.
+69
View File
@@ -290,6 +290,75 @@ interface redis_i
*/
public function clear_auth_session(string $token): self;
/**
* Cache invoice period manual flags payload
* @param array $flags
* @return self
*/
public function cache_invoice_period_manual_flags(array $flags): self;
/**
* Get cached invoice period manual flags payload
* @return array|null
*/
public function get_invoice_period_manual_flags(): array|null;
/**
* Clear cached invoice period manual flags payload
* @return self
*/
public function clear_invoice_period_manual_flags(): self;
/**
* Cache invoice period automatic flags payload for a date range
* @param string $dateFrom
* @param string $dateTo
* @param array $flags
* @return self
*/
public function cache_invoice_period_automatic_flags(string $dateFrom, string $dateTo, array $flags): self;
/**
* Get cached invoice period automatic flags payload for a date range
* @param string $dateFrom
* @param string $dateTo
* @return array|null
*/
public function get_invoice_period_automatic_flags(string $dateFrom, string $dateTo): array|null;
/**
* Clear cached invoice period automatic flags payload for a date range
* @param string $dateFrom
* @param string $dateTo
* @return self
*/
public function clear_invoice_period_automatic_flags(string $dateFrom, string $dateTo): self;
/**
* Cache invoice period order item rows for a date range
* @param string $dateFrom
* @param string $dateTo
* @param array $rows
* @return self
*/
public function cache_invoice_period_order_item_rows(string $dateFrom, string $dateTo, array $rows): self;
/**
* Get cached invoice period order item rows for a date range
* @param string $dateFrom
* @param string $dateTo
* @return array|null
*/
public function get_invoice_period_order_item_rows(string $dateFrom, string $dateTo): array|null;
/**
* Clear cached invoice period order item rows for a date range
* @param string $dateFrom
* @param string $dateTo
* @return self
*/
public function clear_invoice_period_order_item_rows(string $dateFrom, string $dateTo): self;
/**
* Cache a permission evaluation
* @param string $cache_key