From 386131f61889eb3ac9dabb72358cd84d0de24a0f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 10 Jan 2022 07:33:11 +1100 Subject: [PATCH 01/19] minor fixes for settings saver --- app/Utils/Traits/ClientGroupSettingsSaver.php | 2 +- app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php | 2 +- app/Utils/Traits/CompanySettingsSaver.php | 1 - app/Utils/Traits/SettingsSaver.php | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Utils/Traits/ClientGroupSettingsSaver.php b/app/Utils/Traits/ClientGroupSettingsSaver.php index e5e11f0619a4..8ed5856a5586 100644 --- a/app/Utils/Traits/ClientGroupSettingsSaver.php +++ b/app/Utils/Traits/ClientGroupSettingsSaver.php @@ -214,7 +214,7 @@ trait ClientGroupSettingsSaver case 'double': return is_float($value) || is_numeric(strval($value)); case 'string': - return method_exists($value, '__toString') || is_null($value) || is_string($value); + return ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value); case 'bool': case 'boolean': return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); diff --git a/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php b/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php index 86917956a23d..26c4e5d1b5d9 100644 --- a/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php +++ b/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php @@ -61,7 +61,7 @@ trait CompanyGatewayFeesAndLimitsSaver case 'double': return is_float($value) || is_numeric(strval($value)); case 'string': - return method_exists($value, '__toString') || is_null($value) || is_string($value); + return ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value); case 'bool': case 'boolean': return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); diff --git a/app/Utils/Traits/CompanySettingsSaver.php b/app/Utils/Traits/CompanySettingsSaver.php index 7d3de2afde27..cd2534d6ce57 100644 --- a/app/Utils/Traits/CompanySettingsSaver.php +++ b/app/Utils/Traits/CompanySettingsSaver.php @@ -232,7 +232,6 @@ trait CompanySettingsSaver return is_float($value) || is_numeric(strval($value)); case 'string': return (is_string($value) && method_exists($value, '__toString')) || is_null($value) || is_string($value); - //return is_null($value) || is_string($value); case 'bool': case 'boolean': return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); diff --git a/app/Utils/Traits/SettingsSaver.php b/app/Utils/Traits/SettingsSaver.php index aa35e84fd5ea..ac80a055ff49 100644 --- a/app/Utils/Traits/SettingsSaver.php +++ b/app/Utils/Traits/SettingsSaver.php @@ -94,7 +94,7 @@ trait SettingsSaver case 'double': return is_float($value) || is_numeric(strval($value)); case 'string': - return method_exists($value, '__toString') || is_null($value) || is_string($value); + return ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value); case 'bool': case 'boolean': return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); From 239b180a212bc592cebc5fb05dcbf098faaa68c6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 10 Jan 2022 12:47:16 +1100 Subject: [PATCH 02/19] Fixes for pdf regeneration --- app/Http/Controllers/InvoiceController.php | 4 +- app/Jobs/RecurringInvoice/SendRecurring.php | 2 +- app/Jobs/Util/ReminderJob.php | 2 + .../Invoice/InvoiceArchivedActivity.php | 2 +- app/PaymentDrivers/BaseDriver.php | 4 +- app/PaymentDrivers/Stripe/BrowserPay.php | 49 ++++++++++++++++--- app/Services/Invoice/InvoiceService.php | 23 ++++++--- app/Services/Invoice/MarkPaid.php | 1 + app/Services/Payment/UpdateInvoicePayment.php | 2 +- app/Utils/Traits/SettingsSaver.php | 2 +- 10 files changed, 70 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index a04392dd6a12..e2c913152178 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -401,7 +401,7 @@ class InvoiceController extends BaseController $invoice = $this->invoice_repo->save($request->all(), $invoice); - $invoice->service()->triggeredActions($request)->deletePdf(); + $invoice->service()->triggeredActions($request)->deletePdf()->touchPdf(); event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); @@ -708,7 +708,7 @@ class InvoiceController extends BaseController } break; case 'cancel': - $invoice = $invoice->service()->handleCancellation()->deletePdf()->save(); + $invoice = $invoice->service()->handleCancellation()->deletePdf()->touchPdf()->save(); if (! $bulk) { $this->itemResponse($invoice); diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index f688ec47dcd2..24de8b99f883 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -95,7 +95,7 @@ class SendRecurring implements ShouldQueue $invoice = $this->createRecurringInvitations($invoice); /* 09-01-2022 ensure we create the PDFs at this point in time! */ - $invoice->service()->touchPdf(); + $invoice->service()->touchPdf(true); nlog("updating recurring invoice dates"); /* Set next date here to prevent a recurring loop forming */ diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 93c61e7482cd..05c2e8f6bfbd 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -80,6 +80,8 @@ class ReminderJob implements ShouldQueue $invoice->service()->touchReminder($reminder_template)->save(); $invoice = $this->calcLateFee($invoice, $reminder_template); + $invoice->service()->touchPdf(); + //check if this reminder needs to be emailed if(in_array($reminder_template, ['reminder1','reminder2','reminder3','reminder_endless']) && $invoice->client->getSetting("enable_".$reminder_template)) { diff --git a/app/Listeners/Invoice/InvoiceArchivedActivity.php b/app/Listeners/Invoice/InvoiceArchivedActivity.php index 1ceff8ede006..4b37f0070c08 100644 --- a/app/Listeners/Invoice/InvoiceArchivedActivity.php +++ b/app/Listeners/Invoice/InvoiceArchivedActivity.php @@ -43,7 +43,7 @@ class InvoiceArchivedActivity implements ShouldQueue { MultiDB::setDb($event->company->db); - $event->invoice->service()->deletePdf(); + // $event->invoice->service()->deletePdf(); $fields = new stdClass; diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index a85229956014..a1e3e279eb38 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -442,7 +442,7 @@ class BaseDriver extends AbstractPaymentDriver $invoices->each(function ($invoice) { - $invoice->service()->deletePdf(); + $invoice->service()->touchPdf(); }); @@ -494,7 +494,7 @@ class BaseDriver extends AbstractPaymentDriver $invoices->each(function ($invoice){ - $invoice->service()->deletePdf(); + $invoice->service()->touchPdf(); }); diff --git a/app/PaymentDrivers/Stripe/BrowserPay.php b/app/PaymentDrivers/Stripe/BrowserPay.php index 22b4cb111cd2..9de0cbcca8fa 100644 --- a/app/PaymentDrivers/Stripe/BrowserPay.php +++ b/app/PaymentDrivers/Stripe/BrowserPay.php @@ -194,13 +194,18 @@ class BrowserPay implements MethodInterface return; } - $domain = config('ninja.app_url'); + // $domain = config('ninja.app_url'); - if (Ninja::isHosted()) { - $domain = isset($this->stripe->company_gateway->company->portal_domain) - ? $this->stripe->company_gateway->company->portal_domain - : $this->stripe->company_gateway->company->domain(); - } + // if (Ninja::isHosted()) { + // $domain = isset($this->stripe->company_gateway->company->portal_domain) + // ? $this->stripe->company_gateway->company->portal_domain + // : $this->stripe->company_gateway->company->domain(); + // } + + $domain = $this->getAppleDomain(); + + if(!$domain) + throw new PaymentFailed('Unable to register Domain with Apple Pay', 500); $response = ApplePayDomain::create([ 'domain_name' => $domain, @@ -212,4 +217,36 @@ class BrowserPay implements MethodInterface $this->stripe->company_gateway->save(); } + + + private function getAppleDomain() + { + + $domain = ''; + + if(Ninja::isHosted()) + { + + if($this->company_gateway->company->portal_mode == 'domain'){ + $domain = $this->company_gateway->company->portal_domain; + } + else{ + $domain = $this->company_gateway->company->subdomain . '.' . config('ninja.app_domain'); + } + + } + else { + + $domain = config('ninja.app_url'); + } + + $parsed_url = parse_url($domain); + + if(array_key_exists('host', $parsed_url)) + return $parsed_url['host']; + + return false; + + } + } diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 23c50125c89d..281cea3ebc8d 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -390,18 +390,27 @@ class InvoiceService */ public function touchPdf($force = false) { - if($force){ + try { + + if($force){ + + $this->invoice->invitations->each(function ($invitation) { + CreateEntityPdf::dispatchNow($invitation); + }); + + return $this; + } $this->invoice->invitations->each(function ($invitation) { - CreateEntityPdf::dispatchNow($invitation); + CreateEntityPdf::dispatch($invitation); }); - - return $this; + } + catch(\Exception $e){ - $this->invoice->invitations->each(function ($invitation) { - CreateEntityPdf::dispatch($invitation); - }); + nlog("failed creating invoices in Touch PDF"); + + } return $this; } diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index 3178eba97862..49443d4168c6 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -89,6 +89,7 @@ class MarkPaid extends AbstractService ->service() ->applyNumber() ->deletePdf() + ->touchPdf() ->save(); $payment->ledger() diff --git a/app/Services/Payment/UpdateInvoicePayment.php b/app/Services/Payment/UpdateInvoicePayment.php index ca5534245416..e1750713c95b 100644 --- a/app/Services/Payment/UpdateInvoicePayment.php +++ b/app/Services/Payment/UpdateInvoicePayment.php @@ -87,7 +87,7 @@ class UpdateInvoicePayment $invoice->refresh(); $invoice->service() - ->deletePdf() + ->touchPdf() ->workFlow() ->save(); diff --git a/app/Utils/Traits/SettingsSaver.php b/app/Utils/Traits/SettingsSaver.php index ac80a055ff49..0699c04325cf 100644 --- a/app/Utils/Traits/SettingsSaver.php +++ b/app/Utils/Traits/SettingsSaver.php @@ -94,7 +94,7 @@ trait SettingsSaver case 'double': return is_float($value) || is_numeric(strval($value)); case 'string': - return ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value); + return !is_int($value) || ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value); case 'bool': case 'boolean': return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); From 3e3b4e40e5abed0086164436cdd086eda38974aa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 10 Jan 2022 19:48:18 +1100 Subject: [PATCH 03/19] Fixes for permissions on list response --- app/Filters/RecurringExpenseFilters.php | 2 +- app/Filters/RecurringInvoiceFilters.php | 2 +- app/Http/Controllers/BaseController.php | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Filters/RecurringExpenseFilters.php b/app/Filters/RecurringExpenseFilters.php index e8c3c4740436..8a433c896aca 100644 --- a/app/Filters/RecurringExpenseFilters.php +++ b/app/Filters/RecurringExpenseFilters.php @@ -58,7 +58,7 @@ class RecurringExpenseFilters extends QueryFilters return $this->builder; } - $table = 'expenses'; + $table = 'recurring_expenses'; $filters = explode(',', $filter); return $this->builder->where(function ($query) use ($filters, $table) { diff --git a/app/Filters/RecurringInvoiceFilters.php b/app/Filters/RecurringInvoiceFilters.php index 4eb17f127df3..befc31f1d71f 100644 --- a/app/Filters/RecurringInvoiceFilters.php +++ b/app/Filters/RecurringInvoiceFilters.php @@ -53,7 +53,7 @@ class RecurringInvoiceFilters extends QueryFilters return $this->builder; } - $table = 'recurring_'; + $table = 'recurring_invoices'; $filters = explode(',', $filter); return $this->builder->where(function ($query) use ($filters, $table) { diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 716d0deb2c59..af241cd412fb 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -22,6 +22,7 @@ use App\Utils\Traits\AppSetup; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; +use Illuminate\Support\Str; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection; @@ -619,7 +620,9 @@ class BaseController extends Controller $query->with($includes); - if (auth()->user() && ! auth()->user()->hasPermission('view_'.lcfirst(class_basename($this->entity_type)))) { + // 10-01-2022 need to ensure we snake case properly here to ensure permissions work as expected + // if (auth()->user() && ! auth()->user()->hasPermission('view_'.lcfirst(class_basename($this->entity_type)))) { + if (auth()->user() && ! auth()->user()->hasPermission('view'.lcfirst(class_basename(Str::snake($this->entity_type))))) { $query->where('user_id', '=', auth()->user()->id); } From 2bc5a534f400f48516fb90f1ec543df3642208a3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 10 Jan 2022 21:15:30 +1100 Subject: [PATCH 04/19] Fixes for Mollie --- app/PaymentDrivers/MolliePaymentDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/PaymentDrivers/MolliePaymentDriver.php b/app/PaymentDrivers/MolliePaymentDriver.php index bab3adcf2e59..78e6b495c295 100644 --- a/app/PaymentDrivers/MolliePaymentDriver.php +++ b/app/PaymentDrivers/MolliePaymentDriver.php @@ -321,7 +321,7 @@ class MolliePaymentDriver extends BaseDriver // we may not have a payment record - in these cases we need to re-construct the payment // record from the meta data in the payment hash. - if($payment && property_exists($payment->metadata, 'payment_hash') && $payment->metadata->payment_hash){ + if($payment && property_exists($payment->metadata, 'hash') && $payment->metadata->hash){ /* Harvest Payment Hash*/ $payment_hash = PaymentHash::where('hash', $payment->metadata->hash)->first(); From ddc19547c5ea842f8dc1c494254bed472409d01c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 08:19:44 +1100 Subject: [PATCH 05/19] Strip double spaces from file names --- app/Models/BaseModel.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 2f82e3561b4b..d1e8c8ce6023 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -195,8 +195,11 @@ class BaseModel extends Model // Remove any runs of periods (thanks falstro!) $formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number); - $formatted_number = str_replace(" ", "_", $formatted_number); + // $formatted_number = str_replace(" ", "_", $formatted_number); + //11-01-2021 fixes for multiple spaces + $formatted_number = preg_replace('/\s+/', '_', $formatted_number); + return $formatted_number; } From 9910e4f9973976449e0a1afe93c479c06b3548c9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 08:55:11 +1100 Subject: [PATCH 06/19] Test GoCardless webhooks for valid events --- app/PaymentDrivers/GoCardlessPaymentDriver.php | 9 +++++++++ app/Services/Payment/UpdateInvoicePayment.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 721125409090..1cb6e2fb8183 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -234,6 +234,15 @@ class GoCardlessPaymentDriver extends BaseDriver $this->init(); + + if(!is_array($request->events) || !is_object($request->events)){ + + nlog("No GoCardless events to process in response?"); + return response()->json([], 200); + + } + + foreach ($request->events as $event) { if ($event['action'] === 'confirmed') { $payment = Payment::query() diff --git a/app/Services/Payment/UpdateInvoicePayment.php b/app/Services/Payment/UpdateInvoicePayment.php index e1750713c95b..16c5d9738b2c 100644 --- a/app/Services/Payment/UpdateInvoicePayment.php +++ b/app/Services/Payment/UpdateInvoicePayment.php @@ -87,7 +87,7 @@ class UpdateInvoicePayment $invoice->refresh(); $invoice->service() - ->touchPdf() + ->touchPdf(true) ->workFlow() ->save(); From b9cb231773ce9eb8102929de5b7d61189a0129c0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 09:31:08 +1100 Subject: [PATCH 07/19] Fixes for Project Filters --- app/Filters/PaymentTermFilters.php | 3 ++- app/Filters/ProjectFilters.php | 5 +++-- app/Filters/QueryFilters.php | 2 ++ tests/Feature/ProjectApiTest.php | 25 +++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/app/Filters/PaymentTermFilters.php b/app/Filters/PaymentTermFilters.php index 93b62e4ce0a4..86865ecc5ad5 100644 --- a/app/Filters/PaymentTermFilters.php +++ b/app/Filters/PaymentTermFilters.php @@ -112,7 +112,8 @@ class PaymentTermFilters extends QueryFilters */ public function entityFilter() { + return $this->builder->company(); //return $this->builder->whereCompanyId(auth()->user()->company()->id); - return $this->builder->whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null); + // return $this->builder->whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null); } } diff --git a/app/Filters/ProjectFilters.php b/app/Filters/ProjectFilters.php index bdd961a867ae..9821dbe627c2 100644 --- a/app/Filters/ProjectFilters.php +++ b/app/Filters/ProjectFilters.php @@ -107,7 +107,6 @@ class ProjectFilters extends QueryFilters $query = DB::table('projects') ->join('companies', 'companies.id', '=', 'projects.company_id') ->where('projects.company_id', '=', $company_id) - //->whereRaw('(projects.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices ->select( 'projects.id', 'projects.name', @@ -140,6 +139,8 @@ class ProjectFilters extends QueryFilters public function entityFilter() { //return $this->builder->whereCompanyId(auth()->user()->company()->id); - return $this->builder->whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null); + // return $this->builder->whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null); + return $this->builder->company(); + } } diff --git a/app/Filters/QueryFilters.php b/app/Filters/QueryFilters.php index 4d4860eb0077..d5e1bfeee626 100644 --- a/app/Filters/QueryFilters.php +++ b/app/Filters/QueryFilters.php @@ -88,6 +88,8 @@ abstract class QueryFilters } } + // nlog('[Search] SQL: ' . $this->builder->toSql() . " Bindings: " . implode(', ', $this->builder->getBindings())); + return $this->builder->withTrashed(); } diff --git a/tests/Feature/ProjectApiTest.php b/tests/Feature/ProjectApiTest.php index a9062e742cab..8d8ae594462e 100644 --- a/tests/Feature/ProjectApiTest.php +++ b/tests/Feature/ProjectApiTest.php @@ -84,10 +84,35 @@ class ProjectApiTest extends TestCase $response->assertStatus(302); } + } + + public function testProjectPostFilters() + { + $data = [ + 'name' => "Sherlock", + 'client_id' => $this->client->hashed_id, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/projects', $data); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/projects?filter=Sherlock'); + + $arr = $response->json(); + + $this->assertEquals(1, count($arr['data'])); } + public function testProjectPut() { $data = [ From 539fc1dcfdf4d84617ccc11731a59978903cdf21 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 15:08:39 +1100 Subject: [PATCH 08/19] Change location for test png --- tests/Integration/CompanyLedgerTest.php | 2 +- tests/MockAccountData.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/CompanyLedgerTest.php b/tests/Integration/CompanyLedgerTest.php index 76eb3eb78f61..c5ece3e949f6 100644 --- a/tests/Integration/CompanyLedgerTest.php +++ b/tests/Integration/CompanyLedgerTest.php @@ -84,7 +84,7 @@ class CompanyLedgerTest extends TestCase $settings = CompanySettings::defaults(); - $settings->company_logo = 'https://app.invoiceninja.com/favicon-v2.png'; + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; $settings->website = 'www.invoiceninja.com'; $settings->address1 = 'Address 1'; $settings->address2 = 'Address 2'; diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 7983a9d9d4de..b2a6276ad52c 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -192,7 +192,7 @@ trait MockAccountData $settings = CompanySettings::defaults(); - $settings->company_logo = 'https://app.invoiceninja.com/favicon-v2.png'; + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; // $settings->company_logo = asset('images/new_logo.png'); $settings->website = 'www.invoiceninja.com'; $settings->address1 = 'Address 1'; From f66c3076f18a7a0566ab6795d833fe6b99f2edd9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 19:08:08 +1100 Subject: [PATCH 09/19] fixes for adding users into the production system --- app/Http/Requests/User/StoreUserRequest.php | 2 +- .../ValidationRules/Ninja/CanAddUserRule.php | 19 ++++++++++++------- app/Models/Company.php | 2 +- app/Repositories/UserRepository.php | 8 ++++++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php index f9275bd5258f..4a60a339e0ad 100644 --- a/app/Http/Requests/User/StoreUserRequest.php +++ b/app/Http/Requests/User/StoreUserRequest.php @@ -58,7 +58,7 @@ class StoreUserRequest extends Request { $input = $this->all(); -//unique user rule - check company_user table for user_id / company_id / account_id if none exist we can add the user. ELSE return false + //unique user rule - check company_user table for user_id / company_id / account_id if none exist we can add the user. ELSE return false if(array_key_exists('email', $input)) $input['email'] = trim($input['email']); diff --git a/app/Http/ValidationRules/Ninja/CanAddUserRule.php b/app/Http/ValidationRules/Ninja/CanAddUserRule.php index 6333edf1abe6..1a26d1035b90 100644 --- a/app/Http/ValidationRules/Ninja/CanAddUserRule.php +++ b/app/Http/ValidationRules/Ninja/CanAddUserRule.php @@ -33,13 +33,18 @@ class CanAddUserRule implements Rule public function passes($attribute, $value) { - $count = CompanyUser::query() - ->where('company_user.account_id', auth()->user()->account_id) - ->join('users', 'users.id', '=', 'company_user.user_id') - ->whereNull('users.deleted_at') - ->whereNull('company_user.deleted_at') - ->distinct() - ->count('company_user.user_id'); + /* If the user is active then we can add them to the company */ + if(User::where('email', request()->input('email'))->where('account_id', auth()->user()->account_id)->where('is_deleted',0)->exists()) + return true; + + /* Check that we have sufficient quota to allow this to happen */ + $count = CompanyUser::query() + ->where('company_user.account_id', auth()->user()->account_id) + ->join('users', 'users.id', '=', 'company_user.user_id') + ->whereNull('users.deleted_at') + ->whereNull('company_user.deleted_at') + ->distinct() + ->count('company_user.user_id'); return $count < auth()->user()->company()->account->num_users; diff --git a/app/Models/Company.php b/app/Models/Company.php index 86554fe5001e..ec7497f1ebd1 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -171,7 +171,7 @@ class Company extends BaseModel public function users() { - return $this->hasManyThrough(User::class, CompanyUser::class, 'company_id', 'id', 'id', 'user_id'); + return $this->hasManyThrough(User::class, CompanyUser::class, 'company_id', 'id', 'id', 'user_id')->withTrashed(); } public function expense_categories() diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 6e91d344cca7..fdad645cdeb9 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -189,6 +189,14 @@ class UserRepository extends BaseRepository return; } + if (Ninja::isHosted()) { + + $count = User::where('account_id', auth()->user()->account_id)->count(); + if($count >= auth()->user()->account->num_users) + return; + + } + $user->is_deleted = false; $user->save(); $user->restore(); From 058c9ab04913340e55acd4113ce346498140f139 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 19:17:52 +1100 Subject: [PATCH 10/19] Only send payment receipt to a single contact --- app/Services/Payment/SendEmail.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Services/Payment/SendEmail.php b/app/Services/Payment/SendEmail.php index 978a038df2fe..3544a53c7e65 100644 --- a/app/Services/Payment/SendEmail.php +++ b/app/Services/Payment/SendEmail.php @@ -37,6 +37,8 @@ class SendEmail $this->payment->client->contacts->each(function ($contact) { if ($contact->email) { EmailPayment::dispatchNow($this->payment, $this->payment->company, $contact); + return false; + //11-01-2021 only send payment receipt to the first contact } }); } From 3e36f335b4ded95f41e6b6157a1633ddd7fac1de Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 19:42:00 +1100 Subject: [PATCH 11/19] Fixes for required fields --- app/Http/Livewire/RequiredClientInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Livewire/RequiredClientInfo.php b/app/Http/Livewire/RequiredClientInfo.php index 3431cbd0ed3c..fc500ea87758 100644 --- a/app/Http/Livewire/RequiredClientInfo.php +++ b/app/Http/Livewire/RequiredClientInfo.php @@ -150,7 +150,7 @@ class RequiredClientInfo extends Component } if (Str::startsWith($field['name'], 'contact_')) { - if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field})) { + if (empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) { // if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} == 840) { $this->show_form = true; } else { From 8e3b90494bcb6bab93862985d21f757546e848ff Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 11 Jan 2022 20:01:03 +1100 Subject: [PATCH 12/19] remove logging --- app/Services/Client/Statement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 549fec257da0..886579d8a32c 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -236,7 +236,7 @@ class Statement private function invoiceStatuses() :array { $status = 'all'; -nlog($this->options); + if(array_key_exists('status', $this->options)) $status = $this->options['status']; From f3947b104a063adb2d69845f1d1d92fe7195a049 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 13:05:07 +1100 Subject: [PATCH 13/19] change deletePdf() to touchPdf() --- app/Http/Controllers/InvoiceController.php | 2 +- app/Services/Invoice/MarkPaid.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index e2c913152178..f23b04966608 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -401,7 +401,7 @@ class InvoiceController extends BaseController $invoice = $this->invoice_repo->save($request->all(), $invoice); - $invoice->service()->triggeredActions($request)->deletePdf()->touchPdf(); + $invoice->service()->triggeredActions($request)->touchPdf(); event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index 49443d4168c6..90d51588c412 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -88,7 +88,7 @@ class MarkPaid extends AbstractService $this->invoice ->service() ->applyNumber() - ->deletePdf() + // ->deletePdf() ->touchPdf() ->save(); From 5f51ea9002fbc3df6fbccb66ef84357295c897e2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 14:40:05 +1100 Subject: [PATCH 14/19] Fixes for payment request --- .../Requests/Payment/StorePaymentRequest.php | 9 +- .../Payment/ValidInvoicesRules.php | 5 + .../Payment/ValidRefundableRequest.php | 5 + .../PaymentAmountsBalanceRule.php | 8 +- .../Payments/StorePaymentValidationTest.php | 167 ++++++++++++++++++ 5 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 tests/Feature/Payments/StorePaymentValidationTest.php diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 2deffed782ec..c74f91d78ab4 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -54,7 +54,10 @@ class StorePaymentRequest extends Request if (isset($input['invoices']) && is_array($input['invoices']) !== false) { foreach ($input['invoices'] as $key => $value) { $input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']); - $invoices_total += $value['amount']; + + if(array_key_exists('amount', $value)) + $invoices_total += $value['amount']; + } } @@ -91,12 +94,12 @@ class StorePaymentRequest extends Request public function rules() { $rules = [ - 'amount' => 'numeric|required', + 'amount' => 'sometimes|numeric', 'amount' => [new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()], 'client_id' => 'bail|required|exists:clients,id', 'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id', + 'invoices.*.amount' => 'bail|required', 'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()), - 'invoices.*.amount' => 'required', 'credits.*.credit_id' => 'bail|required|exists:credits,id', 'credits.*.credit_id' => new ValidCreditsRules($this->all()), 'credits.*.amount' => ['required', new CreditsSumRule($this->all())], diff --git a/app/Http/ValidationRules/Payment/ValidInvoicesRules.php b/app/Http/ValidationRules/Payment/ValidInvoicesRules.php index 0bc63580ebc7..f3a333a091c7 100644 --- a/app/Http/ValidationRules/Payment/ValidInvoicesRules.php +++ b/app/Http/ValidationRules/Payment/ValidInvoicesRules.php @@ -57,6 +57,11 @@ class ValidInvoicesRules implements Rule $unique_array[] = $invoice['invoice_id']; + if(!array_key_exists('amount', $invoice)){ + $this->error_msg = ctrans('texts.amount') . " required"; + return false; + } + $inv = Invoice::whereId($invoice['invoice_id'])->first(); if (! $inv) { diff --git a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php index b29c6ab06a18..7abba2b7870e 100644 --- a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php +++ b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php @@ -79,6 +79,11 @@ class ValidRefundableRequest implements Rule { $invoice = Invoice::whereId($invoice['invoice_id'])->whereCompanyId($payment->company_id)->withTrashed()->first(); + if(!$invoice){ + $this->error_msg = "Invoice not found for refund"; + return false; + } + if ($payment->invoices()->exists()) { $paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first(); diff --git a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php index 2097b1b77c1d..be8f0d60ec98 100644 --- a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php +++ b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php @@ -59,13 +59,17 @@ class PaymentAmountsBalanceRule implements Rule if (request()->input('credits') && is_array(request()->input('credits'))) { foreach (request()->input('credits') as $credit) { - $payment_amounts += $credit['amount']; + + if(array_key_exists('amount', $credit)) + $payment_amounts += $credit['amount']; } } if (request()->input('invoices') && is_array(request()->input('invoices'))) { foreach (request()->input('invoices') as $invoice) { - $invoice_amounts += $invoice['amount']; + + if(array_key_exists('amount', $invoice)) + $invoice_amounts += $invoice['amount']; } } else { return true; diff --git a/tests/Feature/Payments/StorePaymentValidationTest.php b/tests/Feature/Payments/StorePaymentValidationTest.php new file mode 100644 index 000000000000..7a4d11a44d58 --- /dev/null +++ b/tests/Feature/Payments/StorePaymentValidationTest.php @@ -0,0 +1,167 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + + public function testValidPayment() + { + + $data = [ + 'amount' => 0, + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + ], + 'date' => '2019/12/12', + ]; + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/', $data); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($e->validator->getMessageBag()); + } + + $response->assertStatus(200); + + } + + + public function testValidPaymentWithAmount() + { + + $data = [ + 'amount' => 0, + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + [ + 'invoice_id' => $this->invoice->hashed_id, + 'amount' => 10, + ], + ], + 'credits' => [ + [ + 'credit_id' => $this->credit->hashed_id, + 'amount' => 5 + ] + ], + 'date' => '2019/12/12', + ]; + + $response = false; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/', $data); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($e->validator->getMessageBag()); + } + + $response->assertStatus(200); + + } + + public function testValidPaymentWithInvalidData() + { + + + $data = [ + 'amount' => 0, + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + [ + 'invoice_id' => $this->invoice->hashed_id, + ], + ], + 'credits' => [ + [ + 'credit_id' => $this->credit->hashed_id, + 'amount' => 5 + ] + ], + 'date' => '2019/12/12', + ]; + + $response = false; + + try{ + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/', $data); + }catch(ValidationException $e){ + $response->assertStatus(302); + } + + } + + + +} + From 99db919d609505faebf0fe285fb3650a272e27fa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 17:29:28 +1100 Subject: [PATCH 15/19] Logging for balances --- app/Http/ValidationRules/PaymentAmountsBalanceRule.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php index 2097b1b77c1d..6fa9d089f9ab 100644 --- a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php +++ b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php @@ -71,7 +71,8 @@ class PaymentAmountsBalanceRule implements Rule return true; } - - return $payment_amounts >= $invoice_amounts; + nlog($payment_amounts ." >= " . $invoice_amounts); + + return $payment_amounts >= $invoice_amounts; } } From 5bd0b043e0b6682f7acf0c2c76ec3b1e313680af Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 17:34:47 +1100 Subject: [PATCH 16/19] Minor fixes for playful design --- app/Http/ValidationRules/PaymentAmountsBalanceRule.php | 3 +++ resources/views/pdf-designs/playful.html | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php index be8f0d60ec98..6fe72aa31eba 100644 --- a/app/Http/ValidationRules/PaymentAmountsBalanceRule.php +++ b/app/Http/ValidationRules/PaymentAmountsBalanceRule.php @@ -75,6 +75,9 @@ class PaymentAmountsBalanceRule implements Rule return true; } +nlog(request()->input('invoices')); +nlog($payment_amounts); +nlog($invoice_amounts); return $payment_amounts >= $invoice_amounts; } diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index 550b202b2fef..a9c6135e5e3a 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -38,7 +38,10 @@ border-radius: 10px; } - #entity-details p { margin-right: 20px; } + #entity-details p { + margin-right: 20px; + white-space: nowrap; + } .header-wrapper #entity-details { width: 100%; From 848cb6ae4caaee14c05f111ae08bee9af3447e45 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 17:55:12 +1100 Subject: [PATCH 17/19] Do not translate date if none is present --- app/Utils/Traits/MakesDates.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Utils/Traits/MakesDates.php b/app/Utils/Traits/MakesDates.php index 02cac3cdabef..1ca67e1e0cc1 100644 --- a/app/Utils/Traits/MakesDates.php +++ b/app/Utils/Traits/MakesDates.php @@ -99,6 +99,9 @@ trait MakesDates public function translateDate($date, $format, $locale) { + if(empty($date)) + return ''; + Carbon::setLocale($locale); try { From c26387a3767196051e69be67fde9d9e6b56e835f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 20:29:10 +1100 Subject: [PATCH 18/19] Fixes for import --- app/Http/Controllers/StaticController.php | 37 +++++++++++++++++++++++ app/Jobs/Util/Import.php | 13 ++++++-- app/Services/Invoice/AutoBillInvoice.php | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/StaticController.php b/app/Http/Controllers/StaticController.php index 1cb4ccf1ff00..20e99db5d6ff 100644 --- a/app/Http/Controllers/StaticController.php +++ b/app/Http/Controllers/StaticController.php @@ -23,6 +23,43 @@ use Illuminate\Http\Response; class StaticController extends BaseController { + /** + * Show the list of Invoices. + * + * @param InvoiceFilters $filters The filters + * + * @return Response + * + * @OA\Get( + * path="/api/v1/statics", + * operationId="getStatics", + * tags={"statics"}, + * summary="Gets a list of statics", + * description="Lists all statics", + * + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A list of static data", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ public function __invoke() { diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index 773ef8326243..627619656c9a 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -1866,10 +1866,19 @@ class Import implements ShouldQueue private function processNinjaTokens(array $data) { + nlog("attempting to process Ninja Tokens"); - if(Ninja::isHosted()) - \Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company); + if(Ninja::isHosted()){ + + try{ + \Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company); + } + catch(\Exception $e){ + nlog($e->getMessage()); + } + + } } diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 025e9dcec957..17a511909161 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -125,7 +125,7 @@ class AutoBillInvoice extends AbstractService } catch(\Exception $e){ nlog("payment NOT captured for ". $this->invoice->number . " with error " . $e->getMessage()); - // nlog($e->getMessage()); + $this->invoice->service()->removeUnpaidGatewayFees()->save(); } if($payment){ From 27655905fcc5e070c735a02bc2cbbe71a77c2cab Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 Jan 2022 21:52:50 +1100 Subject: [PATCH 19/19] v5.3.45 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 1690fa814b5b..96fb010e76c9 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.3.44 \ No newline at end of file +5.3.45 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index a485bc0f5733..155797ed4127 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.3.44', - 'app_tag' => '5.3.44', + 'app_version' => '5.3.45', + 'app_tag' => '5.3.45', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),