feature/RVRNEXT-7-calculate-balance-for-community-members #37
@ -31,7 +31,7 @@ main {
 | 
				
			|||||||
    color: #ffffff;
 | 
					    color: #ffffff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::placeholder, select > option[value=""] {
 | 
					::placeholder, select > option[value=""], .gray {
 | 
				
			||||||
    color: #8e8e8e;
 | 
					    color: #8e8e8e;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -67,7 +67,7 @@ p, h2, h3 {
 | 
				
			|||||||
    line-height: 150%;
 | 
					    line-height: 150%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
p {
 | 
					p, th, td {
 | 
				
			||||||
    font-weight: 400;
 | 
					    font-weight: 400;
 | 
				
			||||||
    font-size: 16px;
 | 
					    font-size: 16px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -104,10 +104,23 @@ hr {
 | 
				
			|||||||
    font-weight: 700;
 | 
					    font-weight: 700;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
p.small, span.small {
 | 
					p.small, span.small, td.small {
 | 
				
			||||||
    font-size: 14px;
 | 
					    font-size: 14px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.red {
 | 
				
			||||||
 | 
					    color: #a80908;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.green {
 | 
				
			||||||
 | 
					    color: #008000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mono {
 | 
				
			||||||
 | 
					    font-family: 'Oxygen Mono', mono;
 | 
				
			||||||
 | 
					    font-weight: 400;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.justify {
 | 
					.justify {
 | 
				
			||||||
    text-align: justify;
 | 
					    text-align: justify;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
<?php namespace RVR\Controller;
 | 
					<?php namespace RVR\Controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use DateTime;
 | 
					use DateTime;
 | 
				
			||||||
 | 
					use RVR\Finance\BalanceCalculator;
 | 
				
			||||||
use RVR\PersistentData\Model\Community;
 | 
					use RVR\PersistentData\Model\Community;
 | 
				
			||||||
use RVR\PersistentData\Model\CommunityMember;
 | 
					use RVR\PersistentData\Model\CommunityMember;
 | 
				
			||||||
use RVR\PersistentData\Model\Currency;
 | 
					use RVR\PersistentData\Model\Currency;
 | 
				
			||||||
@ -50,11 +51,34 @@ class CommunityController implements IAuthenticationRequired
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        \Container::$persistentDataManager->loadRelationsFromDb($community, false, ['main_currency']);
 | 
					        \Container::$persistentDataManager->loadRelationsFromDb($community, false, ['main_currency']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $balanceCalculator = new BalanceCalculator($community);
 | 
				
			||||||
 | 
					        $debts = $balanceCalculator->calculate();
 | 
				
			||||||
 | 
					        $debtUsers = [];
 | 
				
			||||||
 | 
					        $debtBalance = 0.0;
 | 
				
			||||||
 | 
					        $outstandingUsers = [];
 | 
				
			||||||
 | 
					        $outstandingBalance = 0.0;
 | 
				
			||||||
 | 
					        foreach ($debts as $debt) {
 | 
				
			||||||
 | 
					            if ($debt['payer']->getId() === \Container::$request->user()->getUniqueId()) {
 | 
				
			||||||
 | 
					                $debtBalance += $debt['amount'];
 | 
				
			||||||
 | 
					                $debtUsers[] = $debt;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if ($debt['payee']->getId() === \Container::$request->user()->getUniqueId()) {
 | 
				
			||||||
 | 
					                $outstandingBalance += $debt['amount'];
 | 
				
			||||||
 | 
					                $outstandingUsers[] = $debt;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $balance = $outstandingBalance - $debtBalance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new HtmlContent('communities/community', [
 | 
					        return new HtmlContent('communities/community', [
 | 
				
			||||||
            'community' => $community,
 | 
					            'community' => $community,
 | 
				
			||||||
            'members' => $this->getMembers($community),
 | 
					            'members' => $this->getMembers($community),
 | 
				
			||||||
            'currencies' => $this->getCurrencies($community),
 | 
					            'currencies' => $this->getCurrencies($community),
 | 
				
			||||||
            'upcomingEvents' => [],
 | 
					            'upcomingEvents' => [],
 | 
				
			||||||
 | 
					            'debtUsers' => $debtUsers,
 | 
				
			||||||
 | 
					            'debtBalance' => $debtBalance,
 | 
				
			||||||
 | 
					            'outstandingUsers' => $outstandingUsers,
 | 
				
			||||||
 | 
					            'outstandingBalance' => $outstandingBalance,
 | 
				
			||||||
 | 
					            'balance' => $balance,
 | 
				
			||||||
            'editPermission' => $ownCommunityMember->getOwner()
 | 
					            'editPermission' => $ownCommunityMember->getOwner()
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										90
									
								
								src/Finance/BalanceCalculator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/Finance/BalanceCalculator.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					<?php namespace RVR\Finance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use RVR\PersistentData\Model\Community;
 | 
				
			||||||
 | 
					use RVR\Repository\CommunityMemberRepository;
 | 
				
			||||||
 | 
					use RVR\Repository\TransactionRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BalanceCalculator
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private Community $community;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private TransactionRepository $transactionRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private CommunityMemberRepository $communityMemberRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ExchangeRateCalculator $exchangeRateCalculator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private array $members;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private array $payments;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(Community $community)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->community = $community;
 | 
				
			||||||
 | 
					        $this->transactionRepository = new TransactionRepository();
 | 
				
			||||||
 | 
					        $this->communityMemberRepository = new CommunityMemberRepository();
 | 
				
			||||||
 | 
					        $this->exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function calculate(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->collectMembers();
 | 
				
			||||||
 | 
					        $this->createPaymentsMatrix();
 | 
				
			||||||
 | 
					        $this->sumTransactions();
 | 
				
			||||||
 | 
					        return $this->calculateActualDebts();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function collectMembers(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->members = [];
 | 
				
			||||||
 | 
					        foreach ($this->communityMemberRepository->getAllByCommunity($this->community, true, ['user']) as $member) {
 | 
				
			||||||
 | 
					            $this->members[$member->getUserId()] = $member;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function createPaymentsMatrix(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->payments = [];
 | 
				
			||||||
 | 
					        foreach ($this->members as $payerUserId => $member) {
 | 
				
			||||||
 | 
					            $this->payments[$payerUserId] = [];
 | 
				
			||||||
 | 
					            foreach ($this->members as $payeeUserId => $member) {
 | 
				
			||||||
 | 
					                $this->payments[$payerUserId][$payeeUserId] = 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function sumTransactions(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $membersCount = count($this->members);
 | 
				
			||||||
 | 
					        $transactions = $this->transactionRepository->getAllByCommunity($this->community, true, ['currency']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach ($transactions as $transaction) {
 | 
				
			||||||
 | 
					            $sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($transaction->getPayeeUserId()) {
 | 
				
			||||||
 | 
					                $this->payments[$transaction->getPayerUserId()][$transaction->getPayeeUserId()] += $sum;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                foreach ($this->members as $payeeUserId => $member) {
 | 
				
			||||||
 | 
					                    $this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function calculateActualDebts(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $actualDebts = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach ($this->payments as $payerUserId => $paymentsOfPayer) {
 | 
				
			||||||
 | 
					            foreach ($paymentsOfPayer as $payeeUserId => $sum) {
 | 
				
			||||||
 | 
					                $actualDebt = $this->payments[$payeeUserId][$payerUserId] - $sum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (round($actualDebt, $this->community->getMainCurrency()->getRoundDigits()) > 0.0) {
 | 
				
			||||||
 | 
					                    $actualDebts[] = ['payer' => $this->members[$payerUserId], 'payee' => $this->members[$payeeUserId], 'amount' => $actualDebt];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $actualDebts;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -46,18 +46,31 @@
 | 
				
			|||||||
            $mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
 | 
					            $mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
 | 
				
			||||||
            ?>
 | 
					            ?>
 | 
				
			||||||
            <h3 class="marginBottom">Finances</h3>
 | 
					            <h3 class="marginBottom">Finances</h3>
 | 
				
			||||||
            <table class="fullWidth">
 | 
					            <a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">Transactions</a>
 | 
				
			||||||
 | 
					            <table class="fullWidth marginTop">
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td>You owe</td>
 | 
					                    <td class="bold">You owe</td>
 | 
				
			||||||
                    <td style="text-align: right; color: red;"><?= number_format(0, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
					                    <td class="mono red" style="text-align: right;"><?= number_format($debtBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <?php foreach ($debtUsers as $owe): ?>
 | 
				
			||||||
                    <tr>
 | 
					                    <tr>
 | 
				
			||||||
                    <td>You're owed</td>
 | 
					                        <td class="small"><?= $owe['payee']->getUser()->getDisplayName() ?></td>
 | 
				
			||||||
                    <td style="text-align: right; color: green;"><?= number_format(0, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
					                        <td class="small mono red" style="text-align: right;"><?= number_format($owe['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
				
			||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                <?php endforeach; ?>
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td>Your balance</td>
 | 
					                    <td class="bold">You're owed</td>
 | 
				
			||||||
                    <td style="text-align: right;"><?= number_format(0, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
					                    <td class="mono green" style="text-align: right;"><?= number_format($outstandingBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <?php foreach ($outstandingUsers as $owe): ?>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <td class="small"><?= $owe['payer']->getUser()->getDisplayName() ?></td>
 | 
				
			||||||
 | 
					                        <td class="small mono green" style="text-align: right;"><?= number_format($owe['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                <?php endforeach; ?>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td class="bold">Your balance</td>
 | 
				
			||||||
 | 
					                    <td class="mono <?= $balance < 0 ? 'red' : ($balance > 0 ? 'green' : '') ?>" style="text-align: right;;"><?= number_format($balance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
            </table>
 | 
					            </table>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -9,13 +9,14 @@
 | 
				
			|||||||
    <?php if (preg_match('/^(http(s)?:)?\/\//', $_ENV['STATIC_ROOT']) === 1): ?>
 | 
					    <?php if (preg_match('/^(http(s)?:)?\/\//', $_ENV['STATIC_ROOT']) === 1): ?>
 | 
				
			||||||
        <link href="<?= $_ENV['STATIC_ROOT'] ?>" rel="preconnect">
 | 
					        <link href="<?= $_ENV['STATIC_ROOT'] ?>" rel="preconnect">
 | 
				
			||||||
    <?php endif; ?>
 | 
					    <?php endif; ?>
 | 
				
			||||||
    <link href="https://fonts.gstatic.com" rel="preconnect">
 | 
					    <link href="https://fonts.googleapis.com" rel="preconnect">
 | 
				
			||||||
 | 
					    <link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
 | 
				
			||||||
    <?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
 | 
					    <?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
 | 
				
			||||||
        <link href="https://www.googletagmanager.com" rel="preconnect">
 | 
					        <link href="https://www.googletagmanager.com" rel="preconnect">
 | 
				
			||||||
        <link href="https://www.google-analytics.com" rel="preconnect">
 | 
					        <link href="https://www.google-analytics.com" rel="preconnect">
 | 
				
			||||||
    <?php endif; ?>
 | 
					    <?php endif; ?>
 | 
				
			||||||
    <link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=swap" rel="stylesheet">
 | 
					    <link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=block" rel="stylesheet">
 | 
				
			||||||
    <link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&display=swap" rel="stylesheet">
 | 
					    <link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&display=block" rel="stylesheet">
 | 
				
			||||||
    <link href="<?= $_ENV['STATIC_ROOT'] ?>/css/rvr.css?rev=<?= REVISION ?>" rel="stylesheet">
 | 
					    <link href="<?= $_ENV['STATIC_ROOT'] ?>/css/rvr.css?rev=<?= REVISION ?>" rel="stylesheet">
 | 
				
			||||||
    @yields('externalCss')
 | 
					    @yields('externalCss')
 | 
				
			||||||
    @yields('inlineCss')
 | 
					    @yields('inlineCss')
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user