<?php declare(strict_types=1); 
 
namespace Shopware\Core\Content\Flow\Dispatching\Action; 
 
use Doctrine\DBAL\Connection; 
use Psr\Log\LoggerInterface; 
use Shopware\Core\Checkout\Cart\LineItem\LineItem; 
use Shopware\Core\Checkout\Document\DocumentConfigurationFactory; 
use Shopware\Core\Checkout\Document\DocumentGenerator\CreditNoteGenerator; 
use Shopware\Core\Checkout\Document\DocumentGenerator\DeliveryNoteGenerator; 
use Shopware\Core\Checkout\Document\DocumentGenerator\InvoiceGenerator; 
use Shopware\Core\Checkout\Document\DocumentGenerator\StornoGenerator; 
use Shopware\Core\Checkout\Document\DocumentService; 
use Shopware\Core\Checkout\Document\FileGenerator\FileTypes; 
use Shopware\Core\Checkout\Document\Service\DocumentGenerator; 
use Shopware\Core\Checkout\Document\Struct\DocumentGenerateOperation; 
use Shopware\Core\Content\Flow\Exception\GenerateDocumentActionException; 
use Shopware\Core\Defaults; 
use Shopware\Core\Framework\Event\DelayAware; 
use Shopware\Core\Framework\Event\FlowEvent; 
use Shopware\Core\Framework\Event\OrderAware; 
use Shopware\Core\Framework\Event\SalesChannelAware; 
use Shopware\Core\Framework\Feature; 
use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface; 
 
class GenerateDocumentAction extends FlowAction 
{ 
    /** 
     * @deprecated tag:v6.5.0 - Property $documentService will be removed due to unused 
     */ 
    protected DocumentService $documentService; 
 
    /** 
     * @deprecated tag:v6.5.0 - Property $connection will be removed due to unused 
     */ 
    protected Connection $connection; 
 
    private DocumentGenerator $documentGenerator; 
 
    /** 
     * @deprecated tag:v6.5.0 - Property $connection will be removed due to unused 
     */ 
    private NumberRangeValueGeneratorInterface $valueGenerator; 
 
    private LoggerInterface $logger; 
 
    /** 
     * @internal 
     */ 
    public function __construct( 
        DocumentService $documentService, 
        DocumentGenerator $documentGenerator, 
        NumberRangeValueGeneratorInterface $valueGenerator, 
        Connection $connection, 
        LoggerInterface $logger 
    ) { 
        $this->documentService = $documentService; 
        $this->documentGenerator = $documentGenerator; 
        $this->connection = $connection; 
        $this->valueGenerator = $valueGenerator; 
        $this->logger = $logger; 
    } 
 
    public static function getName(): string 
    { 
        return 'action.generate.document'; 
    } 
 
    public static function getSubscribedEvents(): array 
    { 
        return [ 
            self::getName() => 'handle', 
        ]; 
    } 
 
    public function requirements(): array 
    { 
        return [OrderAware::class, DelayAware::class]; 
    } 
 
    public function handle(FlowEvent $event): void 
    { 
        $baseEvent = $event->getEvent(); 
 
        $eventConfig = $event->getConfig(); 
 
        if (!$baseEvent instanceof OrderAware || !$baseEvent instanceof SalesChannelAware) { 
            return; 
        } 
 
        if (\array_key_exists('documentType', $eventConfig)) { 
            $this->generateDocument($eventConfig, $baseEvent); 
 
            return; 
        } 
 
        $documentsConfig = $eventConfig['documentTypes']; 
 
        if (!$documentsConfig) { 
            return; 
        } 
 
        // Invoice document should be created first 
        foreach ($documentsConfig as $index => $config) { 
            if ($config['documentType'] === InvoiceGenerator::INVOICE) { 
                $this->generateDocument($config, $baseEvent); 
                unset($documentsConfig[$index]); 
 
                break; 
            } 
        } 
 
        foreach ($documentsConfig as $config) { 
            $this->generateDocument($config, $baseEvent); 
        } 
    } 
 
