Compare commits

..
Author SHA1 Message Date
bence 17d9437a50 Merge pull request 'fix select to avoid getting all events as upcoming/recent' (!65) from bugfix/fix-select-for-upcoming-and-recent-events into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #65
2023-07-24 02:12:15 +02:00
bence aed4548c7b fix select to avoid getting all events as upcoming/recent
rvr-nextgen/pipeline/pr-master This commit looks good
2023-07-24 02:11:27 +02:00
bence 0d6d152b82 Merge pull request 'create endpoints to get current event and current event new transaction' (!64) from feature/create-endpoint-for-currect-event-redirection into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #64
2023-07-24 02:08:11 +02:00
bence 7beec510a1 create endpoints to get current event and current event new transaction
rvr-nextgen/pipeline/pr-master This commit looks good
2023-07-24 02:00:59 +02:00
bence 22b849a1c8 Merge pull request 'select current user as payer by default for new transaction' (!63) from feature/default-payer-is-current-user into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #63
2023-07-24 01:31:22 +02:00
bence 492aa9fcef select current user as payer by default for new transaction
rvr-nextgen/pipeline/pr-master This commit looks good
2023-07-24 01:29:53 +02:00
bence b6da70e015 Merge pull request 'fix transaction edit header' (!62) from bugfix/fix-transaction-edit-header into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #62
2023-07-12 00:40:06 +02:00
bence afead7fd61 Merge pull request 'show balance for events' (!61) from feature/show-balance-for-events into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #61
2023-07-12 00:38:17 +02:00
bence 69acbf708b fix transaction edit header
rvr-nextgen/pipeline/pr-master This commit looks good
2023-07-12 00:32:55 +02:00
bence 08ebfae030 show balance for events
rvr-nextgen/pipeline/pr-master This commit looks good
2023-07-12 00:26:59 +02:00
bence a597839379 Merge pull request 'feature/RVRNEXT-45-show-recent-events-as-well' (!60) from feature/RVRNEXT-45-show-recent-events-as-well into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #60
2023-07-08 14:44:35 +02:00
bence e0fe6a8622 RVRNEXT-45 update soko-web to 0.13.1
rvr-nextgen/pipeline/pr-master This commit looks good
2023-07-08 14:42:09 +02:00
bence 5ebce653dc RVRNEXT-45 show recent events as well with upcoming events 2023-07-08 13:52:24 +02:00
bence 61eb2404a2 Merge pull request 'feature/RVRNEXT-43-split-transactions' (!59) from feature/RVRNEXT-43-split-transactions into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #59
2023-06-17 14:59:09 +02:00
bence e199dfccc7 RVRNEXT-43 update soko-web
rvr-nextgen/pipeline/pr-master This commit looks good
2023-06-17 14:35:00 +02:00
bence 1d0a2b09d5 RVRNEXT-43 adapt balance calculations to multiple payees 2023-06-17 13:47:05 +02:00
bence 1886257b63 RVRNEXT-43 adapt views to multiple payees 2023-06-17 13:46:43 +02:00
bence eda1415e86 RVRNEXT-43 implement multiple payees in transaction controller 2023-06-17 13:46:13 +02:00
bence 4e9c7f8c3c RVRNEXT-43 extend community member repository with count 2023-06-17 13:17:29 +02:00
bence eebf4aff00 RVRNEXT-43 add multi relation for transaction 2023-06-17 13:16:53 +02:00
bence 6dae33507f RVRNEXT-43 add repository for transaction payees 2023-06-17 13:15:54 +02:00
bence 96d5aa2219 RVRNEXT-43 add model for transaction payee 2023-06-17 13:14:43 +02:00
bence 6c326b89e2 RVRNEXT-43 add migration for transaction payees 2023-06-17 13:14:33 +02:00
bence 6c821d41b4 Merge pull request 'feature/RVRNEXT-39-delete-communities' (!58) from feature/RVRNEXT-39-delete-communities into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #58
2023-06-16 21:39:45 +02:00
bence ade02f4736 RVRNEXT-39 add endpoint for community delete
rvr-nextgen/pipeline/pr-master This commit looks good
2023-06-16 21:38:27 +02:00
bence bb5b510315 RVRNEXT-39 add delete button for communities 2023-06-16 21:38:27 +02:00
bence 1267cb3d95 RVRNEXT-39 implement community delete 2023-06-16 21:38:26 +02:00
bence c7641b85e7 RVRNEXT-39 make main currency nullable 2023-06-16 21:28:42 +02:00
bence ee4b8e9d65 Merge pull request 'bugfix/RVRNEXT-31-allow-special-characters-for-local-assets' (!57) from bugfix/RVRNEXT-31-allow-special-characters-for-local-assets into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #57
2023-05-28 22:31:21 +02:00
bence 58d4f982e9 RVRNEXT-31 remove hack for assets of tom-select
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-28 22:30:00 +02:00
bence ad06cfaa58 RVRNEXT-31 update soko-web 2023-05-28 22:30:00 +02:00
bence aa2e0e9b4a Merge pull request 'feature/RVRNEXT-22-add-pagination-template' (!56) from feature/RVRNEXT-22-add-pagination-template into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #56
2023-05-28 20:46:22 +02:00
bence d1c926541a RVRNEXT-22 use the new repository methods in controllers
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-28 20:34:07 +02:00
bence 9929e00a3f RVRNEXT-22 use Select::paginate in repositories 2023-05-28 20:34:07 +02:00
bence bf415a2a46 RVRNEXT-22 use pagination template 2023-05-28 20:34:00 +02:00
bence c0bfa5b334 RVRNEXT-22 add pagination template 2023-05-28 20:33:59 +02:00
bence e122248a97 Merge pull request 'fix sql statement for table transactions' (!55) from bugfix/fix-buggy-migration into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #55
2023-05-28 17:03:11 +02:00
bence d2bb1d4936 fix sql statement for table transactions
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-28 17:01:19 +02:00
29 changed files with 556 additions and 146 deletions

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@
} }
], ],
"require": { "require": {
"esoko/soko-web": "0.12.1", "esoko/soko-web": "0.13.1",
"firebase/php-jwt": "^6.4" "firebase/php-jwt": "^6.4"
}, },
"require-dev": { "require-dev": {
Generated
+4 -4
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": "a6f50b31b14027eec094629b13cc4097", "content-hash": "59130fbd82b1c81275666b16adb0c1a1",
"packages": [ "packages": [
{ {
"name": "cocur/slugify", "name": "cocur/slugify",
@@ -82,11 +82,11 @@
}, },
{ {
"name": "esoko/soko-web", "name": "esoko/soko-web",
"version": "v0.12.1", "version": "v0.13.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": "bda12177ebc201f04fdad5493b93039676a983ca" "reference": "8bf495c89b4ce1456da1133adc285003e544dd48"
}, },
"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-28T13:56:14+00:00" "time": "2023-07-08T12:38:40+00:00"
}, },
{ {
"name": "firebase/php-jwt", "name": "firebase/php-jwt",
@@ -11,8 +11,3 @@ CREATE TABLE `events` (
UNIQUE KEY `slug` (`slug`), UNIQUE KEY `slug` (`slug`),
CONSTRAINT `events_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`) CONSTRAINT `events_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
ALTER TABLE `transactions`
ADD `event_id` int(10) unsigned NULL,
ADD KEY `event_id` (`event_id`) AFTER `community_id`,
ADD CONSTRAINT `transactions_event_id` FOREIGN KEY (`event_id`) REFERENCES `events` (`id`);
@@ -0,0 +1,4 @@
ALTER TABLE `transactions`
ADD `event_id` int(10) unsigned NULL AFTER `community_id`,
ADD KEY `event_id` (`event_id`),
ADD CONSTRAINT `transactions_event_id` FOREIGN KEY (`event_id`) REFERENCES `events` (`id`);
@@ -0,0 +1,11 @@
CREATE TABLE `transaction_payees` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_for_transaction` (`transaction_id`, `user_id`),
KEY `transaction_id` (`transaction_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `transaction_payees_transaction_id` FOREIGN KEY (`transaction_id`) REFERENCES `transactions` (`id`),
CONSTRAINT `transaction_payees_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
+49 -23
View File
@@ -59,32 +59,21 @@ 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(); * @var User $user
$debtItems = []; */
$debtBalance = 0.0; $user = \Container::$request->user();
$outstandingItems = []; $balanceCalculator = new BalanceCalculator($community, $user);
$outstandingBalance = 0.0; $balance = $balanceCalculator->calculate();
foreach ($debts as $debt) {
if ($debt['payer']->getId() === \Container::$request->user()->getUniqueId()) {
$debtBalance += $debt['amount'];
$debtItems[] = $debt;
}
if ($debt['payee']->getId() === \Container::$request->user()->getUniqueId()) {
$outstandingBalance += $debt['amount'];
$outstandingItems[] = $debt;
}
}
$balance = $outstandingBalance - $debtBalance;
return new HtmlContent('communities/community', [ return new HtmlContent('communities/community', [
'community' => $community, 'community' => $community,
'upcomingEvents' => iterator_to_array($this->eventRepository->getUpcomingByCommunity($community, new DateTime(), 3)), 'upcomingAndRecentEvents' => iterator_to_array($this->eventRepository->getUpcomingAndRecentByCommunity($community, new DateTime(), 30, 3)),
'debtItems' => $debtItems, 'debtItems' => $balance['debtItems'],
'debtBalance' => $debtBalance, 'debtBalance' => $balance['debtBalance'],
'outstandingItems' => $outstandingItems, 'outstandingItems' => $balance['outstandingItems'],
'outstandingBalance' => $outstandingBalance, 'outstandingBalance' => $balance['outstandingBalance'],
'balance' => $balance, 'balance' => $balance['absoluteBalance'],
'editPermission' => $ownCommunityMember->getOwner() 'editPermission' => $ownCommunityMember->getOwner()
]); ]);
} }
@@ -176,6 +165,43 @@ class CommunityController implements IAuthenticationRequired
]); ]);
} }
public function deleteCommunity(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
return null;
}
if ($this->transactionRepository->countAllByCommunity($community) > 0) {
return new JsonContent([
'error' => ['errorText' => 'There are transactions for this community!']
]);
}
if ($this->eventRepository->countAllByCommunity($community) > 0) {
return new JsonContent([
'error' => ['errorText' => 'There are events for this community!']
]);
}
foreach ($this->communityMemberRepository->getAllByCommunity($community) as $communityMember) {
\Container::$persistentDataManager->deleteFromDb($communityMember);
}
$community->setMainCurrencyId(null);
\Container::$persistentDataManager->saveToDb($community);
foreach ($this->currencyRepository->getAllByCommunity($community) as $currency) {
foreach ($this->currencyExchangeRatesRepository->getAllByCurrency($currency) as $currencyExchangeRate) {
\Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
}
\Container::$persistentDataManager->deleteFromDb($currency);
}
\Container::$persistentDataManager->deleteFromDb($community);
return new JsonContent(['success' => true]);
}
public function getMembersEdit(): ?IContent public function getMembersEdit(): ?IContent
{ {
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) { if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
+17 -5
View File
@@ -2,6 +2,7 @@
use Container; use Container;
use DateTime; use DateTime;
use RVR\Finance\BalanceCalculator;
use RVR\Finance\ExchangeRateCalculator; use RVR\Finance\ExchangeRateCalculator;
use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember; use RVR\PersistentData\Model\CommunityMember;
@@ -68,17 +69,16 @@ class EventController implements IAuthenticationRequired, ISecured
{ {
$itemsPerPage = 10; $itemsPerPage = 10;
$numberOfEvents = $this->eventRepository->countAllByCommunity($this->community); $numberOfEvents = $this->eventRepository->countAllByCommunity($this->community);
$pages = ceil($numberOfEvents / $itemsPerPage); $currentPage = Container::$request->query('page') ?: 1;
$currentPage = Container::$request->query('page') ?: 0;
$events = $this->eventRepository->getPagedByCommunity( $events = $this->eventRepository->getPagedByCommunity(
$this->community, $this->community,
$currentPage * $itemsPerPage, $currentPage,
$itemsPerPage $itemsPerPage
); );
return new HtmlContent('events/events', [ return new HtmlContent('events/events', [
'community' => $this->community, 'community' => $this->community,
'pages' => $pages, 'pages' => ceil($numberOfEvents / $itemsPerPage),
'currentPage' => $currentPage, 'currentPage' => $currentPage,
'numberOfEvents' => $numberOfEvents, 'numberOfEvents' => $numberOfEvents,
'events' => $events 'events' => $events
@@ -106,10 +106,22 @@ class EventController implements IAuthenticationRequired, ISecured
} }
Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']); Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']);
/**
* @var User $user
*/
$user = \Container::$request->user();
$balanceCalculator = new BalanceCalculator($this->community, $user, $event);
$balance = $balanceCalculator->calculate();
return new HtmlContent('events/event', [ return new HtmlContent('events/event', [
'community' => $this->community, 'community' => $this->community,
'event' => $event, 'event' => $event,
'totalCost' => $this->sumTransactions($event) 'totalCost' => $this->sumTransactions($event),
'debtItems' => $balance['debtItems'],
'debtBalance' => $balance['debtBalance'],
'outstandingItems' => $balance['outstandingItems'],
'outstandingBalance' => $balance['outstandingBalance'],
'balance' => $balance['absoluteBalance'],
]); ]);
} }
@@ -0,0 +1,70 @@
<?php namespace RVR\Controller;
use DateTime;
use Container;
use RVR\PersistentData\Model\Event;
use RVR\PersistentData\Model\User;
use RVR\Repository\EventRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IRedirect;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\Redirect;
class EventRedirectController implements IAuthenticationRequired
{
private EventRepository $eventRepository;
public function __construct()
{
$this->eventRepository = new EventRepository();
}
public function isAuthenticationRequired(): bool
{
return true;
}
public function getEvent()
{
$currentEvent = $this->getCurrentEvent();
if ($currentEvent === null) {
return new HtmlContent('event_redirect/no_event');
}
return new Redirect(
\Container::$routeCollection->getRoute('community.event')
->generateLink([
'communitySlug' => $currentEvent->getCommunity()->getSlug(),
'eventSlug' => $currentEvent->getSlug()
]),
IRedirect::TEMPORARY
);
}
public function getEventNewTransaction()
{
$currentEvent = $this->getCurrentEvent();
if ($currentEvent === null) {
return new HtmlContent('event_redirect/no_event');
}
return new Redirect(
\Container::$routeCollection->getRoute('community.transactions.new')
->generateLink([
'communitySlug' => $currentEvent->getCommunity()->getSlug(),
'event' => $currentEvent->getSlug()
]),
IRedirect::TEMPORARY
);
}
private function getCurrentEvent(): ?Event
{
/**
* @var User $user
*/
$user = Container::$request->user();
return $this->eventRepository->getCurrentByUser($user, new DateTime(), 30, true, ['community']);
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ class HomeController implements IAuthenticationRequired
return new HtmlContent('home', [ return new HtmlContent('home', [
'communities' => $communities, 'communities' => $communities,
'upcomingEvents' => iterator_to_array($this->eventRepository->getUpcomingByUser($user, new DateTime(), 3, true, ['community'])) 'upcomingAndRecentEvents' => iterator_to_array($this->eventRepository->getUpcomingAndRecentByUser($user, new DateTime(), 30, 3, true, ['community']))
]); ]);
} }
} }
+53 -9
View File
@@ -6,11 +6,13 @@ 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\Transaction; use RVR\PersistentData\Model\Transaction;
use RVR\PersistentData\Model\TransactionPayee;
use RVR\PersistentData\Model\User; use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityMemberRepository; use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CommunityRepository; use RVR\Repository\CommunityRepository;
use RVR\Repository\CurrencyRepository; use RVR\Repository\CurrencyRepository;
use RVR\Repository\TransactionRepository; use RVR\Repository\TransactionRepository;
use RVR\Repository\TransactionPayeeRepository;
use RVR\Repository\EventRepository; use RVR\Repository\EventRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Authorization\ISecured; use SokoWeb\Interfaces\Authorization\ISecured;
@@ -28,6 +30,8 @@ class TransactionController implements IAuthenticationRequired, ISecured
private TransactionRepository $transactionRepository; private TransactionRepository $transactionRepository;
private TransactionPayeeRepository $transactionPayeeRepository;
private EventRepository $eventRepository; private EventRepository $eventRepository;
private ?Community $community; private ?Community $community;
@@ -40,6 +44,7 @@ class TransactionController implements IAuthenticationRequired, ISecured
$this->communityMemberRepository = new CommunityMemberRepository(); $this->communityMemberRepository = new CommunityMemberRepository();
$this->currencyRepository = new CurrencyRepository(); $this->currencyRepository = new CurrencyRepository();
$this->transactionRepository = new TransactionRepository(); $this->transactionRepository = new TransactionRepository();
$this->transactionPayeeRepository = new TransactionPayeeRepository();
$this->eventRepository = new EventRepository(); $this->eventRepository = new EventRepository();
} }
@@ -84,32 +89,35 @@ class TransactionController implements IAuthenticationRequired, ISecured
$numberOfTransactions = $event ? $numberOfTransactions = $event ?
$this->transactionRepository->countAllByEvent($event) : $this->transactionRepository->countAllByEvent($event) :
$this->transactionRepository->countAllByCommunity($this->community); $this->transactionRepository->countAllByCommunity($this->community);
$pages = ceil($numberOfTransactions / $itemsPerPage); $currentPage = Container::$request->query('page') ?: 1;
$currentPage = Container::$request->query('page') ?: 0;
$transactions = $event ? $transactions = $event ?
$this->transactionRepository->getPagedByEvent( $this->transactionRepository->getPagedByEvent(
$event, $event,
$currentPage * $itemsPerPage, $currentPage,
$itemsPerPage, $itemsPerPage,
true, true,
['currency', 'payer_user', 'payee_user'] ['currency', 'payer_user']
) : ) :
$this->transactionRepository->getPagedByCommunity( $this->transactionRepository->getPagedByCommunity(
$this->community, $this->community,
$currentPage * $itemsPerPage, $currentPage,
$itemsPerPage, $itemsPerPage,
true, true,
['event', 'currency', 'payer_user', 'payee_user'] ['event', 'currency', 'payer_user']
); );
$transactions = iterator_to_array($transactions);
Container::$persistentDataManager->loadMultiRelationsFromDb($transactions, 'payees', true, ['user']);
return new HtmlContent('communities/transactions', [ return new HtmlContent('communities/transactions', [
'community' => $this->community, 'community' => $this->community,
'event' => $event, 'event' => $event,
'exchangeRateCalculator' => $exchangeRateCalculator, 'exchangeRateCalculator' => $exchangeRateCalculator,
'pages' => $pages, 'pages' => ceil($numberOfTransactions / $itemsPerPage),
'currentPage' => $currentPage, 'currentPage' => $currentPage,
'numberOfTransactions' => $numberOfTransactions, 'numberOfTransactions' => $numberOfTransactions,
'transactions' => $transactions 'transactions' => $transactions,
'members' => $this->getMembers($this->community)
]); ]);
} }
@@ -123,6 +131,10 @@ class TransactionController implements IAuthenticationRequired, ISecured
} }
Container::$persistentDataManager->loadRelationsFromDb($transaction, false, ['event']); Container::$persistentDataManager->loadRelationsFromDb($transaction, false, ['event']);
$event = $transaction->getEvent(); $event = $transaction->getEvent();
$payeeUserIds = [];
foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) {
$payeeUserIds[] = $payee->getUserId();
}
} else { } else {
$transaction = null; $transaction = null;
$eventSlug = Container::$request->query('event'); $eventSlug = Container::$request->query('event');
@@ -131,11 +143,13 @@ class TransactionController implements IAuthenticationRequired, ISecured
} else { } else {
$event = null; $event = null;
} }
$payeeUserIds = [];
} }
return new HtmlContent('communities/transaction_edit', [ return new HtmlContent('communities/transaction_edit', [
'community' => $this->community, 'community' => $this->community,
'transaction' => $transaction, 'transaction' => $transaction,
'payeeUserIds' => $payeeUserIds,
'event' => $event, 'event' => $event,
'members' => $this->getMembers($this->community), 'members' => $this->getMembers($this->community),
'currencies' => $this->getCurrencies($this->community) 'currencies' => $this->getCurrencies($this->community)
@@ -155,18 +169,48 @@ class TransactionController implements IAuthenticationRequired, ISecured
$transaction->setEventId(Container::$request->post('event_id') ?: null); $transaction->setEventId(Container::$request->post('event_id') ?: null);
$transaction->setCurrencyId(Container::$request->post('currency_id')); $transaction->setCurrencyId(Container::$request->post('currency_id'));
$transaction->setPayerUserId(Container::$request->post('payer_user_id')); $transaction->setPayerUserId(Container::$request->post('payer_user_id'));
$transaction->setPayeeUserId(Container::$request->post('payee_user_id') ?: null);
$transaction->setDescription(Container::$request->post('description')); $transaction->setDescription(Container::$request->post('description'));
$transaction->setSum(Container::$request->post('sum')); $transaction->setSum(Container::$request->post('sum'));
$transaction->setTimeDate(new DateTime(Container::$request->post('time'))); $transaction->setTimeDate(new DateTime(Container::$request->post('time')));
Container::$persistentDataManager->saveToDb($transaction); Container::$persistentDataManager->saveToDb($transaction);
$payeeUserIds = array_unique(Container::$request->post('payee_user_ids'));
if (count($payeeUserIds) === $this->communityMemberRepository->countAllByCommunity($this->community)) {
$payeeUserIds = [];
}
$currentPayees = [];
foreach ($payeeUserIds as $payeeUserId) {
$payee = new TransactionPayee();
$payee->setTransaction($transaction);
$payee->setUserId((int)$payeeUserId);
$currentPayees[(int)$payeeUserId] = $payee;
}
$existingPayees = [];
if ($transactionId) {
foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) {
$existingPayees[$payee->getUserId()] = $payee;
}
}
foreach (array_diff_key($currentPayees, $existingPayees) as $newPayee) {
Container::$persistentDataManager->saveToDb($newPayee);
}
foreach (array_diff_key($existingPayees, $currentPayees) as $deletedPayee) {
Container::$persistentDataManager->deleteFromDb($deletedPayee);
}
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
} }
public function deleteTransaction(): IContent public function deleteTransaction(): IContent
{ {
$transaction = $this->transactionRepository->getById(Container::$request->query('transactionId')); $transaction = $this->transactionRepository->getById(Container::$request->query('transactionId'));
foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) {
Container::$persistentDataManager->deleteFromDb($payee);
}
Container::$persistentDataManager->deleteFromDb($transaction); Container::$persistentDataManager->deleteFromDb($transaction);
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
+55 -9
View File
@@ -1,6 +1,9 @@
<?php namespace RVR\Finance; <?php namespace RVR\Finance;
use Container;
use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Event;
use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityMemberRepository; use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\TransactionRepository; use RVR\Repository\TransactionRepository;
@@ -8,6 +11,10 @@ class BalanceCalculator
{ {
private Community $community; private Community $community;
private User $user;
private ?Event $event;
private TransactionRepository $transactionRepository; private TransactionRepository $transactionRepository;
private CommunityMemberRepository $communityMemberRepository; private CommunityMemberRepository $communityMemberRepository;
@@ -18,9 +25,13 @@ class BalanceCalculator
private array $payments; private array $payments;
public function __construct(Community $community) private array $actualDebts;
public function __construct(Community $community, User $user, ?Event $event = null)
{ {
$this->community = $community; $this->community = $community;
$this->user = $user;
$this->event = $event;
$this->transactionRepository = new TransactionRepository(); $this->transactionRepository = new TransactionRepository();
$this->communityMemberRepository = new CommunityMemberRepository(); $this->communityMemberRepository = new CommunityMemberRepository();
$this->exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency()); $this->exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
@@ -31,7 +42,8 @@ class BalanceCalculator
$this->collectMembers(); $this->collectMembers();
$this->createPaymentsMatrix(); $this->createPaymentsMatrix();
$this->sumTransactions(); $this->sumTransactions();
return $this->calculateActualDebts(); $this->calculateActualDebts();
return $this->calculateBalanceForUser();
} }
private function collectMembers(): void private function collectMembers(): void
@@ -56,13 +68,22 @@ class BalanceCalculator
private function sumTransactions(): void private function sumTransactions(): void
{ {
$membersCount = count($this->members); $membersCount = count($this->members);
$transactions = $this->transactionRepository->getAllByCommunity($this->community, true, ['currency']); if ($this->event !== null) {
$transactions = iterator_to_array($this->transactionRepository->getAllByEvent($this->event, true, ['currency']));
} else {
$transactions = iterator_to_array($this->transactionRepository->getAllByCommunity($this->community, true, ['currency']));
}
Container::$persistentDataManager->loadMultiRelationsFromDb($transactions, 'payees');
foreach ($transactions as $transaction) { foreach ($transactions as $transaction) {
$sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate()); $sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
$payees = $transaction->getPayees();
$payeeCount = count($payees);
if ($transaction->getPayeeUserId()) { if ($payeeCount > 0) {
$this->payments[$transaction->getPayerUserId()][$transaction->getPayeeUserId()] += $sum; foreach ($payees as $payee) {
$this->payments[$transaction->getPayerUserId()][$payee->getUserId()] += $sum / $payeeCount;
}
} else { } else {
foreach ($this->members as $payeeUserId => $member) { foreach ($this->members as $payeeUserId => $member) {
$this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount; $this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount;
@@ -71,20 +92,45 @@ class BalanceCalculator
} }
} }
private function calculateActualDebts(): array private function calculateActualDebts(): void
{ {
$actualDebts = []; $this->actualDebts = [];
foreach ($this->payments as $payerUserId => $paymentsOfPayer) { foreach ($this->payments as $payerUserId => $paymentsOfPayer) {
foreach ($paymentsOfPayer as $payeeUserId => $sum) { foreach ($paymentsOfPayer as $payeeUserId => $sum) {
$actualDebt = $this->payments[$payeeUserId][$payerUserId] - $sum; $actualDebt = $this->payments[$payeeUserId][$payerUserId] - $sum;
if (round($actualDebt, $this->community->getMainCurrency()->getRoundDigits()) > 0.0) { if (round($actualDebt, $this->community->getMainCurrency()->getRoundDigits()) > 0.0) {
$actualDebts[] = ['payer' => $this->members[$payerUserId], 'payee' => $this->members[$payeeUserId], 'amount' => $actualDebt]; $this->actualDebts[] = ['payer' => $this->members[$payerUserId], 'payee' => $this->members[$payeeUserId], 'amount' => $actualDebt];
}
} }
} }
} }
return $actualDebts; private function calculateBalanceForUser(): array
{
$debtItems = [];
$debtBalance = 0.0;
$outstandingItems = [];
$outstandingBalance = 0.0;
foreach ($this->actualDebts as $debt) {
if ($debt['payer']->getId() === $this->user->getId()) {
$debtBalance += $debt['amount'];
$debtItems[] = $debt;
}
if ($debt['payee']->getId() === $this->user->getId()) {
$outstandingBalance += $debt['amount'];
$outstandingItems[] = $debt;
}
}
$absoluteBalance = $outstandingBalance - $debtBalance;
return [
'absoluteBalance' => $absoluteBalance,
'debtItems' => $debtItems,
'debtBalance' => $debtBalance,
'outstandingItems' => $outstandingItems,
'outstandingBalance' => $outstandingBalance
];
} }
} }
+2 -2
View File
@@ -33,12 +33,12 @@ class Community extends ModelWithSlug
$this->currency = $currency; $this->currency = $currency;
} }
public function setMainCurrency(Currency $mainCurrency): void public function setMainCurrency(?Currency $mainCurrency): void
{ {
$this->mainCurrency = $mainCurrency; $this->mainCurrency = $mainCurrency;
} }
public function setMainCurrencyId(int $mainCurrencyId): void public function setMainCurrencyId(?int $mainCurrencyId): void
{ {
$this->mainCurrencyId = $mainCurrencyId; $this->mainCurrencyId = $mainCurrencyId;
} }
+16
View File
@@ -17,6 +17,10 @@ class Transaction extends Model
'payee_user' => User::class 'payee_user' => User::class
]; ];
protected static array $multiRelations = [
'payees' => [TransactionPayee::class, 'transaction']
];
private ?Community $community = null; private ?Community $community = null;
private int $communityId; private int $communityId;
@@ -37,6 +41,8 @@ class Transaction extends Model
private ?int $payeeUserId = null; private ?int $payeeUserId = null;
private ?array $payees = null;
private string $description = ''; private string $description = '';
private float $sum = 0.0; private float $sum = 0.0;
@@ -93,6 +99,11 @@ class Transaction extends Model
$this->payeeUserId = $payeeUserId; $this->payeeUserId = $payeeUserId;
} }
public function setPayees(array $payees): void
{
$this->payees = $payees;
}
public function setDescription(string $description): void public function setDescription(string $description): void
{ {
$this->description = $description; $this->description = $description;
@@ -163,6 +174,11 @@ class Transaction extends Model
return $this->payeeUserId; return $this->payeeUserId;
} }
public function getPayees(): ?array
{
return $this->payees;
}
public function getDescription(): string public function getDescription(): string
{ {
return $this->description; return $this->description;
@@ -0,0 +1,60 @@
<?php namespace RVR\PersistentData\Model;
use SokoWeb\PersistentData\Model\Model;
class TransactionPayee extends Model
{
protected static string $table = 'transaction_payees';
protected static array $fields = ['transaction_id', 'user_id'];
protected static array $relations = ['transaction' => Transaction::class, 'user' => User::class];
private ?Transaction $transaction = null;
private ?int $transactionId = null;
private ?User $user = null;
private ?int $userId = null;
public function setTransaction(Transaction $transaction): void
{
$this->transaction = $transaction;
}
public function setTransactionId(int $transactionId): void
{
$this->transactionId = $transactionId;
}
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function getTransaction(): ?Transaction
{
return $this->transaction;
}
public function getTransactionId(): ?int
{
return $this->transactionId;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
}
+13 -2
View File
@@ -15,12 +15,16 @@ class CommunityMemberRepository
public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = new Select(\Container::$dbConnection); $select = $this->selectAllByCommunity($community);
$select->where('community_id', '=', $community->getId());
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations, $withRelations); yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations, $withRelations);
} }
public function countAllByCommunity(Community $community): int
{
return $this->selectAllByCommunity($community)->count();
}
public function getAllByUser(User $user, bool $useRelations = false, array $withRelations = []): Generator public function getAllByUser(User $user, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = new Select(\Container::$dbConnection); $select = new Select(\Container::$dbConnection);
@@ -37,4 +41,11 @@ class CommunityMemberRepository
return \Container::$persistentDataManager->selectFromDb($select, CommunityMember::class); return \Container::$persistentDataManager->selectFromDb($select, CommunityMember::class);
} }
private function selectAllByCommunity(Community $community): Select
{
$select = new Select(\Container::$dbConnection, CommunityMember::getTable());
$select->where('community_id', '=', $community->getId());
return $select;
}
} }
+31 -8
View File
@@ -2,6 +2,7 @@
use Container; use Container;
use DateTime; use DateTime;
use DateInterval;
use Generator; use Generator;
use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Event; use RVR\PersistentData\Model\Event;
@@ -32,31 +33,41 @@ class EventRepository
return $this->selectAllByCommunity($community)->count(); return $this->selectAllByCommunity($community)->count();
} }
public function getUpcomingByCommunity(Community $community, DateTime $from, int $limit, bool $useRelations = false, array $withRelations = []): Generator public function getUpcomingAndRecentByCommunity(Community $community, DateTime $from, int $days, int $limit, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = $this->selectAllByCommunity($community); $select = $this->selectAllByCommunity($community);
$select->where('end', '>', $from->format('Y-m-d H:i:s')); $this->selectUpcomingAndRecent($select, $from, $days);
$select->orderBy('start', 'ASC'); $select->orderBy('start', 'DESC');
$select->limit($limit, 0); $select->limit($limit, 0);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations); yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
} }
public function getUpcomingByUser(User $user, DateTime $from, int $limit, bool $useRelations = false, array $withRelations = []): Generator public function getUpcomingAndRecentByUser(User $user, DateTime $from, int $days, int $limit, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = $this->selectAllByUser($user); $select = $this->selectAllByUser($user);
$select->where('end', '>', $from->format('Y-m-d H:i:s')); $this->selectUpcomingAndRecent($select, $from, $days);
$select->orderBy('start', 'ASC'); $select->orderBy('start', 'DESC');
$select->limit($limit, 0); $select->limit($limit, 0);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations); yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
} }
public function getPagedByCommunity(Community $community, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator public function getCurrentByUser(User $user, DateTime $from, int $days, bool $useRelations = false, array $withRelations = []): ?Event
{
$select = $this->selectAllByUser($user);
$this->selectUpcomingAndRecent($select, $from, $days);
$select->orderBy('start', 'DESC');
$select->limit(1, 0);
return Container::$persistentDataManager->selectFromDb($select, Event::class, $useRelations, $withRelations);
}
public function getPagedByCommunity(Community $community, int $page, int $itemsPerPage, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = $this->selectAllByCommunity($community); $select = $this->selectAllByCommunity($community);
$select->orderBy('start', 'DESC'); $select->orderBy('start', 'DESC');
$select->limit($limit, $start); $select->paginate($page, $itemsPerPage);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations); yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
} }
@@ -86,4 +97,16 @@ class EventRepository
$select->where(['community_members', 'user_id'], '=', $user->getId()); $select->where(['community_members', 'user_id'], '=', $user->getId());
return $select; return $select;
} }
private function selectUpcomingAndRecent(Select $select, DateTime $from, int $days)
{
$select->where(function (Select $select) use ($from, $days) {
$select->where('start', '<', (clone $from)->add(DateInterval::createFromDateString("$days days"))->format('Y-m-d H:i:s'));
$select->where('end', '>', $from->format('Y-m-d H:i:s'));
});
$select->orWhere(function (Select $select) use ($from, $days) {
$select->where('end', '>', (clone $from)->sub(DateInterval::createFromDateString("$days days"))->format('Y-m-d H:i:s'));
$select->where('start', '<', $from->format('Y-m-d H:i:s'));
});
}
} }
@@ -0,0 +1,23 @@
<?php namespace RVR\Repository;
use Container;
use Generator;
use RVR\PersistentData\Model\Transaction;
use RVR\PersistentData\Model\TransactionPayee;
use SokoWeb\Database\Query\Select;
class TransactionPayeeRepository
{
public function getById(int $id): ?TransactionPayee
{
return Container::$persistentDataManager->selectFromDbById($id, TransactionPayee::class);
}
public function getAllByTransaction(Transaction $transaction, bool $useRelations = false, array $withRelations = []): Generator
{
$select = new Select(Container::$dbConnection);
$select->where('transaction_id', '=', $transaction->getId());
yield from Container::$persistentDataManager->selectMultipleFromDb($select, TransactionPayee::class, $useRelations, $withRelations);
}
}
+4 -4
View File
@@ -66,20 +66,20 @@ class TransactionRepository
return $select->count() > 0; return $select->count() > 0;
} }
public function getPagedByCommunity(Community $community, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator public function getPagedByCommunity(Community $community, int $page, int $itemsPerPage, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = $this->selectAllByCommunity($community); $select = $this->selectAllByCommunity($community);
$select->orderBy('time', 'DESC'); $select->orderBy('time', 'DESC');
$select->limit($limit, $start); $select->paginate($page, $itemsPerPage);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations); 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 public function getPagedByEvent(Event $event, int $page, int $itemsPerPage, bool $useRelations = false, array $withRelations = []): Generator
{ {
$select = $this->selectAllByEvent($event); $select = $this->selectAllByEvent($event);
$select->orderBy('time', 'DESC'); $select->orderBy('time', 'DESC');
$select->limit($limit, $start); $select->paginate($page, $itemsPerPage);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations); yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
} }
+4 -4
View File
@@ -12,16 +12,16 @@
<div class="gridContainer marginTop"> <div class="gridContainer marginTop">
<div> <div>
<h3 class="marginBottom">Upcoming events</h3> <h3 class="marginBottom">Upcoming and recent events</h3>
<?php if (count($upcomingEvents) > 0): ?> <?php if (count($upcomingAndRecentEvents) > 0): ?>
<?php foreach ($upcomingEvents as $event): ?> <?php foreach ($upcomingAndRecentEvents as $event): ?>
<p> <p>
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> <a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a>
<span class="small"><?= $event->getStartDate()->format('Y-m-d') ?> – <?= $event->getEndDate()->format('Y-m-d') ?></span> <span class="small"><?= $event->getStartDate()->format('Y-m-d') ?> – <?= $event->getEndDate()->format('Y-m-d') ?></span>
</p> </p>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else: ?>
<p>There is no upcoming event.</p> <p>There is no event to show.</p>
<?php endif; ?> <?php endif; ?>
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">All events</a> | <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')->generateLink(['communitySlug' => $community->getSlug()]) ?>">All events</a> | <a href="<?= Container::$routeCollection->getRoute('community.events.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New event</a></p>
</div> </div>
+7 -1
View File
@@ -25,9 +25,15 @@
<input type="number" class="text big fullWidth" name="main_currency_round_digits" min="0" max="9" required> <input type="number" class="text big fullWidth" name="main_currency_round_digits" min="0" max="9" required>
<?php endif; ?> <?php endif; ?>
<p id="communityFormError" class="formError justify marginTop"></p> <p id="communityFormError" class="formError justify marginTop"></p>
<div class="right marginTop"> <div class="right marginTop" style="font-size: 0;">
<button type="submit" name="submit_button"><?= isset($community) ? '<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($community) ? '<i class="fa-regular fa-floppy-disk"></i> Save' : '<i class="fa-regular fa-plus"></i> Create' ?></button>
<?php if (isset($community)): ?>
<button type="submit" form="deleteCommunity" name="submit_button" data-confirmation="Are you sure you want to delete this community?" class="red marginLeft"><i class="fa-regular fa-trash-can"></i> Delete</button>
<?php endif; ?>
</div> </div>
</form> </form>
<?php if (isset($community)): ?>
<form id="deleteCommunity" action="<?= Container::$routeCollection->getRoute('community.delete-action')->generateLink(['communitySlug' => $community->getSlug()]) ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>"></form>
<?php endif; ?>
</div> </div>
@endsection @endsection
+2 -2
View File
@@ -1,5 +1,5 @@
@css(/static/node_modules/tom-select/dist/css/tom-select.min.css) @css(node_modules/tom-select/dist/css/tom-select.min.css)
@js(/static/node_modules/tom-select/dist/js/tom-select.base.min.js) @js(node_modules/tom-select/dist/js/tom-select.base.min.js)
@js(js/communities/community_members.js) @js(js/communities/community_members.js)
@extends(templates/layout_normal) @extends(templates/layout_normal)
+16 -9
View File
@@ -1,5 +1,5 @@
@css(/static/node_modules/tom-select/dist/css/tom-select.min.css) @css(node_modules/tom-select/dist/css/tom-select.min.css)
@js(/static/node_modules/tom-select/dist/js/tom-select.base.min.js) @js(node_modules/tom-select/dist/js/tom-select.base.min.js)
@js(js/communities/transaction.js) @js(js/communities/transaction.js)
@extends(templates/layout_normal) @extends(templates/layout_normal)
@@ -7,11 +7,11 @@
@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> »
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Transactions</a> »
<?php if (isset($event)): ?> <?php if (isset($event)): ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> » <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> » <a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> »
<?php endif; ?> <?php endif; ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">Transactions</a> »
<?php if (isset($transaction)): ?> <?php if (isset($transaction)): ?>
Edit transaction Edit transaction
<?php else: ?> <?php else: ?>
@@ -36,16 +36,23 @@
<select class="big fullWidth" name="payer_user_id" required> <select class="big fullWidth" name="payer_user_id" required>
<option value="" hidden></option> <option value="" hidden></option>
<?php foreach ($members as $member): ?> <?php foreach ($members as $member): ?>
<option value="<?= $member->getUser()->getId() ?>" <?= isset($transaction) && $transaction->getPayerUserId() === $member->getUser()->getId() ? 'selected' : '' ?>><?= $member->getUser()->getDisplayName() ?></option> <option value="<?= $member->getUser()->getId() ?>"
<?= isset($transaction) ?
($transaction->getPayerUserId() === $member->getUser()->getId() ? 'selected' : '') :
(Container::$request->user()->getUniqueId() == $member->getUser()->getId() ? 'selected' : '') ?>>
<?= $member->getUser()->getDisplayName() ?>
</option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<p class="formLabel marginTop">Payee</p> <p class="formLabel marginTop">Payee(s)</p>
<select class="big fullWidth" name="payee_user_id"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); grid-gap: 10px;">
<option value="">[common]</option>
<?php foreach ($members as $member): ?> <?php foreach ($members as $member): ?>
<option value="<?= $member->getUser()->getId() ?>" <?= isset($transaction) && $transaction->getPayeeUserId() === $member->getUser()->getId() ? 'selected' : '' ?>><?= $member->getUser()->getDisplayName() ?></option> <div style="text-align: center;">
<input id="payee_<?= $member->getUserId() ?>" type="checkbox" name="payee_user_ids[]" value="<?= $member->getUserId() ?>" <?= !isset($transaction) || count($payeeUserIds) === 0 || in_array($member->getUserId(), $payeeUserIds) ? 'checked' : '' ?>><!--
--><label for="payee_<?= $member->getUserId() ?>"><?= $member->getUser()->getDisplayName() ?></label>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</select> </div>
<p class="formLabel marginTop">Description</p> <p class="formLabel marginTop">Description</p>
<input type="text" class="text big fullWidth" name="description" value="<?= isset($transaction) ? $transaction->getDescription() : '' ?>" required> <input type="text" class="text big fullWidth" name="description" value="<?= isset($transaction) ? $transaction->getDescription() : '' ?>" required>
<p class="formLabel marginTop">Sum</p> <p class="formLabel marginTop">Sum</p>
+30 -27
View File
@@ -13,20 +13,13 @@
<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> <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
$paginationRouteId = 'community.transactions';
$paginationRouteParams = ['communitySlug' => $community->getSlug()];
?>
<?php if ($pages > 1): ?> <?php if ($pages > 1): ?>
<p class="paginateContainer marginTop"> @include(templates/pagination)
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => 0]) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
<?php for ($i = 0; $i < $pages; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i + 1 ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $pages - 1]) ?>">»</a>
</p>
<?php endif; ?> <?php endif; ?>
<?php foreach ($transactions as $transaction): ?> <?php foreach ($transactions as $transaction): ?>
@@ -37,7 +30,29 @@
<p><span class="label"><?= $transaction->getEvent()->getTitle() ?></span></p> <p><span class="label"><?= $transaction->getEvent()->getTitle() ?></span></p>
<?php endif; ?> <?php endif; ?>
<p style="font-weight: bold;"><?= $transaction->getDescription() ?></p> <p style="font-weight: bold;"><?= $transaction->getDescription() ?></p>
<p class="small"><?= $transaction->getPayerUser()->getDisplayName() ?> <i class="fa-solid fa-caret-right"></i> <?= $transaction->getPayeeUser() ? $transaction->getPayeeUser()->getDisplayName() : '[common]' ?></p> <p class="small">
<?= $transaction->getPayerUser()->getDisplayName() ?>
<i class="fa-solid fa-caret-right"></i>
<?php foreach ($members as $member): ?>
<?php
if (count($transaction->getPayees()) > 0) {
$found = false;
foreach ($transaction->getPayees() as $payee) {
if ($member->getUserId() === $payee->getUserId()) {
$found = true;
}
}
} else {
$found = true;
}
?>
<?php if ($found): ?>
<?= $member->getUser()->getDisplayName() ?>
<?php else: ?>
<span class="gray" style="text-decoration: line-through;"><?= $member->getUser()->getDisplayName() ?></span>
<?php endif; ?>
<?php endforeach; ?>
</p>
<p class="small"><?= $transaction->getTimeDate()->format('Y-m-d H:i') ?></p> <p class="small"><?= $transaction->getTimeDate()->format('Y-m-d H:i') ?></p>
</div> </div>
<div style="text-align: right;"> <div style="text-align: right;">
@@ -51,19 +66,7 @@
<?php endforeach; ?> <?php endforeach; ?>
<?php if ($pages > 1): ?> <?php if ($pages > 1): ?>
<p class="paginateContainer marginTop"> @include(templates/pagination)
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => 0]) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
<?php for ($i = 0; $i < $pages; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i + 1 ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $pages - 1]) ?>">»</a>
</p>
<?php endif; ?> <?php endif; ?>
<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> <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>
+8
View File
@@ -0,0 +1,8 @@
@extends(templates/layout_normal)
@section(main)
<h2>No event found</h2>
<div class="box compactBox">
<p class="error justify">No upcoming or recent event was found. <a href="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
</div>
@endsection
+27
View File
@@ -22,6 +22,33 @@
<td class="mono" style="text-align: right;"><?= number_format($totalCost, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td> <td class="mono" style="text-align: right;"><?= number_format($totalCost, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr> </tr>
</table> </table>
<table class="fullWidth marginTop">
<tr>
<td class="bold">You owe*</td>
<td class="mono red" style="text-align: right;"><?= number_format($debtBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php foreach ($debtItems as $item): ?>
<tr>
<td class="small"><?= $item['payee']->getUser()->getDisplayName() ?></td>
<td class="small mono red" style="text-align: right;"><?= number_format($item['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php endforeach; ?>
<tr>
<td class="bold">You're owed*</td>
<td class="mono green" style="text-align: right;"><?= number_format($outstandingBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php foreach ($outstandingItems as $item): ?>
<tr>
<td class="small"><?= $item['payer']->getUser()->getDisplayName() ?></td>
<td class="small mono green" style="text-align: right;"><?= number_format($item['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>
</table>
<p class="small right">* Virtual balance only for this event. Check <a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>">community</a> finances for your real balance.</p>
</div> </div>
</div> </div>
@endsection @endsection
+7 -26
View File
@@ -9,20 +9,13 @@
<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 if ($numberOfEvents > 0): ?> <?php if ($numberOfEvents > 0): ?>
<?php
$paginationRouteId = 'community.events';
$paginationRouteParams = ['communitySlug' => $community->getSlug()];
?>
<?php if ($pages > 1): ?> <?php if ($pages > 1): ?>
<p class="paginateContainer marginTop"> @include(templates/pagination)
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => 0]) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
<?php for ($i = 0; $i < $pages; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i + 1 ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $pages - 1]) ?>">»</a>
</p>
<?php endif; ?> <?php endif; ?>
<?php foreach ($events as $event): ?> <?php foreach ($events as $event): ?>
@@ -36,19 +29,7 @@
<?php endforeach; ?> <?php endforeach; ?>
<?php if ($pages > 1): ?> <?php if ($pages > 1): ?>
<p class="paginateContainer marginTop"> @include(templates/pagination)
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => 0]) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
<?php for ($i = 0; $i < $pages; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i + 1 ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug(), 'page' => $pages - 1]) ?>">»</a>
</p>
<?php endif; ?> <?php endif; ?>
<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>
+4 -4
View File
@@ -16,9 +16,9 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
<div> <div>
<h3 class="marginBottom">Upcoming events</h3> <h3 class="marginBottom">Upcoming and recent events</h3>
<?php if (count($upcomingEvents) > 0): ?> <?php if (count($upcomingAndRecentEvents) > 0): ?>
<?php foreach ($upcomingEvents as $event): ?> <?php foreach ($upcomingAndRecentEvents as $event): ?>
<p> <p>
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $event->getCommunity()->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> <a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $event->getCommunity()->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a>
<span class="small"> <span class="small">
@@ -28,7 +28,7 @@
</p> </p>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else: ?>
<p>There is no upcoming event.</p> <p>There is no event to show.</p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
+31
View File
@@ -0,0 +1,31 @@
<p class="paginateContainer marginTop">
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => 1])) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => max(1, $currentPage - 1)])) ?>">‹</a>
<?php
$maxPages = 7;
if ($pages <= $maxPages) {
$start = 1;
$end = $pages;
} else {
$maxAdditionalPages = $maxPages - 1;
$maxAdditionalPagesHalf = ceil($maxAdditionalPages / 2);
$start = $currentPage - $maxAdditionalPagesHalf > 1 ?
($currentPage - $maxAdditionalPagesHalf < $pages - $maxAdditionalPages ?
$currentPage - $maxAdditionalPagesHalf :
$pages - $maxAdditionalPages) :
1;
$end = $start + $maxAdditionalPages < $pages ?
$start + $maxAdditionalPages :
$pages;
}
?>
<?php for ($i = $start; $i <= $end; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => $i])) ?>"><?= $i ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => min($pages, $currentPage + 1)])) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => $pages])) ?>">»</a>
</p>
+6
View File
@@ -13,6 +13,7 @@ use RVR\Controller\UserController;
use RVR\Controller\UserSearchController; use RVR\Controller\UserSearchController;
use RVR\Controller\CommunityController; use RVR\Controller\CommunityController;
use RVR\Controller\TransactionController; use RVR\Controller\TransactionController;
use RVR\Controller\EventRedirectController;
use RVR\Controller\EventController; use RVR\Controller\EventController;
use RVR\Repository\UserRepository; use RVR\Repository\UserRepository;
@@ -62,6 +63,10 @@ Container::$routeCollection->group('account', function (RouteCollection $routeCo
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [UserController::class, 'authenticateWithGoogle']); $routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [UserController::class, 'authenticateWithGoogle']);
}); });
Container::$routeCollection->get('searchUser', 'searchUser', [UserSearchController::class, 'searchUser']); Container::$routeCollection->get('searchUser', 'searchUser', [UserSearchController::class, 'searchUser']);
Container::$routeCollection->group('now', function (RouteCollection $routeCollection) {
$routeCollection->get('now.event', '', [EventRedirectController::class, 'getEvent']);
$routeCollection->get('now.transactions.new', 'transactions/new', [EventRedirectController::class, 'getEventNewTransaction']);
});
Container::$routeCollection->group('communities', function (RouteCollection $routeCollection) { Container::$routeCollection->group('communities', function (RouteCollection $routeCollection) {
$routeCollection->get('community.new', 'new', [CommunityController::class, 'getCommunityNew']); $routeCollection->get('community.new', 'new', [CommunityController::class, 'getCommunityNew']);
$routeCollection->post('community.new-action', 'new', [CommunityController::class, 'saveCommunity']); $routeCollection->post('community.new-action', 'new', [CommunityController::class, 'saveCommunity']);
@@ -70,6 +75,7 @@ Container::$routeCollection->group('communities', function (RouteCollection $rou
$routeCollection->get('community.settings', 'settings', [CommunityController::class, 'getCommunitySettings']); $routeCollection->get('community.settings', 'settings', [CommunityController::class, 'getCommunitySettings']);
$routeCollection->get('community.edit', 'edit', [CommunityController::class, 'getCommunityEdit']); $routeCollection->get('community.edit', 'edit', [CommunityController::class, 'getCommunityEdit']);
$routeCollection->post('community.edit-action', 'edit', [CommunityController::class, 'saveCommunity']); $routeCollection->post('community.edit-action', 'edit', [CommunityController::class, 'saveCommunity']);
$routeCollection->post('community.delete-action', 'delete', [CommunityController::class, 'deleteCommunity']);
$routeCollection->get('community.members', 'members', [CommunityController::class, 'getMembersEdit']); $routeCollection->get('community.members', 'members', [CommunityController::class, 'getMembersEdit']);
$routeCollection->post('community.members.new-action', 'members/new', [CommunityController::class, 'saveMember']); $routeCollection->post('community.members.new-action', 'members/new', [CommunityController::class, 'saveMember']);
$routeCollection->post('community.members.edit-action', 'members/edit', [CommunityController::class, 'saveMember']); $routeCollection->post('community.members.edit-action', 'members/edit', [CommunityController::class, 'saveMember']);