From f604e463c2be8d4f86f2641c7df331bcb43234ef Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 11 May 2022 15:25:33 +1000 Subject: [PATCH] Throttle payment methods to prevent spam: --- app/Factory/PaymentFactory.php | 1 + .../ClientPortal/PaymentMethodController.php | 6 +- .../ValidationRules/Account/BlackListRule.php | 1 + app/Jobs/Company/CompanyImport.php | 14 + app/Jobs/Report/ProfitAndLoss.php | 89 +++++++ app/Repositories/PaymentRepository.php | 18 +- app/Services/Invoice/MarkPaid.php | 4 +- app/Services/Report/ProfitLoss.php | 246 ++++++++++++++++++ routes/client.php | 4 +- 9 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 app/Jobs/Report/ProfitAndLoss.php create mode 100644 app/Services/Report/ProfitLoss.php diff --git a/app/Factory/PaymentFactory.php b/app/Factory/PaymentFactory.php index 3af36ee1b21d..e746cfc0834a 100644 --- a/app/Factory/PaymentFactory.php +++ b/app/Factory/PaymentFactory.php @@ -33,6 +33,7 @@ class PaymentFactory $payment->transaction_reference = null; $payment->payer_id = null; $payment->status_id = Payment::STATUS_PENDING; + $payment->exchange_rate = 1; return $payment; } diff --git a/app/Http/Controllers/ClientPortal/PaymentMethodController.php b/app/Http/Controllers/ClientPortal/PaymentMethodController.php index 0c4407f280a5..778723809956 100644 --- a/app/Http/Controllers/ClientPortal/PaymentMethodController.php +++ b/app/Http/Controllers/ClientPortal/PaymentMethodController.php @@ -30,6 +30,11 @@ class PaymentMethodController extends Controller { use MakesDates; + public function __construct() + { + $this->middleware('throttle:10,1')->only('store'); + } + /** * Display a listing of the resource. * @@ -92,7 +97,6 @@ class PaymentMethodController extends Controller public function verify(ClientGatewayToken $payment_method) { -// $gateway = $this->getClientGateway(); return $payment_method->gateway ->driver(auth()->user()->client) diff --git a/app/Http/ValidationRules/Account/BlackListRule.php b/app/Http/ValidationRules/Account/BlackListRule.php index 8f175708ad6a..8dee26c6ad2b 100644 --- a/app/Http/ValidationRules/Account/BlackListRule.php +++ b/app/Http/ValidationRules/Account/BlackListRule.php @@ -22,6 +22,7 @@ class BlackListRule implements Rule private array $blacklist = [ 'candassociates.com', 'vusra.com', + 'fourthgenet.com', ]; /** diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 8eb40800f5e7..56697694b435 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -1450,6 +1450,20 @@ class CompanyImport implements ShouldQueue $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextRecurringExpenseNumber($new_obj); } + elseif($class == 'App\Models\Project' && is_null($obj->{$match_key})){ + $new_obj = new Project(); + $new_obj->company_id = $this->company->id; + $new_obj->fill($obj_array); + $new_obj->save(['timestamps' => false]); + $new_obj->number = $this->getNextProjectNumber($new_obj); + } + elseif($class == 'App\Models\Task' && is_null($obj->{$match_key})){ + $new_obj = new Task(); + $new_obj->company_id = $this->company->id; + $new_obj->fill($obj_array); + $new_obj->save(['timestamps' => false]); + $new_obj->number = $this->getNextTaskNumber($new_obj); + } elseif($class == 'App\Models\CompanyLedger'){ $new_obj = $class::firstOrNew( [$match_key => $obj->{$match_key}, 'company_id' => $this->company->id], diff --git a/app/Jobs/Report/ProfitAndLoss.php b/app/Jobs/Report/ProfitAndLoss.php new file mode 100644 index 000000000000..da26197b4148 --- /dev/null +++ b/app/Jobs/Report/ProfitAndLoss.php @@ -0,0 +1,89 @@ +company = $company; + + $this->payload = $payload; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() : void + { + + MultiDB::setDb($this->company->db); + + /* + payload variables. + + start_date - Y-m-d + end_date - Y-m-d + date_range - + all + last7 + last30 + this_month + last_month + this_quarter + last_quarter + this_year + custom + income_billed - true = Invoiced || false = Payments + expense_billed - true = Expensed || false = Expenses marked as paid + include_tax - true tax_included || false - tax_excluded + + */ + + $pl = new ProfitLoss($this->company, $this->payload); + + $pl->build(); + + } + + + + + + public function failed($exception = null) + { + + } +} diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 470b92d66aa4..53170d76e718 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -66,7 +66,11 @@ class PaymentRepository extends BaseRepository { //check currencies here and fill the exchange rate data if necessary if (! $payment->id) { - $this->processExchangeRates($data, $payment); + $payment = $this->processExchangeRates($data, $payment); + + /* This is needed here otherwise the ->fill() overwrites anything that exists*/ + if($payment->exchange_rate != 1) + unset($data['exchange_rate']); $is_existing_payment = false; $client = Client::where('id', $data['client_id'])->withTrashed()->first(); @@ -100,7 +104,12 @@ class PaymentRepository extends BaseRepository { $payment->status_id = Payment::STATUS_COMPLETED; if (! $payment->currency_id && $client) { - $payment->currency_id = $client->company->settings->currency_id; + + if(property_exists($client->settings, 'currency_id')) + $payment->currency_id = $client->settings->currency_id; + else + $payment->currency_id = $client->company->settings->currency_id; + } $payment->save(); @@ -199,8 +208,9 @@ class PaymentRepository extends BaseRepository { public function processExchangeRates($data, $payment) { - if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate'])) + if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1){ return $payment; + } $client = Client::withTrashed()->find($data['client_id']); @@ -212,7 +222,6 @@ class PaymentRepository extends BaseRepository { $exchange_rate = new CurrencyApi(); $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); - // $payment->exchange_currency_id = $client_currency; $payment->exchange_currency_id = $company_currency; $payment->currency_id = $client_currency; @@ -221,7 +230,6 @@ class PaymentRepository extends BaseRepository { $payment->currency_id = $company_currency; - return $payment; } diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index 5c4a282d5ff3..87e321625fd9 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -61,7 +61,7 @@ class MarkPaid extends AbstractService $payment->transaction_reference = ctrans('texts.manual_entry'); $payment->currency_id = $this->invoice->client->getSetting('currency_id'); $payment->is_manual = true; - + if($this->invoice->company->timezone()) $payment->date = now()->addSeconds($this->invoice->company->timezone()->utc_offset)->format('Y-m-d'); @@ -149,7 +149,7 @@ class MarkPaid extends AbstractService //$payment->exchange_currency_id = $client_currency; // 23/06/2021 $payment->exchange_currency_id = $company_currency; - $payment->save(); + $payment->saveQuietly(); } diff --git a/app/Services/Report/ProfitLoss.php b/app/Services/Report/ProfitLoss.php new file mode 100644 index 000000000000..de6c2ab10e45 --- /dev/null +++ b/app/Services/Report/ProfitLoss.php @@ -0,0 +1,246 @@ +company = $company; + + $this->payload = $payload; + + $this->setBillingReportType(); + } + + public function build() + { + //get income + + //sift foreign currencies - calculate both converted foreign amounts to native currency and also also group amounts by currency. + + //get expenses + + + } + + + /* + //returns an array of objects + => [ + {#2047 + +"amount": "706.480000", + +"total_taxes": "35.950000", + +"currency_id": ""1"", + +"net_converted_amount": "670.5300000000", + }, + {#2444 + +"amount": "200.000000", + +"total_taxes": "0.000000", + +"currency_id": ""23"", + +"net_converted_amount": "1.7129479802", + }, + {#2654 + +"amount": "140.000000", + +"total_taxes": "40.000000", + +"currency_id": ""12"", + +"net_converted_amount": "69.3275024282", + }, + ] + */ + private function invoiceIncome() + { + return \DB::select( \DB::raw(" + SELECT + sum(invoices.amount) as amount, + sum(invoices.total_taxes) as total_taxes, + sum(invoices.amount - invoices.total_taxes) as net_amount, + IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id, + (sum(invoices.amount - invoices.total_taxes) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_amount + FROM clients + JOIN invoices + on invoices.client_id = clients.id + WHERE invoices.status_id IN (2,3,4) + AND invoices.company_id = :company_id + AND invoices.amount > 0 + AND clients.is_deleted = 0 + AND invoices.is_deleted = 0 + AND (invoices.date BETWEEN :start_date AND :end_date) + GROUP BY currency_id + "), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] ); + + + // + // $total = array_reduce( commissionsArray, function ($sum, $entry) { + // $sum += $entry->commission; + // return $sum; + // }, 0); + } + + private function paymentIncome() + { + return \DB::select( \DB::raw(" + SELECT + SUM(coalesce(payments.amount - payments.refunded,0)) as payments, + SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted + FROM clients + INNER JOIN + payments ON + clients.id=payments.client_id + WHERE payments.status_id IN (1,4,5,6) + AND clients.is_deleted = false + AND payments.is_deleted = false + AND payments.company_id = :company_id + AND (payments.date BETWEEN :start_date AND :end_date) + GROUP BY payments.currency_id + ORDER BY payments.currency_id; + "), ['company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]); + + + } + + private function expenseCalc() + { + + return \DB::select( \DB::raw(" + SELECT sum(expenses.amount) as amount, + IFNULL(expenses.currency_id, :company_currency) as currency_id + FROM expenses + WHERE expenses.is_deleted = 0 + AND expenses.company_id = :company_id + AND (expenses.date BETWEEN :start_date AND :end_date) + GROUP BY currency_id + "), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] ); + + } + + private function setBillingReportType() + { + + if(array_key_exists('income_billed', $this->payload)) + $this->is_income_billed = boolval($this->payload['income_billed']); + + if(array_key_exists('expense_billed', $this->payload)) + $this->is_expense_billed = boolval($this->payload['expense_billed']); + + if(array_key_exists('include_tax', $this->payload)) + $this->is_tax_included = boolval($this->payload['is_tax_included']); + + return $this; + + } + + private function addDateRange($query) + { + + $date_range = $this->payload['date_range']; + + try{ + + $custom_start_date = Carbon::parse($this->payload['start_date']); + $custom_end_date = Carbon::parse($this->payload['end_date']); + + } + catch(\Exception $e){ + + $custom_start_date = now()->startOfYear(); + $custom_end_date = now(); + + } + + switch ($date_range) { + + case 'all': + $this->start_date = now()->subYears(50); + $this->end_date = now(); + // return $query; + case 'last7': + $this->start_date = now()->subDays(7); + $this->end_date = now(); + // return $query->whereBetween($this->date_key, [now()->subDays(7), now()])->orderBy($this->date_key, 'ASC'); + case 'last30': + $this->start_date = now()->subDays(30); + $this->end_date = now(); + // return $query->whereBetween($this->date_key, [now()->subDays(30), now()])->orderBy($this->date_key, 'ASC'); + case 'this_month': + $this->start_date = now()->startOfMonth(); + $this->end_date = now(); + //return $query->whereBetween($this->date_key, [now()->startOfMonth(), now()])->orderBy($this->date_key, 'ASC'); + case 'last_month': + $this->start_date = now()->startOfMonth()->subMonth(); + $this->end_date = now()->startOfMonth()->subMonth()->endOfMonth(); + //return $query->whereBetween($this->date_key, [now()->startOfMonth()->subMonth(), now()->startOfMonth()->subMonth()->endOfMonth()])->orderBy($this->date_key, 'ASC'); + case 'this_quarter': + $this->start_date = (new \Carbon\Carbon('-3 months'))->firstOfQuarter(); + $this->end_date = (new \Carbon\Carbon('-3 months'))->lastOfQuarter(); + //return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-3 months'))->firstOfQuarter(), (new \Carbon\Carbon('-3 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC'); + case 'last_quarter': + $this->start_date = (new \Carbon\Carbon('-6 months'))->firstOfQuarter(); + $this->end_date = (new \Carbon\Carbon('-6 months'))->lastOfQuarter(); + //return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-6 months'))->firstOfQuarter(), (new \Carbon\Carbon('-6 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC'); + case 'this_year': + $this->start_date = now()->startOfYear(); + $this->end_date = now(); + //return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC'); + case 'custom': + $this->start_date = $custom_start_date; + $this->end_date = $custom_end_date; + //return $query->whereBetween($this->date_key, [$custom_start_date, $custom_end_date])->orderBy($this->date_key, 'ASC'); + default: + $this->start_date = now()->startOfYear(); + $this->end_date = now(); + // return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC'); + + } + + } + +} diff --git a/routes/client.php b/routes/client.php index 295957d4db35..b6750d0ca76a 100644 --- a/routes/client.php +++ b/routes/client.php @@ -8,7 +8,7 @@ Route::get('client/login', 'Auth\ContactLoginController@showLoginForm')->name('c Route::post('client/login', 'Auth\ContactLoginController@login')->name('client.login.submit'); Route::get('client/register/{company_key?}', 'Auth\ContactRegisterController@showRegisterForm')->name('client.register')->middleware(['domain_db', 'contact_account', 'contact_register','locale']); -Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale','throttle:10,1']); +Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale', 'throttle:10,1']); Route::get('client/password/reset', 'Auth\ContactForgotPasswordController@showLinkRequestForm')->name('client.password.request')->middleware(['domain_db', 'contact_account','locale']); Route::post('client/password/email', 'Auth\ContactForgotPasswordController@sendResetLinkEmail')->name('client.password.email')->middleware('locale'); @@ -62,7 +62,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie Route::put('profile/{client_contact}/localization', 'ClientPortal\ProfileController@updateClientLocalization')->name('profile.edit_localization'); Route::get('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@verify')->name('payment_methods.verification'); - Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification'); + Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification')->middleware(['throttle:10,1']); Route::get('payment_methods/confirm', 'ClientPortal\PaymentMethodController@store')->name('payment_methods.confirm');