platform/src/Core/Content/Flow/Dispatching/Action/GenerateDocumentAction.php line 60

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 Psr\Log\LoggerInterface;
  5. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  6. use Shopware\Core\Checkout\Document\DocumentConfigurationFactory;
  7. use Shopware\Core\Checkout\Document\DocumentGenerator\CreditNoteGenerator;
  8. use Shopware\Core\Checkout\Document\DocumentGenerator\DeliveryNoteGenerator;
  9. use Shopware\Core\Checkout\Document\DocumentGenerator\InvoiceGenerator;
  10. use Shopware\Core\Checkout\Document\DocumentGenerator\StornoGenerator;
  11. use Shopware\Core\Checkout\Document\DocumentService;
  12. use Shopware\Core\Checkout\Document\FileGenerator\FileTypes;
  13. use Shopware\Core\Defaults;
  14. use Shopware\Core\Framework\Event\FlowEvent;
  15. use Shopware\Core\Framework\Event\OrderAware;
  16. use Shopware\Core\Framework\Event\SalesChannelAware;
  17. use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface;
  18. class GenerateDocumentAction extends FlowAction
  19. {
  20.     protected DocumentService $documentService;
  21.     protected Connection $connection;
  22.     private NumberRangeValueGeneratorInterface $valueGenerator;
  23.     private LoggerInterface $logger;
  24.     public function __construct(
  25.         DocumentService $documentService,
  26.         NumberRangeValueGeneratorInterface $valueGenerator,
  27.         Connection $connection,
  28.         LoggerInterface $logger
  29.     ) {
  30.         $this->documentService $documentService;
  31.         $this->valueGenerator $valueGenerator;
  32.         $this->connection $connection;
  33.         $this->logger $logger;
  34.     }
  35.     public static function getName(): string
  36.     {
  37.         return 'action.generate.document';
  38.     }
  39.     public static function getSubscribedEvents(): array
  40.     {
  41.         return [
  42.             self::getName() => 'handle',
  43.         ];
  44.     }
  45.     public function requirements(): array
  46.     {
  47.         return [OrderAware::class];
  48.     }
  49.     public function handle(FlowEvent $event): void
  50.     {
  51.         $baseEvent $event->getEvent();
  52.         $eventConfig $event->getConfig();
  53.         if (!$baseEvent instanceof OrderAware || !$baseEvent instanceof SalesChannelAware) {
  54.             return;
  55.         }
  56.         if (\array_key_exists('documentType'$eventConfig)) {
  57.             $this->generateDocument($eventConfig$baseEvent);
  58.             return;
  59.         }
  60.         $documentsConfig $eventConfig['documentTypes'];
  61.         if (!$documentsConfig) {
  62.             return;
  63.         }
  64.         // Invoice document should be created first
  65.         foreach ($documentsConfig as $index => $config) {
  66.             if ($config['documentType'] === InvoiceGenerator::INVOICE) {
  67.                 $this->generateDocument($config$baseEvent);
  68.                 unset($documentsConfig[$index]);
  69.                 break;
  70.             }
  71.         }
  72.         foreach ($documentsConfig as $config) {
  73.             $this->generateDocument($config$baseEvent);
  74.         }
  75.     }
  76.     /**
  77.      * @param OrderAware&SalesChannelAware $baseEvent
  78.      */
  79.     private function generateDocument(array $eventConfig$baseEvent): void
  80.     {
  81.         $documentType $eventConfig['documentType'];
  82.         $documentRangerType $eventConfig['documentRangerType'];
  83.         if (!$documentType || !$documentRangerType) {
  84.             return;
  85.         }
  86.         $documentNumber $this->valueGenerator->getValue(
  87.             $documentRangerType,
  88.             $baseEvent->getContext(),
  89.             $baseEvent->getSalesChannelId()
  90.         );
  91.         $now = (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  92.         $eventConfig['documentNumber'] = $documentNumber;
  93.         $eventConfig['documentDate'] = $now;
  94.         $customConfig $this->getEventCustomConfig(
  95.             $documentType,
  96.             $documentNumber,
  97.             $now,
  98.             $baseEvent->getOrderId()
  99.         );
  100.         if (empty($customConfig)) {
  101.             return;
  102.         }
  103.         $eventConfig['custom'] = $customConfig;
  104.         $documentConfig DocumentConfigurationFactory::createConfiguration($eventConfig);
  105.         $this->documentService->create(
  106.             $baseEvent->getOrderId(),
  107.             $documentType,
  108.             $eventConfig['fileType'] ?? FileTypes::PDF,
  109.             $documentConfig,
  110.             $baseEvent->getContext(),
  111.             $customConfig['referencedInvoiceId'] ?? null,
  112.             $eventConfig['static'] ?? false
  113.         );
  114.     }
  115.     private function getEventCustomConfig(string $documentTypestring $documentNumberstring $nowstring $orderId): array
  116.     {
  117.         switch ($documentType) {
  118.             case InvoiceGenerator::INVOICE:
  119.                 return ['invoiceNumber' => $documentNumber];
  120.             case DeliveryNoteGenerator::DELIVERY_NOTE:
  121.                 return [
  122.                     'deliveryNoteNumber' => $documentNumber,
  123.                     'deliveryDate' => $now,
  124.                     'deliveryNoteDate' => $now,
  125.                 ];
  126.             case StornoGenerator::STORNO:
  127.             case CreditNoteGenerator::CREDIT_NOTE:
  128.                 return $this->getConfigWithReferenceDoc($documentType$documentNumber$orderId);
  129.             default:
  130.                 return [];
  131.         }
  132.     }
  133.     private function getConfigWithReferenceDoc(string $documentTypestring $documentNumberstring $orderId): array
  134.     {
  135.         $referencedInvoiceDocument $this->connection->fetchAssociative(
  136.             'SELECT LOWER (HEX(`document`.`id`)) as `id` , `document`.`config` as `config`
  137.                     FROM `document` JOIN `document_type` ON `document`.`document_type_id` = `document_type`.`id`
  138.                     WHERE `document_type`.`technical_name` = :techName AND hex(`document`.`order_id`) = :orderId
  139.                     ORDER BY `document`.`created_at` DESC LIMIT 1',
  140.             [
  141.                 'techName' => InvoiceGenerator::INVOICE,
  142.                 'orderId' => $orderId,
  143.             ]
  144.         );
  145.         if (empty($referencedInvoiceDocument)) {
  146.             $this->logger->info(
  147.                 'Can not generate ' $documentType ' document because no invoice document exists. OrderId: ' $orderId
  148.             );
  149.             return [];
  150.         }
  151.         if ($documentType === CreditNoteGenerator::CREDIT_NOTE && !$this->hasCreditItem($orderId)) {
  152.             $this->logger->info(
  153.                 'Can not generate the credit note document because no credit items exist. OrderId: ' $orderId
  154.             );
  155.             return [];
  156.         }
  157.         $documentRefer json_decode($referencedInvoiceDocument['config'], true);
  158.         $documentNumberRefer $documentRefer['custom']['invoiceNumber'];
  159.         return array_filter([
  160.             'invoiceNumber' => $documentNumberRefer,
  161.             'stornoNumber' => $documentType === StornoGenerator::STORNO $documentNumber null,
  162.             'creditNoteNumber' => $documentType === CreditNoteGenerator::CREDIT_NOTE $documentNumber null,
  163.             'referencedInvoiceId' => $referencedInvoiceDocument['id'],
  164.         ]);
  165.     }
  166.     private function hasCreditItem(string $orderId): bool
  167.     {
  168.         $lineItem $this->connection->fetchFirstColumn(
  169.             'SELECT 1 FROM `order_line_item` WHERE hex(`order_id`) = :orderId and `type` = :itemType LIMIT 1',
  170.             [
  171.                 'orderId' => $orderId,
  172.                 'itemType' => LineItem::CREDIT_LINE_ITEM_TYPE,
  173.             ]
  174.         );
  175.         return !empty($lineItem);
  176.     }
  177. }