vendor/shopware/core/Content/Flow/Dispatching/Action/GenerateDocumentAction.php line 104

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Flow\Dispatching\Action;
  3. use Doctrine\DBAL\Connection;
  4. use Doctrine\DBAL\Exception;
  5. use Psr\Log\LoggerInterface;
  6. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  7. use Shopware\Core\Checkout\Document\DocumentConfigurationFactory;
  8. use Shopware\Core\Checkout\Document\DocumentGenerator\CreditNoteGenerator;
  9. use Shopware\Core\Checkout\Document\DocumentGenerator\DeliveryNoteGenerator;
  10. use Shopware\Core\Checkout\Document\DocumentGenerator\InvoiceGenerator;
  11. use Shopware\Core\Checkout\Document\DocumentGenerator\StornoGenerator;
  12. use Shopware\Core\Checkout\Document\DocumentService;
  13. use Shopware\Core\Checkout\Document\FileGenerator\FileTypes;
  14. use Shopware\Core\Checkout\Document\Service\DocumentGenerator;
  15. use Shopware\Core\Checkout\Document\Struct\DocumentGenerateOperation;
  16. use Shopware\Core\Content\Flow\Dispatching\DelayableAction;
  17. use Shopware\Core\Content\Flow\Dispatching\StorableFlow;
  18. use Shopware\Core\Content\Flow\Exception\GenerateDocumentActionException;
  19. use Shopware\Core\Defaults;
  20. use Shopware\Core\Framework\Context;
  21. use Shopware\Core\Framework\Event\FlowEvent;
  22. use Shopware\Core\Framework\Event\MailAware;
  23. use Shopware\Core\Framework\Event\OrderAware;
  24. use Shopware\Core\Framework\Event\SalesChannelAware;
  25. use Shopware\Core\Framework\Feature;
  26. use Shopware\Core\Framework\Log\Package;
  27. use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface;
  28. /**
  29.  * @deprecated tag:v6.5.0 - reason:remove-subscriber - FlowActions won't be executed over the event system anymore,
  30.  * therefore the actions won't implement the EventSubscriberInterface anymore.
  31.  */
  32. #[Package('business-ops')]
  33. class GenerateDocumentAction extends FlowAction implements DelayableAction
  34. {
  35.     /**
  36.      * @deprecated tag:v6.5.0 - Property $documentService will be removed due to unused
  37.      */
  38.     protected DocumentService $documentService;
  39.     /**
  40.      * @deprecated tag:v6.5.0 - Property $connection will be removed due to unused
  41.      */
  42.     protected Connection $connection;
  43.     private DocumentGenerator $documentGenerator;
  44.     /**
  45.      * @deprecated tag:v6.5.0 - Property $connection will be removed due to unused
  46.      */
  47.     private NumberRangeValueGeneratorInterface $valueGenerator;
  48.     private LoggerInterface $logger;
  49.     /**
  50.      * @internal
  51.      */
  52.     public function __construct(
  53.         DocumentService $documentService,
  54.         DocumentGenerator $documentGenerator,
  55.         NumberRangeValueGeneratorInterface $valueGenerator,
  56.         Connection $connection,
  57.         LoggerInterface $logger
  58.     ) {
  59.         $this->documentService $documentService;
  60.         $this->documentGenerator $documentGenerator;
  61.         $this->connection $connection;
  62.         $this->valueGenerator $valueGenerator;
  63.         $this->logger $logger;
  64.     }
  65.     public static function getName(): string
  66.     {
  67.         return 'action.generate.document';
  68.     }
  69.     /**
  70.      * @deprecated tag:v6.5.0 - reason:remove-subscriber - Will be removed
  71.      */
  72.     public static function getSubscribedEvents(): array
  73.     {
  74.         if (Feature::isActive('v6.5.0.0')) {
  75.             return [];
  76.         }
  77.         return [
  78.             self::getName() => 'handle',
  79.         ];
  80.     }
  81.     /**
  82.      * @return array<int, string>
  83.      */
  84.     public function requirements(): array
  85.     {
  86.         return [OrderAware::class];
  87.     }
  88.     /**
  89.      * @deprecated tag:v6.5.0 Will be removed, implement handleFlow instead
  90.      */
  91.     public function handle(FlowEvent $event): void
  92.     {
  93.         Feature::triggerDeprecationOrThrow(
  94.             'v6.5.0.0',
  95.             Feature::deprecatedMethodMessage(__CLASS____METHOD__'v6.5.0.0')
  96.         );
  97.         $baseEvent $event->getEvent();
  98.         if (!$baseEvent instanceof OrderAware || !$baseEvent instanceof SalesChannelAware) {
  99.             return;
  100.         }
  101.         $this->generate($baseEvent->getContext(), $event->getConfig(), $baseEvent->getOrderId(), $baseEvent->getSalesChannelId());
  102.     }
  103.     public function handleFlow(StorableFlow $flow): void
  104.     {
  105.         if (!$flow->hasStore(OrderAware::ORDER_ID) || !$flow->hasStore(MailAware::SALES_CHANNEL_ID)) {
  106.             return;
  107.         }
  108.         $this->generate($flow->getContext(), $flow->getConfig(), $flow->getStore(OrderAware::ORDER_ID), $flow->getStore(MailAware::SALES_CHANNEL_ID));
  109.     }
  110.     /**
  111.      * @param array<string, mixed> $eventConfig
  112.      */
  113.     private function generate(Context $context, array $eventConfigstring $orderIdstring $salesChannelId): void
  114.     {
  115.         if (\array_key_exists('documentType'$eventConfig)) {
  116.             $this->generateDocument($eventConfig$context$orderId$salesChannelId);
  117.             return;
  118.         }
  119.         $documentsConfig $eventConfig['documentTypes'];
  120.         if (!$documentsConfig) {
  121.             return;
  122.         }
  123.         // Invoice document should be created first
  124.         foreach ($documentsConfig as $index => $config) {
  125.             if ($config['documentType'] === InvoiceGenerator::INVOICE) {
  126.                 $this->generateDocument($config$context$orderId$salesChannelId);
  127.                 unset($documentsConfig[$index]);
  128.                 break;
  129.             }
  130.         }
  131.         foreach ($documentsConfig as $config) {
  132.             $this->generateDocument($config$context$orderId$salesChannelId);
  133.         }
  134.     }
  135.     /**
  136.      * @param array<string, mixed> $eventConfig
  137.      */
  138.     private function generateDocument(array $eventConfigContext $contextstring $orderIdstring $salesChannelId): void
  139.     {
  140.         $documentType $eventConfig['documentType'];
  141.         $documentRangerType $eventConfig['documentRangerType'];
  142.         if (!$documentType || !$documentRangerType) {
  143.             return;
  144.         }
  145.         if (Feature::isActive('v6.5.0.0')) {
  146.             $fileType $eventConfig['fileType'] ?? FileTypes::PDF;
  147.             $config $eventConfig['config'] ?? [];
  148.             $static $eventConfig['static'] ?? false;
  149.             $operation = new DocumentGenerateOperation($orderId$fileType$confignull$static);
  150.             $result $this->documentGenerator->generate($documentType, [$orderId => $operation], $context);
  151.             if (!empty($result->getErrors())) {
  152.                 foreach ($result->getErrors() as $error) {
  153.                     $this->logger->error($error->getMessage());
  154.                 }
  155.             }
  156.             return;
  157.         }
  158.         $documentNumber $this->valueGenerator->getValue(
  159.             $documentRangerType,
  160.             $context,
  161.             $salesChannelId
  162.         );
  163.         $now = (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  164.         $eventConfig['documentNumber'] = $documentNumber;
  165.         $eventConfig['documentDate'] = $now;
  166.         $customConfig $this->getEventCustomConfig(
  167.             $documentType,
  168.             $documentNumber,
  169.             $now,
  170.             $orderId
  171.         );
  172.         $eventConfig['custom'] = $customConfig;
  173.         $documentConfig DocumentConfigurationFactory::createConfiguration($eventConfig);
  174.         $this->documentService->create(
  175.             $orderId,
  176.             $documentType,
  177.             $eventConfig['fileType'] ?? FileTypes::PDF,
  178.             $documentConfig,
  179.             $context,
  180.             $customConfig['referencedInvoiceId'] ?? null,
  181.             $eventConfig['static'] ?? false
  182.         );
  183.     }
  184.     /**
  185.      * @return array<string, mixed>
  186.      */
  187.     private function getEventCustomConfig(string $documentTypestring $documentNumberstring $nowstring $orderId): array
  188.     {
  189.         switch ($documentType) {
  190.             case InvoiceGenerator::INVOICE:
  191.                 return ['invoiceNumber' => $documentNumber];
  192.             case DeliveryNoteGenerator::DELIVERY_NOTE:
  193.                 return [
  194.                     'deliveryNoteNumber' => $documentNumber,
  195.                     'deliveryDate' => $now,
  196.                     'deliveryNoteDate' => $now,
  197.                 ];
  198.             case StornoGenerator::STORNO:
  199.             case CreditNoteGenerator::CREDIT_NOTE:
  200.                 return $this->getConfigWithReferenceDoc($documentType$documentNumber$orderId);
  201.             default:
  202.                 return [];
  203.         }
  204.     }
  205.     /**
  206.      * @throws Exception
  207.      *
  208.      * @return array<string, mixed>
  209.      */
  210.     private function getConfigWithReferenceDoc(string $documentTypestring $documentNumberstring $orderId): array
  211.     {
  212.         $referencedInvoiceDocument $this->connection->fetchAssociative(
  213.             'SELECT LOWER (HEX(`document`.`id`)) as `id` , `document`.`config` as `config`
  214.                     FROM `document` JOIN `document_type` ON `document`.`document_type_id` = `document_type`.`id`
  215.                     WHERE `document_type`.`technical_name` = :techName AND hex(`document`.`order_id`) = :orderId
  216.                     ORDER BY `document`.`created_at` DESC LIMIT 1',
  217.             [
  218.                 'techName' => InvoiceGenerator::INVOICE,
  219.                 'orderId' => $orderId,
  220.             ]
  221.         );
  222.         if (empty($referencedInvoiceDocument)) {
  223.             throw new GenerateDocumentActionException('Cannot generate ' $documentType ' document because no invoice document exists. OrderId: ' $orderId);
  224.         }
  225.         if ($documentType === CreditNoteGenerator::CREDIT_NOTE && !$this->hasCreditItem($orderId)) {
  226.             throw new GenerateDocumentActionException('Cannot generate the credit note document because no credit items exist. OrderId: ' $orderId);
  227.         }
  228.         $documentRefer json_decode($referencedInvoiceDocument['config'], true);
  229.         $documentNumberRefer $documentRefer['custom']['invoiceNumber'];
  230.         return array_filter([
  231.             'invoiceNumber' => $documentNumberRefer,
  232.             'stornoNumber' => $documentType === StornoGenerator::STORNO $documentNumber null,
  233.             'creditNoteNumber' => $documentType === CreditNoteGenerator::CREDIT_NOTE $documentNumber null,
  234.             'referencedInvoiceId' => $referencedInvoiceDocument['id'],
  235.         ]);
  236.     }
  237.     private function hasCreditItem(string $orderId): bool
  238.     {
  239.         $lineItem $this->connection->fetchFirstColumn(
  240.             'SELECT 1 FROM `order_line_item` WHERE hex(`order_id`) = :orderId and `type` = :itemType LIMIT 1',
  241.             [
  242.                 'orderId' => $orderId,
  243.                 'itemType' => LineItem::CREDIT_LINE_ITEM_TYPE,
  244.             ]
  245.         );
  246.         return !empty($lineItem);
  247.     }
  248. }