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