1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace Application\Service; |
||||
6 | |||||
7 | use Application\Enum\ProductType; |
||||
8 | use Application\Model\AbstractProduct; |
||||
9 | use Application\Model\Order; |
||||
10 | use Application\Model\OrderLine; |
||||
11 | use Application\Model\Product; |
||||
12 | use Application\Model\Subscription; |
||||
13 | use Application\Model\User; |
||||
14 | use Application\Repository\UserRepository; |
||||
15 | use Doctrine\ORM\EntityManager; |
||||
16 | use Ecodev\Felix\Api\Exception; |
||||
17 | use Money\Money; |
||||
18 | |||||
19 | /** |
||||
20 | * Service to create order and transactions for products and their quantity. |
||||
21 | */ |
||||
22 | class Invoicer |
||||
23 | { |
||||
24 | private readonly UserRepository $userRepository; |
||||
25 | |||||
26 | 1 | public function __construct( |
|||
27 | private readonly EntityManager $entityManager, |
||||
28 | ) { |
||||
29 | 1 | $this->userRepository = $this->entityManager->getRepository(User::class); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
30 | } |
||||
31 | |||||
32 | 14 | public function createOrder(array $orderInput): ?Order |
|||
33 | { |
||||
34 | 14 | $lines = $orderInput['orderLines']; |
|||
35 | 14 | if (!$lines) { |
|||
36 | return null; |
||||
37 | } |
||||
38 | |||||
39 | 14 | $order = new Order(); |
|||
40 | 14 | $order->setPaymentMethod($orderInput['paymentMethod']); |
|||
41 | 14 | $order->setFirstName($orderInput['firstName'] ?? ''); |
|||
42 | 14 | $order->setLastName($orderInput['lastName'] ?? ''); |
|||
43 | 14 | $order->setStreet($orderInput['street'] ?? ''); |
|||
44 | 14 | $order->setLocality($orderInput['locality'] ?? ''); |
|||
45 | 14 | $order->setPostcode($orderInput['postcode'] ?? ''); |
|||
46 | 14 | $order->setCountry($orderInput['country'] ?? null); |
|||
47 | |||||
48 | 14 | $this->userRepository->getAclFilter()->runWithoutAcl(function () use ($lines, $order): void { |
|||
49 | 14 | $this->entityManager->persist($order); |
|||
50 | |||||
51 | 14 | $total = Money::CHF(0); |
|||
52 | 14 | foreach ($lines as $line) { |
|||
53 | 14 | $args = $this->extractArgsFromLine($line); |
|||
54 | |||||
55 | 13 | $orderLine = $this->createOrderLine($order, ...$args); |
|||
0 ignored issues
–
show
It seems like
$args can also be of type Money\Money ; however, parameter $product of Application\Service\Invoicer::createOrderLine() does only seem to accept Application\Model\AbstractProduct|null , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
56 | 13 | $total = $total->add($orderLine->getBalanceCHF()); |
|||
57 | } |
||||
58 | 14 | }); |
|||
59 | |||||
60 | 13 | return $order; |
|||
61 | } |
||||
62 | |||||
63 | 13 | private function createOrderLine(Order $order, ?AbstractProduct $product, Money $pricePerUnit, int $quantity, bool $isCHF, ProductType $type, array $additionalEmails): OrderLine |
|||
64 | { |
||||
65 | 13 | $orderLine = new OrderLine(); |
|||
66 | 13 | $this->entityManager->persist($orderLine); |
|||
67 | 13 | $orderLine->setOrder($order); |
|||
68 | |||||
69 | 13 | $this->updateOrderLine($orderLine, $product, $pricePerUnit, $quantity, $isCHF, $type, $additionalEmails); |
|||
70 | |||||
71 | 13 | return $orderLine; |
|||
72 | } |
||||
73 | |||||
74 | 15 | private function extractArgsFromLine($input): array |
|||
75 | { |
||||
76 | 15 | $product = $input['product'] ?? null; |
|||
77 | 15 | $subscription = $input['subscription'] ?? null; |
|||
78 | 15 | $quantity = $input['quantity']; |
|||
79 | 15 | $isCHF = $input['isCHF']; |
|||
80 | 15 | $type = $input['type']; |
|||
81 | 15 | $additionalEmails = $input['additionalEmails']; |
|||
82 | 15 | $pricePerUnit = $input['pricePerUnit'] ?? null; |
|||
83 | 15 | $this->assertExactlyOneNotNull($product, $subscription, $pricePerUnit); |
|||
84 | |||||
85 | 15 | $abstractProduct = $product ?? $subscription; |
|||
86 | 15 | $pricePerUnit = $this->getPricePerUnit($abstractProduct, $pricePerUnit, $isCHF); |
|||
87 | |||||
88 | 14 | if ($additionalEmails && !$subscription) { |
|||
89 | throw new Exception('Cannot submit additionalEmails without a subscription'); |
||||
90 | } |
||||
91 | |||||
92 | 14 | if ($additionalEmails && !$subscription->isPro()) { |
|||
93 | throw new Exception('Cannot submit additionalEmails with a subscription that is not pro'); |
||||
94 | } |
||||
95 | |||||
96 | // User cannot choose type of a subscription |
||||
97 | 14 | if ($subscription) { |
|||
98 | 2 | $type = $subscription->getType(); |
|||
99 | } |
||||
100 | |||||
101 | 14 | return [ |
|||
102 | 14 | $abstractProduct, |
|||
103 | 14 | $pricePerUnit, |
|||
104 | 14 | $quantity, |
|||
105 | 14 | $isCHF, |
|||
106 | 14 | $type, |
|||
107 | 14 | $additionalEmails, |
|||
108 | 14 | ]; |
|||
109 | } |
||||
110 | |||||
111 | 15 | private function assertExactlyOneNotNull(...$args): void |
|||
112 | { |
||||
113 | 15 | $onlyNotNull = array_filter($args, fn ($val) => $val !== null); |
|||
114 | |||||
115 | 15 | if (count($onlyNotNull) !== 1) { |
|||
116 | throw new Exception('Must have a product, or a subscription, or a pricePerUnit. And not a mixed of those.'); |
||||
117 | } |
||||
118 | } |
||||
119 | |||||
120 | 4 | public function updateOrderLineAndTransactionLine(OrderLine $orderLine, array $line): void |
|||
121 | { |
||||
122 | 4 | $this->userRepository->getAclFilter()->runWithoutAcl(function () use ($orderLine, $line): void { |
|||
123 | 4 | $args = $this->extractArgsFromLine($line); |
|||
124 | 4 | $this->updateOrderLine($orderLine, ...$args); |
|||
0 ignored issues
–
show
It seems like
$args can also be of type Money\Money ; however, parameter $product of Application\Service\Invoicer::updateOrderLine() does only seem to accept Application\Model\AbstractProduct|null , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
125 | 4 | }); |
|||
126 | } |
||||
127 | |||||
128 | 14 | private function updateOrderLine(OrderLine $orderLine, ?AbstractProduct $product, Money $pricePerUnit, int $quantity, bool $isCHF, ProductType $type, array $additionalEmails): void |
|||
129 | { |
||||
130 | 14 | if ($isCHF) { |
|||
131 | 13 | $balanceCHF = $pricePerUnit->multiply($quantity); |
|||
132 | 13 | $balanceEUR = Money::EUR(0); |
|||
133 | } else { |
||||
134 | 2 | $balanceCHF = Money::CHF(0); |
|||
135 | 2 | $balanceEUR = $pricePerUnit->multiply($quantity); |
|||
136 | } |
||||
137 | |||||
138 | 14 | if (!$product) { |
|||
139 | 3 | $orderLine->setDonation(); |
|||
140 | 11 | } elseif ($product instanceof Product) { |
|||
141 | 9 | $orderLine->setProduct($product); |
|||
142 | 2 | } elseif ($product instanceof Subscription) { |
|||
143 | 2 | $orderLine->setSubscription($product); |
|||
144 | } else { |
||||
145 | throw new Exception('Unsupported subclass of product'); |
||||
146 | } |
||||
147 | |||||
148 | 14 | $orderLine->setIsCHF($isCHF); |
|||
149 | 14 | $orderLine->setType($type); |
|||
150 | 14 | $orderLine->setQuantity($quantity); |
|||
151 | 14 | $orderLine->setBalanceCHF($balanceCHF); |
|||
152 | 14 | $orderLine->setBalanceEUR($balanceEUR); |
|||
153 | 14 | $orderLine->setAdditionalEmails($additionalEmails); |
|||
154 | } |
||||
155 | |||||
156 | 15 | private function getPricePerUnit(?AbstractProduct $product, ?Money $pricePerUnit, bool $isCHF): Money |
|||
157 | { |
||||
158 | 15 | if ($product && $isCHF) { |
|||
159 | 11 | return $product->getPricePerUnitCHF(); |
|||
160 | } |
||||
161 | |||||
162 | 5 | if ($product) { |
|||
163 | 1 | return $product->getPricePerUnitEUR(); |
|||
164 | } |
||||
165 | |||||
166 | 4 | if ($pricePerUnit === null || !$pricePerUnit->isPositive()) { |
|||
167 | 1 | throw new Exception('A donation must have strictly positive price'); |
|||
168 | } |
||||
169 | |||||
170 | // The API always assume CHF, but if the client specifically say it is EUR, we need to convert |
||||
171 | 3 | if (!$isCHF) { |
|||
172 | 1 | return Money::EUR($pricePerUnit->getAmount()); |
|||
173 | } |
||||
174 | |||||
175 | 2 | return $pricePerUnit; |
|||
176 | } |
||||
177 | } |
||||
178 |