Compare commits

..

9 Commits

10 changed files with 116 additions and 27 deletions

View File

@ -10,7 +10,7 @@
} }
], ],
"require": { "require": {
"esoko/soko-web": "0.11", "esoko/soko-web": "0.12.1",
"firebase/php-jwt": "^6.4" "firebase/php-jwt": "^6.4"
}, },
"require-dev": { "require-dev": {

8
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "c17dd5eb82ff8f509a17404bd4c471b8", "content-hash": "a6f50b31b14027eec094629b13cc4097",
"packages": [ "packages": [
{ {
"name": "cocur/slugify", "name": "cocur/slugify",
@ -82,11 +82,11 @@
}, },
{ {
"name": "esoko/soko-web", "name": "esoko/soko-web",
"version": "v0.11", "version": "v0.12.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.esoko.eu/esoko/soko-web.git", "url": "https://git.esoko.eu/esoko/soko-web.git",
"reference": "78f891fbff0dc8e5de5607529f4c8282f1dff40f" "reference": "bda12177ebc201f04fdad5493b93039676a983ca"
}, },
"require": { "require": {
"cocur/slugify": "^4.3", "cocur/slugify": "^4.3",
@ -108,7 +108,7 @@
"GNU GPL 3.0" "GNU GPL 3.0"
], ],
"description": "Lightweight web framework", "description": "Lightweight web framework",
"time": "2023-05-06T23:52:34+00:00" "time": "2023-05-28T13:56:14+00:00"
}, },
{ {
"name": "firebase/php-jwt", "name": "firebase/php-jwt",

View File

@ -2,6 +2,7 @@
use Container; use Container;
use DateTime; use DateTime;
use RVR\Finance\ExchangeRateCalculator;
use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember; use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\Event; use RVR\PersistentData\Model\Event;
@ -9,6 +10,7 @@ use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityMemberRepository; use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CommunityRepository; use RVR\Repository\CommunityRepository;
use RVR\Repository\EventRepository; use RVR\Repository\EventRepository;
use RVR\Repository\TransactionRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Authorization\ISecured; use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Response\IContent; use SokoWeb\Interfaces\Response\IContent;
@ -23,6 +25,8 @@ class EventController implements IAuthenticationRequired, ISecured
private EventRepository $eventRepository; private EventRepository $eventRepository;
private TransactionRepository $transactionRepository;
private ?Community $community; private ?Community $community;
private ?CommunityMember $ownCommunityMember; private ?CommunityMember $ownCommunityMember;
@ -32,6 +36,7 @@ class EventController implements IAuthenticationRequired, ISecured
$this->communityRepository = new CommunityRepository(); $this->communityRepository = new CommunityRepository();
$this->communityMemberRepository = new CommunityMemberRepository(); $this->communityMemberRepository = new CommunityMemberRepository();
$this->eventRepository = new EventRepository(); $this->eventRepository = new EventRepository();
$this->transactionRepository = new TransactionRepository();
} }
public function isAuthenticationRequired(): bool public function isAuthenticationRequired(): bool
@ -103,7 +108,8 @@ class EventController implements IAuthenticationRequired, ISecured
return new HtmlContent('events/event', [ return new HtmlContent('events/event', [
'community' => $this->community, 'community' => $this->community,
'event' => $event 'event' => $event,
'totalCost' => $this->sumTransactions($event)
]); ]);
} }
@ -141,6 +147,35 @@ class EventController implements IAuthenticationRequired, ISecured
$event->setEndDate(new DateTime(Container::$request->post('end'))); $event->setEndDate(new DateTime(Container::$request->post('end')));
Container::$persistentDataManager->saveToDb($event); Container::$persistentDataManager->saveToDb($event);
return new JsonContent([
'redirect' => ['target' => \Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $this->community->getSlug(), 'eventSlug' => $event->getSlug()])]
]);
}
public function deleteEvent(): IContent
{
$event = $this->eventRepository->getBySlug(Container::$request->query('eventSlug'));
foreach ($this->transactionRepository->getAllByEvent($event) as $transaction) {
$transaction->setEventId(null);
Container::$persistentDataManager->saveToDb($transaction);
}
Container::$persistentDataManager->deleteFromDb($event);
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
} }
private function sumTransactions(Event $event): int
{
$exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
$transactions = $this->transactionRepository->getAllByEvent($event, true, ['currency']);
$sum = 0.0;
foreach ($transactions as $transaction) {
$sum += $exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
}
return $sum;
}
} }

View File

