Failed Conditions
Push — master ( 3cf6df...56f0b0 )
by Adrien
07:17
created

AccountRepository::updateAccountBalance()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 10
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Repository;
6
7
use Application\DBAL\Types\AccountTypeType;
8
use Application\Model\Account;
9
use Application\Model\User;
10
use Ecodev\Felix\Repository\LimitedAccessSubQuery;
11
use Exception;
12
use Money\Money;
13
14
class AccountRepository extends AbstractRepository implements LimitedAccessSubQuery
15
{
16
    private const PARENT_ACCOUNT_ID_FOR_USER = 10011;
17
    const ACCOUNT_ID_FOR_BANK = 10025;
18
19
    /**
20
     * In memory max code that keep being incremented if we create several account at once without flushing in DB
21
     */
22
    private ?int $maxCode = null;
23
24
    /**
25
     * Clear all caches
26
     */
27 113
    public function clearCache(): void
28
    {
29 113
        $this->maxCode = null;
30 113
    }
31
32
    /**
33
     * Returns pure SQL to get ID of all objects that are accessible to given user.
34
     *
35
     * @param null|User $user
36
     */
37 19
    public function getAccessibleSubQuery(?\Ecodev\Felix\Model\User $user): string
38
    {
39 19
        if (!$user) {
40 1
            return '-1';
41
        }
42
43 18
        if (in_array($user->getRole(), [User::ROLE_RESPONSIBLE, User::ROLE_ADMINISTRATOR], true)) {
44 8
            return $this->getAllIdsQuery();
45
        }
46
47 10
        return $this->getAllIdsForFamilyQuery($user);
48
    }
49
50
    /**
51
     * Unsecured way to get a account from its ID.
52
     *
53
     * This should only be used in tests or controlled environment.
54
     */
55 16
    public function getOneById(int $id): Account
56
    {
57
        $account = $this->getAclFilter()->runWithoutAcl(function () use ($id) {
58 16
            return $this->findOneById($id);
59 16
        });
60
61 16
        if (!$account) {
62 1
            throw new Exception('Account #' . $id . ' not found');
63
        }
64
65 16
        return $account;
66
    }
67
68
    /**
69
     * This will return, and potentially create, an account for the given user
70
     */
71 20
    public function getOrCreate(User $user): Account
72
    {
73
        // If an account already exists, because getOrCreate was called once before without flushing in between,
74
        // then can return immediately
75 20
        if ($user->getAccount()) {
76 13
            return $user->getAccount();
77
        }
78
79
        // If user have an owner, then create account for the owner instead
80 9
        if ($user->getOwner()) {
81
            $user = $user->getOwner();
82
        }
83
84
        $account = $this->getAclFilter()->runWithoutAcl(function () use ($user) {
85 9
            return $this->findOneByOwner($user);
0 ignored issues
show
Bug introduced by
The method findOneByOwner() does not exist on Application\Repository\AccountRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

85
            return $this->/** @scrutinizer ignore-call */ findOneByOwner($user);
Loading history...
86 9
        });
87
88 9
        if (!$account) {
89 9
            $account = new Account();
90 9
            $this->getEntityManager()->persist($account);
91 9
            $account->setOwner($user);
92 9
            $account->setType(AccountTypeType::LIABILITY);
93 9
            $account->setName($user->getName());
94
95 9
            $parent = $this->getOneById(self::PARENT_ACCOUNT_ID_FOR_USER);
96
97
            // Find the max account code, using the liability parent code as prefix
98 9
            if (!$this->maxCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->maxCode of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
99 9
                $maxQuery = 'SELECT MAX(code) FROM account WHERE code LIKE ' . $this->getEntityManager()->getConnection()->quote($parent->getCode() . '%');
100 9
                $this->maxCode = (int) $this->getEntityManager()->getConnection()->fetchColumn($maxQuery);
101
102
                // If there is no child account yet, reserve enough digits for many users
103 9
                if ($this->maxCode === $parent->getCode()) {
104 1
                    $this->maxCode = $parent->getCode() * 10000;
105
                }
106
            }
107
108 9
            $nextCode = ++$this->maxCode;
109 9
            $account->setCode($nextCode);
110
111 9
            $account->setParent($parent);
112
        }
113
114 9
        return $account;
115
    }
116
117
    /**
118
     * Sum balance by account type
119
     *
120
     * @API\Input(type="AccountType")
121
     */
122 2
    public function totalBalanceByType(string $accountType): Money
123
    {
124 2
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
125 2
            ->select('SUM(balance)')
126 2
            ->from($this->getClassMetadata()->getTableName())
127 2
            ->where('type = :type');
128
129 2
        $qb->setParameter('type', $accountType);
130
131 2
        $result = $qb->execute();
132
133 2
        return Money::CHF($result->fetchColumn());
0 ignored issues
show
Bug introduced by
It seems like $result->fetchColumn() can also be of type false; however, parameter $amount of Money\Money::CHF() does only seem to accept integer|string, 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 ignore-type  annotation

133
        return Money::CHF(/** @scrutinizer ignore-type */ $result->fetchColumn());
Loading history...
134
    }
135
136
    /**
137
     * Update accounts' balance
138
     *
139
     * @param null|Account $account the account to update, or null for all accounts
140
     */
141 1
    public function updateAccountBalance(?Account $account = null): void
142
    {
143 1
        $connection = $this->getEntityManager()->getConnection();
144 1
        $sql = 'CALL update_account_balance(?)';
145
146 1
        if ($account) {
147
            $connection->executeQuery($sql, [$account->getId()]);
148
        } else {
149 1
            foreach ($this->findAll() as $a) {
150 1
                $connection->executeQuery($sql, [$a->getId()]);
151
            }
152
        }
153 1
    }
154
155
    /**
156
     * Return the next available Account code
157
     */
158
    public function getNextCodeAvailable(): int
159
    {
160
        $qb = _em()->getConnection()->createQueryBuilder()
161
            ->select('IFNULL(MAX(a.code) + 1, 1)')
162
            ->from('account', 'a');
163
164
        return (int) $qb->execute()->fetchColumn();
165
    }
166
167 2
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
168
    {
169
        $sql = <<<STRING
170 2
                DELETE account FROM account
171
                INNER JOIN user ON account.owner_id = user.id
172
                AND user.owner_id IS NOT NULL
173
                AND user.owner_id != user.id
174
                WHERE
175
                account.id NOT IN (SELECT credit_id FROM transaction_line WHERE credit_id IS NOT NULL)
176
                AND account.id NOT IN (SELECT debit_id FROM transaction_line WHERE debit_id IS NOT NULL) 
177
            STRING;
178
179 2
        $count = $this->getEntityManager()->getConnection()->executeUpdate($sql);
180
181 2
        return $count;
182
    }
183
}
184