diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index b2d32a506824..b1cc83ebc56b 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,6 +15,7 @@ use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Util\ReminderJob; use App\Jobs\Util\SendFailedEmails; +use App\Jobs\Util\UpdateExchangeRates; use App\Jobs\Util\VersionCheck; use App\Utils\Ninja; use Illuminate\Console\Scheduling\Schedule; @@ -44,6 +45,8 @@ class Kernel extends ConsoleKernel $schedule->job(new VersionCheck)->daily(); $schedule->job(new ReminderJob)->daily(); + + $schedule->job(new UpdateExchangeRates)->daily(); /* Run hosted specific jobs */ if(Ninja::isHosted()) { diff --git a/app/Jobs/Product/UpdateOrCreateProduct.php b/app/Jobs/Product/UpdateOrCreateProduct.php index 30d9bb8577c1..e93dbdc6dafb 100644 --- a/app/Jobs/Product/UpdateOrCreateProduct.php +++ b/app/Jobs/Product/UpdateOrCreateProduct.php @@ -58,6 +58,10 @@ class UpdateOrCreateProduct implements ShouldQueue MultiDB::setDB($this->company->db); 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_key = $item->product_key; diff --git a/app/Jobs/Util/UpdateExchangeRates.php b/app/Jobs/Util/UpdateExchangeRates.php new file mode 100644 index 000000000000..a2f1db43280e --- /dev/null +++ b/app/Jobs/Util/UpdateExchangeRates.php @@ -0,0 +1,80 @@ +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); + } +} diff --git a/app/Libraries/Currency/Conversion/CurrencyApi.php b/app/Libraries/Currency/Conversion/CurrencyApi.php index 0fe3661bdb6f..87c3504c3b6c 100644 --- a/app/Libraries/Currency/Conversion/CurrencyApi.php +++ b/app/Libraries/Currency/Conversion/CurrencyApi.php @@ -12,7 +12,6 @@ namespace App\Libraries\Currency\Conversion; use App\Models\Currency; -use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; use Illuminate\Support\Carbon; class CurrencyApi implements CurrencyConversionInterface @@ -24,30 +23,51 @@ class CurrencyApi implements CurrencyConversionInterface if(!$date) $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(); - - return $exchangeRates->convert($amount, $from_currency, $to_currency, $date); + $usd_amount = $this->convertToUsd($amount, $from_currency); + return $this->convertFromUsdToCurrency($usd_amount, $to_currency); + } 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) - $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); + return $this->convertFromUsdToCurrency($usd_amount, $to_currency); } + /** + * 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; + } + } \ No newline at end of file diff --git a/app/Libraries/OAuth/OAuth.php b/app/Libraries/OAuth/OAuth.php index 2c4d83fc2715..292938131076 100644 --- a/app/Libraries/OAuth/OAuth.php +++ b/app/Libraries/OAuth/OAuth.php @@ -12,7 +12,7 @@ namespace App\Libraries\OAuth; use App\Libraries\MultiDB; -use App\Ninja\OAuth\Providers\Google; +use App\Libraries\OAuth\Providers\Google; use Laravel\Socialite\Facades\Socialite; /** diff --git a/app/Models/Currency.php b/app/Models/Currency.php index ddf6ff08be06..9f945906a887 100644 --- a/app/Models/Currency.php +++ b/app/Models/Currency.php @@ -17,6 +17,8 @@ class Currency extends StaticModel { public $timestamps = false; + protected $guarded = ['id']; + protected $casts = [ 'exchange_rate' => 'float', 'swap_currency_symbol' => 'boolean', diff --git a/app/Utils/EmailStats.php b/app/Utils/EmailStats.php new file mode 100644 index 000000000000..0e4415df9ad0 --- /dev/null +++ b/app/Utils/EmailStats.php @@ -0,0 +1,69 @@ +each(function ($company) { + self::clear($company->company_key); + }); + } +} diff --git a/composer.json b/composer.json index 3048b1a401fa..8542149e9c44 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "php": ">=7.3", "ext-json": "*", "asgrim/ofxparser": "^1.2", - "ashallendesign/laravel-exchange-rates": "^2.1", "beganovich/omnipay-checkout": "dev-master", "cleverit/ubl_invoice": "^1.3", "codedge/laravel-selfupdater": "~3.0", @@ -54,7 +53,6 @@ "spatie/browsershot": "^3.29", "staudenmeir/eloquent-has-many-deep": "^1.11", "stripe/stripe-php": "^7.0", - "superbalist/laravel-google-cloud-storage": "^2.2", "turbo124/beacon": "^0", "webpatser/laravel-countries": "dev-master#75992ad", "yajra/laravel-datatables-oracle": "~9.0" diff --git a/config/ninja.php b/config/ninja.php index a528a135345c..9a524a6f8d23 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -24,6 +24,7 @@ return [ 'error_email' => env('ERROR_EMAIL', ''), 'company_id' => 0, 'hash_salt' => env('HASH_SALT', ''), + 'currency_converter_api_key' => env('OPENEXCHANGE_APP_ID',''), 'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller' diff --git a/tests/Feature/PreviewTest.php b/tests/Feature/PreviewTest.php index 9d5e44574d95..4d6d10e15583 100644 --- a/tests/Feature/PreviewTest.php +++ b/tests/Feature/PreviewTest.php @@ -56,7 +56,6 @@ class PreviewTest extends TestCase ]; - $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token diff --git a/tests/Feature/UpdateExchangeRatesTest.php b/tests/Feature/UpdateExchangeRatesTest.php new file mode 100644 index 000000000000..51ee61f5ff57 --- /dev/null +++ b/tests/Feature/UpdateExchangeRatesTest.php @@ -0,0 +1,126 @@ +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); + + + } + +}