diff --git a/composer.json b/composer.json index ef33e6c..62f3eb7 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "esoko/soko-web": "0.12.2", + "esoko/soko-web": "0.13", "firebase/php-jwt": "^6.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 243e661..ccb3e71 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "de44c3f047848705e8e3d6b460f73ff1", + "content-hash": "707bad5bd796500db300c5384f2ce378", "packages": [ { "name": "cocur/slugify", @@ -82,11 +82,11 @@ }, { "name": "esoko/soko-web", - "version": "v0.12.2", + "version": "v0.13", "source": { "type": "git", "url": "https://git.esoko.eu/esoko/soko-web.git", - "reference": "8d490e48aaeb7ad2843e402fa42ec266db52e809" + "reference": "4283bc9bb15d17914393b4ba3463d83717487c53" }, "require": { "cocur/slugify": "^4.3", @@ -108,7 +108,7 @@ "GNU GPL 3.0" ], "description": "Lightweight web framework", - "time": "2023-05-28T19:13:20+00:00" + "time": "2023-06-17T12:32:56+00:00" }, { "name": "firebase/php-jwt", diff --git a/database/migrations/structure/20230617_0214_multiple_payees.sql b/database/migrations/structure/20230617_0214_multiple_payees.sql new file mode 100644 index 0000000..d4c1c94 --- /dev/null +++ b/database/migrations/structure/20230617_0214_multiple_payees.sql @@ -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; diff --git a/src/Controller/TransactionController.php b/src/Controller/TransactionController.php index 0bbb91c..b6a220b 100644 --- a/src/Controller/TransactionController.php +++ b/src/Controller/TransactionController.php @@ -6,11 +6,13 @@ use RVR\Finance\ExchangeRateCalculator; use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\CommunityMember; use RVR\PersistentData\Model\Transaction; +use RVR\PersistentData\Model\TransactionPayee; use RVR\PersistentData\Model\User; use RVR\Repository\CommunityMemberRepository; use RVR\Repository\CommunityRepository; use RVR\Repository\CurrencyRepository; use RVR\Repository\TransactionRepository; +use RVR\Repository\TransactionPayeeRepository; use RVR\Repository\EventRepository; use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Authorization\ISecured; @@ -28,6 +30,8 @@ class TransactionController implements IAuthenticationRequired, ISecured private TransactionRepository $transactionRepository; + private TransactionPayeeRepository $transactionPayeeRepository; + private EventRepository $eventRepository; private ?Community $community; @@ -40,6 +44,7 @@ class TransactionController implements IAuthenticationRequired, ISecured $this->communityMemberRepository = new CommunityMemberRepository(); $this->currencyRepository = new CurrencyRepository(); $this->transactionRepository = new TransactionRepository(); + $this->transactionPayeeRepository = new TransactionPayeeRepository(); $this->eventRepository = new EventRepository(); } @@ -91,16 +96,19 @@ class TransactionController implements IAuthenticationRequired, ISecured $currentPage, $itemsPerPage, true, - ['currency', 'payer_user', 'payee_user'] + ['currency', 'payer_user'] ) : $this->transactionRepository->getPagedByCommunity( $this->community, $currentPage, $itemsPerPage, 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', [ 'community' => $this->community, 'event' => $event, @@ -108,7 +116,8 @@ class TransactionController implements IAuthenticationRequired, ISecured 'pages' => ceil($numberOfTransactions / $itemsPerPage), 'currentPage' => $currentPage, 'numberOfTransactions' => $numberOfTransactions, - 'transactions' => $transactions + 'transactions' => $transactions, + 'members' => $this->getMembers($this->community) ]); } @@ -122,6 +131,10 @@ class TransactionController implements IAuthenticationRequired, ISecured } Container::$persistentDataManager->loadRelationsFromDb($transaction, false, ['event']); $event = $transaction->getEvent(); + $payeeUserIds = []; + foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) { + $payeeUserIds[] = $payee->getUserId(); + } } else { $transaction = null; $eventSlug = Container::$request->query('event'); @@ -130,11 +143,13 @@ class TransactionController implements IAuthenticationRequired, ISecured } else { $event = null; } + $payeeUserIds = []; } return new HtmlContent('communities/transaction_edit', [ 'community' => $this->community, 'transaction' => $transaction, + 'payeeUserIds' => $payeeUserIds, 'event' => $event, 'members' => $this->getMembers($this->community), 'currencies' => $this->getCurrencies($this->community) @@ -154,18 +169,48 @@ class TransactionController implements IAuthenticationRequired, ISecured $transaction->setEventId(Container::$request->post('event_id') ?: null); $transaction->setCurrencyId(Container::$request->post('currency_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->setSum(Container::$request->post('sum')); $transaction->setTimeDate(new DateTime(Container::$request->post('time'))); 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]); } public function deleteTransaction(): IContent { $transaction = $this->transactionRepository->getById(Container::$request->query('transactionId')); + + foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) { + Container::$persistentDataManager->deleteFromDb($payee); + } + Container::$persistentDataManager->deleteFromDb($transaction); return new JsonContent(['success' => true]); diff --git a/src/Finance/BalanceCalculator.php b/src/Finance/BalanceCalculator.php index 6842b0f..fa4db62 100644 --- a/src/Finance/BalanceCalculator.php +++ b/src/Finance/BalanceCalculator.php @@ -1,5 +1,6 @@ members); - $transactions = $this->transactionRepository->getAllByCommunity($this->community, true, ['currency']); + $transactions = iterator_to_array($this->transactionRepository->getAllByCommunity($this->community, true, ['currency'])); + Container::$persistentDataManager->loadMultiRelationsFromDb($transactions, 'payees'); foreach ($transactions as $transaction) { $sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate()); + $payees = $transaction->getPayees(); + $payeeCount = count($payees); - if ($transaction->getPayeeUserId()) { - $this->payments[$transaction->getPayerUserId()][$transaction->getPayeeUserId()] += $sum; + if ($payeeCount > 0) { + foreach ($payees as $payee) { + $this->payments[$transaction->getPayerUserId()][$payee->getUserId()] += $sum / $payeeCount; + } } else { foreach ($this->members as $payeeUserId => $member) { $this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount; diff --git a/src/PersistentData/Model/Transaction.php b/src/PersistentData/Model/Transaction.php index fe73d49..64111e9 100644 --- a/src/PersistentData/Model/Transaction.php +++ b/src/PersistentData/Model/Transaction.php @@ -17,6 +17,10 @@ class Transaction extends Model 'payee_user' => User::class ]; + protected static array $multiRelations = [ + 'payees' => [TransactionPayee::class, 'transaction'] + ]; + private ?Community $community = null; private int $communityId; @@ -37,6 +41,8 @@ class Transaction extends Model private ?int $payeeUserId = null; + private ?array $payees = null; + private string $description = ''; private float $sum = 0.0; @@ -93,6 +99,11 @@ class Transaction extends Model $this->payeeUserId = $payeeUserId; } + public function setPayees(array $payees): void + { + $this->payees = $payees; + } + public function setDescription(string $description): void { $this->description = $description; @@ -163,6 +174,11 @@ class Transaction extends Model return $this->payeeUserId; } + public function getPayees(): ?array + { + return $this->payees; + } + public function getDescription(): string { return $this->description; diff --git a/src/PersistentData/Model/TransactionPayee.php b/src/PersistentData/Model/TransactionPayee.php new file mode 100644 index 0000000..289f3c9 --- /dev/null +++ b/src/PersistentData/Model/TransactionPayee.php @@ -0,0 +1,60 @@ + 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; + } +} diff --git a/src/Repository/CommunityMemberRepository.php b/src/Repository/CommunityMemberRepository.php index d0a1e2f..9757955 100644 --- a/src/Repository/CommunityMemberRepository.php +++ b/src/Repository/CommunityMemberRepository.php @@ -15,12 +15,16 @@ class CommunityMemberRepository public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator { - $select = new Select(\Container::$dbConnection); - $select->where('community_id', '=', $community->getId()); + $select = $this->selectAllByCommunity($community); 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 { $select = new Select(\Container::$dbConnection); @@ -37,4 +41,11 @@ class CommunityMemberRepository 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; + } } diff --git a/src/Repository/TransactionPayeeRepository.php b/src/Repository/TransactionPayeeRepository.php new file mode 100644 index 0000000..b35320c --- /dev/null +++ b/src/Repository/TransactionPayeeRepository.php @@ -0,0 +1,23 @@ +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); + } +} diff --git a/views/communities/transaction_edit.php b/views/communities/transaction_edit.php index bff0ee5..b146d87 100644 --- a/views/communities/transaction_edit.php +++ b/views/communities/transaction_edit.php @@ -39,13 +39,15 @@ -
Payee
-