update
This commit is contained in:
@@ -16,8 +16,13 @@ use Throwable;
|
||||
final class AutomationController
|
||||
{
|
||||
private const HISTORY_PER_PAGE = 25;
|
||||
private const ALLOWED_EVENTS = ['receipt.created', 'shipment.created', 'shipment.status_changed'];
|
||||
private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status'];
|
||||
private const ALLOWED_EVENTS = ['receipt.created', 'shipment.created', 'shipment.status_changed', 'payment.status_changed', 'order.status_changed', 'order.status_aged'];
|
||||
private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status', 'payment_status', 'order_status', 'days_in_status'];
|
||||
private const PAYMENT_STATUS_OPTIONS = [
|
||||
'0' => 'Nieopłacone',
|
||||
'1' => 'Częściowo opłacone',
|
||||
'2' => 'Opłacone',
|
||||
];
|
||||
private const ALLOWED_ACTION_TYPES = ['send_email', 'issue_receipt', 'update_shipment_status', 'update_order_status'];
|
||||
private const ALLOWED_RECIPIENTS = ['client', 'client_and_company', 'company'];
|
||||
private const ALLOWED_RECEIPT_ISSUE_DATE_MODES = ['today', 'order_date', 'payment_date'];
|
||||
@@ -107,8 +112,7 @@ final class AutomationController
|
||||
|
||||
$validationError = $this->validateInput($request);
|
||||
if ($validationError !== null) {
|
||||
Flash::set('settings.automation.error', $validationError);
|
||||
return Response::redirect('/settings/automation/create');
|
||||
return $this->renderForm($this->buildRuleFromRequest($request), $validationError);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -119,7 +123,7 @@ final class AutomationController
|
||||
);
|
||||
Flash::set('settings.automation.success', 'Zadanie automatyczne zostalo utworzone');
|
||||
} catch (Throwable) {
|
||||
Flash::set('settings.automation.error', 'Blad zapisu zadania automatycznego');
|
||||
return $this->renderForm($this->buildRuleFromRequest($request), 'Blad zapisu zadania automatycznego');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -140,8 +144,7 @@ final class AutomationController
|
||||
|
||||
$validationError = $this->validateInput($request);
|
||||
if ($validationError !== null) {
|
||||
Flash::set('settings.automation.error', $validationError);
|
||||
return Response::redirect('/settings/automation/edit?id=' . $id);
|
||||
return $this->renderForm($this->buildRuleFromRequest($request, $id), $validationError);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -153,7 +156,7 @@ final class AutomationController
|
||||
);
|
||||
Flash::set('settings.automation.success', 'Zadanie automatyczne zostalo zaktualizowane');
|
||||
} catch (Throwable) {
|
||||
Flash::set('settings.automation.error', 'Blad aktualizacji zadania automatycznego');
|
||||
return $this->renderForm($this->buildRuleFromRequest($request, $id), 'Blad aktualizacji zadania automatycznego');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/automation');
|
||||
@@ -228,10 +231,10 @@ final class AutomationController
|
||||
return Response::redirect('/settings/automation');
|
||||
}
|
||||
|
||||
private function renderForm(?array $rule): Response
|
||||
private function renderForm(?array $rule, string $errorMessage = ''): Response
|
||||
{
|
||||
$html = $this->template->render('automation/form', [
|
||||
'title' => $rule !== null ? 'Edytuj zadanie automatyczne' : 'Nowe zadanie automatyczne',
|
||||
'title' => $rule !== null && isset($rule['id']) ? 'Edytuj zadanie automatyczne' : 'Nowe zadanie automatyczne',
|
||||
'activeMenu' => 'settings',
|
||||
'activeSettings' => 'automation',
|
||||
'user' => $this->auth->user(),
|
||||
@@ -247,13 +250,69 @@ final class AutomationController
|
||||
'receiptIssueDateModes' => self::ALLOWED_RECEIPT_ISSUE_DATE_MODES,
|
||||
'receiptDuplicatePolicies' => self::ALLOWED_RECEIPT_DUPLICATE_POLICIES,
|
||||
'shipmentStatusOptions' => self::SHIPMENT_STATUS_OPTIONS,
|
||||
'paymentStatusOptions' => self::PAYMENT_STATUS_OPTIONS,
|
||||
'orderStatusOptions' => $this->repository->listActiveOrderStatuses(),
|
||||
'errorMessage' => Flash::get('settings.automation.error', ''),
|
||||
'errorMessage' => $errorMessage !== '' ? $errorMessage : Flash::get('settings.automation.error', ''),
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
private function buildRuleFromRequest(Request $request, ?int $id = null): array
|
||||
{
|
||||
$raw = $request->input('conditions', []);
|
||||
$conditions = [];
|
||||
if (is_array($raw)) {
|
||||
foreach ($raw as $cond) {
|
||||
if (!is_array($cond)) {
|
||||
continue;
|
||||
}
|
||||
$type = (string) ($cond['type'] ?? '');
|
||||
$value = [];
|
||||
if ($type === 'integration') {
|
||||
$value = ['integration_ids' => is_array($cond['integration_ids'] ?? null) ? $cond['integration_ids'] : []];
|
||||
} elseif ($type === 'shipment_status') {
|
||||
$value = ['status_keys' => is_array($cond['shipment_status_keys'] ?? null) ? $cond['shipment_status_keys'] : []];
|
||||
} elseif ($type === 'payment_status') {
|
||||
$value = ['status_keys' => is_array($cond['payment_status_keys'] ?? null) ? $cond['payment_status_keys'] : []];
|
||||
} elseif ($type === 'order_status') {
|
||||
$value = ['order_status_codes' => is_array($cond['order_status_codes'] ?? null) ? $cond['order_status_codes'] : []];
|
||||
} elseif ($type === 'days_in_status') {
|
||||
$value = ['days' => max(1, (int) ($cond['days'] ?? 0))];
|
||||
}
|
||||
$conditions[] = ['condition_type' => $type, 'condition_value' => $value];
|
||||
}
|
||||
}
|
||||
|
||||
$rawActions = $request->input('actions', []);
|
||||
$actions = [];
|
||||
if (is_array($rawActions)) {
|
||||
foreach ($rawActions as $act) {
|
||||
if (!is_array($act)) {
|
||||
continue;
|
||||
}
|
||||
$type = (string) ($act['type'] ?? '');
|
||||
$config = $act;
|
||||
unset($config['type']);
|
||||
$actions[] = ['action_type' => $type, 'action_config' => $config];
|
||||
}
|
||||
}
|
||||
|
||||
$rule = [
|
||||
'name' => trim((string) $request->input('name', '')),
|
||||
'event_type' => (string) $request->input('event_type', ''),
|
||||
'is_active' => $request->input('is_active', null) !== null ? 1 : 0,
|
||||
'conditions' => $conditions,
|
||||
'actions' => $actions,
|
||||
];
|
||||
|
||||
if ($id !== null) {
|
||||
$rule['id'] = $id;
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
private function validateCsrf(Request $request): ?Response
|
||||
{
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
@@ -367,6 +426,46 @@ final class AutomationController
|
||||
return count($statusKeys) > 0 ? ['status_keys' => array_values(array_unique($statusKeys))] : null;
|
||||
}
|
||||
|
||||
if ($type === 'payment_status') {
|
||||
$keys = $condition['payment_status_keys'] ?? [];
|
||||
if (!is_array($keys)) {
|
||||
$keys = [];
|
||||
}
|
||||
|
||||
$allowedKeys = array_map('strval', array_keys(self::PAYMENT_STATUS_OPTIONS));
|
||||
$statusKeys = array_values(array_filter(
|
||||
array_map(static fn (mixed $key): string => trim((string) $key), $keys),
|
||||
static fn (string $key): bool => $key !== '' && in_array($key, $allowedKeys, true)
|
||||
));
|
||||
|
||||
return count($statusKeys) > 0 ? ['status_keys' => array_values(array_unique($statusKeys))] : null;
|
||||
}
|
||||
|
||||
if ($type === 'order_status') {
|
||||
$codes = $condition['order_status_codes'] ?? [];
|
||||
if (!is_array($codes)) {
|
||||
$codes = [];
|
||||
}
|
||||
|
||||
$availableCodes = array_map(
|
||||
static fn (array $row): string => strtolower(trim((string) ($row['code'] ?? ''))),
|
||||
$this->repository->listActiveOrderStatuses()
|
||||
);
|
||||
|
||||
$statusCodes = array_values(array_filter(
|
||||
array_map(static fn (mixed $code): string => strtolower(trim((string) $code)), $codes),
|
||||
static fn (string $code): bool => $code !== '' && in_array($code, $availableCodes, true)
|
||||
));
|
||||
|
||||
return count($statusCodes) > 0 ? ['order_status_codes' => array_values(array_unique($statusCodes))] : null;
|
||||
}
|
||||
|
||||
if ($type === 'days_in_status') {
|
||||
$days = (int) ($condition['days'] ?? 0);
|
||||
|
||||
return $days >= 1 ? ['days' => $days] : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,15 @@ final class AutomationService
|
||||
if ($type === 'shipment_status') {
|
||||
return $this->evaluateShipmentStatusCondition($value, $context);
|
||||
}
|
||||
if ($type === 'payment_status') {
|
||||
return $this->evaluatePaymentStatusCondition($value, $context);
|
||||
}
|
||||
if ($type === 'order_status') {
|
||||
return $this->evaluateOrderStatusCondition($value, $context);
|
||||
}
|
||||
if ($type === 'days_in_status') {
|
||||
return $this->evaluateDaysInStatusCondition($value, $context);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -185,6 +194,65 @@ final class AutomationService
|
||||
return isset($allowedStatuses[$deliveryStatus]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $value
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function evaluatePaymentStatusCondition(array $value, array $context): bool
|
||||
{
|
||||
$statusKeys = is_array($value['status_keys'] ?? null) ? $value['status_keys'] : [];
|
||||
if ($statusKeys === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newPaymentStatus = trim((string) ($context['new_payment_status'] ?? ''));
|
||||
if ($newPaymentStatus === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($newPaymentStatus, array_map(static fn (mixed $k): string => trim((string) $k), $statusKeys), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $value
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function evaluateOrderStatusCondition(array $value, array $context): bool
|
||||
{
|
||||
$orderStatusCodes = is_array($value['order_status_codes'] ?? null) ? $value['order_status_codes'] : [];
|
||||
if ($orderStatusCodes === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newStatus = strtolower(trim((string) ($context['new_status'] ?? '')));
|
||||
if ($newStatus === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$normalizedCodes = array_map(
|
||||
static fn (mixed $code): string => strtolower(trim((string) $code)),
|
||||
$orderStatusCodes
|
||||
);
|
||||
|
||||
return in_array($newStatus, $normalizedCodes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $value
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function evaluateDaysInStatusCondition(array $value, array $context): bool
|
||||
{
|
||||
$requiredDays = (int) ($value['days'] ?? 0);
|
||||
if ($requiredDays < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actualDays = (int) ($context['days_in_status'] ?? 0);
|
||||
|
||||
return $actualDays >= $requiredDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $actions
|
||||
* @param array<string, mixed> $context
|
||||
@@ -211,7 +279,7 @@ final class AutomationService
|
||||
}
|
||||
|
||||
if ($type === 'update_order_status') {
|
||||
$this->handleUpdateOrderStatus($config, $orderId, $ruleName);
|
||||
$this->handleUpdateOrderStatus($config, $orderId, $ruleName, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,28 +514,47 @@ final class AutomationService
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function handleUpdateOrderStatus(array $config, int $orderId, string $ruleName): void
|
||||
private function handleUpdateOrderStatus(array $config, int $orderId, string $ruleName, array $context): void
|
||||
{
|
||||
$statusCode = trim((string) ($config['status_code'] ?? ''));
|
||||
if ($statusCode === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
$oldStatus = strtolower(trim((string) ($order['external_status_id'] ?? '')));
|
||||
|
||||
$actorName = 'Automatyzacja: ' . $ruleName;
|
||||
$updated = $this->orders->updateOrderStatus($orderId, $statusCode, 'system', $actorName);
|
||||
if ($updated) {
|
||||
if (!$updated) {
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'automation_order_status_failed',
|
||||
$actorName . ' - nie udalo sie zmienic statusu zamowienia',
|
||||
['target_status_code' => $statusCode],
|
||||
'system',
|
||||
$actorName
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'automation_order_status_failed',
|
||||
$actorName . ' - nie udalo sie zmienic statusu zamowienia',
|
||||
['target_status_code' => $statusCode],
|
||||
'system',
|
||||
$actorName
|
||||
);
|
||||
$newStatus = strtolower(trim($statusCode));
|
||||
if ($oldStatus !== $newStatus) {
|
||||
$this->emitEvent(
|
||||
'order.status_changed',
|
||||
$orderId,
|
||||
$context,
|
||||
[
|
||||
'old_status' => $oldStatus,
|
||||
'new_status' => $newStatus,
|
||||
'automation_source' => 'update_order_status',
|
||||
'automation_rule' => $ruleName,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveStatusFromActionKey(string $statusKey): ?string
|
||||
|
||||
164
src/Modules/Automation/OrderStatusAgedService.php
Normal file
164
src/Modules/Automation/OrderStatusAgedService.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use App\Modules\Accounting\ReceiptRepository;
|
||||
use App\Modules\Automation\AutomationRepository;
|
||||
use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Automation\AutomationExecutionLogRepository;
|
||||
use App\Modules\Automation\OrderStatusAgedService;
|
||||
use App\Modules\Email\AttachmentGenerator;
|
||||
use App\Modules\Email\EmailSendingService;
|
||||
use App\Modules\Email\VariableResolver;
|
||||
@@ -106,15 +107,16 @@ final class CronHandlerFactory
|
||||
$shopproStatusMappingRepo,
|
||||
$this->db
|
||||
);
|
||||
$automationService = $this->buildAutomationService($ordersRepository);
|
||||
|
||||
$shopproPaymentSyncService = new ShopproPaymentStatusSyncService(
|
||||
$shopproIntegrationsRepo,
|
||||
new ShopproApiClient(),
|
||||
$ordersRepository,
|
||||
$this->db
|
||||
$this->db,
|
||||
$automationService
|
||||
);
|
||||
|
||||
$automationService = $this->buildAutomationService($ordersRepository);
|
||||
|
||||
return new CronRunner(
|
||||
$cronRepository,
|
||||
$logger,
|
||||
@@ -166,6 +168,13 @@ final class CronHandlerFactory
|
||||
'automation_history_cleanup' => new AutomationHistoryCleanupHandler(
|
||||
new AutomationExecutionLogRepository($this->db)
|
||||
),
|
||||
'order_status_aged' => new OrderStatusAgedHandler(
|
||||
new OrderStatusAgedService(
|
||||
new AutomationRepository($this->db),
|
||||
$automationService,
|
||||
$this->db
|
||||
)
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
27
src/Modules/Cron/OrderStatusAgedHandler.php
Normal file
27
src/Modules/Cron/OrderStatusAgedHandler.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Cron;
|
||||
|
||||
use App\Modules\Automation\OrderStatusAgedService;
|
||||
|
||||
final class OrderStatusAgedHandler
|
||||
{
|
||||
public function __construct(private readonly OrderStatusAgedService $service)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function handle(array $payload): array
|
||||
{
|
||||
$triggered = $this->service->scan();
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'triggered_count' => $triggered,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use App\Modules\Email\EmailSendingService;
|
||||
use App\Modules\Settings\EmailMailboxRepository;
|
||||
use App\Modules\Settings\EmailTemplateRepository;
|
||||
use App\Modules\Settings\ReceiptConfigRepository;
|
||||
use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Settings\ShopproApiClient;
|
||||
use App\Modules\Settings\ShopproIntegrationsRepository;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
@@ -35,7 +36,8 @@ final class OrdersController
|
||||
private readonly ?EmailMailboxRepository $emailMailboxRepo = null,
|
||||
private readonly string $storagePath = '',
|
||||
private readonly ?\App\Modules\Printing\PrintJobRepository $printJobRepo = null,
|
||||
private readonly ?ShopproIntegrationsRepository $shopproIntegrations = null
|
||||
private readonly ?ShopproIntegrationsRepository $shopproIntegrations = null,
|
||||
private readonly ?AutomationService $automation = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -275,8 +277,25 @@ final class OrdersController
|
||||
$user = $this->auth->user();
|
||||
$actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;
|
||||
|
||||
$oldDetails = $this->orders->findDetails($orderId);
|
||||
$oldOrder = is_array($oldDetails['order'] ?? null) ? $oldDetails['order'] : [];
|
||||
$oldStatus = strtolower(trim((string) ($oldOrder['external_status_id'] ?? '')));
|
||||
|
||||
$success = $this->orders->updateOrderStatus($orderId, $newStatus, 'user', $actorName !== '' ? $actorName : null);
|
||||
|
||||
if ($success) {
|
||||
$normalizedNew = strtolower(trim($newStatus));
|
||||
if ($oldStatus !== $normalizedNew) {
|
||||
try {
|
||||
$this->automation?->trigger('order.status_changed', $orderId, [
|
||||
'old_status' => $oldStatus,
|
||||
'new_status' => $normalizedNew,
|
||||
]);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isAjax) {
|
||||
if (!$success) {
|
||||
return Response::json(['success' => false, 'error' => $this->translator->get('orders.details.status_change.failed')], 500);
|
||||
@@ -791,7 +810,7 @@ final class OrdersController
|
||||
return Response::json(['ok' => false, 'error' => 'Nieprawidłowe ID zamówienia.'], 400);
|
||||
}
|
||||
|
||||
if (!Csrf::verify((string) $request->input('_token', ''))) {
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
return Response::json(['ok' => false, 'error' => 'Nieprawidłowy token CSRF.'], 403);
|
||||
}
|
||||
|
||||
@@ -807,12 +826,16 @@ final class OrdersController
|
||||
return Response::json(['ok' => false, 'error' => 'Wybierz typ płatności.'], 422);
|
||||
}
|
||||
|
||||
$result = $this->orders->addPayment($orderId, [
|
||||
'amount' => $amount,
|
||||
'payment_type_id' => $paymentTypeId,
|
||||
'payment_date' => $paymentDate !== '' ? $paymentDate . ' ' . date('H:i:s') : '',
|
||||
'comment' => $comment,
|
||||
]);
|
||||
try {
|
||||
$result = $this->orders->addPayment($orderId, [
|
||||
'amount' => $amount,
|
||||
'payment_type_id' => $paymentTypeId,
|
||||
'payment_date' => $paymentDate !== '' ? $paymentDate . ' ' . date('H:i:s') : '',
|
||||
'comment' => $comment,
|
||||
]);
|
||||
} catch (\Throwable $ex) {
|
||||
return Response::json(['ok' => false, 'error' => 'Błąd zapisu: ' . $ex->getMessage()], 500);
|
||||
}
|
||||
|
||||
if ($result === null) {
|
||||
return Response::json(['ok' => false, 'error' => 'Nie udało się zapisać płatności.'], 500);
|
||||
@@ -824,9 +847,18 @@ final class OrdersController
|
||||
'Dodano płatność: ' . number_format($amount, 2, '.', ' ') . ' PLN (' . $paymentTypeId . ')',
|
||||
['payment_id' => $result['id'], 'amount' => $amount, 'type' => $paymentTypeId],
|
||||
'user',
|
||||
$this->auth->user()['name'] ?? null
|
||||
($this->auth->user() ?? [])['name'] ?? null
|
||||
);
|
||||
|
||||
try {
|
||||
$this->automation?->trigger('payment.status_changed', $orderId, [
|
||||
'new_payment_status' => (string) $result['payment_status'],
|
||||
'total_paid' => $result['total_paid'],
|
||||
'payment_type_id' => $paymentTypeId,
|
||||
]);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
$this->pushPaymentToShoppro($orderId, $result['payment_status']);
|
||||
|
||||
return Response::json([
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Modules\Settings;
|
||||
|
||||
use App\Core\Constants\IntegrationSources;
|
||||
use App\Core\Support\StringHelper;
|
||||
use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Orders\OrdersRepository;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
@@ -32,7 +33,8 @@ final class ShopproPaymentStatusSyncService
|
||||
private readonly ShopproIntegrationsRepository $integrations,
|
||||
private readonly ShopproApiClient $apiClient,
|
||||
private readonly OrdersRepository $orders,
|
||||
private readonly PDO $pdo
|
||||
private readonly PDO $pdo,
|
||||
private readonly ?AutomationService $automation = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -249,6 +251,18 @@ final class ShopproPaymentStatusSyncService
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($existingPaymentStatus !== $newPaymentStatus) {
|
||||
try {
|
||||
$this->automation?->trigger('payment.status_changed', $orderId, [
|
||||
'new_payment_status' => (string) $newPaymentStatus,
|
||||
'old_payment_status' => $existingPaymentStatus !== null ? (string) $existingPaymentStatus : '',
|
||||
'total_paid' => $newTotalPaid,
|
||||
'payment_method' => $paymentMethod,
|
||||
]);
|
||||
} catch (Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
$summary = $isPaid
|
||||
? 'shopPRO: zamowienie oznaczone jako oplacone'
|
||||
: 'shopPRO: zamowienie oznaczone jako nieoplacone';
|
||||
|
||||
Reference in New Issue
Block a user