From aca580780abbaf239fd17f36a474aa69de7c0adf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 09:22:33 +1100 Subject: [PATCH 1/6] Ensure order of Item exports --- app/Export/CSV/InvoiceItemExport.php | 16 +++++++++------- app/Export/CSV/PurchaseOrderItemExport.php | 1 + app/Export/CSV/QuoteItemExport.php | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 5ac3c9fb2fbf..ba2dc00da4f5 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -137,16 +137,16 @@ class InvoiceItemExport extends BaseExport if (str_contains($key, "item.")) { - $key = str_replace("item.", "", $key); + $tmp_key = str_replace("item.", "", $key); - if($key == 'type_id') - $key = 'type'; + if($tmp_key == 'type_id') + $tmp_key = 'type'; - if($key == 'tax_id') - $key = 'tax_category'; + if($tmp_key == 'tax_id') + $tmp_key = 'tax_category'; - if (property_exists($item, $key)) { - $item_array[$key] = $item->{$key}; + if (property_exists($item, $tmp_key)) { + $item_array[$key] = $item->{$tmp_key}; } else { $item_array[$key] = ''; @@ -156,6 +156,8 @@ class InvoiceItemExport extends BaseExport $transformed_items = array_merge($transformed_invoice, $item_array); $entity = $this->decorateAdvancedFields($invoice, $transformed_items); + + $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity); $this->storage_array[] = $entity; diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index fb09ea6fa8e9..1ba5db7a12a6 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -147,6 +147,7 @@ class PurchaseOrderItemExport extends BaseExport $transformed_items = array_merge($transformed_purchase_order, $item_array); $entity = $this->decorateAdvancedFields($purchase_order, $transformed_items); + $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity); $this->storage_array[] = $entity; } diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index 48bc25b95e6a..5e327af612d8 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -152,6 +152,7 @@ class QuoteItemExport extends BaseExport $transformed_items = array_merge($transformed_quote, $item_array); $entity = $this->decorateAdvancedFields($quote, $transformed_items); + $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity); $this->storage_array[] = $entity; } From e54dec8762f72eff1ef06d9947210be5cc6cf524 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 09:39:25 +1100 Subject: [PATCH 2/6] Fixes for exports --- app/Export/CSV/PurchaseOrderItemExport.php | 14 +++++++------- app/Export/CSV/QuoteItemExport.php | 14 +++++++------- app/Http/Requests/Client/StoreClientRequest.php | 2 -- .../GroupSetting/StoreGroupSettingRequest.php | 10 ++++++++-- tests/Feature/Export/ReportCsvGenerationTest.php | 3 +-- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index 1ba5db7a12a6..3738ec5b6dd0 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -127,18 +127,18 @@ class PurchaseOrderItemExport extends BaseExport if (str_contains($key, "item.")) { - $key = str_replace("item.", "", $key); + $tmp_key = str_replace("item.", "", $key); - if($key == 'type_id') { - $keyval = 'type'; + if($tmp_key == 'type_id') { + $tmp_key = 'type'; } - if($key == 'tax_id') { - $keyval = 'tax_category'; + if($tmp_key == 'tax_id') { + $tmp_key = 'tax_category'; } - if (property_exists($item, $key)) { - $item_array[$key] = $item->{$key}; + if (property_exists($item, $tmp_key)) { + $item_array[$key] = $item->{$tmp_key}; } else { $item_array[$key] = ''; } diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index 5e327af612d8..0db08629c664 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -133,16 +133,16 @@ class QuoteItemExport extends BaseExport if (str_contains($key, "item.")) { - $key = str_replace("item.", "", $key); + $tmp_key = str_replace("item.", "", $key); - if($key == 'type_id') - $key = 'type'; + if($tmp_key == 'type_id') + $tmp_key = 'type'; - if($key == 'tax_id') - $key = 'tax_category'; + if($tmp_key == 'tax_id') + $tmp_key = 'tax_category'; - if (property_exists($item, $key)) { - $item_array[$key] = $item->{$key}; + if (property_exists($item, $tmp_key)) { + $item_array[$key] = $item->{$tmp_key}; } else { $item_array[$key] = ''; diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index a8ae0fb5fbae..7262b6ef5d0b 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -180,8 +180,6 @@ class StoreClientRequest extends Request public function messages() { return [ - // 'unique' => ctrans('validation.unique', ['attribute' => ['email','number']), - //'required' => trans('validation.required', ['attribute' => 'email']), 'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']), 'currency_code' => 'Currency code does not exist', ]; diff --git a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php index ec629276068a..7099cc6317ae 100644 --- a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php @@ -26,12 +26,18 @@ class StoreGroupSettingRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', GroupSetting::class) && auth()->user()->account->hasFeature(Account::FEATURE_API); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', GroupSetting::class) && $user->account->hasFeature(Account::FEATURE_API); } public function rules() { - $rules['name'] = 'required|unique:group_settings,name,null,null,company_id,'.auth()->user()->companyId(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $rules['name'] = 'required|unique:group_settings,name,null,null,company_id,'.$user->companyId(); $rules['settings'] = new ValidClientGroupSettingsRule(); diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 87c967c883bf..90b12ab96adc 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -1171,7 +1171,7 @@ class ReportCsvGenerationTest extends TestCase public function testQuoteItemsCustomColumnsCsvGeneration() { - \App\Models\Quote::factory()->create([ + $q = \App\Models\Quote::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id, @@ -1217,7 +1217,6 @@ class ReportCsvGenerationTest extends TestCase $csv = $response->streamedContent(); - $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote Number')); $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Item Quantity')); From 979036a27b66745f6672c5d28d4c96c4992466ac Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 10:34:17 +1100 Subject: [PATCH 3/6] Fixes for item exports --- app/Export/CSV/BaseExport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index fd1c1b5a3ef4..84c92439569c 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -1154,9 +1154,9 @@ class BaseExport $clean_row[$key]['entity'] = $report_keys[0]; $clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0]; $clean_row[$key]['hashed_id'] = $report_keys[0] == $entity ? null : $resource->{$report_keys[0]}->hashed_id ?? null; - $clean_row[$key]['value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$report_keys[1]]; + $clean_row[$key]['value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$value]; $clean_row[$key]['identifier'] = $value; - $clean_row[$key]['display_value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$report_keys[1]]; + $clean_row[$key]['display_value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$value]; } From 238d6668e85eb372ade5bdbc37b17acfaa03980e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 16:30:11 +1100 Subject: [PATCH 4/6] Improvements to type casts --- app/DataMapper/Settings/SettingsData.php | 515 ++++++++++++++++++ .../GroupSetting/StoreGroupSettingRequest.php | 55 +- .../UpdateGroupSettingRequest.php | 15 +- tests/Feature/GroupSettingTest.php | 90 ++- tests/Unit/GroupSettingsTest.php | 4 + 5 files changed, 658 insertions(+), 21 deletions(-) create mode 100644 app/DataMapper/Settings/SettingsData.php diff --git a/app/DataMapper/Settings/SettingsData.php b/app/DataMapper/Settings/SettingsData.php new file mode 100644 index 000000000000..b76379806ff6 --- /dev/null +++ b/app/DataMapper/Settings/SettingsData.php @@ -0,0 +1,515 @@ + $value) { + + try{ + settype($object->{$key}, gettype($this->{$key})); + } + catch(\Exception | \Error | \Throwable $e){ + + if(property_exists($this, $key)) + $object->{$key} = $this->{$key}; + else + unset($object->{$key}); + + } + + // if(!property_exists($this, $key)) { + // unset($object->{$key}); + // } + // elseif(is_array($object->{$key}) && gettype($this->{$key} != 'array')){ + // $object->{$key} = $this->{$key}; + // } + // else { + // settype($object->{$key}, gettype($this->{$key})); + // } + } + } + $this->object = $object; + + return $this; + } + + public function toObject(): object + { + return (object)$this->object; + } + + public function toArray(): array + { + return (array)$this->object; + } +} \ No newline at end of file diff --git a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php index 7099cc6317ae..d35e7ac2bc78 100644 --- a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php @@ -11,11 +11,13 @@ namespace App\Http\Requests\GroupSetting; -use App\DataMapper\ClientSettings; -use App\Http\Requests\Request; -use App\Http\ValidationRules\ValidClientGroupSettingsRule; use App\Models\Account; use App\Models\GroupSetting; +use App\Http\Requests\Request; +use App\DataMapper\ClientSettings; +use App\DataMapper\CompanySettings; +use App\DataMapper\Settings\SettingsData; +use App\Http\ValidationRules\ValidClientGroupSettingsRule; class StoreGroupSettingRequest extends Request { @@ -48,15 +50,12 @@ class StoreGroupSettingRequest extends Request { $input = $this->all(); - $group_settings = ClientSettings::defaults(); - - if (array_key_exists('settings', $input) && ! empty($input['settings'])) { - foreach ($input['settings'] as $key => $value) { - $group_settings->{$key} = $value; - } + if (array_key_exists('settings', $input)) { + $input['settings'] = $this->filterSaveableSettings($input['settings']); + } + else { + $input['settings'] = (array)ClientSettings::defaults(); } - - $input['settings'] = (array)$group_settings; $this->replace($input); } @@ -67,4 +66,38 @@ class StoreGroupSettingRequest extends Request 'settings' => 'settings must be a valid json structure', ]; } + + /** + * For the hosted platform, we restrict the feature settings. + * + * This method will trim the company settings object + * down to the free plan setting properties which + * are saveable + * + * @param object $settings + * @return array $settings + */ + private function filterSaveableSettings($settings) + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $settings_data = new SettingsData(); + $settings = $settings_data->cast($settings)->toObject(); + + if (! $user->account->isFreeHostedClient()) { + return (array)$settings; + } + + $saveable_casts = CompanySettings::$free_plan_casts; + + foreach ($settings as $key => $value) { + if (! array_key_exists($key, $saveable_casts)) { + unset($settings->{$key}); + } + } + + return (array)$settings; + } + } diff --git a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php index 84120cc3475a..a264d74c018d 100644 --- a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php @@ -11,8 +11,9 @@ namespace App\Http\Requests\GroupSetting; -use App\DataMapper\CompanySettings; use App\Http\Requests\Request; +use App\DataMapper\CompanySettings; +use App\DataMapper\Settings\SettingsData; use App\Http\ValidationRules\ValidClientGroupSettingsRule; class UpdateGroupSettingRequest extends Request @@ -62,10 +63,14 @@ class UpdateGroupSettingRequest extends Request */ private function filterSaveableSettings($settings) { - $account = $this->group_setting->company->account; + /** @var \App\Models\User $user */ + $user = auth()->user(); - if (! $account->isFreeHostedClient()) { - return $settings; + $settings_data = new SettingsData(); + $settings = $settings_data->cast($settings)->toObject(); + + if (! $user->account->isFreeHostedClient()) { + return (array)$settings; } $saveable_casts = CompanySettings::$free_plan_casts; @@ -75,7 +80,7 @@ class UpdateGroupSettingRequest extends Request unset($settings->{$key}); } } - + return (array)$settings; } } diff --git a/tests/Feature/GroupSettingTest.php b/tests/Feature/GroupSettingTest.php index da637d39506c..98bad3daa703 100644 --- a/tests/Feature/GroupSettingTest.php +++ b/tests/Feature/GroupSettingTest.php @@ -11,20 +11,21 @@ namespace Tests\Feature; +use Tests\TestCase; +use Tests\MockAccountData; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; -use Tests\TestCase; +use App\DataMapper\Settings\SettingsData; +use Spatie\LaravelData\Support\Wrapping\WrapExecutionType; class GroupSettingTest extends TestCase { use MakesHash; - - //use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp(): void { parent::setUp(); @@ -38,6 +39,85 @@ class GroupSettingTest extends TestCase $this->makeTestData(); } + public function testCastingMagic() + { + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = ''; + $settings->tax_rate1 = 0; + $s = new SettingsData(); + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("", $settings->tax_name1); + $settings = null; + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = "1"; + $settings->tax_rate1 = 0; + + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("1", $settings->tax_name1); + + $settings = $s->cast($settings)->toArray(); + $this->assertEquals("1", $settings['tax_name1']); + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = []; + $settings->tax_rate1 = 0; + + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("", $settings->tax_name1); + + $settings = $s->cast($settings)->toArray(); + $this->assertEquals("", $settings['tax_name1']); + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = new \stdClass; + $settings->tax_rate1 = 0; + + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("", $settings->tax_name1); + + $settings = $s->cast($settings)->toArray(); + $this->assertEquals("", $settings['tax_name1']); + + + + // nlog(json_encode($settings)); + } + + public function testTaxNameInGroupFilters() + { + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = ''; + $settings->tax_rate1 = 0; + + $data = [ + 'name' => 'testX', + 'settings' => $settings, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/group_settings', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals("", (string)NULL); + $this->assertNotNull($arr['data']['settings']['tax_name1']); + } + public function testAddGroupFilters() { diff --git a/tests/Unit/GroupSettingsTest.php b/tests/Unit/GroupSettingsTest.php index 6dd5b662eba0..4874753e5fa2 100644 --- a/tests/Unit/GroupSettingsTest.php +++ b/tests/Unit/GroupSettingsTest.php @@ -28,6 +28,10 @@ class GroupSettingsTest extends TestCase use DatabaseTransactions; use ClientGroupSettingsSaver; + public $company_settings; + public $client_settings; + public $settings; + protected function setUp() :void { parent::setUp(); From 33c8c713b1099865a152102a99cf6bcd21692554 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 16:40:53 +1100 Subject: [PATCH 5/6] fixes for deleted payments being displayed on invoices with variable --- app/Services/Payment/DeletePayment.php | 6 +++++- app/Utils/HtmlEngine.php | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 1e803faf10cd..8884dbdef622 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -109,7 +109,11 @@ class DeletePayment if ($paymentable_invoice->balance == $paymentable_invoice->amount) { $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); - } else { + } + elseif($paymentable_invoice->balance == 0){ + $paymentable_invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); + } + else { $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); } } else { diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 538b40b1d311..01d2d7d6fe43 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -670,16 +670,16 @@ class HtmlEngine $data['$payment.transaction_reference'] = ['value' => '', 'label' => ctrans('texts.transaction_reference')]; - if ($this->entity_string == 'invoice' && $this->entity->payments()->exists()) { + if ($this->entity_string == 'invoice' && $this->entity->net_payments()->exists()) { $payment_list = '

'; - foreach ($this->entity->payments as $payment) { + foreach ($this->entity->net_payments as $payment) { $payment_list .= ctrans('texts.payment_subject') . ": " . $this->formatDate($payment->date, $this->client->date_format()) . " :: " . Number::formatMoney($payment->amount, $this->client) ." :: ". GatewayType::getAlias($payment->gateway_type_id) . "
"; } $data['$payments'] = ['value' => $payment_list, 'label' => ctrans('texts.payments')]; - $payment = $this->entity->payments()->first(); + $payment = $this->entity->net_payments()->first(); $data['$payment.custom1'] = ['value' => $payment->custom_value1, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')]; $data['$payment.custom2'] = ['value' => $payment->custom_value2, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')]; From 28f3de3f284498f4ce3e13bc0d293d2edea30194 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 21:36:45 +1100 Subject: [PATCH 6/6] Update postmark to only update existing records with message IDs --- app/Jobs/PostMark/ProcessPostmarkWebhook.php | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 18dde45e430c..8e4429ca148d 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -56,6 +56,23 @@ class ProcessPostmarkWebhook implements ShouldQueue { } + private function getSystemLog(string $message_id): ?SystemLog + { + return SystemLog::query() + ->where('company_id', $this->invitation->company_id) + ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->whereJsonContains('log', ['MessageID' => $message_id]) + ->orderBy('id','desc') + ->first(); + + } + + private function updateSystemLog(SystemLog $system_log, array $data): void + { + $system_log->log = $data; + $system_log->save(); + } + /** * Execute the job. * @@ -135,6 +152,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl){ + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger( $data, SystemLog::CATEGORY_MAIL, @@ -166,6 +190,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl) { + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger( $data, SystemLog::CATEGORY_MAIL, @@ -217,6 +248,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl) { + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); // if(config('ninja.notification.slack')) @@ -263,6 +301,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl) { + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); if (config('ninja.notification.slack')) {