Passed
Push — main ( d69a4e...ed0bb0 )
by Iain
06:17
created

ReceiptGenerator::generateReceiptForPayment()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 43
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 30
nc 4
nop 1
dl 0
loc 43
rs 9.44
c 1
b 0
f 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright Iain Cambridge 2020-2023.
7
 *
8
 * Use of this software is governed by the Business Source License included in the LICENSE file and at https://getparthenon.com/docs/next/license.
9
 *
10
 * Change Date: TBD ( 3 years after 2.2.0 release )
11
 *
12
 * On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
13
 */
14
15
namespace Parthenon\Billing\Receipt;
16
17
use Brick\Math\RoundingMode;
18
use Brick\Money\Money;
19
use Parthenon\Billing\Entity\CustomerInterface;
20
use Parthenon\Billing\Entity\Payment;
21
use Parthenon\Billing\Entity\Receipt;
22
use Parthenon\Billing\Entity\ReceiptLine;
23
use Parthenon\Billing\Entity\Subscription;
24
use Parthenon\Billing\Repository\PaymentRepositoryInterface;
25
use Parthenon\Billing\Tax\TaxCalculatorInterface;
26
27
class ReceiptGenerator implements ReceiptGeneratorInterface
28
{
29
    public function __construct(
30
        private PaymentRepositoryInterface $paymentRepository,
31
        private TaxCalculatorInterface $taxCalculator,
32
    ) {
33
    }
34
35
    public function generateInvoiceForPeriod(\DateTimeInterface $startDate, \DateTimeInterface $endDate, CustomerInterface $customer): Receipt
36
    {
37
        $payments = $this->paymentRepository->getPaymentsForCustomerDuring($startDate, $endDate, $customer);
38
39
        if (empty($payments)) {
40
            throw new \Exception('No payments for receipt');
41
        }
42
43
        $total = null;
44
        $vatTotal = null;
45
        $subTotalTotal = null;
46
        $subscriptions = [];
47
        $lines = [];
48
49
        $receipt = new Receipt();
50
        foreach ($payments as $payment) {
51
            $subscriptions = array_merge($subscriptions, $payment->getSubscriptions()->toArray());
52
            $money = $payment->getMoneyAmount();
53
54
            $total = $this->addToTotal($total, $money);
55
56
            if (0 === $payment->getSubscriptions()->count()) {
57
                $vat = $this->taxCalculator->calculateVatAmountForCustomer($customer, $payment->getMoneyAmount());
58
                $subTotal = $this->taxCalculator->calculateSubTotalForCustomer($customer, $money);
59
                $vatTotal = $this->addToTotal($vatTotal, $vat);
60
                $subTotalTotal = $this->addToTotal($subTotalTotal, $subTotal);
61
62
                $line = new ReceiptLine();
63
                $line->setTotal($payment->getAmount());
64
                $line->setCurrency($payment->getCurrency());
65
                $line->setDescription($payment->getDescription());
66
                $line->setReceipt($receipt);
67
                $line->setVatTotal($vat->getMinorAmount()->toInt());
68
                $line->setSubTotal($subTotal->getMinorAmount()->toInt());
69
70
                $lines[] = $line;
71
            }
72
        }
73
74
        /** @var Subscription $subscription */
75
        foreach ($subscriptions as $subscription) {
76
            $money = $subscription->getMoneyAmount();
77
78
            $vat = $this->taxCalculator->calculateVatAmountForCustomer($customer, $money);
79
            $subTotal = $money->minus($vat, RoundingMode::HALF_DOWN);
80
81
            $vatTotal = $this->addToTotal($vatTotal, $vat);
82
            $subTotalTotal = $this->addToTotal($subTotalTotal, $subTotal);
83
84
            $line = new ReceiptLine();
85
            $line->setTotal($subscription->getAmount());
86
            $line->setCurrency($subscription->getCurrency());
87
            $line->setDescription($subscription->getPlanName());
88
            $line->setReceipt($receipt);
89
            $line->setVatTotal($vat->getMinorAmount()->toInt());
90
            $line->setSubTotal($subTotal->getMinorAmount()->toInt());
91
92
            $lines[] = $line;
93
        }
94
95
        if (!$total instanceof Money) {
96
            throw new \LogicException('Total must be money if payments exist');
97
        }
98
99
        $receipt->setCustomer($customer);
100
        $receipt->setPayments($payments);
101
        $receipt->setSubscriptions($subscriptions);
102
        $receipt->setTotal($total->getMinorAmount()->toInt());
103
        $receipt->setSubTotal($subTotalTotal->getMinorAmount()->toInt());
104
        $receipt->setVatTotal($vatTotal->getMinorAmount()->toInt());
105
        $receipt->setLines($lines);
106
107
        return $receipt;
108
    }
109
110
    public function generateReceiptForPayment(Payment $payment): Receipt
111
    {
112
        $receipt = new Receipt();
113
        $total = $payment->getMoneyAmount();
114
        $vatTotal = null;
115
        $subTotalTotal = null;
116
        $lines = [];
117
        $customer = $payment->getCustomer();
118
119
        /** @var Subscription $subscription */
120
        foreach ($payment->getSubscriptions() as $subscription) {
121
            $money = $subscription->getMoneyAmount();
122
123
            $vat = $this->taxCalculator->calculateVatAmountForCustomer($customer, $money);
124
            $subTotal = $this->taxCalculator->calculateSubTotalForCustomer($customer, $money);
125
126
            $vatTotal = $this->addToTotal($vatTotal, $vat);
127
            $subTotalTotal = $this->addToTotal($subTotalTotal, $subTotal);
128
129
            $line = new ReceiptLine();
130
            $line->setTotal($subscription->getAmount());
131
            $line->setCurrency($subscription->getCurrency());
132
            $line->setDescription($subscription->getPlanName());
133
            $line->setReceipt($receipt);
134
            $line->setVatTotal($vat->getMinorAmount()->toInt());
135
            $line->setSubTotal($subTotal->getMinorAmount()->toInt());
136
137
            $lines[] = $line;
138
        }
139
140
        if (!$total instanceof Money) {
0 ignored issues
show
introduced by
$total is always a sub-type of Brick\Money\Money.
Loading history...
141
            throw new \LogicException('Total must be money if payments exist');
142
        }
143
144
        $receipt->setCustomer($customer);
145
        $receipt->setPayments([$payment]);
146
        $receipt->setSubscriptions($payment->getSubscriptions());
147
        $receipt->setTotal($total->getMinorAmount()->toInt());
148
        $receipt->setSubTotal($subTotalTotal->getMinorAmount()->toInt());
149
        $receipt->setVatTotal($vatTotal->getMinorAmount()->toInt());
150
        $receipt->setLines($lines);
151
152
        return $receipt;
153
    }
154
155
    private function addToTotal(?Money $total, Money $money): Money
156
    {
157
        if (null === $total) {
158
            $total = $money;
159
        } else {
160
            $total = $total->plus($money, RoundingMode::HALF_EVEN);
161
        }
162
163
        return $total;
164
    }
165
}
166