First basic code for currency exchange rate routines.

This commit is contained in:
James Cole 2017-04-13 20:47:59 +02:00
parent 8db96025a3
commit 994542c75d
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
11 changed files with 397 additions and 32 deletions

View File

@ -0,0 +1,60 @@
<?php
/**
* ExchangeController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Json;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Services\Currency\ExchangeRateInterface;
use Illuminate\Http\Request;
use Response;
/**
* Class ExchangeController
*
* @package FireflyIII\Http\Controllers\Json
*/
class ExchangeController extends Controller
{
/**
* @param Request $request
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*/
public function getRate(Request $request, TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date)
{
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date);
$amount = null;
if (is_null($rate->id)) {
$preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service'));
$class = config('firefly.currency_exchange_services.' . $preferred);
/** @var ExchangeRateInterface $object */
$object = app($class);
$object->setUser(auth()->user());
$rate = $object->getRate($fromCurrency, $toCurrency, $date);
}
$return = $rate->toArray();
$return['amount'] = null;
if (!is_null($request->get('amount'))) {
// assume amount is in "from" currency:
$return['amount'] = bcmul($request->get('amount'), $rate->rate);
}
return Response::json($return);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* CurrencyExchange.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class CurrencyExchange
*
* @package FireflyIII\Models
*/
class CurrencyExchangeRate extends Model
{
protected $dates = ['created_at', 'updated_at', 'date'];
/**
* @return BelongsTo
*/
public function fromCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class, 'from_currency_id');
}
/**
* @return BelongsTo
*/
public function toCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class, 'to_currency_id');
}
/**
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@ -14,6 +14,8 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Currency;
use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
@ -178,6 +180,28 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return $preferred;
}
/**
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*
* @return CurrencyExchangeRate
*/
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate
{
$rate = $this->user->currencyExchangeRates()
->where('from_currency_id', $fromCurrency->id)
->where('to_currency_id', $toCurrency->id)
->where('date', $date->format('Y-m-d'))->first();
if (!is_null($rate)) {
return $rate;
}
return new CurrencyExchangeRate;
}
/**
* @param User $user
*/

View File

@ -14,6 +14,8 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Currency;
use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
@ -95,6 +97,15 @@ interface CurrencyRepositoryInterface
*/
public function getCurrencyByPreference(Preference $preference): TransactionCurrency;
/**
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*
* @return CurrencyExchangeRate
*/
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate;
/**
* @param User $user
*/

View File

@ -0,0 +1,38 @@
<?php
/**
* ExchangeRateInterface.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Currency;
use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
interface ExchangeRateInterface
{
/**
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*
* @return CurrencyExchangeRate
*/
public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate;
/**
* @param User $user
*
* @return mixed
*/
public function setUser(User $user);
}

View File

@ -0,0 +1,69 @@
<?php
/**
* FixerIO.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Currency;
use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
use Log;
use Requests;
/**
* Class FixerIO
*
* @package FireflyIII\Services\Currency
*/
class FixerIO implements ExchangeRateInterface
{
/** @var User */
protected $user;
public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate
{
$uri = sprintf('https://api.fixer.io/%s?base=%s&symbols=%s', $date->format('Y-m-d'), $fromCurrency->code, $toCurrency->code);
$result = Requests::get($uri);
$rate = 1.0;
$content = null;
if ($result->status_code !== 200) {
Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $result->status_code, $result->body));
}
// get rate from body:
if ($result->status_code === 200) {
$content = json_decode($result->body, true);
}
if (!is_null($content)) {
$code = $toCurrency->code;
$rate = isset($content['rates'][$code]) ? $content['rates'][$code] : '1';
}
// create new currency exchange rate object:
$exchangeRate = new CurrencyExchangeRate;
$exchangeRate->user()->associate($this->user);
$exchangeRate->fromCurrency()->associate($fromCurrency);
$exchangeRate->toCurrency()->associate($toCurrency);
$exchangeRate->date = $date;
$exchangeRate->rate = $rate;
$exchangeRate->save();
return $exchangeRate;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* CurrencyCode.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Binder;
use FireflyIII\Models\TransactionCurrency;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class CurrencyCode
*
* @package FireflyIII\Support\Binder
*/
class CurrencyCode implements BinderInterface
{
/**
* @param $value
* @param $route
*
* @return mixed
*/
public static function routeBinder($value, $route)
{
$currency = TransactionCurrency::where('code', $value)->first();
if (!is_null($currency)) {
return $currency;
}
throw new NotFoundHttpException;
}
}

