Merge pull request 'feature/RVRNEXT-5-handling-currencies' (!31) from feature/RVRNEXT-5-handling-currencies into master
All checks were successful
rvr-nextgen/pipeline/head This commit looks good

Reviewed-on: #31
This commit is contained in:
Bence Pőcze 2023-04-23 20:21:31 +02:00 committed by Gitea
commit ef16e70235
Signed by: Gitea
GPG Key ID: 7B89B83EED9AD2C6
12 changed files with 270 additions and 18 deletions

View File

@ -0,0 +1,10 @@
CREATE TABLE `currencies` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`community_id` int(10) unsigned NOT NULL,
`code` varchar(3) NOT NULL,
`round_digits` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_currency_for_community` (`community_id`, `code`),
KEY `community_id` (`community_id`),
CONSTRAINT `currencies_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@ -459,6 +459,14 @@ table th, table td {
vertical-align: middle; vertical-align: middle;
} }
table th:not(:first-child), table td:not(:first-child) {
padding-left: 3px;
}
table th:not(:last-child), table td:not(:last-child) {
padding-right: 3px;
}
.choices__inner { .choices__inner {
box-sizing: border-box; box-sizing: border-box;
} }

View File

@ -49,7 +49,7 @@ var RVR = {
document.getElementById('loading').style.visibility = 'visible'; document.getElementById('loading').style.visibility = 'visible';
var formData = new FormData(form); var formData = new FormData(form);
var formError = form.getElementsByClassName('formError')[0]; var formError = document.getElementsByClassName('formError')[0];
var pageLeaveOnSuccess = form.dataset.redirectOnSuccess || form.dataset.reloadOnSuccess; var pageLeaveOnSuccess = form.dataset.redirectOnSuccess || form.dataset.reloadOnSuccess;
RVR.httpRequest('POST', form.action, function () { RVR.httpRequest('POST', form.action, function () {

View File

@ -7,7 +7,7 @@ if [[ "${BRANCH_NAME}" =~ $BRANCH_PATTERN ]]; then
TICKET_ID=$(echo $BRANCH_NAME | sed -E "s@$BRANCH_PATTERN@\\2@") TICKET_ID=$(echo $BRANCH_NAME | sed -E "s@$BRANCH_PATTERN@\\2@")
COMMIT_MESSAGE=$(head -n 1 $1) COMMIT_MESSAGE=$(head -n 1 $1)
COMMIT_MESSAGE_REGEX="^$TICKET_ID .*" COMMIT_MESSAGE_REGEX="^(fixup! )?$TICKET_ID .*"
if [[ ! "${COMMIT_MESSAGE}" =~ $COMMIT_MESSAGE_REGEX ]]; then if [[ ! "${COMMIT_MESSAGE}" =~ $COMMIT_MESSAGE_REGEX ]]; then
sed -i.bak -e "1s/^/$TICKET_ID /" $1 sed -i.bak -e "1s/^/$TICKET_ID /" $1

View File

@ -3,9 +3,11 @@
use DateTime; use DateTime;
use RVR\PersistentData\Model\Community; use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember; use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\User; use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityRepository; use RVR\Repository\CommunityRepository;
use RVR\Repository\CommunityMemberRepository; use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CurrencyRepository;
use RVR\Repository\UserRepository; use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent; use SokoWeb\Interfaces\Response\IContent;
@ -20,11 +22,14 @@ class CommunityController implements IAuthenticationRequired
private CommunityMemberRepository $communityMemberRepository; private CommunityMemberRepository $communityMemberRepository;
private CurrencyRepository $currencyRepository;
public function __construct() public function __construct()
{ {
$this->userRepository = new UserRepository(); $this->userRepository = new UserRepository();
$this->communityRepository = new CommunityRepository(); $this->communityRepository = new CommunityRepository();
$this->communityMemberRepository = new CommunityMemberRepository(); $this->communityMemberRepository = new CommunityMemberRepository();
$this->currencyRepository = new CurrencyRepository();
} }
public function isAuthenticationRequired(): bool public function isAuthenticationRequired(): bool
@ -38,10 +43,15 @@ class CommunityController implements IAuthenticationRequired
return null; return null;
} }
$currencyCodes = [];
foreach ($this->getCurrencies($community) as $currency) {
$currencyCodes[] = $currency->getCode();
}
return new HtmlContent('communities/community', [ return new HtmlContent('communities/community', [
'community' => $community, 'community' => $community,
'members' => $this->getMembers($community), 'members' => $this->getMembers($community),
'currencyNames' => [], 'currencyCodes' => $currencyCodes,
'upcomingEvents' => [], 'upcomingEvents' => [],
'editPermission' => $ownCommunityMember->getOwner() 'editPermission' => $ownCommunityMember->getOwner()
]); ]);
@ -75,16 +85,6 @@ class CommunityController implements IAuthenticationRequired
]); ]);
} }
private function getMembers(Community $community): array
{
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true));
usort($members, function($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
});
return $members;
}
public function newMember(): ?IContent public function newMember(): ?IContent
{ {
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) { if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
@ -139,6 +139,74 @@ class CommunityController implements IAuthenticationRequired
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
} }
public function getCurrenciesEdit(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
return new HtmlContent('communities/community_currencies', [
'community' => $community,
'currencies' => $this->getCurrencies($community)
]);
}
public function newCurrency(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$code = \Container::$request->post('code');
$roundDigits = (int)\Container::$request->post('round_digits');
if (strlen($code) === 0 || strlen($code) > 3 || $roundDigits < 0 || $roundDigits > 9) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$existingCurrency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, $code);
if ($existingCurrency !== null) {
return new JsonContent([
'error' => ['errorText' => 'A currency with the same code exists for this community.']
]);
}
$currency = new Currency();
$currency->setCommunity($community);
$currency->setCode($code);
$currency->setRoundDigits($roundDigits);
\Container::$persistentDataManager->saveToDb($currency);
return new JsonContent(['success' => true]);
}
public function editCurrency(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getById(\Container::$request->query('currency_id'));
$currency->setCode(\Container::$request->post('code'));
$currency->setRoundDigits((int)\Container::$request->post('round_digits'));
\Container::$persistentDataManager->saveToDb($currency);
return new JsonContent(['success' => true]);
}
public function deleteCurrency(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getById(\Container::$request->query('currency_id'));
\Container::$persistentDataManager->deleteFromDb($currency);
return new JsonContent(['success' => true]);
}
public function saveCommunity(): ?IContent public function saveCommunity(): ?IContent
{ {
$communityId = \Container::$request->query('communityId'); $communityId = \Container::$request->query('communityId');
@ -183,6 +251,24 @@ class CommunityController implements IAuthenticationRequired
]); ]);
} }
private function getMembers(Community $community): array
{
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true));
usort($members, function($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
});
return $members;
}
private function getCurrencies(Community $community): array
{
$currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
usort($currencies, function($a, $b) {
return strnatcmp($a->getCode(), $b->getCode());
});
return $currencies;
}
private function checkPermission( private function checkPermission(
int $communityId, int $communityId,
bool $needToBeOwner, bool $needToBeOwner,

View File

@ -0,0 +1,60 @@
<?php namespace RVR\PersistentData\Model;
use SokoWeb\PersistentData\Model\Model;
class Currency extends Model
{
protected static string $table = 'currencies';
protected static array $fields = ['community_id', 'code', 'round_digits'];
protected static array $relations = ['community' => Community::class];
private ?Community $community = null;
private ?int $communityId = null;
private string $code = '';
private int $roundDigits = 0;
public function setCommunity(Community $community): void
{
$this->community = $community;
}
public function setCommunityId(int $communityId): void
{
$this->communityId = $communityId;
}
public function setCode(string $code): void
{
$this->code = $code;
}
public function setRoundDigits(int $roundDigits): void
{
$this->roundDigits = $roundDigits;
}
public function getCommunity(): ?Community
{
return $this->community;
}
public function getCommunityId(): ?int
{
return $this->communityId;
}
public function getCode(): string
{
return $this->code;
}
public function getRoundDigits(): int
{
return $this->roundDigits;
}
}

View File

@ -0,0 +1,32 @@
<?php namespace RVR\Repository;
use Generator;
use Container;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Currency;
use SokoWeb\Database\Query\Select;
class CurrencyRepository
{
public function getById(int $id): ?Currency
{
return \Container::$persistentDataManager->selectFromDbById($id, Currency::class);
}
public function getAllByCommunity(Community $community, bool $useRelations = false): Generator
{
$select = new Select(Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Currency::class, $useRelations);
}
public function getByCommunityAndCurrencyCode(Community $community, string $code): ?Currency
{
$select = new Select(Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
$select->where('code', '=', $code);
return Container::$persistentDataManager->selectFromDb($select, Currency::class);
}
}

View File

@ -17,7 +17,13 @@
<div> <div>
<h3 class="marginBottom">Currencies</h3> <h3 class="marginBottom">Currencies</h3>
<p>Main currency: <b><?= $community->getCurrency() ?></b></p> <p>Main currency: <b><?= $community->getCurrency() ?></b></p>
<p>Further currencies: <b><?= implode(', ', $currencyNames) ?></b></p> <?php if (count($currencyCodes) > 0): ?>
<p>Further currencies: <b><?= implode(', ', $currencyCodes) ?></b></p>
<?php endif; ?>
<?php if ($editPermission): ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-currencies')->generateLink(['communityId' => $community->getId()]) ?>">Edit currencies</a></p>
<?php endif; ?>
</div> </div>
<div> <div>
<h3 class="marginBottom">Upcoming events</h3> <h3 class="marginBottom">Upcoming events</h3>

View File

@ -0,0 +1,45 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= $community->getName() ?> - Edit currencies</h2>
<div class="box">
<table class="fullWidth">
<thead>
<tr>
<th style="width: calc(100% / 3); text-align: left;">Currency</th>
<th style="width: calc(100% / 3); text-align: left;">Round digits</th>
<th style="width: calc(100% / 3);"></th>
</tr>
</thead>
<?php foreach ($currencies as $currency): ?>
<tr>
<td>
<form id="editCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currencies-edit')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<form id="deleteCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currencies-delete')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<input type="text" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="code" value="<?= $currency->getCode() ?>" maxlength="3" required>
</td>
<td>
<input type="number" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="round_digits" value="<?= $currency->getRoundDigits() ?>" min="0" max="9" required>
</td>
<td style="text-align: right;">
<button type="submit" form="editCurrency_<?= $currency->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currency->getId() ?>" class="small red">Delete</button>
</td>
</tr>
<?php endforeach; ?>
<tr>
<td>
<form id="newCurrency" action="<?= Container::$routeCollection->getRoute('community-currencies-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<input type="text" form="newCurrency" class="text fullWidth" name="code" placeholder="Currency" maxlength="3" required>
</td>
<td>
<input type="number" form="newCurrency" class="text fullWidth" name="round_digits" placeholder="Round digits" min="0" max="9" required>
</td>
<td style="text-align: right;">
<button type="submit" form="newCurrency" name="submit" class="small" disabled>Add</button>
</td>
</tr>
</table>
<p class="formError justify marginTop"></p>
</div>
@endsection

View File

@ -11,7 +11,7 @@
<form id="communityForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true"> <form id="communityForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
<input type="text" class="text big fullWidth" name="name" placeholder="Name" value="<?= isset($community) ? $community->getName() : '' ?>" required> <input type="text" class="text big fullWidth" name="name" placeholder="Name" value="<?= isset($community) ? $community->getName() : '' ?>" required>
<input type="text" class="text big fullWidth marginTop" name="currency" value="<?= isset($community) ? $community->getCurrency() : '' ?>" placeholder="Default currency" maxlength="3" required> <input type="text" class="text big fullWidth marginTop" name="currency" value="<?= isset($community) ? $community->getCurrency() : '' ?>" placeholder="Default currency" maxlength="3" required>
<p id="accountFormError" class="formError justify marginTop"></p> <p id="communityFormError" class="formError justify marginTop"></p>
<div class="right marginTop"> <div class="right marginTop">
<button type="submit" name="submit"><?= isset($community) ? 'Save' : 'Create' ?></button> <button type="submit" name="submit"><?= isset($community) ? 'Save' : 'Create' ?></button>
</div> </div>

View File

@ -29,24 +29,25 @@
<td style="text-align: right;"> <td style="text-align: right;">
<?php if ($editable): ?> <?php if ($editable): ?>
<button type="submit" form="editMember_<?= $member->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!-- <button type="submit" form="editMember_<?= $member->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteMember_<?= $member->getId() ?>" class="small red delete_member" data-action="<?= Container::$routeCollection->getRoute('community-members-delete')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>">Delete</button> --><button type="submit" form="deleteMember_<?= $member->getId() ?>" class="small red">Delete</button>
<?php endif; ?> <?php endif; ?>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<tr> <tr>
<td> <td>
<form id="newMember" action="<?= Container::$routeCollection->getRoute('community-members-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form> <form id="newMember" action="<?= Container::$routeCollection->getRoute('community-members-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form>
<select type="text" form="newMember" name="user_id"> <select type="text" form="newMember" name="user_id">
</td> </td>
<td style="text-align: center;"> <td style="text-align: center;">
<input type="checkbox" form="newMember" name="owner" /> <input type="checkbox" form="newMember" name="owner" />
</td> </td>
<td style="text-align: right;"> <td style="text-align: right;">
<button type="submit" form="newMember" name="submit" class="small" disabled>Add</button> <button type="submit" form="newMember" name="submit" class="small" disabled>Add</button>
</td> </td>
</tr> </tr>
</table> </table>
<p class="formError justify marginTop"></p>
</div> </div>
@endsection @endsection

View File

@ -66,6 +66,10 @@ Container::$routeCollection->group('communities', function (RouteCollection $rou
$routeCollection->post('community-members-new', 'newMember', [CommunityController::class, 'newMember']); $routeCollection->post('community-members-new', 'newMember', [CommunityController::class, 'newMember']);
$routeCollection->post('community-members-edit', 'editMember', [CommunityController::class, 'editMember']); $routeCollection->post('community-members-edit', 'editMember', [CommunityController::class, 'editMember']);
$routeCollection->post('community-members-delete', 'deleteMember', [CommunityController::class, 'deleteMember']); $routeCollection->post('community-members-delete', 'deleteMember', [CommunityController::class, 'deleteMember']);
$routeCollection->get('community-currencies', 'currencies', [CommunityController::class, 'getCurrenciesEdit']);
$routeCollection->post('community-currencies-new', 'newCurrency', [CommunityController::class, 'newCurrency']);
$routeCollection->post('community-currencies-edit', 'editCurrency', [CommunityController::class, 'editCurrency']);
$routeCollection->post('community-currencies-delete', 'deleteCurrency', [CommunityController::class, 'deleteCurrency']);
}); });
}); });