    /** 
     * @param OrderAware&SalesChannelAware $baseEvent 
     */ 
    private function generateDocument(array $eventConfig, $baseEvent): void 
    { 
        $documentType = $eventConfig['documentType']; 
        $documentRangerType = $eventConfig['documentRangerType']; 
 
        if (!$documentType || !$documentRangerType) { 
            return; 
        } 
 
        if (Feature::isActive('v6.5.0.0')) { 
            $fileType = $eventConfig['fileType'] ?? FileTypes::PDF; 
            $config = $eventConfig['config'] ?? []; 
            $static = $eventConfig['static'] ?? false; 
 
            $operation = new DocumentGenerateOperation($baseEvent->getOrderId(), $fileType, $config, null, $static); 
 
            $result = $this->documentGenerator->generate($documentType, [$baseEvent->getOrderId() => $operation], $baseEvent->getContext()); 
 
            if (!empty($result->getErrors())) { 
                foreach ($result->getErrors() as $error) { 
                    $this->logger->error($error->getMessage()); 
                } 
            } 
 
            return; 
        } 
 
        $documentNumber = $this->valueGenerator->getValue( 
            $documentRangerType, 
            $baseEvent->getContext(), 
            $baseEvent->getSalesChannelId() 
        ); 
 
        $now = (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT); 
        $eventConfig['documentNumber'] = $documentNumber; 
        $eventConfig['documentDate'] = $now; 
 
        $customConfig = $this->getEventCustomConfig( 
            $documentType, 
            $documentNumber, 
            $now, 
            $baseEvent->getOrderId() 
        ); 
 
        $eventConfig['custom'] = $customConfig; 
 
        $documentConfig = DocumentConfigurationFactory::createConfiguration($eventConfig); 
 
        $this->documentService->create( 
            $baseEvent->getOrderId(), 
            $documentType, 
            $eventConfig['fileType'] ?? FileTypes::PDF, 
            $documentConfig, 
            $baseEvent->getContext(), 
            $customConfig['referencedInvoiceId'] ?? null, 
            $eventConfig['static'] ?? false 
        ); 
    } 
 
    private function getEventCustomConfig(string $documentType, string $documentNumber, string $now, string $orderId): array 
    { 
        switch ($documentType) { 
            case InvoiceGenerator::INVOICE: 
                return ['invoiceNumber' => $documentNumber]; 
            case DeliveryNoteGenerator::DELIVERY_NOTE: 
                return [ 
                    'deliveryNoteNumber' => $documentNumber, 
                    'deliveryDate' => $now, 
                    'deliveryNoteDate' => $now, 
                ]; 
            case StornoGenerator::STORNO: 
            case CreditNoteGenerator::CREDIT_NOTE: 
                return $this->getConfigWithReferenceDoc($documentType, $documentNumber, $orderId); 
            default: 
                return []; 
        } 
    } 
 
    private function getConfigWithReferenceDoc(string $documentType, string $documentNumber, string $orderId): array 
    { 
        $referencedInvoiceDocument = $this->connection->fetchAssociative( 
            'SELECT LOWER (HEX(`document`.`id`)) as `id` , `document`.`config` as `config` 
                    FROM `document` JOIN `document_type` ON `document`.`document_type_id` = `document_type`.`id` 
                    WHERE `document_type`.`technical_name` = :techName AND hex(`document`.`order_id`) = :orderId 
                    ORDER BY `document`.`created_at` DESC LIMIT 1', 
            [ 
                'techName' => InvoiceGenerator::INVOICE, 
                'orderId' => $orderId, 
            ] 
        ); 
 
        if (empty($referencedInvoiceDocument)) { 
            throw new GenerateDocumentActionException('Cannot generate ' . $documentType . ' document because no invoice document exists. OrderId: ' . $orderId); 
        } 
 
        if ($documentType === CreditNoteGenerator::CREDIT_NOTE && !$this->hasCreditItem($orderId)) { 
            throw new GenerateDocumentActionException('Cannot generate the credit note document because no credit items exist. OrderId: ' . $orderId); 
        } 
 
        $documentRefer = json_decode($referencedInvoiceDocument['config'], true); 
        $documentNumberRefer = $documentRefer['custom']['invoiceNumber']; 
 
        return array_filter([ 
            'invoiceNumber' => $documentNumberRefer, 
            'stornoNumber' => $documentType === StornoGenerator::STORNO ? $documentNumber : null, 
            'creditNoteNumber' => $documentType === CreditNoteGenerator::CREDIT_NOTE ? $documentNumber : null, 
            'referencedInvoiceId' => $referencedInvoiceDocument['id'], 
        ]); 
    } 
 
    private function hasCreditItem(string $orderId): bool 
    { 
        $lineItem = $this->connection->fetchFirstColumn( 
            'SELECT 1 FROM `order_line_item` WHERE hex(`order_id`) = :orderId and `type` = :itemType LIMIT 1', 
            [ 
                'orderId' => $orderId, 
                'itemType' => LineItem::CREDIT_LINE_ITEM_TYPE, 
            ] 
        ); 
 
        return !empty($lineItem); 
    } 
}