View File

@ -15,6 +15,7 @@ declare(strict_types=1);
namespace FireflyIII;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Models\CurrencyExchangeRate;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
@ -119,6 +120,14 @@ class User extends Authenticatable
return $this->hasMany('FireflyIII\Models\Category');
}
/**
* @return HasMany
*/
public function currencyExchangeRates(): HasMany
{
return $this->hasMany(CurrencyExchangeRate::class);
}
/**
* @return HasMany
*/

View File

@ -18,29 +18,29 @@ declare(strict_types=1);
*/
return [
'configuration' => [
'configuration' => [
'single_user_mode' => true,
'is_demo_site' => false,
],
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
'version' => '4.3.8',
'maxUploadSize' => 5242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
'list_length' => 10,
'export_formats' => [
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
'version' => '4.3.8',
'maxUploadSize' => 5242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
'list_length' => 10,
'export_formats' => [
'csv' => 'FireflyIII\Export\Exporter\CsvExporter',
],
'import_formats' => [
'import_formats' => [
'csv' => 'FireflyIII\Import\Importer\CsvImporter',
],
'default_export_format' => 'csv',
'default_import_format' => 'csv',
'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset',],
'ccTypes' => [
'default_export_format' => 'csv',
'default_import_format' => 'csv',
'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset',],
'ccTypes' => [
'monthlyFull' => 'Full payment every month',
],
'range_to_repeat_freq' => [
'range_to_repeat_freq' => [
'1D' => 'weekly',
'1W' => 'weekly',
'1M' => 'monthly',
@ -49,14 +49,14 @@ return [
'1Y' => 'yearly',
'custom' => 'custom',
],
'subTitlesByIdentifier' =>
'subTitlesByIdentifier' =>
[
'asset' => 'Asset accounts',
'expense' => 'Expense accounts',
'revenue' => 'Revenue accounts',
'cash' => 'Cash accounts',
],
'subIconsByIdentifier' =>
'subIconsByIdentifier' =>
[
'asset' => 'fa-money',
'Asset account' => 'fa-money',
@ -70,14 +70,14 @@ return [
'import' => 'fa-download',
'Import account' => 'fa-download',
],
'accountTypesByIdentifier' =>
'accountTypesByIdentifier' =>
[
'asset' => ['Default account', 'Asset account'],
'expense' => ['Expense account', 'Beneficiary account'],
'revenue' => ['Revenue account'],
'import' => ['Import account'],
],
'accountTypeByIdentifier' =>
'accountTypeByIdentifier' =>
[
'asset' => 'Asset account',
'expense' => 'Expense account',
@ -86,7 +86,7 @@ return [
'initial' => 'Initial balance account',
'import' => 'Import account',
],
'shortNamesByFullName' =>
'shortNamesByFullName' =>
[
'Default account' => 'asset',
'Asset account' => 'asset',
@ -96,7 +96,7 @@ return [
'Revenue account' => 'revenue',
'Cash account' => 'cash',
],
'languages' => [
'languages' => [
'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German', 'complete' => true],
'en_US' => ['name_locale' => 'English', 'name_english' => 'English', 'complete' => true],
'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish', 'complete' => false],
@ -109,7 +109,7 @@ return [
'zh-HK' => ['name_locale' => '繁體中文(香港)', 'name_english' => 'Chinese Traditional, Hong Kong', 'complete' => false],
'zh-TW' => ['name_locale' => '正體中文', 'name_english' => 'Chinese Traditional', 'complete' => false],
],
'transactionTypesByWhat' => [
'transactionTypesByWhat' => [
'expenses' => ['Withdrawal'],
'withdrawal' => ['Withdrawal'],
'revenue' => ['Deposit'],
@ -117,7 +117,7 @@ return [
'transfer' => ['Transfer'],
'transfers' => ['Transfer'],
],
'transactionIconsByWhat' => [
'transactionIconsByWhat' => [
'expenses' => 'fa-long-arrow-left',
'withdrawal' => 'fa-long-arrow-left',
'revenue' => 'fa-long-arrow-right',
@ -126,7 +126,7 @@ return [
'transfers' => 'fa-exchange',
],
'bindables' => [
'bindables' => [
'account' => 'FireflyIII\Models\Account',
'attachment' => 'FireflyIII\Models\Attachment',
'bill' => 'FireflyIII\Models\Bill',
@ -134,6 +134,8 @@ return [
'category' => 'FireflyIII\Models\Category',
'transaction_type' => 'FireflyIII\Models\TransactionType',
'currency' => 'FireflyIII\Models\TransactionCurrency',
'fromCurrencyCode' => 'FireflyIII\Support\Binder\CurrencyCode',
'toCurrencyCode' => 'FireflyIII\Support\Binder\CurrencyCode',
'limitrepetition' => 'FireflyIII\Models\LimitRepetition',
'budgetlimit' => 'FireflyIII\Models\BudgetLimit',
'piggyBank' => 'FireflyIII\Models\PiggyBank',
@ -152,7 +154,7 @@ return [
'start_date' => 'FireflyIII\Support\Binder\Date',
'end_date' => 'FireflyIII\Support\Binder\Date',
],
'rule-triggers' => [
'rule-triggers' => [
'user_action' => 'FireflyIII\Rules\Triggers\UserAction',
'from_account_starts' => 'FireflyIII\Rules\Triggers\FromAccountStarts',
'from_account_ends' => 'FireflyIII\Rules\Triggers\FromAccountEnds',
@ -174,7 +176,7 @@ return [
'budget_is' => 'FireflyIII\Rules\Triggers\BudgetIs',
'tag_is' => 'FireflyIII\Rules\Triggers\TagIs',
],
'rule-actions' => [
'rule-actions' => [
'set_category' => 'FireflyIII\Rules\Actions\SetCategory',
'clear_category' => 'FireflyIII\Rules\Actions\ClearCategory',
'set_budget' => 'FireflyIII\Rules\Actions\SetBudget',
@ -189,7 +191,7 @@ return [
'set_source_account' => 'FireflyIII\Rules\Actions\SetSourceAccount',
'set_destination_account' => 'FireflyIII\Rules\Actions\SetDestinationAccount',
],
'rule-actions-text' => [
'rule-actions-text' => [
'set_category',
'set_budget',
'add_tag',
@ -198,13 +200,19 @@ return [
'append_description',
'prepend_description',
],
'test-triggers' => [
'test-triggers' => [
'limit' => 10,
'range' => 200,
],
'default_currency' => 'EUR',
'default_language' => 'en_US',
'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category',
'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'],
'default_currency' => 'EUR',
'default_language' => 'en_US',
'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category',
'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'],
// tag notes has_attachments
'currency_exchange_services' => [
'fixerio' => 'FireflyIII\Services\Currency\FixerIO',
'openexchangerates' => 'FireflyIII\Services\Currency\OpenExchangeRates',
],
'preferred_exchange_service' => 'fixerio',
];

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
/**
* Class ChangesForV440
*/
class ChangesForV440 extends Migration
{
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (Schema::hasTable('currency_exchange_rates')) {
Schema::drop('currency_exchange_rates');
}
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!Schema::hasTable('currency_exchange_rates')) {
Schema::create(
'currency_exchange_rates', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->softDeletes();
$table->integer('user_id', false, true);
$table->integer('from_currency_id', false, true);
$table->integer('to_currency_id', false, true);
$table->date('date');
$table->decimal('rate', 22, 12);
$table->decimal('user_rate', 22, 12)->nullable();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('from_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
$table->foreign('to_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
}
);
}
}
}

View File

@ -414,7 +414,7 @@ Route::group(
);
/**
* JSON Controller
* JSON Controller(s)
*/
Route::group(
['middleware' => 'user-full-auth', 'prefix' => 'json', 'as' => 'json.'], function () {
@ -437,6 +437,9 @@ Route::group(
Route::post('end-tour', ['uses' => 'JsonController@endTour', 'as' => 'end-tour']);
// currency conversion:
Route::get('rate/{fromCurrencyCode}/{toCurrencyCode}/{date}', ['uses' => 'Json\ExchangeController@getRate', 'as' => 'rate']);
}
);