Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
QuoteConversionApplicationService
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
3 / 3
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPdo
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 convertQuoteToOrder
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Quotes\Application\Service;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Application\Service\DataMappingApplicationService;
12use App\Core\Engine\Application\Service\InventoryApplicationService;
13use App\Core\Engine\Application\Service\UniversalCrudServiceInterface;
14use App\Core\Engine\Domain\Model\PermissionContext;
15use App\Core\Engine\Domain\Repository\MetadataRepositoryInterface;
16use App\Core\Instance\Application\Service\InstanceContextManagerInterface;
17use App\Modules\Quotes\Domain\Exception\QuoteConversionException;
18use PDO;
19use Throwable;
20
21/**
22 * Service responsible for converting a Quote to an Order with transaction support.
23 */
24final readonly class QuoteConversionApplicationService implements QuoteConversionApplicationServiceInterface
25{
26    public function __construct(
27        private UniversalCrudServiceInterface $crudService,
28        private MetadataRepositoryInterface $metadataRepository,
29        private DataMappingApplicationService $mappingService,
30        private InventoryApplicationService $inventoryService,
31        private ?PDO $pdo = null,
32        private ?InstanceContextManagerInterface $instanceManager = null,
33        private ?PDO $clientPdo = null
34    ) {
35    }
36
37    private function getPdo(): PDO
38    {
39        if ($this->instanceManager !== null && $this->instanceManager->isRemote() && $this->clientPdo !== null) {
40            return $this->clientPdo;
41        }
42
43        if ($this->pdo === null) {
44            throw new QuoteConversionException('Database connection is not available for quote conversion.');
45        }
46
47        return $this->pdo;
48    }
49
50    /**
51     * Converts a Quote into a new Sales Order.
52     *
53     * @param int $quoteId
54     * @param PermissionContext $context
55     * @return int The ID of the newly created Order.
56     * @throws RuntimeException If conversion fails.
57     */
58    public function convertQuoteToOrder(int $quoteId, PermissionContext $context): int
59    {
60        $pdo = $this->getPdo();
61        $pdo->beginTransaction();
62
63        try {
64            $quote = $this->crudService->read('quotes', $quoteId, $context);
65
66            $quoteModule = $this->metadataRepository->findModule('quotes');
67            $orderModule = $this->metadataRepository->findModule('orders');
68
69            $mappedData = $this->mappingService->mapRecordData($quoteModule->id, $orderModule->id, $quote);
70
71            if (empty($mappedData['subject'])) {
72                $quoteSubject = (string) ($quote['subject'] ?? "Quote #{$quoteId}");
73                $mappedData['subject'] = "Order from {$quoteSubject}";
74            }
75
76            if (empty($mappedData['order_status'])) {
77                $mappedData['order_status'] = 'Created';
78            }
79
80            if (isset($quote['account_id']) && empty($mappedData['account_id'])) {
81                $mappedData['account_id'] = $quote['account_id'];
82            }
83
84            $mappedData['quote_id'] = $quoteId;
85
86            $newOrderId = $this->crudService->create('orders', $mappedData, $context);
87
88            $shouldCopy = $this->mappingService->shouldCopyInventory($quoteModule->id, $orderModule->id)
89                || ($quoteModule->hasInventory && $orderModule->hasInventory);
90
91            if ($shouldCopy) {
92                $quoteItems = $this->inventoryService->getRecordItems($quoteModule->id, $quoteId);
93                if (!empty($quoteItems)) {
94                    $this->inventoryService->saveRecordItems(
95                        $orderModule->id,
96                        $newOrderId,
97                        $quoteItems,
98                        $context->actorUserId
99                    );
100                }
101            }
102
103            try {
104                $this->crudService->update('quotes', $quoteId, ['quote_status' => 'Accepted'], $context);
105            } catch (Throwable) {
106                // Ignore if quote_status field is not configured on quotes
107            }
108
109            $pdo->commit();
110
111            return $newOrderId;
112        } catch (Throwable $e) {
113            $pdo->rollBack();
114            throw new QuoteConversionException('Conversion failed: ' . $e->getMessage(), 0, $e);
115        }
116    }
117}