Failed Conditions
Push — master ( 4b335d...ccf9db )
by Sylvain
07:33
created

AccountRepository::getRootAccountsQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
cc 1
nc 1
nop 0
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
 * 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 114
    public function clearCache(): void
33
    {
34 114
        $this->maxCode = null;
35 114
    }
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()->fetchColumn($maxQuery);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

111
                $this->maxCode = (int) /** @scrutinizer ignore-deprecated */ $this->getEntityManager()->getConnection()->fetchColumn($maxQuery);

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...
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->fetchColumn());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\Res...tatement::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

144
        return Money::CHF(/** @scrutinizer ignore-deprecated */ $result->fetchColumn());

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...
Bug introduced by
It seems like $result->fetchColumn() can also be of type false; however, parameter $amount of Money\Money::CHF() does only seem to accept integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

144
        return Money::CHF(/** @scrutinizer ignore-type */ $result->fetchColumn());
Loading history...
145
    }
146
147
    /**
148
     * Update accounts' balance
149
     *
150
     * @param null|Account $account the account to update, or null for all accounts
151
     */
152 1
    public function updateAccountBalance(?Account $account = null): void
153
    {
154 1
        $connection = $this->getEntityManager()->getConnection();
155 1
        $sql = 'CALL update_account_balance(?)';
156
157 1
        if ($account) {
158
            $connection->executeQuery($sql, [$account->getId()]);
159
        } else {
160 1
            foreach ($this->findAll() as $a) {
161 1
                $connection->executeQuery($sql, [$a->getId()]);
162
            }
163
        }
164 1
    }
165
166
    /**
167
     * Return the next available Account code
168
     */
169
    public function getNextCodeAvailable(): int
170
    {
171
        $qb = _em()->getConnection()->createQueryBuilder()
172
            ->select('IFNULL(MAX(a.code) + 1, 1)')
173
            ->from('account', 'a');
174
175
        return (int) $qb->execute()->fetchColumn();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\Res...tatement::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

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

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

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...
176
    }
177
178 1
    public function getRootAccountsQuery(): Query
179
    {
180 1
        $qb = $this->createQueryBuilder('account')
181 1
            ->andWhere('account.parent IS NULL')
182 1
            ->orderBy('account.code');
183
184 1
        return $qb->getQuery();
185
    }
186
187 2
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
188
    {
189
        $sql = <<<STRING
190 2
                DELETE account FROM account
191
                INNER JOIN user ON account.owner_id = user.id
192
                AND user.owner_id IS NOT NULL
193
                AND user.owner_id != user.id
194
                WHERE
195
                account.id NOT IN (SELECT credit_id FROM transaction_line WHERE credit_id IS NOT NULL)
196
                AND account.id NOT IN (SELECT debit_id FROM transaction_line WHERE debit_id IS NOT NULL) 
197
            STRING;
198
199 2
        $count = $this->getEntityManager()->getConnection()->executeUpdate($sql);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::executeUpdate() has been deprecated: Use {@link 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

199
        $count = /** @scrutinizer ignore-deprecated */ $this->getEntityManager()->getConnection()->executeUpdate($sql);

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...
200
201 2
        return $count;
202
    }
203
}
204