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

Reviewed-on: #32
This commit is contained in:
Bence Pőcze 2023-04-25 19:32:11 +02:00 committed by Gitea
commit a021aff92c
Signed by: Gitea
GPG Key ID: 7B89B83EED9AD2C6
11 changed files with 278 additions and 14 deletions

View File

@ -0,0 +1,10 @@
CREATE TABLE `currency_exchange_rates` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`currency_id` int(10) unsigned NOT NULL,
`exchange_rate` decimal(19,9) unsigned NOT NULL,
`valid_from` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `currency_id` (`currency_id`),
INDEX `valid_from` (`valid_from`),
CONSTRAINT `currency_exchange_rates_currency_id` FOREIGN KEY (`currency_id`) REFERENCES `currencies` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@ -113,7 +113,7 @@ p.small, span.small {
} }
.marginLeft { .marginLeft {
margin-left: 10px; margin-left: 5px;
} }
.marginBottom { .marginBottom {
@ -121,7 +121,7 @@ p.small, span.small {
} }
.marginRight { .marginRight {
margin-right: 10px; margin-right: 5px;
} }
.center { .center {
@ -455,7 +455,7 @@ table th {
} }
table th, table td { table th, table td {
padding: 2px 0; padding: 3px 0;
vertical-align: middle; vertical-align: middle;
} }
@ -485,6 +485,7 @@ table th:not(:last-child), table td:not(:last-child) {
margin-top: 4px; margin-top: 4px;
} }
button, a.button { button, a.button {
margin: 3px 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
} }

View File

@ -4,9 +4,11 @@ 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\Currency;
use RVR\PersistentData\Model\CurrencyExchangeRate;
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\CurrencyExchangeRateRepository;
use RVR\Repository\CurrencyRepository; use RVR\Repository\CurrencyRepository;
use RVR\Repository\UserRepository; use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired; use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
@ -24,12 +26,15 @@ class CommunityController implements IAuthenticationRequired
private CurrencyRepository $currencyRepository; private CurrencyRepository $currencyRepository;
private CurrencyExchangeRateRepository $currencyExchangeRatesRepository;
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(); $this->currencyRepository = new CurrencyRepository();
$this->currencyExchangeRatesRepository = new CurrencyExchangeRateRepository();
} }
public function isAuthenticationRequired(): bool public function isAuthenticationRequired(): bool
@ -43,15 +48,10 @@ 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),
'currencyCodes' => $currencyCodes, 'currencies' => $this->getCurrencies($community),
'upcomingEvents' => [], 'upcomingEvents' => [],
'editPermission' => $ownCommunityMember->getOwner() 'editPermission' => $ownCommunityMember->getOwner()
]); ]);
@ -207,6 +207,91 @@ class CommunityController implements IAuthenticationRequired
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
} }
public function getCurrencyExchangeRates(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$currencyExchangeRates = $this->currencyExchangeRatesRepository->getAllByCurrency($currency);
return new HtmlContent('communities/currency_exchange_rates', [
'community' => $community,
'currency' => $currency,
'currencyExchangeRates' => $currencyExchangeRates,
'editPermission' => $ownCommunityMember->getOwner()
]);
}
public function newCurrencyExchangeRate(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$exchangeRate = (float)\Container::$request->post('exchange_rate');
if ($exchangeRate < 0) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$currencyExchangeRate = new CurrencyExchangeRate();
$currencyExchangeRate->setCurrency($currency);
$currencyExchangeRate->setExchangeRate($exchangeRate);
$currencyExchangeRate->setValidFromDate(new DateTime(\Container::$request->post('valid_from')));
\Container::$persistentDataManager->saveToDb($currencyExchangeRate);
return new JsonContent(['success' => true]);
}
public function editCurrencyExchangeRate(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$currencyExchangeRate = $this->currencyExchangeRatesRepository->getById(\Container::$request->query('currency_exchange_rate_id'));
$currencyExchangeRate->setCurrency($currency);
$currencyExchangeRate->setExchangeRate((float)\Container::$request->post('exchange_rate'));
$currencyExchangeRate->setValidFromDate(new DateTime(\Container::$request->post('valid_from')));
\Container::$persistentDataManager->saveToDb($currencyExchangeRate);
return new JsonContent(['success' => true]);
}
public function deleteCurrencyExchangeRate(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$currencyExchangeRate = $this->currencyExchangeRatesRepository->getById(\Container::$request->query('currency_exchange_rate_id'));
\Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
return new JsonContent(['success' => true]);
}
public function saveCommunity(): ?IContent public function saveCommunity(): ?IContent
{ {
$communityId = \Container::$request->query('communityId'); $communityId = \Container::$request->query('communityId');

View File

@ -0,0 +1,71 @@
<?php namespace RVR\PersistentData\Model;
use DateTime;
use SokoWeb\PersistentData\Model\Model;
class CurrencyExchangeRate extends Model
{
protected static string $table = 'currency_exchange_rates';
protected static array $fields = ['currency_id', 'exchange_rate', 'valid_from'];
protected static array $relations = ['currency' => Currency::class];
private ?Currency $currency = null;
private ?int $currencyId = null;
private float $exchangeRate = 0.0;
private DateTime $validFrom;
public function setCurrency(Currency $currency): void
{
$this->currency = $currency;
}
public function setCurrencyId(int $currencyId): void
{
$this->currencyId = $currencyId;
}
public function setExchangeRate(float $exchangeRate): void
{
$this->exchangeRate = $exchangeRate;
}
public function setValidFromDate(DateTime $validFrom): void
{
$this->validFrom = $validFrom;
}
public function setValidFrom(string $validFrom): void
{
$this->validFrom = new DateTime($validFrom);
}
public function getCurrency(): ?Currency
{
return $this->currency;
}
public function getCurrencyId(): ?int
{
return $this->currencyId;
}
public function getExchangeRate(): float
{
return $this->exchangeRate;
}
public function getValidFromDate(): DateTime
{
return $this->validFrom;
}
public function getValidFrom(): string
{
return $this->validFrom->format('Y-m-d H:i:s');
}
}

View File

@ -0,0 +1,24 @@
<?php namespace RVR\Repository;
use Generator;
use Container;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\CurrencyExchangeRate;
use SokoWeb\Database\Query\Select;
class CurrencyExchangeRateRepository
{
public function getById(int $id): ?CurrencyExchangeRate
{
return \Container::$persistentDataManager->selectFromDbById($id, CurrencyExchangeRate::class);
}
public function getAllByCurrency(Currency $currency): Generator
{
$select = new Select(Container::$dbConnection);
$select->where('currency_id', '=', $currency->getId());
$select->orderBy('valid_from');
yield from Container::$persistentDataManager->selectMultipleFromDb($select, CurrencyExchangeRate::class);
}
}

View File

@ -17,8 +17,12 @@
<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>
<?php if (count($currencyCodes) > 0): ?> <?php if (count($currencies) > 0): ?>
<p>Further currencies: <b><?= implode(', ', $currencyCodes) ?></b></p> <p>Further currencies: <b>
<?php foreach ($currencies as $currency): ?>
<a href="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>"><?= $currency->getCode() ?></a>
<?php endforeach; ?>
</b></p>
<?php endif; ?> <?php endif; ?>
<?php if ($editPermission): ?> <?php if ($editPermission): ?>
<hr> <hr>

View File

@ -1,7 +1,7 @@
@extends(templates/layout_normal) @extends(templates/layout_normal)
@section(main) @section(main)
<h2><?= $community->getName() ?> - Edit currencies</h2> <h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit currencies</h2>
<div class="box"> <div class="box">
<table class="fullWidth"> <table class="fullWidth">
<thead> <thead>

View File

@ -1,7 +1,13 @@
@extends(templates/layout_normal) @extends(templates/layout_normal)
@section(main) @section(main)
<h2><?= isset($community) ? $community->getName() . ' - Edit' : 'New community' ?></h2> <h2>
<?php if (isset($community)): ?>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit
<?php else: ?>
New community
<?php endif; ?>
</h2>
<div class="box"> <div class="box">
<?php <?php
$formAction = isset($community) ? $formAction = isset($community) ?

View File

@ -5,7 +5,7 @@
@extends(templates/layout_normal) @extends(templates/layout_normal)
@section(main) @section(main)
<h2><?= $community->getName() ?> - Edit members</h2> <h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit members</h2>
<div class="box"> <div class="box">
<table class="fullWidth"> <table class="fullWidth">
<thead> <thead>

View File

@ -0,0 +1,57 @@
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Exchange rates for <?= $currency->getCode() ?></h2>
<div class="box">
<table class="fullWidth">
<thead>
<tr>
<?php if ($editPermission): ?>
<th style="width: 25%; text-align: left;">Exchange rate</th>
<th style="width: 50%; text-align: left;">Valid from</th>
<th style="width: 25%;"></th>
<?php else: ?>
<th style="width: 35%; text-align: left;">Exchange rate</th>
<th style="width: 65%; text-align: left;">Valid from</th>
<?php endif; ?>
</tr>
</thead>
<?php foreach ($currencyExchangeRates as $currencyExchangeRate): ?>
<tr>
<?php if ($editPermission): ?>
<td>
<form id="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-edit')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<form id="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-delete')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<input type="number" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="exchange_rate" value="<?= $currencyExchangeRate->getExchangeRate() ?>" min="0" step="0.000000001" required>
</td>
<td>
<input type="datetime-local" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="valid_from" value="<?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d\TH:i') ?>" required>
</td>
<td style="text-align: right;">
<button type="submit" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" class="small red">Delete</button>
</td>
<?php else: ?>
<td><?= $currencyExchangeRate->getExchangeRate() ?></td>
<td><?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d H:i') ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php if ($editPermission): ?>
<tr>
<td>
<form id="newExchangeRate" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-new')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<input type="number" form="newExchangeRate" class="text fullWidth" name="exchange_rate" placeholder="Exchange rate" min="0" step="0.000000001" required>
</td>
<td>
<input type="datetime-local" form="newExchangeRate" class="text fullWidth" name="valid_from" placeholder="Valid from" required>
</td>
<td style="text-align: right;">
<button type="submit" form="newExchangeRate" name="submit" class="small" disabled>Add</button>
</td>
</tr>
<?php endif; ?>
</table>
<p class="formError justify marginTop"></p>
</div>
@endsection

View File

@ -70,6 +70,12 @@ Container::$routeCollection->group('communities', function (RouteCollection $rou
$routeCollection->post('community-currencies-new', 'newCurrency', [CommunityController::class, 'newCurrency']); $routeCollection->post('community-currencies-new', 'newCurrency', [CommunityController::class, 'newCurrency']);
$routeCollection->post('community-currencies-edit', 'editCurrency', [CommunityController::class, 'editCurrency']); $routeCollection->post('community-currencies-edit', 'editCurrency', [CommunityController::class, 'editCurrency']);
$routeCollection->post('community-currencies-delete', 'deleteCurrency', [CommunityController::class, 'deleteCurrency']); $routeCollection->post('community-currencies-delete', 'deleteCurrency', [CommunityController::class, 'deleteCurrency']);
$routeCollection->group('currencyExchangeRates', function (RouteCollection $routeCollection) {
$routeCollection->get('community-currency-exchange-rates', '{code}', [CommunityController::class, 'getCurrencyExchangeRates']);
$routeCollection->post('community-currency-exchange-rates-new', '{code}/new', [CommunityController::class, 'newCurrencyExchangeRate']);
$routeCollection->post('community-currency-exchange-rates-edit', '{code}/edit', [CommunityController::class, 'editCurrencyExchangeRate']);
$routeCollection->post('community-currency-exchange-rates-delete', '{code}/delete', [CommunityController::class, 'deleteCurrencyExchangeRate']);
});
}); });
}); });