@ -73,20 +73,38 @@ class TransactionController implements IAuthenticationRequired, ISecured
Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']); Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']);
$exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency()); $exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
$eventSlug = Container::$request->query('event');
if ($eventSlug) {
$event = $this->eventRepository->getBySlug($eventSlug);
} else {
$event = null;
}
$itemsPerPage = 50; $itemsPerPage = 50;
$numberOfTransactions = $this->transactionRepository->countAllByCommunity($this->community); $numberOfTransactions = $event ?
$this->transactionRepository->countAllByEvent($event) :
$this->transactionRepository->countAllByCommunity($this->community);
$pages = ceil($numberOfTransactions / $itemsPerPage); $pages = ceil($numberOfTransactions / $itemsPerPage);
$currentPage = Container::$request->query('page') ?: 0; $currentPage = Container::$request->query('page') ?: 0;
$transactions = $this->transactionRepository->getPagedByCommunity( $transactions = $event ?
$this->community, $this->transactionRepository->getPagedByEvent(
$currentPage * $itemsPerPage, $event,
$itemsPerPage, $currentPage * $itemsPerPage,
true, $itemsPerPage,
['event', 'currency', 'payer_user', 'payee_user'] true,
); ['currency', 'payer_user', 'payee_user']
) :
$this->transactionRepository->getPagedByCommunity(
$this->community,
$currentPage * $itemsPerPage,
$itemsPerPage,
true,
['event', 'currency', 'payer_user', 'payee_user']
);
return new HtmlContent('communities/transactions', [ return new HtmlContent('communities/transactions', [
'community' => $this->community, 'community' => $this->community,
'event' => $event,
'exchangeRateCalculator' => $exchangeRateCalculator, 'exchangeRateCalculator' => $exchangeRateCalculator,
'pages' => $pages, 'pages' => $pages,
'currentPage' => $currentPage, 'currentPage' => $currentPage,
@ -157,7 +175,7 @@ class TransactionController implements IAuthenticationRequired, ISecured
private function getMembers(Community $community): array private function getMembers(Community $community): array
{ {
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user'])); $members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user']));
usort($members, function($a, $b) { usort($members, function ($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName()); return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
}); });
return $members; return $members;
@ -166,10 +184,10 @@ class TransactionController implements IAuthenticationRequired, ISecured
private function getCurrencies(Community $community): array private function getCurrencies(Community $community): array
{ {
$currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community)); $currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
usort($currencies, function($a, $b) { usort($currencies, function ($a, $b) {
return strnatcmp($a->getCode(), $b->getCode()); return strnatcmp($a->getCode(), $b->getCode());
}); });
usort($currencies, function($a, $b) use ($community) { usort($currencies, function ($a, $b) use ($community) {
return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId()); return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId());
}); });
return $currencies; return $currencies;

View File

@ -4,6 +4,7 @@ use Container;
use Generator; use Generator;
use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Currency; use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\Event;
use RVR\PersistentData\Model\Transaction; use RVR\PersistentData\Model\Transaction;
use RVR\PersistentData\Model\User; use RVR\PersistentData\Model\User;
use SokoWeb\Database\Query\Select; use SokoWeb\Database\Query\Select;
@ -22,11 +23,23 @@ class TransactionRepository
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations); yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
} }
public function getAllByEvent(Event $event, bool $useRelations = false, array $withRelations = []): Generator
{
$select = $this->selectAllByEvent($event);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
}
public function countAllByCommunity(Community $community): int public function countAllByCommunity(Community $community): int
{ {
return $this->selectAllByCommunity($community)->count(); return $this->selectAllByCommunity($community)->count();
} }
public function countAllByEvent(Event $event): int
{
return $this->selectAllByEvent($event)->count();
}
public function isAnyForUser(User $user): bool public function isAnyForUser(User $user): bool
{ {
$select = new Select(Container::$dbConnection, Transaction::getTable()); $select = new Select(Container::$dbConnection, Transaction::getTable());
@ -55,8 +68,16 @@ class TransactionRepository
public function getPagedByCommunity(Community $community, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator public function getPagedByCommunity(Community $community, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = new Select(Container::$dbConnection); $select = $this->selectAllByCommunity($community);
$select->where('community_id', '=', $community->getId()); $select->orderBy('time', 'DESC');
$select->limit($limit, $start);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
}
public function getPagedByEvent(Event $event, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator
{
$select = $this->selectAllByEvent($event);
$select->orderBy('time', 'DESC'); $select->orderBy('time', 'DESC');
$select->limit($limit, $start); $select->limit($limit, $start);
@ -69,4 +90,11 @@ class TransactionRepository
$select->where('community_id', '=', $community->getId()); $select->where('community_id', '=', $community->getId());
return $select; return $select;
} }
private function selectAllByEvent(Event $event)
{
$select = new Select(Container::$dbConnection, Transaction::getTable());
$select->where('event_id', '=', $event->getId());
return $select;
}
} }

View File

@ -8,6 +8,10 @@
<h2> <h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> » <a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Transactions</a> » <a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Transactions</a> »
<?php if (isset($event)): ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> »
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> »
<?php endif; ?>
<?php if (isset($transaction)): ?> <?php if (isset($transaction)): ?>
Edit transaction Edit transaction
<?php else: ?> <?php else: ?>
@ -20,7 +24,7 @@
Container::$routeCollection->getRoute('community.transactions.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'transactionId' => $transaction->getId()]) : Container::$routeCollection->getRoute('community.transactions.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'transactionId' => $transaction->getId()]) :
Container::$routeCollection->getRoute('community.transactions.new-action')->generateLink(['communitySlug' => $community->getSlug()]); Container::$routeCollection->getRoute('community.transactions.new-action')->generateLink(['communitySlug' => $community->getSlug()]);
?> ?>
<form id="transactionForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug()]) ?>"> <form id="transactionForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">
<p class="formLabel">Event</p> <p class="formLabel">Event</p>
<select name="event_id"> <select name="event_id">
<option value="">[none]</option> <option value="">[none]</option>

