Failed Conditions
Push — master ( 2695cf...ba7e19 )
by Sylvain
08:48 queued 11s
created

Invoicer::invoiceInitial()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.049

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 16
ccs 9
cts 10
cp 0.9
rs 8.8333
c 0
b 0
f 0
cc 7
nc 1
nop 3
crap 7.049
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Service;
6
7
use Application\DBAL\Types\BookingStatusType;
8
use Application\DBAL\Types\BookingTypeType;
9
use Application\Model\Account;
10
use Application\Model\Bookable;
11
use Application\Model\Booking;
12
use Application\Model\Transaction;
13
use Application\Model\TransactionLine;
14
use Application\Model\User;
15
use Application\Repository\AccountRepository;
16
use Application\Repository\BookingRepository;
17
use Cake\Chronos\Chronos;
18
use Doctrine\ORM\EntityManager;
19
use Money\Money;
20
21
/**
22
 * Service to create transactions for non-free booking, if needed, for all users or one user
23
 */
24
class Invoicer
25
{
26
    /**
27
     * @var EntityManager
28
     */
29
    private $entityManager;
30
31
    /**
32
     * @var int
33
     */
34
    private $count = 0;
35
36
    /**
37
     * @var BookingRepository
38
     */
39
    private $bookingRepository;
40
41 1
    public function __construct(EntityManager $entityManager)
42
    {
43 1
        $this->entityManager = $entityManager;
44 1
        $this->bookingRepository = $this->entityManager->getRepository(Booking::class);
45 1
    }
46
47 1
    public function invoicePeriodic(?User $onlyUser = null): int
48
    {
49 1
        $this->count = 0;
50
51 1
        $this->bookingRepository->getAclFilter()->runWithoutAcl(function () use ($onlyUser): void {
52 1
            $bookings = $this->bookingRepository->getAllToInvoice($onlyUser);
53
54 1
            $user = null;
55 1
            $bookingPerUser = [];
56
57
            /** @var Booking $booking */
58 1
            foreach ($bookings as $booking) {
59 1
                $nextUser = $booking->getOwner();
60 1
                if ($user !== $nextUser) {
61 1
                    $this->createTransaction($user, $bookingPerUser, false);
62
63 1
                    $user = $nextUser;
64 1
                    $bookingPerUser = [];
65
                }
66
67 1
                $bookingPerUser[] = $booking;
68
            }
69 1
            $this->createTransaction($user, $bookingPerUser, false);
70 1
        });
71
72 1
        return $this->count;
73
    }
74
75 8
    public function invoiceInitial(User $user, Booking $booking, ?string $previousStatus = null): void
76
    {
77 8
        $this->bookingRepository->getAclFilter()->runWithoutAcl(function () use ($user, $booking, $previousStatus): void {
78 8
            if ($previousStatus !== BookingStatusType::APPLICATION || !in_array($booking->getStatus(), [BookingStatusType::BOOKED, BookingStatusType::PROCESSED], true)) {
79 8
                return;
80
            }
81 5
            $bookable = $booking->getBookable();
82
            // Never invoice bookings of application type bookable, only used to request the admin to create the actual booking
83 5
            if ($bookable->getBookingType() === BookingTypeType::APPLICATION) {
84
                return;
85
            }
86 5
            if (!$bookable->getCreditAccount() || ($bookable->getInitialPrice()->isZero() && $bookable->getPeriodicPrice()->isZero())) {
87 1
                return;
88
            }
89
90 4
            $this->createTransaction($user, [$booking], true);
91 8
        });
92 8
    }
93
94 5
    private function createTransaction(?User $user, array $bookings, bool $isInitial): void
95
    {
96 5
        if (!$user || !$bookings) {
97 1
            return;
98
        }
99
100
        /** @var AccountRepository $accountRepository */
101 5
        $accountRepository = $this->entityManager->getRepository(Account::class);
102 5
        $account = $accountRepository->getOrCreate($user);
103 5
        $transaction = new Transaction();
104 5
        $transaction->setTransactionDate(Chronos::now());
105 5
        $transaction->setName('Cotisation et services ' . Chronos::today()->format('Y'));
106 5
        $this->entityManager->persist($transaction);
107
108 5
        foreach ($bookings as $booking) {
109 5
            $bookable = $booking->getBookable();
110 5
            if ($isInitial) {
111 4
                $balance = $this->calculateInitialBalance($booking);
112 4
                $this->createTransactionLine($transaction, $bookable, $account, $balance, 'Prestation ponctuelle', $user->getName());
113
            }
114
115 5
            $balance = $this->calculatePeriodicBalance($booking);
116 5
            $this->createTransactionLine($transaction, $bookable, $account, $balance, 'Prestation annuelle', $user->getName());
117
        }
118
119 5
        ++$this->count;
120 5
    }
121
122 4
    private function calculateInitialBalance(Booking $booking): Money
123
    {
124 4
        $bookable = $booking->getBookable();
125
126
        // TODO: https://support.ecodev.ch/issues/6227
127
128 4
        return $bookable->getInitialPrice();
129
    }
130
131 5
    private function calculatePeriodicBalance(Booking $booking): Money
132
    {
133 5
        return $booking->getPeriodicPrice();
134
    }
135
136 5
    private function createTransactionLine(Transaction $transaction, Bookable $bookable, Account $account, Money $balance, string $name, string $remarks = ''): void
137
    {
138 5
        if ($balance->isPositive()) {
139 4
            $debit = $account;
140 4
            $credit = $bookable->getCreditAccount();
141 3
        } elseif ($balance->isNegative()) {
142 1
            $debit = $bookable->getCreditAccount();
143 1
            $credit = $account;
144 1
            $balance = $balance->absolute();
145
        } else {
146
            // Never create a line with 0 balance
147 2
            return;
148
        }
149
150 5
        $transactionLine = new TransactionLine();
151 5
        $this->entityManager->persist($transactionLine);
152
153 5
        $transactionLine->setName($name);
154 5
        $transactionLine->setBookable($bookable);
155 5
        $transactionLine->setDebit($debit);
156 5
        $transactionLine->setCredit($credit);
157 5
        $transactionLine->setBalance($balance);
158 5
        $transactionLine->setTransaction($transaction);
159 5
        $transactionLine->setTransactionDate(Chronos::now());
160 5
        $transactionLine->setRemarks($remarks);
161 5
    }
162
}
163