165 lines
4.9 KiB
PHP
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));
|
|
}
|
|
}
|