1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace ApplicationTest\Service; |
||||
6 | |||||
7 | use Application\Enum\PaymentMethod; |
||||
8 | use Application\Enum\ProductType; |
||||
9 | use Application\Model\AbstractProduct; |
||||
10 | use Application\Model\Order; |
||||
11 | use Application\Model\OrderLine; |
||||
12 | use Application\Model\Product; |
||||
13 | use Application\Model\Subscription; |
||||
14 | use Application\Service\Invoicer; |
||||
15 | use ApplicationTest\Traits\TestWithTransactionAndUser; |
||||
16 | use Money\Money; |
||||
17 | use PHPUnit\Framework\TestCase; |
||||
18 | |||||
19 | class InvoicerTest extends TestCase |
||||
20 | { |
||||
21 | use TestWithTransactionAndUser; |
||||
22 | |||||
23 | /** |
||||
24 | * @dataProvider providerCreateOrder |
||||
25 | */ |
||||
26 | public function testCreateOrder(array $input, array $expectedOrderLines): void |
||||
27 | { |
||||
28 | $input['orderLines'] = $this->hydrateTestData($input['orderLines']); |
||||
29 | |||||
30 | global $container; |
||||
31 | /** @var Invoicer $invoicer */ |
||||
32 | $invoicer = $container->get(Invoicer::class); |
||||
33 | $order = $invoicer->createOrder($input); |
||||
34 | |||||
35 | $actualOrderLines = $this->extractOrderLines($order); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
36 | self::assertSame($expectedOrderLines, $actualOrderLines); |
||||
37 | } |
||||
38 | |||||
39 | public static function providerCreateOrder(): iterable |
||||
40 | { |
||||
41 | return [ |
||||
42 | 'free product should create order, even with transactions for zero dollars' => [ |
||||
43 | [ |
||||
44 | 'paymentMethod' => PaymentMethod::Bvr, |
||||
45 | 'orderLines' => [ |
||||
46 | [ |
||||
47 | 'quantity' => 1, |
||||
48 | 'isCHF' => true, |
||||
49 | 'type' => ProductType::Digital, |
||||
50 | 'product' => [ |
||||
51 | 'name' => 'My product', |
||||
52 | 'pricePerUnitCHF' => Money::CHF(0), |
||||
53 | 'pricePerUnitEUR' => Money::EUR(0), |
||||
54 | ], |
||||
55 | 'additionalEmails' => [], |
||||
56 | ], |
||||
57 | ], |
||||
58 | ], |
||||
59 | [ |
||||
60 | [ |
||||
61 | 'My product', |
||||
62 | 1, |
||||
63 | '0', |
||||
64 | '0', |
||||
65 | true, |
||||
66 | ProductType::Digital, |
||||
67 | ], |
||||
68 | ], |
||||
69 | ], |
||||
70 | 'normal' => [ |
||||
71 | [ |
||||
72 | 'paymentMethod' => PaymentMethod::Bvr, |
||||
73 | 'orderLines' => [ |
||||
74 | [ |
||||
75 | 'quantity' => 3, |
||||
76 | 'isCHF' => true, |
||||
77 | 'type' => ProductType::Digital, |
||||
78 | 'product' => [ |
||||
79 | 'name' => 'My product 1', |
||||
80 | 'pricePerUnitCHF' => Money::CHF(275), |
||||
81 | 'pricePerUnitEUR' => Money::EUR(280), |
||||
82 | ], |
||||
83 | 'additionalEmails' => [], |
||||
84 | ], |
||||
85 | [ |
||||
86 | 'quantity' => 1, |
||||
87 | 'isCHF' => true, |
||||
88 | 'type' => ProductType::Digital, |
||||
89 | 'product' => [ |
||||
90 | 'name' => 'My product 2', |
||||
91 | 'pricePerUnitCHF' => Money::CHF(20000), |
||||
92 | 'pricePerUnitEUR' => Money::EUR(25000), |
||||
93 | ], |
||||
94 | 'additionalEmails' => [], |
||||
95 | ], |
||||
96 | ], |
||||
97 | ], |
||||
98 | [ |
||||
99 | [ |
||||
100 | 'My product 1', |
||||
101 | 3, |
||||
102 | '825', |
||||
103 | '0', |
||||
104 | true, |
||||
105 | ProductType::Digital, |
||||
106 | ], |
||||
107 | [ |
||||
108 | 'My product 2', |
||||
109 | 1, |
||||
110 | '20000', |
||||
111 | '0', |
||||
112 | true, |
||||
113 | ProductType::Digital, |
||||
114 | ], |
||||
115 | ], |
||||
116 | ], |
||||
117 | 'with mixed CHF/EURO prices' => [ |
||||
118 | [ |
||||
119 | 'paymentMethod' => PaymentMethod::Bvr, |
||||
120 | 'orderLines' => [ |
||||
121 | [ |
||||
122 | 'quantity' => 3, |
||||
123 | 'isCHF' => false, |
||||
124 | 'type' => ProductType::Digital, |
||||
125 | 'product' => [ |
||||
126 | 'name' => 'My product 1', |
||||
127 | 'pricePerUnitCHF' => Money::CHF(275), |
||||
128 | 'pricePerUnitEUR' => Money::EUR(280), |
||||
129 | ], |
||||
130 | 'additionalEmails' => [], |
||||
131 | ], |
||||
132 | [ |
||||
133 | 'quantity' => 1, |
||||
134 | 'isCHF' => true, |
||||
135 | 'type' => ProductType::Paper, |
||||
136 | 'product' => [ |
||||
137 | 'name' => 'My product 2', |
||||
138 | 'pricePerUnitCHF' => Money::CHF(20000), |
||||
139 | 'pricePerUnitEUR' => Money::EUR(25000), |
||||
140 | ], |
||||
141 | 'additionalEmails' => [], |
||||
142 | ], |
||||
143 | ], |
||||
144 | ], |
||||
145 | [ |
||||
146 | [ |
||||
147 | 'My product 1', |
||||
148 | 3, |
||||
149 | '0', |
||||
150 | '840', |
||||
151 | false, |
||||
152 | ProductType::Digital, |
||||
153 | ], |
||||
154 | [ |
||||
155 | 'My product 2', |
||||
156 | 1, |
||||
157 | '20000', |
||||
158 | '0', |
||||
159 | true, |
||||
160 | ProductType::Paper, |
||||
161 | ], |
||||
162 | ], |
||||
163 | ], |
||||
164 | 'negative balance should swap accounts' => [ |
||||
165 | [ |
||||
166 | 'paymentMethod' => PaymentMethod::Bvr, |
||||
167 | 'orderLines' => [ |
||||
168 | [ |
||||
169 | 'quantity' => 1, |
||||
170 | 'isCHF' => true, |
||||
171 | 'type' => ProductType::Digital, |
||||
172 | 'product' => [ |
||||
173 | 'name' => 'My product', |
||||
174 | 'pricePerUnitCHF' => Money::CHF(-10000), |
||||
175 | 'pricePerUnitEUR' => Money::EUR(-15000), |
||||
176 | ], |
||||
177 | 'additionalEmails' => [], |
||||
178 | ], |
||||
179 | ], |
||||
180 | ], |
||||
181 | [ |
||||
182 | [ |
||||
183 | 'My product', |
||||
184 | 1, |
||||
185 | '-10000', |
||||
186 | '0', |
||||
187 | true, |
||||
188 | ProductType::Digital, |
||||
189 | ], |
||||
190 | ], |
||||
191 | ], |
||||
192 | 'can create order for subscription' => [ |
||||
193 | [ |
||||
194 | 'paymentMethod' => PaymentMethod::Bvr, |
||||
195 | 'orderLines' => [ |
||||
196 | [ |
||||
197 | 'quantity' => 1, |
||||
198 | 'isCHF' => true, |
||||
199 | 'type' => ProductType::Digital, |
||||
200 | 'subscription' => [ |
||||
201 | 'name' => 'My subscription', |
||||
202 | 'pricePerUnitCHF' => Money::CHF(10000), |
||||
203 | 'pricePerUnitEUR' => Money::EUR(15000), |
||||
204 | 'type' => ProductType::Both, |
||||
205 | ], |
||||
206 | 'additionalEmails' => [], |
||||
207 | ], |
||||
208 | ], |
||||
209 | ], |
||||
210 | [ |
||||
211 | [ |
||||
212 | 'My subscription', |
||||
213 | 1, |
||||
214 | '10000', |
||||
215 | '0', |
||||
216 | true, |
||||
217 | ProductType::Both, |
||||
218 | ], |
||||
219 | ], |
||||
220 | ], |
||||
221 | 'can create order for donation' => [ |
||||
222 | [ |
||||
223 | 'paymentMethod' => PaymentMethod::Bvr, |
||||
224 | 'orderLines' => [ |
||||
225 | [ |
||||
226 | 'quantity' => 1, |
||||
227 | 'isCHF' => true, |
||||
228 | 'type' => ProductType::Digital, |
||||
229 | 'pricePerUnit' => Money::CHF(10000), |
||||
230 | 'additionalEmails' => [], |
||||
231 | ], |
||||
232 | ], |
||||
233 | ], |
||||
234 | [ |
||||
235 | [ |
||||
236 | 'Don', |
||||
237 | 1, |
||||
238 | '10000', |
||||
239 | '0', |
||||
240 | true, |
||||
241 | ProductType::Digital, |
||||
242 | ], |
||||
243 | ], |
||||
244 | ], |
||||
245 | ]; |
||||
246 | } |
||||
247 | |||||
248 | /** |
||||
249 | * @dataProvider providerUpdateOrderLineAndTransactionLine |
||||
250 | */ |
||||
251 | public function testUpdateOrderLineAndTransactionLine(string $originalOrder, ?array $newProduct, array $expectedOrderLines): void |
||||
252 | { |
||||
253 | $input = $this->providerCreateOrder()[$originalOrder][0]; |
||||
254 | $input['orderLines'] = $this->hydrateTestData($input['orderLines']); |
||||
255 | |||||
256 | global $container; |
||||
257 | /** @var Invoicer $invoicer */ |
||||
258 | $invoicer = $container->get(Invoicer::class); |
||||
259 | $order = $invoicer->createOrder($input); |
||||
260 | |||||
261 | if ($newProduct) { |
||||
262 | $product = $this->hydrateProduct($newProduct); |
||||
263 | } else { |
||||
264 | $product = $input['orderLines'][0]['product']; |
||||
265 | } |
||||
266 | |||||
267 | $line = [ |
||||
268 | 'quantity' => 100, |
||||
269 | 'isCHF' => true, |
||||
270 | 'type' => ProductType::Digital, |
||||
271 | 'product' => $product, |
||||
272 | 'additionalEmails' => [], |
||||
273 | ]; |
||||
274 | |||||
275 | $invoicer->updateOrderLineAndTransactionLine($order->getOrderLines()->first(), $line); |
||||
276 | |||||
277 | $actualOrderLines = $this->extractOrderLines($order); |
||||
0 ignored issues
–
show
It seems like
$order can also be of type null ; however, parameter $order of ApplicationTest\Service\...st::extractOrderLines() does only seem to accept Application\Model\Order , 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
![]() |
|||||
278 | self::assertSame($expectedOrderLines, $actualOrderLines); |
||||
279 | } |
||||
280 | |||||
281 | public static function providerUpdateOrderLineAndTransactionLine(): iterable |
||||
282 | { |
||||
283 | yield 'more quantity of same product' => [ |
||||
284 | 'normal', |
||||
285 | null, |
||||
286 | [ |
||||
287 | [ |
||||
288 | 'My product 1', |
||||
289 | 100, |
||||
290 | '27500', |
||||
291 | '0', |
||||
292 | true, |
||||
293 | ProductType::Digital, |
||||
294 | ], |
||||
295 | [ |
||||
296 | 'My product 2', |
||||
297 | 1, |
||||
298 | '20000', |
||||
299 | '0', |
||||
300 | true, |
||||
301 | ProductType::Digital, |
||||
302 | ], |
||||
303 | ], |
||||
304 | ]; |
||||
305 | yield 'more quantity of different, negative product' => [ |
||||
306 | 'normal', |
||||
307 | [ |
||||
308 | 'name' => 'My negative product', |
||||
309 | 'pricePerUnitCHF' => Money::CHF(-10000), |
||||
310 | 'pricePerUnitEUR' => Money::EUR(-15000), |
||||
311 | ], |
||||
312 | [ |
||||
313 | [ |
||||
314 | 'My negative product', |
||||
315 | 100, |
||||
316 | '-1000000', |
||||
317 | '0', |
||||
318 | true, |
||||
319 | ProductType::Digital, |
||||
320 | ], |
||||
321 | [ |
||||
322 | 'My product 2', |
||||
323 | 1, |
||||
324 | '20000', |
||||
325 | '0', |
||||
326 | true, |
||||
327 | ProductType::Digital, |
||||
328 | ], |
||||
329 | ], |
||||
330 | ]; |
||||
331 | yield 'from negative goes back to positive' => [ |
||||
332 | 'negative balance should swap accounts', |
||||
333 | [ |
||||
334 | 'name' => 'My positive product', |
||||
335 | 'pricePerUnitCHF' => Money::CHF(10000), |
||||
336 | 'pricePerUnitEUR' => Money::EUR(15000), |
||||
337 | ], |
||||
338 | [ |
||||
339 | [ |
||||
340 | 'My positive product', |
||||
341 | 100, |
||||
342 | '1000000', |
||||
343 | '0', |
||||
344 | true, |
||||
345 | ProductType::Digital, |
||||
346 | ], |
||||
347 | ], |
||||
348 | ]; |
||||
349 | } |
||||
350 | |||||
351 | private function hydrateTestData(array $input): array |
||||
352 | { |
||||
353 | foreach ($input as &$i) { |
||||
354 | if (array_key_exists('product', $i)) { |
||||
355 | $i['product'] = $this->hydrateProduct($i['product']); |
||||
356 | } |
||||
357 | if (array_key_exists('subscription', $i)) { |
||||
358 | $i['subscription'] = $this->hydrateSubscription($i['subscription']); |
||||
359 | } |
||||
360 | } |
||||
361 | |||||
362 | return $input; |
||||
363 | } |
||||
364 | |||||
365 | private function extractOrderLines(Order $order): array |
||||
366 | { |
||||
367 | $actualOrderLines = []; |
||||
368 | /** @var OrderLine $orderLine */ |
||||
369 | foreach ($order->getOrderLines() as $orderLine) { |
||||
370 | $abstractProduct = $orderLine->getProduct() ?: $orderLine->getSubscription(); |
||||
371 | $expectedName = $abstractProduct ? $abstractProduct->getName() : 'Don'; |
||||
372 | self::assertSame($expectedName, $orderLine->getName()); |
||||
373 | |||||
374 | $actualOrderLines[] = [ |
||||
375 | $orderLine->getName(), |
||||
376 | $orderLine->getQuantity(), |
||||
377 | $orderLine->getBalanceCHF()->getAmount(), |
||||
378 | $orderLine->getBalanceEUR()->getAmount(), |
||||
379 | $orderLine->isCHF(), |
||||
380 | $orderLine->getType(), |
||||
381 | ]; |
||||
382 | } |
||||
383 | |||||
384 | return $actualOrderLines; |
||||
385 | } |
||||
386 | |||||
387 | private function hydrateProduct(array $p): Product |
||||
388 | { |
||||
389 | $product = new Product(); |
||||
390 | $this->hydrateAbstractProduct($product, $p); |
||||
391 | |||||
392 | return $product; |
||||
393 | } |
||||
394 | |||||
395 | private function hydrateSubscription(array $s): Subscription |
||||
396 | { |
||||
397 | $subscription = new Subscription(); |
||||
398 | $this->hydrateAbstractProduct($subscription, $s); |
||||
399 | |||||
400 | return $subscription; |
||||
401 | } |
||||
402 | |||||
403 | private function hydrateAbstractProduct(AbstractProduct $product, array $p): void |
||||
404 | { |
||||
405 | $product->setName($p['name']); |
||||
406 | $product->setPricePerUnitCHF($p['pricePerUnitCHF']); |
||||
407 | $product->setPricePerUnitEUR($p['pricePerUnitEUR']); |
||||
408 | $product->setType($p['type'] ?? ProductType::Digital); |
||||
409 | } |
||||
410 | } |
||||
411 |