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

95
        $account = $this->getAclFilter()->runWithoutAcl(fn () => $this->/** @scrutinizer ignore-call */ findOneByOwner($user));
Loading history...
96
97 16
        if (!$account) {
98 16
            $account = new Account();
99 16
            $this->getEntityManager()->persist($account);
100 16
            $account->setOwner($user);
101 16
            $account->setType(AccountTypeType::LIABILITY);
102 16
            $account->setName($user->getName());
103
104 16
            $config = $container->get('config');
105 16
            $parentCode = (int) $config['accounting']['customerDepositsAccountCode'];
106 16
            $parent = $this->getAclFilter()->runWithoutAcl(fn () => $this->findOneByCode($parentCode));
107
108
            // Find the max account code, using the liability parent code as prefix
109 16
            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...
110 15
                $maxQuery = 'SELECT MAX(code) FROM account WHERE code LIKE ' . $this->getEntityManager()->getConnection()->quote($parent->getCode() . '%');
111 15
                $this->maxCode = (int) $this->getEntityManager()->getConnection()->fetchOne($maxQuery);
112
113
                // If there is no child account yet, reserve enough digits for many users
114 15
                if ($this->maxCode === $parent->getCode()) {
115 1
                    $this->maxCode = $parent->getCode() * 10000;
116
                }
117
            }
118
119 16
            $nextCode = ++$this->maxCode;
120 16
            $account->setCode($nextCode);
121
122 16
            $account->setParent($parent);
123
        }
124
125 16
        return $account;
126
    }
127
128
    /**
129
     * Sum balance by account type.
130
     *
131
     * @API\Input(type="AccountType")
132
     */
133 2
    public function totalBalanceByType(string $accountType): Money
134
    {
135 2
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
136 2
            ->select('SUM(balance)')
137 2
            ->from($this->getClassMetadata()->getTableName())
138 2
            ->where('type = :type');
139
140 2
        $qb->setParameter('type', $accountType);
141
142 2
        $result = $qb->executeQuery();
143
144 2
        return Money::CHF($result->fetchOne());
145
    }
146
147
    /**
148
     * Update all accounts' balance.
149
     */
150 1
    public function updateAccountsBalance(): void
151
    {
152 1
        $connection = $this->getEntityManager()->getConnection();
153 1
        $sql = 'CALL update_account_balance(0)';
154 1
        $connection->executeQuery($sql);
155
    }
156
157
    /**
158
     * Return the next available Account code.
159
     */
160
    public function getNextCodeAvailable(): int
161
    {
162
        $qb = _em()->getConnection()->createQueryBuilder()
163
            ->select('IFNULL(MAX(a.code) + 1, 1)')
164
            ->from('account', 'a');
165
166
        return (int) $qb->execute()->fetchOne();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Query\QueryBuilder::execute() has been deprecated: Use {@see executeQuery()} or {@see 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

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

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...
167
    }
168
169 1
    public function getRootAccountsQuery(): Query
170
    {
171 1
        $qb = $this->createQueryBuilder('account')
172 1
            ->andWhere('account.parent IS NULL')
173 1
            ->orderBy('account.code');
174
175 1
        return $qb->getQuery();
176
    }
177
178 2
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
179
    {
180 2
        $sql = <<<STRING
181
                DELETE account FROM account
182
                INNER JOIN user ON account.owner_id = user.id
183
                AND user.owner_id IS NOT NULL
184
                AND user.owner_id != user.id
185
                WHERE
186
                account.id NOT IN (SELECT credit_id FROM transaction_line WHERE credit_id IS NOT NULL)
187
                AND account.id NOT IN (SELECT debit_id FROM transaction_line WHERE debit_id IS NOT NULL) 
188
            STRING;
189
190 2
        $count = $this->getEntityManager()->getConnection()->executeStatement($sql);
191
192 2
        return $count;
193
    }
194
}
195