Passed
Push — master ( 3ba489...3505e6 )
by Adrien
11:36
created

AccountRepository::getOrCreate()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 6.0026

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 22
nc 9
nop 1
dl 0
loc 44
ccs 23
cts 24
cp 0.9583
crap 6.0026
rs 8.9457
c 3
b 0
f 0
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 16
        $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 9
        $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);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

100
                $this->maxCode = (int) /** @scrutinizer ignore-deprecated */ $this->getEntityManager()->getConnection()->fetchColumn($maxQuery);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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...
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\Res...tatement::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

133
        return Money::CHF(/** @scrutinizer ignore-deprecated */ $result->fetchColumn());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

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();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\Res...tatement::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

164
        return (int) /** @scrutinizer ignore-deprecated */ $qb->execute()->fetchColumn();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::executeUpdate() has been deprecated: Use {@link executeStatement()} instead. ( Ignorable by Annotation )

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

179
        $count = /** @scrutinizer ignore-deprecated */ $this->getEntityManager()->getConnection()->executeUpdate($sql);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
180
181 2
        return $count;
182
    }
183
}
184