Failed Conditions
Push — master ( 76f788...13b360 )
by Sylvain
08:55
created

AccountRepository::updateAccountBalance()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 10
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 10
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 Ecodev\Felix\Repository\LimitedAccessSubQuery;
11
use Exception;
12
use Money\Money;
13
14
class AccountRepository extends AbstractRepository implements LimitedAccessSubQuery
15
{
16
    const ACCOUNT_ID_FOR_BANK = 10025;
17
18
    /**
19
     * In memory max code that keep being incremented if we create several account at once without flushing in DB
20
     */
21
    private ?int $maxCode = null;
22
23
    /**
24
     * Clear all caches
25
     */
26 115
    public function clearCache(): void
27
    {
28 115
        $this->maxCode = null;
29 115
    }
30
31
    /**
32
     * Returns pure SQL to get ID of all objects that are accessible to given user.
33
     *
34
     * @param null|User $user
35
     */
36 20
    public function getAccessibleSubQuery(?\Ecodev\Felix\Model\User $user): string
37
    {
38 20
        if (!$user) {
39 1
            return '-1';
40
        }
41
42 19
        if (in_array($user->getRole(), [User::ROLE_RESPONSIBLE, User::ROLE_ADMINISTRATOR], true)) {
43 9
            return $this->getAllIdsQuery();
44
        }
45
46 10
        return $this->getAllIdsForFamilyQuery($user);
47
    }
48
49
    /**
50
     * Unsecured way to get a account from its ID.
51
     *
52
     * This should only be used in tests or controlled environment.
53
     */
54 4
    public function getOneById(int $id): Account
55
    {
56 4
        $account = $this->getAclFilter()->runWithoutAcl(function () use ($id) {
57 4
            return $this->findOneById($id);
58 4
        });
59
60 4
        if (!$account) {
61 1
            throw new Exception('Account #' . $id . ' not found');
62
        }
63
64 4
        return $account;
65
    }
66
67
    /**
68
     * This will return, and potentially create, an account for the given user
69
     */
70 21
    public function getOrCreate(User $user): Account
71
    {
72
        global $container;
73
74
        // If an account already exists, because getOrCreate was called once before without flushing in between,
75
        // then can return immediately
76 21
        if ($user->getAccount()) {
77 14
            return $user->getAccount();
78
        }
79
80
        // If user have an owner, then create account for the owner instead
81 9
        if ($user->getOwner()) {
82
            $user = $user->getOwner();
83
        }
84
85 9
        $account = $this->getAclFilter()->runWithoutAcl(function () use ($user) {
86 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

86
            return $this->/** @scrutinizer ignore-call */ findOneByOwner($user);
Loading history...
87 9
        });
88
89 9
        if (!$account) {
90 9
            $account = new Account();
91 9
            $this->getEntityManager()->persist($account);
92 9
            $account->setOwner($user);
93 9
            $account->setType(AccountTypeType::LIABILITY);
94 9
            $account->setName($user->getName());
95
96 9
            $config = $container->get('config');
97 9
            $parentCode = (int) $config['accounting']['customerDepositsAccountCode'];
98 9
            $parent = $this->getAclFilter()->runWithoutAcl(function () use ($parentCode) {
99 9
                return $this->findOneByCode($parentCode);
0 ignored issues
show
Bug introduced by
The method findOneByCode() 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

99
                return $this->/** @scrutinizer ignore-call */ findOneByCode($parentCode);
Loading history...
100 9
            });
101
102
            // Find the max account code, using the liability parent code as prefix
103 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...
104 9
                $maxQuery = 'SELECT MAX(code) FROM account WHERE code LIKE ' . $this->getEntityManager()->getConnection()->quote($parent->getCode() . '%');
105 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

105
                $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...
106
107
                // If there is no child account yet, reserve enough digits for many users
108 9
                if ($this->maxCode === $parent->getCode()) {
109 1
                    $this->maxCode = $parent->getCode() * 10000;
110
                }
111
            }
112
113 9
            $nextCode = ++$this->maxCode;
114 9
            $account->setCode($nextCode);
115
116 9
            $account->setParent($parent);
117
        }
118
119 9
        return $account;
120
    }
121
122
    /**
123
     * Sum balance by account type
124
     *
125
     * @API\Input(type="AccountType")
126
     */
127 2
    public function totalBalanceByType(string $accountType): Money
128
    {
129 2
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
130 2
            ->select('SUM(balance)')
131 2
            ->from($this->getClassMetadata()->getTableName())
132 2
            ->where('type = :type');
133
134 2
        $qb->setParameter('type', $accountType);
135
136 2
        $result = $qb->execute();
137
138 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

138
        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

138
        return Money::CHF(/** @scrutinizer ignore-type */ $result->fetchColumn());
Loading history...
139
    }
140
141
    /**
142
     * Update accounts' balance
143
     *
144
     * @param null|Account $account the account to update, or null for all accounts
145
     */
146 1
    public function updateAccountBalance(?Account $account = null): void
147
    {
148 1
        $connection = $this->getEntityManager()->getConnection();
149 1
        $sql = 'CALL update_account_balance(?)';
150
151 1
        if ($account) {
152
            $connection->executeQuery($sql, [$account->getId()]);
153
        } else {
154 1
            foreach ($this->findAll() as $a) {
155 1
                $connection->executeQuery($sql, [$a->getId()]);
156
            }
157
        }
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()->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

169
        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...
170
    }
171
172 2
    public function deleteAccountOfNonFamilyOwnerWithoutAnyTransactions(): int
173
    {
174
        $sql = <<<STRING
175 2
                DELETE account FROM account
176
                INNER JOIN user ON account.owner_id = user.id
177
                AND user.owner_id IS NOT NULL
178
                AND user.owner_id != user.id
179
                WHERE
180
                account.id NOT IN (SELECT credit_id FROM transaction_line WHERE credit_id IS NOT NULL)
181
                AND account.id NOT IN (SELECT debit_id FROM transaction_line WHERE debit_id IS NOT NULL) 
182
            STRING;
183
184 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

184
        $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...
185
186 2
        return $count;
187
    }
188
}
189