Failed Conditions
Push — master ( 893dc8...0c1f2a )
by Adrien
06:01
created

AccountRepository   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Test Coverage

Coverage 91.3%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 18
eloc 73
c 4
b 0
f 0
dl 0
loc 176
ccs 63
cts 69
cp 0.913
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A clearCache() 0 3 1
A getAccessibleSubQuery() 0 20 4
A getNextCodeAvailable() 0 7 1
A updateAccountsBalance() 0 5 1
A getOneById() 0 9 2
A totalBalanceByType() 0 12 1
B getOrCreate() 0 46 6
A getRootAccountsQuery() 0 7 1
A deleteAccountOfNonFamilyOwnerWithoutAnyTransactions() 0 15 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
 * Class AccountRepository.
17
 *
18
 * @method null|Account findOneByCode(int $code)
19
 */
20
class AccountRepository extends AbstractRepository implements LimitedAccessSubQuery
21
{
22
    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 136
    }
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 22
            User::ROLE_TRAINER,
50
            User::ROLE_ACCOUNTING_VERIFICATOR,
51
            User::ROLE_RESPONSIBLE,
52
            User::ROLE_ADMINISTRATOR,
53 22
        ], 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->execute();
146
147 2
        return Money::CHF($result->fetchOne());
0 ignored issues
show
Bug introduced by
The method fetchOne() does not exist on integer. ( Ignorable by Annotation )

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

147
        return Money::CHF($result->/** @scrutinizer ignore-call */ fetchOne());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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 1
    }
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
Bug introduced by
The method fetchOne() does not exist on integer. ( Ignorable by Annotation )

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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
        $sql = <<<STRING
184 2
                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