View File

@ -3,10 +3,14 @@
@section(main) @section(main)
<h2> <h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> » <a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
<?php if (isset($event)): ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> »
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> »
<?php endif; ?>
Transactions Transactions
</h2> </h2>
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New transaction</a></p> <p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">New transaction</a></p>
<?php if ($numberOfTransactions > 0): ?> <?php if ($numberOfTransactions > 0): ?>
<?php if ($pages > 1): ?> <?php if ($pages > 1): ?>
@ -62,7 +66,7 @@
</p> </p>
<?php endif; ?> <?php endif; ?>
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New transaction</a></p> <p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">New transaction</a></p>
<?php else: ?> <?php else: ?>
<div class="box"> <div class="box">
<p>There are no transactions yet.</p> <p>There are no transactions yet.</p>

View File

@ -15,11 +15,11 @@
$mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits(); $mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
?> ?>
<h3 class="marginBottom">Finances</h3> <h3 class="marginBottom">Finances</h3>
<p><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => $event->getSlug()]) ?>">New transaction</a></p> <p><a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'event' => $event->getSlug()]) ?>">Transactions</a> | <a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => $event->getSlug()]) ?>">New transaction</a></p>
<table class="fullWidth marginTop"> <table class="fullWidth marginTop">
<tr> <tr>
<td class="bold">Total cost</td> <td class="bold">Total cost</td>
<td class="mono" style="text-align: right;"><?= number_format(0, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td> <td class="mono" style="text-align: right;"><?= number_format($totalCost, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -16,7 +16,7 @@
Container::$routeCollection->getRoute('community.event.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) : Container::$routeCollection->getRoute('community.event.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) :
Container::$routeCollection->getRoute('community.events.new-action')->generateLink(['communitySlug' => $community->getSlug()]); Container::$routeCollection->getRoute('community.events.new-action')->generateLink(['communitySlug' => $community->getSlug()]);
?> ?>
<form id="eventForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>"> <form id="eventForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
<p class="formLabel">Title</p> <p class="formLabel">Title</p>
<input type="text" class="text big fullWidth" name="title" value="<?= isset($event) ? $event->getTitle() : '' ?>" required> <input type="text" class="text big fullWidth" name="title" value="<?= isset($event) ? $event->getTitle() : '' ?>" required>
<p class="formLabel marginTop">Description</p> <p class="formLabel marginTop">Description</p>
@ -29,7 +29,7 @@
<div class="right marginTop" style="font-size: 0;"> <div class="right marginTop" style="font-size: 0;">
<button type="submit" name="submit_button"><?= isset($event) ? '<i class="fa-regular fa-floppy-disk"></i> Save' : '<i class="fa-regular fa-plus"></i> Create' ?></button> <button type="submit" name="submit_button"><?= isset($event) ? '<i class="fa-regular fa-floppy-disk"></i> Save' : '<i class="fa-regular fa-plus"></i> Create' ?></button>
<?php if (isset($event)): ?> <?php if (isset($event)): ?>
<button type="submit" form="deleteEvent" name="submit_button" data-confirmation="Are you sure you want to delete this event?" class="red marginLeft"><i class="fa-regular fa-trash-can"></i> Delete</button> <button type="submit" form="deleteEvent" name="submit_button" data-confirmation="Are you sure you want to delete this event? All linked transactions will be unlinked!" class="red marginLeft"><i class="fa-regular fa-trash-can"></i> Delete</button>
<?php endif; ?> <?php endif; ?>
</div> </div>
</form> </form>

View File

@ -54,7 +54,7 @@
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.events.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New event</a></p> <p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.events.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New event</a></p>
<?php else: ?> <?php else: ?>
<div class="box"> <div class="box">
<p>There are no transactions yet.</p> <p>There are no events yet.</p>
</div> </div>
<?php endif; ?> <?php endif; ?>
@endsection @endsection