Passed
Push — master ( fb4a0d...6e1638 )
by Sylvain
07:33
created

AccountRepository::clearCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

91
            return $this->/** @scrutinizer ignore-call */ findOneByOwner($user);
Loading history...
92 9
        });
93
94 9
        if (!$account) {
95 9
            $account = new Account();
96 9
            $this->getEntityManager()->persist($account);
97 9
            $account->setOwner($user);
98 9
            $account->setType(AccountTypeType::LIABILITY);
99 9
            $account->setName($user->getName());
100
101 9
            $config = $container->get('config');
102 9
            $parentCode = (int) $config['accounting']['customerDepositsAccountCode'];
103 9
            $parent = $this->getAclFilter()->runWithoutAcl(function () use ($parentCode) {
104 9
                return $this->findOneByCode($parentCode);
105 9
            });
106
107
            // Find the max account code, using the liability parent code as prefix
108 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...
109 9
                $maxQuery = 'SELECT MAX(code) FROM account WHERE code LIKE ' . $this->getEntityManager()->getConnection()->quote($parent->getCode() . '%');
110 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

110
                $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...
111
112
                // If there is no child account yet, reserve enough digits for many users
113 9
                if ($this->maxCode === $parent->getCode()) {
114 1
                    $this->maxCode = $parent->getCode() * 10000;
115
                }
116
            }
117
118 9
            $nextCode = ++$this->maxCode;
119 9
            $account->setCode($nextCode);
120
121 9
            $account->setParent($parent);
122
        }
123
124 9
        return $account;
125
    }
126
127
    /**
128
     * Sum balance by account type
129
     *
130
     * @API\Input(type="AccountType")
131
     */
132 2
    public function totalBalanceByType(string $accountType): Money
133
    {
134 2
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
135 2
            ->select('SUM(balance)')
136 2
            ->from($this->getClassMetadata()->getTableName())
137 2
            ->where('type = :type');
138
139 2
        $qb->setParameter('type', $accountType);
140
141 2
        $result = $qb->execute();
142
143 2
        return Money::CHF($result->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

143
        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...
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

143
        return Money::CHF(/** @scrutinizer ignore-type */ $result->fetchColumn());
Loading history...
144
    }
145
146
    /**
147
     * Update accounts' balance
148
     *
149
     * @param null|Account $account the account to update, or null for all accounts
150
     */
151 1
    public function updateAccountBalance(?Account $account = null): void
152
    {
153 1
        $connection = $this->getEntityManager()->getConnection();
154 1
        $sql = 'CALL update_account_balance(?)';
155
156 1
        if ($account) {
157
            $connection->executeQuery($sql, [$account->getId()]);
158
        } else {
159 1
            foreach ($this->findAll() as $a) {
160 1
                $connection->executeQuery($sql, [$a->getId()]);
161
            }
162
        }
163 1
    }
164
165
    /**
166
     * Return the next available Account code
167
     */
168
    public function getNextCodeAvailable(): int
169
    {
170
        $qb = _em()->getConnection()->createQueryBuilder()
171
            ->select('IFNULL(MAX(a.code) + 1, 1)')
172
            ->from('account', 'a');
173
174
        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

174
        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...
175
    }
176
177 2
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
178
    {
179
        $sql = <<<STRING
180 2
                DELETE account FROM account
181
                INNER JOIN user ON account.owner_id = user.id
182
                AND user.owner_id IS NOT NULL
183
                AND user.owner_id != user.id
184
                WHERE
185
                account.id NOT IN (SELECT credit_id FROM transaction_line WHERE credit_id IS NOT NULL)
186
                AND account.id NOT IN (SELECT debit_id FROM transaction_line WHERE debit_id IS NOT NULL) 
187
            STRING;
188
189 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

189
        $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...
190
191 2
        return $count;
192
    }
193
}
194