feat(v1.5): complete phases 40-43 workflow cleanup
This commit is contained in:
@@ -250,7 +250,8 @@ final class Application
|
||||
try {
|
||||
$factory = new CronHandlerFactory(
|
||||
$this->db,
|
||||
(string) $this->config('app.integrations.secret', '')
|
||||
(string) $this->config('app.integrations.secret', ''),
|
||||
$this->basePath()
|
||||
);
|
||||
$runner = $factory->build($repository, $this->logger);
|
||||
$runner->run($webLimit);
|
||||
|
||||
@@ -14,10 +14,19 @@ use Throwable;
|
||||
|
||||
final class AutomationController
|
||||
{
|
||||
private const ALLOWED_EVENTS = ['receipt.created'];
|
||||
private const ALLOWED_CONDITION_TYPES = ['integration'];
|
||||
private const ALLOWED_EVENTS = ['receipt.created', 'shipment.status_changed'];
|
||||
private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status'];
|
||||
private const ALLOWED_ACTION_TYPES = ['send_email'];
|
||||
private const ALLOWED_RECIPIENTS = ['client', 'client_and_company', 'company'];
|
||||
private const SHIPMENT_STATUS_OPTIONS = [
|
||||
'registered' => ['label' => 'Przesylka zarejestrowana', 'statuses' => ['created', 'confirmed']],
|
||||
'ready_for_pickup' => ['label' => 'Przesylka do odbioru', 'statuses' => ['ready_for_pickup']],
|
||||
'dropped_at_point' => ['label' => 'Przesylka nadana w punkcie', 'statuses' => ['confirmed', 'in_transit']],
|
||||
'picked_up' => ['label' => 'Przesylka odebrana', 'statuses' => ['delivered']],
|
||||
'cancelled' => ['label' => 'Przesylka anulowana', 'statuses' => ['cancelled']],
|
||||
'unclaimed' => ['label' => 'Przesylka nieodebrana', 'statuses' => ['problem']],
|
||||
'picked_up_return' => ['label' => 'Przesylka odebrana (zwrot)', 'statuses' => ['returned']],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
@@ -185,6 +194,7 @@ final class AutomationController
|
||||
'conditionTypes' => self::ALLOWED_CONDITION_TYPES,
|
||||
'actionTypes' => self::ALLOWED_ACTION_TYPES,
|
||||
'recipientOptions' => self::ALLOWED_RECIPIENTS,
|
||||
'shipmentStatusOptions' => self::SHIPMENT_STATUS_OPTIONS,
|
||||
'errorMessage' => Flash::get('settings.automation.error', ''),
|
||||
], 'layouts/app');
|
||||
|
||||
@@ -289,6 +299,21 @@ final class AutomationController
|
||||
return count($integrationIds) > 0 ? ['integration_ids' => $integrationIds] : null;
|
||||
}
|
||||
|
||||
if ($type === 'shipment_status') {
|
||||
$keys = $condition['shipment_status_keys'] ?? [];
|
||||
if (!is_array($keys)) {
|
||||
$keys = [];
|
||||
}
|
||||
|
||||
$allowedKeys = array_keys(self::SHIPMENT_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;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@ use Throwable;
|
||||
|
||||
final class AutomationService
|
||||
{
|
||||
private const SHIPMENT_STATUS_OPTION_MAP = [
|
||||
'registered' => ['created', 'confirmed'],
|
||||
'ready_for_pickup' => ['ready_for_pickup'],
|
||||
'dropped_at_point' => ['confirmed', 'in_transit'],
|
||||
'picked_up' => ['delivered'],
|
||||
'cancelled' => ['cancelled'],
|
||||
'unclaimed' => ['problem'],
|
||||
'picked_up_return' => ['returned'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly AutomationRepository $repository,
|
||||
private readonly EmailSendingService $emailService,
|
||||
@@ -18,7 +28,10 @@ final class AutomationService
|
||||
) {
|
||||
}
|
||||
|
||||
public function trigger(string $eventType, int $orderId): void
|
||||
/**
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
public function trigger(string $eventType, int $orderId, array $context = []): void
|
||||
{
|
||||
$rules = $this->repository->findActiveByEvent($eventType);
|
||||
if ($rules === []) {
|
||||
@@ -38,7 +51,7 @@ final class AutomationService
|
||||
$actions = is_array($rule['actions'] ?? null) ? $rule['actions'] : [];
|
||||
$ruleName = (string) ($rule['name'] ?? '');
|
||||
|
||||
if ($this->evaluateConditions($conditions, $order)) {
|
||||
if ($this->evaluateConditions($conditions, $order, $context)) {
|
||||
$this->executeActions($actions, $orderId, $ruleName);
|
||||
}
|
||||
} catch (Throwable) {
|
||||
@@ -50,14 +63,15 @@ final class AutomationService
|
||||
/**
|
||||
* @param list<array<string, mixed>> $conditions
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function evaluateConditions(array $conditions, array $order): bool
|
||||
private function evaluateConditions(array $conditions, array $order, array $context): bool
|
||||
{
|
||||
foreach ($conditions as $condition) {
|
||||
$type = (string) ($condition['condition_type'] ?? '');
|
||||
$value = is_array($condition['condition_value'] ?? null) ? $condition['condition_value'] : [];
|
||||
|
||||
if (!$this->evaluateSingleCondition($type, $value, $order)) {
|
||||
if (!$this->evaluateSingleCondition($type, $value, $order, $context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -68,12 +82,16 @@ final class AutomationService
|
||||
/**
|
||||
* @param array<string, mixed> $value
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function evaluateSingleCondition(string $type, array $value, array $order): bool
|
||||
private function evaluateSingleCondition(string $type, array $value, array $order, array $context): bool
|
||||
{
|
||||
if ($type === 'integration') {
|
||||
return $this->evaluateIntegrationCondition($value, $order);
|
||||
}
|
||||
if ($type === 'shipment_status') {
|
||||
return $this->evaluateShipmentStatusCondition($value, $context);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -97,6 +115,40 @@ final class AutomationService
|
||||
return in_array($orderIntegrationId, array_map('intval', $allowedIds), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $value
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function evaluateShipmentStatusCondition(array $value, array $context): bool
|
||||
{
|
||||
$statusKeys = is_array($value['status_keys'] ?? null) ? $value['status_keys'] : [];
|
||||
if ($statusKeys === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$deliveryStatus = trim((string) ($context['delivery_status'] ?? ''));
|
||||
if ($deliveryStatus === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowedStatuses = [];
|
||||
foreach ($statusKeys as $statusKeyRaw) {
|
||||
$statusKey = trim((string) $statusKeyRaw);
|
||||
if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
|
||||
continue;
|
||||
}
|
||||
foreach (self::SHIPMENT_STATUS_OPTION_MAP[$statusKey] as $mappedStatus) {
|
||||
$allowedStatuses[$mappedStatus] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($allowedStatuses === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($allowedStatuses[$deliveryStatus]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $actions
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Cron;
|
||||
|
||||
use App\Core\I18n\Translator;
|
||||
use App\Core\Support\Logger;
|
||||
use App\Core\View\Template;
|
||||
use App\Modules\Accounting\ReceiptRepository;
|
||||
use App\Modules\Automation\AutomationRepository;
|
||||
use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Email\AttachmentGenerator;
|
||||
use App\Modules\Email\EmailSendingService;
|
||||
use App\Modules\Email\VariableResolver;
|
||||
use App\Modules\Orders\OrderImportRepository;
|
||||
use App\Modules\Orders\OrdersRepository;
|
||||
use App\Modules\Settings\AllegroApiClient;
|
||||
@@ -15,13 +23,21 @@ use App\Modules\Settings\AllegroOrderSyncStateRepository;
|
||||
use App\Modules\Settings\AllegroStatusMappingRepository;
|
||||
use App\Modules\Settings\AllegroStatusSyncService;
|
||||
use App\Modules\Settings\AllegroTokenManager;
|
||||
use App\Modules\Settings\ApaczkaApiClient;
|
||||
use App\Modules\Settings\ApaczkaIntegrationRepository;
|
||||
use App\Modules\Settings\CompanySettingsRepository;
|
||||
use App\Modules\Settings\EmailMailboxRepository;
|
||||
use App\Modules\Settings\EmailTemplateRepository;
|
||||
use App\Modules\Settings\InpostIntegrationRepository;
|
||||
use App\Modules\Settings\IntegrationSecretCipher;
|
||||
use App\Modules\Settings\ReceiptConfigRepository;
|
||||
use App\Modules\Settings\ShopproApiClient;
|
||||
use App\Modules\Settings\ShopproIntegrationsRepository;
|
||||
use App\Modules\Settings\ShopproOrderMapper;
|
||||
use App\Modules\Settings\ShopproOrdersSyncService;
|
||||
use App\Modules\Settings\ShopproOrderSyncStateRepository;
|
||||
use App\Modules\Settings\ShopproProductImageResolver;
|
||||
use App\Modules\Settings\ShopproPaymentStatusSyncService;
|
||||
use App\Modules\Settings\ShopproProductImageResolver;
|
||||
use App\Modules\Settings\ShopproStatusMappingRepository;
|
||||
use App\Modules\Settings\ShopproStatusSyncService;
|
||||
use App\Modules\Shipments\AllegroTrackingService;
|
||||
@@ -29,17 +45,16 @@ use App\Modules\Shipments\ApaczkaTrackingService;
|
||||
use App\Modules\Shipments\InpostTrackingService;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
use App\Modules\Shipments\ShipmentTrackingRegistry;
|
||||
use App\Modules\Settings\ApaczkaApiClient;
|
||||
use App\Modules\Settings\ApaczkaIntegrationRepository;
|
||||
use App\Modules\Settings\InpostIntegrationRepository;
|
||||
use PDO;
|
||||
|
||||
final class CronHandlerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PDO $db,
|
||||
private readonly string $integrationSecret
|
||||
) {}
|
||||
private readonly string $integrationSecret,
|
||||
private readonly string $basePath
|
||||
) {
|
||||
}
|
||||
|
||||
public function build(CronRepository $cronRepository, Logger $logger): CronRunner
|
||||
{
|
||||
@@ -48,14 +63,17 @@ final class CronHandlerFactory
|
||||
$tokenManager = new AllegroTokenManager($integrationRepository, $oauthClient);
|
||||
$apiClient = new AllegroApiClient();
|
||||
$statusMappingRepository = new AllegroStatusMappingRepository($this->db);
|
||||
$ordersRepository = new OrdersRepository($this->db);
|
||||
|
||||
$orderImportService = new AllegroOrderImportService(
|
||||
$integrationRepository,
|
||||
$tokenManager,
|
||||
$apiClient,
|
||||
new OrderImportRepository($this->db),
|
||||
$statusMappingRepository,
|
||||
new OrdersRepository($this->db)
|
||||
$ordersRepository
|
||||
);
|
||||
|
||||
$ordersSyncService = new AllegroOrdersSyncService(
|
||||
$integrationRepository,
|
||||
new AllegroOrderSyncStateRepository($this->db),
|
||||
@@ -63,6 +81,7 @@ final class CronHandlerFactory
|
||||
$apiClient,
|
||||
$orderImportService
|
||||
);
|
||||
|
||||
$shopproIntegrationsRepo = new ShopproIntegrationsRepository($this->db, $this->integrationSecret);
|
||||
$shopproApiClient = new ShopproApiClient();
|
||||
$shopproSyncService = new ShopproOrdersSyncService(
|
||||
@@ -71,18 +90,21 @@ final class CronHandlerFactory
|
||||
$shopproApiClient,
|
||||
new OrderImportRepository($this->db),
|
||||
new ShopproStatusMappingRepository($this->db),
|
||||
new OrdersRepository($this->db),
|
||||
$ordersRepository,
|
||||
new ShopproOrderMapper(),
|
||||
new ShopproProductImageResolver($shopproApiClient)
|
||||
);
|
||||
|
||||
$shopproStatusSyncService = new ShopproStatusSyncService($shopproIntegrationsRepo, $shopproSyncService);
|
||||
$shopproPaymentSyncService = new ShopproPaymentStatusSyncService(
|
||||
$shopproIntegrationsRepo,
|
||||
new ShopproApiClient(),
|
||||
new OrdersRepository($this->db),
|
||||
$ordersRepository,
|
||||
$this->db
|
||||
);
|
||||
|
||||
$automationService = $this->buildAutomationService($ordersRepository);
|
||||
|
||||
return new CronRunner(
|
||||
$cronRepository,
|
||||
$logger,
|
||||
@@ -124,9 +146,45 @@ final class CronHandlerFactory
|
||||
$tokenManager
|
||||
),
|
||||
]),
|
||||
new ShipmentPackageRepository($this->db)
|
||||
new ShipmentPackageRepository($this->db),
|
||||
$automationService
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function buildAutomationService(OrdersRepository $ordersRepository): AutomationService
|
||||
{
|
||||
$automationRepository = new AutomationRepository($this->db);
|
||||
$companySettingsRepository = new CompanySettingsRepository($this->db);
|
||||
$emailTemplateRepository = new EmailTemplateRepository($this->db);
|
||||
$emailMailboxRepository = new EmailMailboxRepository(
|
||||
$this->db,
|
||||
new IntegrationSecretCipher($this->integrationSecret)
|
||||
);
|
||||
$template = new Template(
|
||||
$this->basePath . '/resources/views',
|
||||
new Translator($this->basePath . '/resources/lang', 'pl')
|
||||
);
|
||||
|
||||
$emailService = new EmailSendingService(
|
||||
$this->db,
|
||||
$ordersRepository,
|
||||
$emailTemplateRepository,
|
||||
$emailMailboxRepository,
|
||||
new VariableResolver(),
|
||||
new AttachmentGenerator(
|
||||
new ReceiptRepository($this->db),
|
||||
new ReceiptConfigRepository($this->db),
|
||||
$template
|
||||
)
|
||||
);
|
||||
|
||||
return new AutomationService(
|
||||
$automationRepository,
|
||||
$emailService,
|
||||
$ordersRepository,
|
||||
$companySettingsRepository
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Cron;
|
||||
|
||||
use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
use App\Modules\Shipments\ShipmentTrackingRegistry;
|
||||
use Throwable;
|
||||
@@ -11,7 +12,8 @@ final class ShipmentTrackingHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ShipmentTrackingRegistry $registry,
|
||||
private readonly ShipmentPackageRepository $repository
|
||||
private readonly ShipmentPackageRepository $repository,
|
||||
private readonly AutomationService $automationService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -38,12 +40,42 @@ final class ShipmentTrackingHandler
|
||||
try {
|
||||
$result = $service->getDeliveryStatus($package);
|
||||
if ($result !== null) {
|
||||
$previousStatus = trim((string) ($package['delivery_status'] ?? 'unknown'));
|
||||
if ($previousStatus === '') {
|
||||
$previousStatus = 'unknown';
|
||||
}
|
||||
$previousStatusRaw = trim((string) ($package['delivery_status_raw'] ?? ''));
|
||||
$newStatus = trim((string) ($result['status'] ?? 'unknown'));
|
||||
if ($newStatus === '') {
|
||||
$newStatus = 'unknown';
|
||||
}
|
||||
$newStatusRaw = trim((string) ($result['status_raw'] ?? ''));
|
||||
$statusChanged = $newStatus !== $previousStatus;
|
||||
$statusRawChanged = $newStatusRaw !== $previousStatusRaw;
|
||||
if (!$statusChanged && !$statusRawChanged) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->repository->updateDeliveryStatus(
|
||||
$packageId,
|
||||
$result['status'],
|
||||
$result['status_raw']
|
||||
$newStatus,
|
||||
$newStatusRaw !== '' ? $newStatusRaw : null
|
||||
);
|
||||
$updated++;
|
||||
|
||||
if ($statusChanged) {
|
||||
$orderId = (int) ($package['order_id'] ?? 0);
|
||||
if ($orderId > 0) {
|
||||
$this->automationService->trigger('shipment.status_changed', $orderId, [
|
||||
'package_id' => $packageId,
|
||||
'provider' => $provider,
|
||||
'delivery_status' => $newStatus,
|
||||
'delivery_status_raw' => $newStatusRaw,
|
||||
'previous_status' => $previousStatus,
|
||||
'previous_status_raw' => $previousStatusRaw,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable) {
|
||||
$errors++;
|
||||
|
||||
@@ -136,14 +136,7 @@ final class OrdersController
|
||||
'selectable' => true,
|
||||
'select_name' => 'selected_ids[]',
|
||||
'select_value_key' => 'id',
|
||||
'header_actions' => [
|
||||
[
|
||||
'type' => 'button',
|
||||
'label' => 'Drukuj etykiety',
|
||||
'class' => 'btn btn--secondary js-bulk-print-labels',
|
||||
'attrs' => ['data-csrf' => Csrf::token()],
|
||||
],
|
||||
],
|
||||
'header_actions' => [],
|
||||
'empty_message' => $this->translator->get('orders.empty'),
|
||||
'show_actions' => false,
|
||||
],
|
||||
|
||||
@@ -759,6 +759,59 @@ final class OrdersRepository
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $details
|
||||
*/
|
||||
public function shouldSkipDuplicateImportActivity(int $orderId, array $details): bool
|
||||
{
|
||||
if ($orderId <= 0 || !empty($details['created'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sourceOrderId = trim((string) ($details['source_order_id'] ?? ''));
|
||||
$sourceUpdatedAt = trim((string) ($details['source_updated_at'] ?? ''));
|
||||
$trigger = trim((string) ($details['trigger'] ?? ''));
|
||||
|
||||
if ($sourceOrderId === '' || $sourceUpdatedAt === '' || $trigger === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT details_json
|
||||
FROM order_activity_log
|
||||
WHERE order_id = :order_id
|
||||
AND event_type = :event_type
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([
|
||||
'order_id' => $orderId,
|
||||
'event_type' => 'import',
|
||||
]);
|
||||
$lastDetailsJson = $stmt->fetchColumn();
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_string($lastDetailsJson) || trim($lastDetailsJson) === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastDetails = json_decode($lastDetailsJson, true);
|
||||
if (!is_array($lastDetails) || !empty($lastDetails['created'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastSourceOrderId = trim((string) ($lastDetails['source_order_id'] ?? ''));
|
||||
$lastSourceUpdatedAt = trim((string) ($lastDetails['source_updated_at'] ?? ''));
|
||||
$lastTrigger = trim((string) ($lastDetails['trigger'] ?? ''));
|
||||
|
||||
return $lastSourceOrderId === $sourceOrderId
|
||||
&& $lastSourceUpdatedAt === $sourceUpdatedAt
|
||||
&& $lastTrigger === $trigger;
|
||||
}
|
||||
|
||||
public function recordStatusChange(
|
||||
int $orderId,
|
||||
?string $fromStatus,
|
||||
|
||||
@@ -162,69 +162,4 @@ final class PrintApiController
|
||||
return Response::json(['id' => $jobId, 'status' => 'completed']);
|
||||
}
|
||||
|
||||
public function bulkCreateJobs(Request $request): Response
|
||||
{
|
||||
$body = json_decode((string) file_get_contents('php://input'), true);
|
||||
if (!is_array($body)) {
|
||||
$body = [];
|
||||
}
|
||||
|
||||
$token = (string) ($body['_token'] ?? $request->input('_token', ''));
|
||||
if (!Csrf::validate($token)) {
|
||||
return Response::json(['error' => 'Invalid CSRF token'], 403);
|
||||
}
|
||||
|
||||
$packageIds = $body['package_ids'] ?? $request->input('package_ids', []);
|
||||
if (!is_array($packageIds) || $packageIds === []) {
|
||||
$orderIds = $body['order_ids'] ?? $request->input('order_ids', []);
|
||||
if (!is_array($orderIds) || $orderIds === []) {
|
||||
return Response::json(['error' => 'package_ids or order_ids required'], 400);
|
||||
}
|
||||
|
||||
$intOrderIds = array_map('intval', $orderIds);
|
||||
$packages = $this->printJobs->findPackagesWithLabelsByOrderIds($intOrderIds);
|
||||
$packageIds = array_map(static fn(array $p): int => (int) $p['id'], $packages);
|
||||
}
|
||||
|
||||
$user = $this->auth->user();
|
||||
$userId = (int) ($user['id'] ?? 0);
|
||||
$created = [];
|
||||
$skipped = [];
|
||||
|
||||
foreach ($packageIds as $pkgId) {
|
||||
$pkgId = (int) $pkgId;
|
||||
if ($pkgId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = $this->printJobs->findPendingByPackageId($pkgId);
|
||||
if ($existing !== null) {
|
||||
$skipped[] = ['package_id' => $pkgId, 'reason' => 'already_pending'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$package = $this->packages->findById($pkgId);
|
||||
if ($package === null) {
|
||||
$skipped[] = ['package_id' => $pkgId, 'reason' => 'not_found'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$labelPath = $this->ensureLabel($pkgId, $package);
|
||||
if ($labelPath === '') {
|
||||
$skipped[] = ['package_id' => $pkgId, 'reason' => 'no_label'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$jobId = $this->printJobs->create([
|
||||
'order_id' => (int) ($package['order_id'] ?? 0),
|
||||
'package_id' => $pkgId,
|
||||
'label_path' => $labelPath,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
|
||||
$created[] = ['id' => $jobId, 'package_id' => $pkgId];
|
||||
}
|
||||
|
||||
return Response::json(['created' => $created, 'skipped' => $skipped], 201);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,14 @@ final class PrintJobRepository
|
||||
$statement->execute(['id' => $id]);
|
||||
}
|
||||
|
||||
public function deleteById(int $id): bool
|
||||
{
|
||||
$statement = $this->pdo->prepare('DELETE FROM print_jobs WHERE id = :id');
|
||||
$statement->execute(['id' => $id]);
|
||||
|
||||
return $statement->rowCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
@@ -141,25 +149,4 @@ final class PrintJobRepository
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $orderIds
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function findPackagesWithLabelsByOrderIds(array $orderIds): array
|
||||
{
|
||||
if ($orderIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($orderIds), '?'));
|
||||
$statement = $this->pdo->prepare(
|
||||
"SELECT id, order_id, label_path FROM shipment_packages
|
||||
WHERE order_id IN ($placeholders) AND label_path IS NOT NULL AND label_path != ''
|
||||
AND status != 'error'"
|
||||
);
|
||||
$statement->execute(array_values($orderIds));
|
||||
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ use Throwable;
|
||||
|
||||
final class AllegroOrderImportService
|
||||
{
|
||||
private const IMPORT_TRIGGERS = [
|
||||
'manual_import' => 'Import reczny',
|
||||
'orders_sync' => 'Synchronizacja zamowien',
|
||||
'status_sync' => 'Synchronizacja statusow',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly AllegroIntegrationRepository $integrationRepository,
|
||||
private readonly AllegroTokenManager $tokenManager,
|
||||
@@ -26,12 +32,14 @@ final class AllegroOrderImportService
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function importSingleOrder(string $checkoutFormId): array
|
||||
public function importSingleOrder(string $checkoutFormId, string $trigger = 'manual_import'): array
|
||||
{
|
||||
$orderId = trim($checkoutFormId);
|
||||
if ($orderId === '') {
|
||||
throw new AllegroApiException('Podaj ID zamowienia Allegro.');
|
||||
}
|
||||
$normalizedTrigger = $this->normalizeTrigger($trigger);
|
||||
$triggerLabel = self::IMPORT_TRIGGERS[$normalizedTrigger];
|
||||
|
||||
[$accessToken, $env] = $this->tokenManager->resolveToken();
|
||||
|
||||
@@ -61,21 +69,29 @@ final class AllegroOrderImportService
|
||||
$wasCreated = !empty($saveResult['created']);
|
||||
|
||||
if ($savedOrderId > 0) {
|
||||
$sourceUpdatedAt = trim((string) ($mapped['order']['source_updated_at'] ?? ''));
|
||||
$summary = $wasCreated
|
||||
? 'Zaimportowano zamowienie z Allegro'
|
||||
: 'Zaktualizowano zamowienie z Allegro (re-import)';
|
||||
$this->ordersRepository->recordActivity(
|
||||
$savedOrderId,
|
||||
'import',
|
||||
$summary,
|
||||
[
|
||||
'source' => IntegrationSources::ALLEGRO,
|
||||
'source_order_id' => trim($checkoutFormId),
|
||||
'created' => $wasCreated,
|
||||
],
|
||||
'import',
|
||||
'Allegro'
|
||||
);
|
||||
$details = [
|
||||
'source' => IntegrationSources::ALLEGRO,
|
||||
'source_order_id' => trim($checkoutFormId),
|
||||
'source_updated_at' => $sourceUpdatedAt,
|
||||
'created' => $wasCreated,
|
||||
'trigger' => $normalizedTrigger,
|
||||
'trigger_label' => $triggerLabel,
|
||||
];
|
||||
|
||||
if (!$this->ordersRepository->shouldSkipDuplicateImportActivity($savedOrderId, $details)) {
|
||||
$this->ordersRepository->recordActivity(
|
||||
$savedOrderId,
|
||||
'import',
|
||||
$summary,
|
||||
$details,
|
||||
'import',
|
||||
'Allegro'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -86,6 +102,16 @@ final class AllegroOrderImportService
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeTrigger(string $trigger): string
|
||||
{
|
||||
$value = trim($trigger);
|
||||
if ($value === '' || !array_key_exists($value, self::IMPORT_TRIGGERS)) {
|
||||
return 'manual_import';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array{
|
||||
|
||||
@@ -127,7 +127,7 @@ final class AllegroOrdersSyncService
|
||||
$result['processed'] = (int) $result['processed'] + 1;
|
||||
|
||||
try {
|
||||
$importResult = $this->orderImportService->importSingleOrder($sourceOrderId);
|
||||
$importResult = $this->orderImportService->importSingleOrder($sourceOrderId, 'orders_sync');
|
||||
if (!empty($importResult['created'])) {
|
||||
$result['imported_created'] = (int) $result['imported_created'] + 1;
|
||||
} else {
|
||||
|
||||
@@ -59,7 +59,7 @@ final class AllegroStatusSyncService
|
||||
$sourceOrderId = (string) ($order['source_order_id'] ?? '');
|
||||
|
||||
try {
|
||||
$this->orderImportService->importSingleOrder($sourceOrderId);
|
||||
$this->orderImportService->importSingleOrder($sourceOrderId, 'status_sync');
|
||||
$result['processed']++;
|
||||
$this->markOrderStatusChecked((int) ($order['id'] ?? 0));
|
||||
} catch (Throwable $exception) {
|
||||
|
||||
@@ -91,6 +91,27 @@ final class PrintSettingsController
|
||||
$this->apiKeys->delete($id);
|
||||
Flash::set('settings_success', 'Klucz API został usunięty');
|
||||
|
||||
return Response::redirect('/settings/printing');
|
||||
}
|
||||
public function deleteJob(Request $request): Response
|
||||
{
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('settings_error', 'Nieprawidlowy token CSRF');
|
||||
return Response::redirect('/settings/printing');
|
||||
}
|
||||
|
||||
$id = (int) $request->input('id', 0);
|
||||
if ($id <= 0) {
|
||||
Flash::set('settings_error', 'Nieprawidlowy ID wpisu kolejki');
|
||||
return Response::redirect('/settings/printing');
|
||||
}
|
||||
|
||||
if ($this->printJobs->deleteById($id)) {
|
||||
Flash::set('settings_success', 'Wpis kolejki wydruku zostal usuniety');
|
||||
} else {
|
||||
Flash::set('settings_error', 'Nie znaleziono wpisu kolejki do usuniecia');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/printing');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user