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 $eventConfig, string $orderId, string $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 $eventConfig, Context $context, string $orderId, string $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, $config, null, $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 $documentType, string $documentNumber, string $now, string $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 $documentType, string $documentNumber, string $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. }