diff --git a/services/nginx/app/classes/invoice_period_flag_service.php b/services/nginx/app/classes/invoice_period_flag_service.php index 6ea19d85..c6a2a329 100644 --- a/services/nginx/app/classes/invoice_period_flag_service.php +++ b/services/nginx/app/classes/invoice_period_flag_service.php @@ -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); diff --git a/services/nginx/app/classes/redis.php b/services/nginx/app/classes/redis.php index 5c4372ac..19958bf4 100644 --- a/services/nginx/app/classes/redis.php +++ b/services/nginx/app/classes/redis.php @@ -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 */ diff --git a/services/nginx/app/cron/Cron.php b/services/nginx/app/cron/Cron.php index ba454e4c..2793b966 100644 --- a/services/nginx/app/cron/Cron.php +++ b/services/nginx/app/cron/Cron.php @@ -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. diff --git a/services/nginx/app/interfaces/redis_i.php b/services/nginx/app/interfaces/redis_i.php index fb5da8a6..e11b028c 100644 --- a/services/nginx/app/interfaces/redis_i.php +++ b/services/nginx/app/interfaces/redis_i.php @@ -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