Files
orderPRO/src/Modules/Automation/OrderStatusAgedService.php
2026-03-31 00:30:50 +02:00

165 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Automation;
use PDO;
use Throwable;
final class OrderStatusAgedService
{
private const MAX_ORDERS_PER_RULE = 100;
public function __construct(
private readonly AutomationRepository $repository,
private readonly AutomationService $automation,
private readonly PDO $db
) {
}
public function scan(): int
{
$rules = $this->repository->findActiveByEvent('order.status_aged');
if ($rules === []) {
return 0;
}
$totalTriggered = 0;
foreach ($rules as $rule) {
try {
$totalTriggered += $this->processRule($rule);
} catch (Throwable) {
// Blad jednej reguly nie blokuje kolejnych
}
}
return $totalTriggered;
}
/**
* @param array<string, mixed> $rule
*/
private function processRule(array $rule): int
{
$conditions = is_array($rule['conditions'] ?? null) ? $rule['conditions'] : [];
$statusCodes = $this->extractStatusCodes($conditions);
$days = $this->extractDays($conditions);
if ($statusCodes === [] || $days < 1) {
return 0;
}
$orders = $this->findAgedOrders($statusCodes, $days);
$triggered = 0;
foreach ($orders as $order) {
$orderId = (int) ($order['id'] ?? 0);
if ($orderId <= 0) {
continue;
}
try {
$currentStatus = strtolower(trim((string) ($order['external_status_id'] ?? '')));
$lastChanged = (string) ($order['last_changed'] ?? '');
$actualDays = $lastChanged !== '' ? $this->daysSince($lastChanged) : $days;
$this->automation->trigger('order.status_aged', $orderId, [
'current_status' => $currentStatus,
'days_in_status' => $actualDays,
'status_changed_at' => $lastChanged,
]);
$triggered++;
} catch (Throwable) {
// Blad jednego zamowienia nie blokuje kolejnych
}
}
return $triggered;
}
/**
* @param list<array<string, mixed>> $conditions
* @return list<string>
*/
private function extractStatusCodes(array $conditions): array
{
foreach ($conditions as $condition) {
$type = (string) ($condition['condition_type'] ?? '');
$value = is_array($condition['condition_value'] ?? null) ? $condition['condition_value'] : [];
if ($type === 'order_status') {
$codes = is_array($value['order_status_codes'] ?? null) ? $value['order_status_codes'] : [];
return array_values(array_filter(
array_map(static fn (mixed $c): string => strtolower(trim((string) $c)), $codes),
static fn (string $c): bool => $c !== ''
));
}
}
return [];
}
/**
* @param list<array<string, mixed>> $conditions
*/
private function extractDays(array $conditions): int
{
foreach ($conditions as $condition) {
$type = (string) ($condition['condition_type'] ?? '');
$value = is_array($condition['condition_value'] ?? null) ? $condition['condition_value'] : [];
if ($type === 'days_in_status') {
return max(0, (int) ($value['days'] ?? 0));
}
}
return 0;
}
/**
* @param list<string> $statusCodes
* @return list<array<string, mixed>>
*/
private function findAgedOrders(array $statusCodes, int $days): array
{
if ($statusCodes === [] || $days < 1) {
return [];
}
$placeholders = implode(', ', array_fill(0, count($statusCodes), '?'));
$sql = "SELECT o.id, o.external_status_id, MAX(h.changed_at) AS last_changed
FROM orders o
INNER JOIN order_status_history h ON h.order_id = o.id
AND LOWER(h.to_status_id) = LOWER(o.external_status_id)
WHERE LOWER(COALESCE(o.external_status_id, '')) IN ({$placeholders})
GROUP BY o.id, o.external_status_id
HAVING MAX(h.changed_at) <= DATE_SUB(NOW(), INTERVAL ? DAY)
LIMIT " . self::MAX_ORDERS_PER_RULE;
try {
$stmt = $this->db->prepare($sql);
$params = $statusCodes;
$params[] = $days;
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
return is_array($rows) ? $rows : [];
} catch (Throwable) {
return [];
}
}
private function daysSince(string $datetime): int
{
$timestamp = strtotime($datetime);
if ($timestamp === false) {
return 0;
}
$diff = time() - $timestamp;
return max(0, (int) floor($diff / 86400));
}
}