Merge pull request 'feature/RVRNEXT-7-calculate-balance-for-community-members' (!37) from feature/RVRNEXT-7-calculate-balance-for-community-members into master
All checks were successful
rvr-nextgen/pipeline/head This commit looks good

Reviewed-on: #37
This commit is contained in:
Bence Pőcze 2023-05-01 22:26:03 +02:00 committed by Gitea
commit 4dbbb1321c
Signed by: Gitea
GPG Key ID: 7B89B83EED9AD2C6
5 changed files with 154 additions and 13 deletions

View File

@ -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;
} }

View File

@ -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()
]); ]);
} }

View 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;
}
}

View File

@ -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>
<td class="small"><?= $owe['payee']->getUser()->getDisplayName() ?></td>
<td class="small mono red" style="text-align: right;"><?= number_format($owe['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php endforeach; ?>
<tr> <tr>
<td>You're owed</td> <td class="bold">You're owed</td>
<td style="text-align: right; color: green;"><?= number_format(0, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td> <td class="mono green" style="text-align: right;"><?= number_format($outstandingBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr> </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> <tr>
<td>Your balance</td> <td class="bold">Your balance</td>
<td style="text-align: right;"><?= number_format(0, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></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>

View File

@ -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&amp;display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&amp;display=block" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&amp;display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&amp;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')