Passed
Push — master ( 6ea352...edbc52 )
by Adrien
04:07
created

AccountRepository::getNextCodeAvailable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 145
    public function clearCache(): void
33
    {
34 145
        $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 25
    public function getAccessibleSubQuery(?\Ecodev\Felix\Model\User $user): string
43
    {
44 25
        if (!$user) {
45 1
            return '-1';
46
        }
47
48 24
        if (in_array($user->getRole(), [
49 24
            User::ROLE_TRAINER,
50 24
            User::ROLE_ACCOUNTING_VERIFICATOR,
51 24
            User::ROLE_FORMATION_RESPONSIBLE,
52 24
            User::ROLE_RESPONSIBLE,
53 24
            User::ROLE_ADMINISTRATOR,
54 24
        ], true)) {
55 13
            return '';
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 33
    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 33
        if ($user->getAccount()) {
87 19
            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 2
    public function totalBalanceByType(string $accountType): Money
132
    {
133 2
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
134 2
            ->select('SUM(balance)')
135 2
            ->from($this->getClassMetadata()->getTableName())
136 2
            ->where('type = :type');
137
138 2
        $qb->setParameter('type', $accountType);
139
140 2
        $result = $qb->executeQuery();
141
142 2
        return Money::CHF($result->fetchOne());
143
    }
144
145
    /**
146
     * Update all accounts' balance.
147
     */
148 1
    public function updateAccountsBalance(): void
149
    {
150 1
        $connection = $this->getEntityManager()->getConnection();
151 1
        $sql = 'CALL update_account_balance(0)';
152 1
        $connection->executeQuery($sql);
153
    }
154
155
    /**
156
     * Return the next available Account code.
157
     */
158 3
    public function getNextCodeAvailable(?Account $parent): int
159
    {
160 3
        $connection = $this->getEntityManager()->getConnection();
161
162 3
        return (int) $connection->fetchOne('SELECT IFNULL(MAX(code) + 1, 1) FROM account WHERE IF(:parent IS NULL, parent_id IS NULL, parent_id = :parent)', [
163 3
            'parent' => $parent?->getId(),
164 3
        ]);
165
    }
166
167 1
    public function getRootAccountsQuery(): Query
168
    {
169 1
        $qb = $this->createQueryBuilder('account')
170 1
            ->andWhere('account.parent IS NULL')
171 1
            ->orderBy('account.code');
172
173 1
        return $qb->getQuery();
174
    }
175
176 2
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
177
    {
178 2
        $sql = <<<STRING
179
                DELETE account FROM account
180
                INNER JOIN user ON account.owner_id = user.id
181
                AND user.owner_id IS NOT NULL
182
                AND user.owner_id != user.id
183
                WHERE
184
                account.id NOT IN (SELECT credit_id FROM transaction_line WHERE credit_id IS NOT NULL)
185
                AND account.id NOT IN (SELECT debit_id FROM transaction_line WHERE debit_id IS NOT NULL) 
186 2
            STRING;
187
188 2
        $count = $this->getEntityManager()->getConnection()->executeStatement($sql);
189
190 2
        return $count;
191
    }
192
}
193