Passed
Push — master ( f9ddb3...e7daf4 )
by Sylvain
13:39
created

AccountRepository::getOneById()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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

92
            return $this->/** @scrutinizer ignore-call */ findOneByOwner($user);
Loading history...
93 9
        });
94
95 9
        if (!$account) {
96 9
            $account = new Account();
97 9
            $this->getEntityManager()->persist($account);
98 9
            $account->setOwner($user);
99 9
            $account->setType(AccountTypeType::LIABILITY);
100 9
            $account->setName($user->getName());
101
102 9
            $config = $container->get('config');
103 9
            $parentCode = (int) $config['accounting']['customerDepositsAccountCode'];
104 9
            $parent = $this->getAclFilter()->runWithoutAcl(function () use ($parentCode) {
105 9
                return $this->findOneByCode($parentCode);
106 9
            });
107
108
            // Find the max account code, using the liability parent code as prefix
109 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...
110 9
                $maxQuery = 'SELECT MAX(code) FROM account WHERE code LIKE ' . $this->getEntityManager()->getConnection()->quote($parent->getCode() . '%');
111 9
                $this->maxCode = (int) $this->getEntityManager()->getConnection()->fetchOne($maxQuery);
112
113
                // If there is no child account yet, reserve enough digits for many users
114 9
                if ($this->maxCode === $parent->getCode()) {
115 1
                    $this->maxCode = $parent->getCode() * 10000;
116
                }
117
            }
118
119 9
            $nextCode = ++$this->maxCode;
120 9
            $account->setCode($nextCode);
121
122 9
            $account->setParent($parent);
123
        }
124
125 9
        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->execute();
143
144 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

144
        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...
145
    }
146
147
    /**
148
     * Update accounts' balance
149
     */
150
    public function updateAccountsBalance(): void
151
    {
152 1
        $connection = $this->getEntityManager()->getConnection();
153
        $sql = 'CALL update_accounts_balance()';
154 1
        $connection->executeQuery($sql);
155 1
    }
156
157 1
    /**
158
     * Return the next available Account code
159
     */
160 1
    public function getNextCodeAvailable(): int
161 1
    {
162
        $qb = _em()->getConnection()->createQueryBuilder()
163
            ->select('IFNULL(MAX(a.code) + 1, 1)')
164 1
            ->from('account', 'a');
165
166
        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

166
        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...
167
    }
168
169
    public function getRootAccountsQuery(): Query
170
    {
171
        $qb = $this->createQueryBuilder('account')
172
            ->andWhere('account.parent IS NULL')
173
            ->orderBy('account.code');
174
175
        return $qb->getQuery();
176
    }
177
178 1
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
179
    {
180 1
        $sql = <<<STRING
181 1
                DELETE account FROM account
182 1
                INNER JOIN user ON account.owner_id = user.id
183
                AND user.owner_id IS NOT NULL
184 1
                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 2
                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
        return $count;
193
    }
194
}
195