mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Working on OAuth (#3686)
* Update Exchange rate Data once a day * Tests for currency conversions * Fixes for tests * Fix for adding blank product keys * Class for logging emails sent * fixes for oauth
This commit is contained in:
parent
1f846e3136
commit
6d0d6c10cd
@ -15,6 +15,7 @@ use App\Jobs\Cron\RecurringInvoicesCron;
|
|||||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||||
use App\Jobs\Util\ReminderJob;
|
use App\Jobs\Util\ReminderJob;
|
||||||
use App\Jobs\Util\SendFailedEmails;
|
use App\Jobs\Util\SendFailedEmails;
|
||||||
|
use App\Jobs\Util\UpdateExchangeRates;
|
||||||
use App\Jobs\Util\VersionCheck;
|
use App\Jobs\Util\VersionCheck;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
@ -44,6 +45,8 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->job(new VersionCheck)->daily();
|
$schedule->job(new VersionCheck)->daily();
|
||||||
|
|
||||||
$schedule->job(new ReminderJob)->daily();
|
$schedule->job(new ReminderJob)->daily();
|
||||||
|
|
||||||
|
$schedule->job(new UpdateExchangeRates)->daily();
|
||||||
|
|
||||||
/* Run hosted specific jobs */
|
/* Run hosted specific jobs */
|
||||||
if(Ninja::isHosted()) {
|
if(Ninja::isHosted()) {
|
||||||
|
@ -58,6 +58,10 @@ class UpdateOrCreateProduct implements ShouldQueue
|
|||||||
MultiDB::setDB($this->company->db);
|
MultiDB::setDB($this->company->db);
|
||||||
|
|
||||||
foreach ($this->products as $item) {
|
foreach ($this->products as $item) {
|
||||||
|
|
||||||
|
if(empty($item->product_key))
|
||||||
|
continue;
|
||||||
|
|
||||||
$product = Product::firstOrNew(['product_key' => $item->product_key, 'company_id' => $this->invoice->company->id]);
|
$product = Product::firstOrNew(['product_key' => $item->product_key, 'company_id' => $this->invoice->company->id]);
|
||||||
|
|
||||||
$product->product_key = $item->product_key;
|
$product->product_key = $item->product_key;
|
||||||
|
80
app/Jobs/Util/UpdateExchangeRates.php
Normal file
80
app/Jobs/Util/UpdateExchangeRates.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Jobs\Util;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Currency;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class UpdateExchangeRates implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
|
||||||
|
if(config('ninja.db.multi_db_enabled'))
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach (MultiDB::$dbs as $db) {
|
||||||
|
MultiDB::setDB($db);
|
||||||
|
$this->updateCurrencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->updateCurrencies();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateCurrencies()
|
||||||
|
{
|
||||||
|
|
||||||
|
if(empty(config('ninja.currency_converter_api_key')))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$cc_endpoint = sprintf('https://openexchangerates.org/api/latest.json?app_id=%s', config('ninja.currency_converter_api_key'));
|
||||||
|
|
||||||
|
$client = new \GuzzleHttp\Client();
|
||||||
|
$response = $client->get($cc_endpoint);
|
||||||
|
|
||||||
|
$currency_api = json_decode($response->getBody());
|
||||||
|
|
||||||
|
/* Update all currencies */
|
||||||
|
Currency::all()->each(function ($currency) use($currency_api){
|
||||||
|
|
||||||
|
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
||||||
|
$currency->save();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Rebuild the cache */
|
||||||
|
$currencies = Currency::orderBy('name')->get();
|
||||||
|
|
||||||
|
Cache::forever('currencies', $currencies);
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@
|
|||||||
namespace App\Libraries\Currency\Conversion;
|
namespace App\Libraries\Currency\Conversion;
|
||||||
|
|
||||||
use App\Models\Currency;
|
use App\Models\Currency;
|
||||||
use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate;
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
class CurrencyApi implements CurrencyConversionInterface
|
class CurrencyApi implements CurrencyConversionInterface
|
||||||
@ -24,30 +23,51 @@ class CurrencyApi implements CurrencyConversionInterface
|
|||||||
if(!$date)
|
if(!$date)
|
||||||
$date = Carbon::now();
|
$date = Carbon::now();
|
||||||
|
|
||||||
$from_currency = Currency::find($from_currency_id)->code;
|
$from_currency = Currency::find($from_currency_id);
|
||||||
|
|
||||||
$to_currency = Currency::find($to_currency_id)->code;
|
$to_currency = Currency::find($to_currency_id);
|
||||||
|
|
||||||
$exchangeRates = new ExchangeRate();
|
$usd_amount = $this->convertToUsd($amount, $from_currency);
|
||||||
|
|
||||||
return $exchangeRates->convert($amount, $from_currency, $to_currency, $date);
|
|
||||||
|
|
||||||
|
return $this->convertFromUsdToCurrency($usd_amount, $to_currency);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exchangeRate($from_currency_id, $to_currency_id, $date = null)
|
public function exchangeRate($from_currency_id, $to_currency_id, $date = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
$from_currency = Currency::find($from_currency_id);
|
||||||
|
|
||||||
|
$to_currency = Currency::find($to_currency_id);
|
||||||
|
|
||||||
|
$usd_amount = $this->convertToUsd(1, $from_currency);
|
||||||
|
|
||||||
if(!$date)
|
return $this->convertFromUsdToCurrency($usd_amount, $to_currency);
|
||||||
$date = Carbon::now();
|
|
||||||
|
|
||||||
$from_currency = Currency::find($from_currency_id)->code;
|
|
||||||
|
|
||||||
$to_currency = Currency::find($to_currency_id)->code;
|
|
||||||
|
|
||||||
$exchangeRates = new ExchangeRate();
|
|
||||||
|
|
||||||
return $exchangeRates->exchangeRate($from_currency, $to_currency, $date);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a currency amount to USD
|
||||||
|
*
|
||||||
|
* @param float $amount amount
|
||||||
|
* @param object $currency currency object
|
||||||
|
* @return float USD Amount
|
||||||
|
*/
|
||||||
|
private function convertToUsd($amount, $currency)
|
||||||
|
{
|
||||||
|
return $amount / $currency->exchange_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts USD to any other denomination
|
||||||
|
*
|
||||||
|
* @param float $amount amount
|
||||||
|
* @param object $currency destination currency
|
||||||
|
* @return float the converted amount
|
||||||
|
*/
|
||||||
|
private function convertFromUsdToCurrency($amount, $currency)
|
||||||
|
{
|
||||||
|
return $amount * $currency->exchange_rate;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -12,7 +12,7 @@
|
|||||||
namespace App\Libraries\OAuth;
|
namespace App\Libraries\OAuth;
|
||||||
|
|
||||||
use App\Libraries\MultiDB;
|
use App\Libraries\MultiDB;
|
||||||
use App\Ninja\OAuth\Providers\Google;
|
use App\Libraries\OAuth\Providers\Google;
|
||||||
use Laravel\Socialite\Facades\Socialite;
|
use Laravel\Socialite\Facades\Socialite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,6 +17,8 @@ class Currency extends StaticModel
|
|||||||
{
|
{
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $guarded = ['id'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'exchange_rate' => 'float',
|
'exchange_rate' => 'float',
|
||||||
'swap_currency_symbol' => 'boolean',
|
'swap_currency_symbol' => 'boolean',
|
||||||
|
69
app/Utils/EmailStats.php
Normal file
69
app/Utils/EmailStats.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class EmailStats.
|
||||||
|
*/
|
||||||
|
class EmailStats
|
||||||
|
{
|
||||||
|
const EMAIL = 'email_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the counter for emails sent
|
||||||
|
* for a company
|
||||||
|
* @param string $company_key The company key
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function inc($company_key)
|
||||||
|
{
|
||||||
|
Cache::increment(self::EMAIL . $company_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email sent count
|
||||||
|
*
|
||||||
|
* @param string $company_key The company key
|
||||||
|
* @return int The number email sent so far 'today'
|
||||||
|
*/
|
||||||
|
public static function count($company_key)
|
||||||
|
{
|
||||||
|
return Cache::get(self::EMAIL . $company_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the cache for the emails sent
|
||||||
|
*
|
||||||
|
* @param string $company_key The company key
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function clear($company_key)
|
||||||
|
{
|
||||||
|
Cache::forget(self::EMAIL . $company_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates through a list of companies
|
||||||
|
* and flushes the email sent data
|
||||||
|
*
|
||||||
|
* @param string $companies The company key
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function clearCompanies($companies)
|
||||||
|
{
|
||||||
|
$companies->each(function ($company) {
|
||||||
|
self::clear($company->company_key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,6 @@
|
|||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"asgrim/ofxparser": "^1.2",
|
"asgrim/ofxparser": "^1.2",
|
||||||
"ashallendesign/laravel-exchange-rates": "^2.1",
|
|
||||||
"beganovich/omnipay-checkout": "dev-master",
|
"beganovich/omnipay-checkout": "dev-master",
|
||||||
"cleverit/ubl_invoice": "^1.3",
|
"cleverit/ubl_invoice": "^1.3",
|
||||||
"codedge/laravel-selfupdater": "~3.0",
|
"codedge/laravel-selfupdater": "~3.0",
|
||||||
@ -54,7 +53,6 @@
|
|||||||
"spatie/browsershot": "^3.29",
|
"spatie/browsershot": "^3.29",
|
||||||
"staudenmeir/eloquent-has-many-deep": "^1.11",
|
"staudenmeir/eloquent-has-many-deep": "^1.11",
|
||||||
"stripe/stripe-php": "^7.0",
|
"stripe/stripe-php": "^7.0",
|
||||||
"superbalist/laravel-google-cloud-storage": "^2.2",
|
|
||||||
"turbo124/beacon": "^0",
|
"turbo124/beacon": "^0",
|
||||||
"webpatser/laravel-countries": "dev-master#75992ad",
|
"webpatser/laravel-countries": "dev-master#75992ad",
|
||||||
"yajra/laravel-datatables-oracle": "~9.0"
|
"yajra/laravel-datatables-oracle": "~9.0"
|
||||||
|
@ -24,6 +24,7 @@ return [
|
|||||||
'error_email' => env('ERROR_EMAIL', ''),
|
'error_email' => env('ERROR_EMAIL', ''),
|
||||||
'company_id' => 0,
|
'company_id' => 0,
|
||||||
'hash_salt' => env('HASH_SALT', ''),
|
'hash_salt' => env('HASH_SALT', ''),
|
||||||
|
'currency_converter_api_key' => env('OPENEXCHANGE_APP_ID',''),
|
||||||
|
|
||||||
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'
|
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'
|
||||||
|
|
||||||
|
@ -56,7 +56,6 @@ class PreviewTest extends TestCase
|
|||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
$response = $this->withHeaders([
|
$response = $this->withHeaders([
|
||||||
'X-API-SECRET' => config('ninja.api_secret'),
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
'X-API-TOKEN' => $this->token
|
'X-API-TOKEN' => $this->token
|
||||||
|
126
tests/Feature/UpdateExchangeRatesTest.php
Normal file
126
tests/Feature/UpdateExchangeRatesTest.php
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\DataMapper\DefaultSettings;
|
||||||
|
use App\Jobs\Util\UpdateExchangeRates;
|
||||||
|
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||||
|
use App\Models\Account;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\Currency;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use AshAllenDesign\LaravelExchangeRates\Facades\ExchangeRate;
|
||||||
|
use Faker\Factory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Tests\MockAccountData;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers App\Jobs\Util\UpdateExchangeRates
|
||||||
|
*/
|
||||||
|
class UpdateExchangeRatesTest extends TestCase
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
use DatabaseTransactions;
|
||||||
|
use MockAccountData;
|
||||||
|
|
||||||
|
public function setUp() :void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->makeTestData();
|
||||||
|
|
||||||
|
Session::start();
|
||||||
|
|
||||||
|
$this->faker = \Faker\Factory::create();
|
||||||
|
|
||||||
|
Model::reguard();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testExchangeRate()
|
||||||
|
{
|
||||||
|
|
||||||
|
if(!empty(config('ninja.currency_converter_api_key')))
|
||||||
|
{
|
||||||
|
$cc_endpoint = sprintf('https://openexchangerates.org/api/latest.json?app_id=%s', config('ninja.currency_converter_api_key'));
|
||||||
|
|
||||||
|
$client = new \GuzzleHttp\Client();
|
||||||
|
$response = $client->get($cc_endpoint);
|
||||||
|
|
||||||
|
$currency_api = json_decode($response->getBody());
|
||||||
|
|
||||||
|
UpdateExchangeRates::dispatchNow();
|
||||||
|
|
||||||
|
$currencies = Cache::get('currencies');
|
||||||
|
|
||||||
|
$gbp_currency = $currencies->filter(function ($item) {
|
||||||
|
return $item->id == 2;
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
$this->assertEquals($currency_api->rates->GBP, $gbp_currency->exchange_rate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->markTestSkipped('No API Key set');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExchangeRateConversion()
|
||||||
|
{
|
||||||
|
$usd = Currency::find(1);
|
||||||
|
$gbp = Currency::find(2);
|
||||||
|
|
||||||
|
|
||||||
|
$usd->exchange_rate = 1;
|
||||||
|
$usd->save();
|
||||||
|
|
||||||
|
$gbp->exchange_rate = 0.5;
|
||||||
|
$gbp->save();
|
||||||
|
|
||||||
|
$currency_api = new CurrencyApi();
|
||||||
|
|
||||||
|
$convert_to_gbp = $currency_api->convert(10, 1, 2);
|
||||||
|
|
||||||
|
$this->assertEquals($convert_to_gbp, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSyntheticExchangeRate()
|
||||||
|
{
|
||||||
|
$usd = Currency::find(1);
|
||||||
|
$gbp = Currency::find(2);
|
||||||
|
$aud = Currency::find(12);
|
||||||
|
|
||||||
|
$usd->exchange_rate = 1;
|
||||||
|
$usd->save();
|
||||||
|
|
||||||
|
$gbp->exchange_rate = 0.5;
|
||||||
|
$gbp->save();
|
||||||
|
|
||||||
|
$aud->exchange_rate = 1.5;
|
||||||
|
$aud->save();
|
||||||
|
|
||||||
|
$currency_api = new CurrencyApi();
|
||||||
|
|
||||||
|
$convert_to_aud = $currency_api->convert(10, 1, 12);
|
||||||
|
|
||||||
|
$this->assertEquals($convert_to_aud, 15);
|
||||||
|
|
||||||
|
$synthetic_exchange = $currency_api->exchangeRate($gbp->id, $aud->id);
|
||||||
|
|
||||||
|
$this->assertEquals($synthetic_exchange, 3);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user