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
All checks were successful
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #37
This commit is contained in:
commit
4dbbb1321c
@ -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…
Reference in New Issue
Block a user