Failed Conditions
Push — master ( a427b2...509ea4 )
by Adrien
16:44
created

AccountRepository::getAccessibleSubQuery()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 12
dl 0
loc 20
ccs 8
cts 8
cp 1
rs 9.8666
c 2
b 0
f 0
cc 4
nc 4
nop 1
crap 4
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
 * Class AccountRepository.
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 136
    public function clearCache(): void
33
    {
34 136
        $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 23
    public function getAccessibleSubQuery(?\Ecodev\Felix\Model\User $user): string
43
    {
44 23
        if (!$user) {
45 1
            return '-1';
46
        }
47
48 22
        if (in_array($user->getRole(), [
49
            User::ROLE_TRAINER,
50
            User::ROLE_ACCOUNTING_VERIFICATOR,
51
            User::ROLE_RESPONSIBLE,
52
            User::ROLE_ADMINISTRATOR,
53
        ], true)) {
54 11
            return $this->getAllIdsQuery();
55
        }
56
57 11
        if ($user->getRole() === User::ROLE_FORMATION_RESPONSIBLE) {
58 1
            return $this->getAllIdsForAnyOwner();
59
        }
60
61 10
        return $this->getAllIdsForFamilyQuery($user);
62
    }
63
64
    /**
65
     * Unsecured way to get a account from its ID.
66
     *
67
     * This should only be used in tests or controlled environment.
68
     */
69 5
    public function getOneById(int $id): Account
70
    {
71 5
        $account = $this->getAclFilter()->runWithoutAcl(fn () => $this->findOneById($id));
72
73 5
        if (!$account) {
74 1
            throw new Exception('Account #' . $id . ' not found');
75
        }
76
77 5
        return $account;
78
    }
79
80
    /**
81
     * This will return, and potentially create, an account for the given user.
82
     */
83 30
    public function getOrCreate(User $user): Account
84
    {
85
        global $container;
86
87
        // If an account already exists, because getOrCreate was called once before without flushing in between,
88
        // then can return immediately
89 30
        if ($user->getAccount()) {
90 17
            return $user->getAccount();
91
        }
92
93
        // If user have an owner, then create account for the owner instead
94 15
        if ($user->getOwner()) {
95
            $user = $user->getOwner();
96
        }
97
98 15
        $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

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

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