Ecodev /
artisans
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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 |