From 721bc60f64ca25d00b110db9e1f7955a4e0c2a0b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 10:26:47 +1000 Subject: [PATCH 01/29] Update texts for tax info placeholders --- app/Models/Client.php | 2 +- app/Models/Company.php | 2 +- app/Utils/HtmlEngine.php | 37 ++++++++++++++++++++++++++++++++++++- lang/en/texts.php | 3 +++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/app/Models/Client.php b/app/Models/Client.php index 570e497ebc2d..9f69bfc0d1a3 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -58,7 +58,7 @@ use Illuminate\Contracts\Translation\HasLocalePreference; * @property string|null $city * @property string|null $state * @property string|null $postal_code - * @property string|null $country_id + * @property int|null $country_id * @property string|null $custom_value1 * @property string|null $custom_value2 * @property string|null $custom_value3 diff --git a/app/Models/Company.php b/app/Models/Company.php index 5fb8a4477791..0c8ac30a5990 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -58,7 +58,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string|null $portal_domain * @property int $enable_modules * @property object $custom_fields - * @property object $settings + * @property \App\DataMapper\CompanySettings $settings * @property string $slack_webhook_url * @property string $google_analytics_key * @property int|null $created_at diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 621568d2d0f2..01e4efd532c4 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -35,16 +35,22 @@ class HtmlEngine use MakesHash; use DesignCalculator; + /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\RecurringInvoice | \App\Models\Quote $entity **/ public $entity; + /** @var \App\Models\CreditInvitation | CreditInvitation | \App\Models\RecurringInvoiceInvitation | \App\Models\QuoteInvitation $invitation **/ public $invitation; + /** @var \App\Models\Client $client */ public $client; + /** @var \App\Models\ClientContact $contact */ public $contact; + /** @var \App\Models\Company $company */ public $company; + /** @var \App\DataMapper\CompanySettings $settings **/ public $settings; public $entity_calc; @@ -53,6 +59,13 @@ class HtmlEngine private $helpers; + + /** + * __construct + * + * @param InvoiceInvitation | CreditInvitation | RecurringInvoiceInvitation | QuoteInvitation $invitation + * @return void + */ public function __construct($invitation) { $this->invitation = $invitation; @@ -150,7 +163,9 @@ class HtmlEngine $data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')]; $data['$payment_qrcode'] = ['value' => $this->invitation->getPaymentQrCode(), 'label' => ctrans('texts.pay_now')]; $data['$exchange_rate'] = ['value' => $this->entity->exchange_rate ?: ' ', 'label' => ctrans('texts.exchange_rate')]; - + $data['$triangular_tax'] = ['value' => ctrans('texts.triangular_tax'), 'label' => '']; + $data['$tax_info'] = ['value' => $this->taxLabel(), 'label' => '']; + if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') { $data['$entity'] = ['value' => ctrans('texts.invoice'), 'label' => ctrans('texts.invoice')]; $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; @@ -676,6 +691,26 @@ class HtmlEngine return $data; } + + /** + * Returns a localized string for tax compliance purposes + * + * @return string + */ + private function taxLabel(): string + { + $tax_label = ''; + + if (collect($this->entity->line_items)->contains('tax_id', \App\Models\Product::PRODUCT_TYPE_REVERSE_TAX)) { + $tax_label .= ctrans('texts.reverse_tax_info') . "\n"; + } + + if($this->client->country_id !== (int)$this->company->settings->country_id){ + $tax_label .= ctrans('texts.tax_info') . "\n"; + } + + return $tax_label; + } private function getBalance() { diff --git a/lang/en/texts.php b/lang/en/texts.php index d75fe8f3bbda..01cd88c8b0df 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5143,6 +5143,9 @@ $LANG = array( 'is_tax_exempt' => 'Tax Exempt', 'drop_files_here' => 'Drop files here', 'upload_files' => 'Upload Files', + 'triangular_tax_info' => 'Intra-community triangular transaction', + 'intracommunity_tax_info' => 'Tax-free intra-community delivery', + 'reverse_tax_info' => 'Please note that this supply is subject to reverse charge', ); return $LANG; From b426788de349bb9b9abdbca51a50a4df6d8822d0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 10:47:50 +1000 Subject: [PATCH 02/29] Updates for tests for html generation --- app/Utils/HtmlEngine.php | 4 ++-- tests/Feature/ClientTest.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 01e4efd532c4..00ab85d568be 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -702,11 +702,11 @@ class HtmlEngine $tax_label = ''; if (collect($this->entity->line_items)->contains('tax_id', \App\Models\Product::PRODUCT_TYPE_REVERSE_TAX)) { - $tax_label .= ctrans('texts.reverse_tax_info') . "\n"; + $tax_label .= ctrans('texts.reverse_tax_info') . "
"; } if($this->client->country_id !== (int)$this->company->settings->country_id){ - $tax_label .= ctrans('texts.tax_info') . "\n"; + $tax_label .= ctrans('texts.intracommunity_tax_info') . "
"; } return $tax_label; diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 8fcef3d6c14f..0244a3e8227b 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -41,6 +41,8 @@ class ClientTest extends TestCase use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -63,7 +65,6 @@ class ClientTest extends TestCase $this->makeTestData(); } - public function testClientMergeContactDrop() { From 60b8402d763f2063279e5c5adb607193d48cc522 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 10:53:10 +1000 Subject: [PATCH 03/29] Updates for tests for html generation with special translations --- app/Utils/HtmlEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 00ab85d568be..97b2e7ea46f5 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -705,7 +705,7 @@ class HtmlEngine $tax_label .= ctrans('texts.reverse_tax_info') . "
"; } - if($this->client->country_id !== (int)$this->company->settings->country_id){ + if((int)$this->client->country_id !== (int)$this->company->settings->country_id){ $tax_label .= ctrans('texts.intracommunity_tax_info') . "
"; } From 22d5f6e4f0060361fe6f7addd791d056342cc6c5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 18:29:33 +1000 Subject: [PATCH 04/29] Fixes for yodlee --- app/Http/Controllers/ClientController.php | 47 +++++++----- app/Http/Controllers/CompanyController.php | 72 ++++++++++++------- .../Company/DefaultCompanyRequest.php | 5 +- app/Jobs/Bank/ProcessBankTransactions.php | 4 +- routes/api.php | 3 +- 5 files changed, 84 insertions(+), 47 deletions(-) diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 59c6d5c576dd..060be29f0d31 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -11,30 +11,31 @@ namespace App\Http\Controllers; -use App\Events\Client\ClientWasCreated; -use App\Events\Client\ClientWasUpdated; +use App\Utils\Ninja; +use App\Models\Client; +use App\Models\Account; +use Illuminate\Http\Response; use App\Factory\ClientFactory; use App\Filters\ClientFilters; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\Uploadable; +use App\Utils\Traits\BulkOptions; +use App\Jobs\Client\UpdateTaxData; +use App\Utils\Traits\SavesDocuments; +use App\Repositories\ClientRepository; +use App\Events\Client\ClientWasCreated; +use App\Events\Client\ClientWasUpdated; +use App\Transformers\ClientTransformer; +use Illuminate\Support\Facades\Storage; use App\Http\Requests\Client\BulkClientRequest; -use App\Http\Requests\Client\CreateClientRequest; -use App\Http\Requests\Client\DestroyClientRequest; use App\Http\Requests\Client\EditClientRequest; -use App\Http\Requests\Client\PurgeClientRequest; use App\Http\Requests\Client\ShowClientRequest; +use App\Http\Requests\Client\PurgeClientRequest; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Client\CreateClientRequest; use App\Http\Requests\Client\UpdateClientRequest; use App\Http\Requests\Client\UploadClientRequest; -use App\Models\Account; -use App\Models\Client; -use App\Repositories\ClientRepository; -use App\Transformers\ClientTransformer; -use App\Utils\Ninja; -use App\Utils\Traits\BulkOptions; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\SavesDocuments; -use App\Utils\Traits\Uploadable; -use Illuminate\Http\Response; -use Illuminate\Support\Facades\Storage; +use App\Http\Requests\Client\DestroyClientRequest; /** * Class ClientController. @@ -285,4 +286,18 @@ class ClientController extends BaseController return $this->itemResponse($merged_client); } + + /** + * Updates the client's tax data + * + * @param PurgeClientRequest $request + * @param Client $client + * @return \Illuminate\Http\JsonResponse + */ + public function updateTaxData(PurgeClientRequest $request, Client $client) + { + (new UpdateTaxData($client, $client->company))->handle(); + + return $this->itemResponse($client->fresh()); + } } diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 3741c61359c0..4347f6a28b10 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -11,38 +11,39 @@ namespace App\Http\Controllers; -use App\DataMapper\Analytics\AccountDeleted; -use App\DataMapper\CompanySettings; -use App\Http\Requests\Company\CreateCompanyRequest; -use App\Http\Requests\Company\DefaultCompanyRequest; -use App\Http\Requests\Company\DestroyCompanyRequest; -use App\Http\Requests\Company\EditCompanyRequest; -use App\Http\Requests\Company\ShowCompanyRequest; -use App\Http\Requests\Company\StoreCompanyRequest; -use App\Http\Requests\Company\UpdateCompanyRequest; -use App\Http\Requests\Company\UploadCompanyRequest; -use App\Jobs\Company\CreateCompany; -use App\Jobs\Company\CreateCompanyPaymentTerms; -use App\Jobs\Company\CreateCompanyTaskStatuses; -use App\Jobs\Company\CreateCompanyToken; -use App\Jobs\Mail\NinjaMailerJob; -use App\Jobs\Mail\NinjaMailerObject; -use App\Mail\Company\CompanyDeleted; +use Str; +use App\Utils\Ninja; use App\Models\Account; use App\Models\Company; use App\Models\CompanyUser; -use App\Repositories\CompanyRepository; -use App\Transformers\CompanyTransformer; -use App\Transformers\CompanyUserTransformer; -use App\Utils\Ninja; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\SavesDocuments; -use App\Utils\Traits\Uploadable; -use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Http\Response; -use Illuminate\Support\Facades\Storage; -use Str; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\Uploadable; +use App\Jobs\Mail\NinjaMailerJob; +use App\DataMapper\CompanySettings; +use App\Jobs\Company\CreateCompany; +use App\Jobs\Company\CompanyTaxRate; +use App\Jobs\Mail\NinjaMailerObject; +use App\Mail\Company\CompanyDeleted; +use App\Utils\Traits\SavesDocuments; use Turbo124\Beacon\Facades\LightLogs; +use App\Repositories\CompanyRepository; +use Illuminate\Support\Facades\Storage; +use App\Jobs\Company\CreateCompanyToken; +use App\Transformers\CompanyTransformer; +use App\DataMapper\Analytics\AccountDeleted; +use App\Transformers\CompanyUserTransformer; +use Illuminate\Foundation\Bus\DispatchesJobs; +use App\Jobs\Company\CreateCompanyPaymentTerms; +use App\Jobs\Company\CreateCompanyTaskStatuses; +use App\Http\Requests\Company\EditCompanyRequest; +use App\Http\Requests\Company\ShowCompanyRequest; +use App\Http\Requests\Company\StoreCompanyRequest; +use App\Http\Requests\Company\CreateCompanyRequest; +use App\Http\Requests\Company\UpdateCompanyRequest; +use App\Http\Requests\Company\UploadCompanyRequest; +use App\Http\Requests\Company\DefaultCompanyRequest; +use App\Http\Requests\Company\DestroyCompanyRequest; /** * Class CompanyController. @@ -679,4 +680,21 @@ class CompanyController extends BaseController return $this->itemResponse($company->fresh()); } + + public function updateOriginTaxData(DefaultCompanyRequest $request, Company $company) + { + + if($company->settings->country_id == "840" && !$company?->account->isFreeHostedClient()) + { + try { + (new CompanyTaxRate($company))->handle(); + } catch(\Exception $e) { + return response()->json(['message' => 'There was a problem updating the tax rates. Please try again.'], 400); + } + } + else + return response()->json(['message' => 'Tax configuration not available due to settings / plan restriction.'], 400); + + return $this->itemResponse($company->fresh()); + } } diff --git a/app/Http/Requests/Company/DefaultCompanyRequest.php b/app/Http/Requests/Company/DefaultCompanyRequest.php index a2de14480409..342b71fe7e3d 100644 --- a/app/Http/Requests/Company/DefaultCompanyRequest.php +++ b/app/Http/Requests/Company/DefaultCompanyRequest.php @@ -22,7 +22,10 @@ class DefaultCompanyRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } public function rules() diff --git a/app/Jobs/Bank/ProcessBankTransactions.php b/app/Jobs/Bank/ProcessBankTransactions.php index c5af8fd49a02..2015b19d74d8 100644 --- a/app/Jobs/Bank/ProcessBankTransactions.php +++ b/app/Jobs/Bank/ProcessBankTransactions.php @@ -109,8 +109,8 @@ class ProcessBankTransactions implements ShouldQueue $account = $at->transform($account_summary); if($account[0]['current_balance']) { - $this->bank_integration->balance = $account['current_balance']; - $this->bank_integration->currency = $account['account_currency']; + $this->bank_integration->balance = $account[0]['current_balance']; + $this->bank_integration->currency = $account[0]['account_currency']; $this->bank_integration->save(); } diff --git a/routes/api.php b/routes/api.php index 0331edea846f..393805f6b1df 100644 --- a/routes/api.php +++ b/routes/api.php @@ -157,6 +157,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::resource('clients', ClientController::class); // name = (clients. index / create / show / update / destroy / edit Route::put('clients/{client}/upload', [ClientController::class, 'upload'])->name('clients.upload'); Route::post('clients/{client}/purge', [ClientController::class, 'purge'])->name('clients.purge')->middleware('password_protected'); + Route::post('clients/{client}/updateTaxData', [ClientController::class, 'updateTaxData'])->name('clients.purge')->middleware('throttle:3,1'); Route::post('clients/{client}/{mergeable_client}/merge', [ClientController::class, 'merge'])->name('clients.merge')->middleware('password_protected'); Route::post('clients/bulk', [ClientController::class, 'bulk'])->name('clients.bulk'); @@ -171,11 +172,11 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('companies/purge/{company}', [MigrationController::class, 'purgeCompany'])->middleware('password_protected'); Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected'); - Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit Route::put('companies/{company}/upload', [CompanyController::class, 'upload']); Route::post('companies/{company}/default', [CompanyController::class, 'default']); + Route::post('companies/updateOriginTaxData/{company}', [CompanyController::class, 'updateOriginTaxData'])->middleware('throttle:3,1'); Route::get('company_ledger', [CompanyLedgerController::class, 'index'])->name('company_ledger.index'); From e5140e28dc809903bc8d2b785046ce235f8d2c14 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 09:34:52 +1000 Subject: [PATCH 05/29] Additional currencies --- .../Controllers/Bank/YodleeController.php | 31 ++++++++++-- .../Requests/Yodlee/YodleeAuthRequest.php | 1 + ...23_08_09_224955_add_nicaragua_currency.php | 47 +++++++++++++++++++ resources/views/pdf-designs/business.html | 2 +- routes/api.php | 2 + 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2023_08_09_224955_add_nicaragua_currency.php diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index 8f4c1691afdc..34bfa509c6ed 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -11,12 +11,14 @@ namespace App\Http\Controllers\Bank; +use App\Helpers\Bank\Yodlee\DTO\AccountSummary; +use Illuminate\Http\Request; +use App\Models\BankIntegration; use App\Helpers\Bank\Yodlee\Yodlee; use App\Http\Controllers\BaseController; -use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactions; -use App\Models\BankIntegration; -use Illuminate\Http\Request; +use App\Http\Requests\Yodlee\YodleeAuthRequest; +use App\Http\Requests\Yodlee\YodleeAdminRequest; class YodleeController extends BaseController { @@ -277,4 +279,27 @@ class YodleeController extends BaseController // return response()->json(['message' => 'Unauthorized'], 403); } + + public function accountStatus(YodleeAdminRequest $request, $account_number) + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $bank_integration = BankIntegration::query() + ->withTrashed() + ->where('company_id', $user->company()->id) + ->where('account_id', $account_number) + ->exists(); + + if(!$bank_integration) + return response()->json(['message' => 'Account does not exist.'], 400); + + $yodlee = new Yodlee($user->account->bank_integration_account_id); + + $summary = $yodlee->getAccountSummary($account_number); + + $transformed_summary = AccountSummary::from($summary[0]); + + return response()->json($transformed_summary, 200); + } } diff --git a/app/Http/Requests/Yodlee/YodleeAuthRequest.php b/app/Http/Requests/Yodlee/YodleeAuthRequest.php index ff267c5f98f5..942139a2e25e 100644 --- a/app/Http/Requests/Yodlee/YodleeAuthRequest.php +++ b/app/Http/Requests/Yodlee/YodleeAuthRequest.php @@ -39,6 +39,7 @@ class YodleeAuthRequest extends Request return []; } + /** @var $token */ public function getTokenContent() { if ($this->state) { diff --git a/database/migrations/2023_08_09_224955_add_nicaragua_currency.php b/database/migrations/2023_08_09_224955_add_nicaragua_currency.php new file mode 100644 index 000000000000..c7c92fc2c3e5 --- /dev/null +++ b/database/migrations/2023_08_09_224955_add_nicaragua_currency.php @@ -0,0 +1,47 @@ +id = 118; + $cur->code = 'NIO'; + $cur->name = 'Nicaraguan Córdoba'; + $cur->symbol = 'C$'; + $cur->thousand_separator = ','; + $cur->decimal_separator = '.'; + $cur->precision = 2; + $cur->save(); + } + + Schema::table('vendors', function (Blueprint $table) { + $table->unsignedInteger('language_id')->nullable(); + $table->timestamp('last_login')->nullable(); + }); + + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +}; diff --git a/resources/views/pdf-designs/business.html b/resources/views/pdf-designs/business.html index b5377d58052b..090bee0fb7d2 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -190,7 +190,7 @@ padding-top: 0.5rem; padding-bottom: 0.8rem; padding-left: 0.7rem; - page-break-inside:auto; + /*page-break-inside:auto; this may cause weird breaking*/ overflow: visible !important; } diff --git a/routes/api.php b/routes/api.php index 393805f6b1df..3210f8c92e75 100644 --- a/routes/api.php +++ b/routes/api.php @@ -383,6 +383,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('subscriptions/bulk', [SubscriptionController::class, 'bulk'])->name('subscriptions.bulk'); Route::get('statics', StaticController::class); // Route::post('apple_pay/upload_file','ApplyPayController::class, 'upload'); + + Route::post('api/v1/yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); }); Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:10,1'); From 79c4d4989699569915828a42ee749270479856f0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 09:39:59 +1000 Subject: [PATCH 06/29] add translations for currencies --- app/Utils/TranslationHelper.php | 2 +- database/seeders/CurrenciesSeeder.php | 1 + lang/en/texts.php | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Utils/TranslationHelper.php b/app/Utils/TranslationHelper.php index e401f806bb91..15850f62e445 100644 --- a/app/Utils/TranslationHelper.php +++ b/app/Utils/TranslationHelper.php @@ -12,7 +12,7 @@ namespace App\Utils; use App\Models\PaymentTerm; -use Cache; +use \Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; class TranslationHelper diff --git a/database/seeders/CurrenciesSeeder.php b/database/seeders/CurrenciesSeeder.php index 65778f979b29..3005940fa1f9 100644 --- a/database/seeders/CurrenciesSeeder.php +++ b/database/seeders/CurrenciesSeeder.php @@ -140,6 +140,7 @@ class CurrenciesSeeder extends Seeder ['id' => 115, 'name' => 'Libyan Dinar', 'code' => 'LYD', 'symbol' => 'LD', 'precision' => '3', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['id' => 116, 'name' => 'Silver Troy Ounce', 'code' => 'XAG', 'symbol' => 'XAG', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['id' => 117, 'name' => 'Gold Troy Ounce', 'code' => 'XAU', 'symbol' => 'XAU', 'precision' => '3', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['id' => 118, 'name' => 'Nicaraguan Córdoba', 'code' => 'NIO', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ]; foreach ($currencies as $currency) { diff --git a/lang/en/texts.php b/lang/en/texts.php index 01cd88c8b0df..de744e933230 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5146,6 +5146,7 @@ $LANG = array( 'triangular_tax_info' => 'Intra-community triangular transaction', 'intracommunity_tax_info' => 'Tax-free intra-community delivery', 'reverse_tax_info' => 'Please note that this supply is subject to reverse charge', + 'currency_nicaraguan_cordoba' => 'Nicaraguan Córdoba', ); return $LANG; From 92336b7f0533d151a5c278de028bb3ad54de4084 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 10:04:16 +1000 Subject: [PATCH 07/29] Tests for adding language ID for vendors --- .../Bank/Yodlee/DTO/AccountSummary.php | 111 ++++++++++++++++++ .../Requests/Vendor/StoreVendorRequest.php | 17 ++- .../Requests/Vendor/UpdateVendorRequest.php | 14 ++- app/Models/Vendor.php | 3 + app/Transformers/VendorTransformer.php | 1 + lang/en/texts.php | 9 ++ lang/fr_CA/texts.php | 27 +++-- routes/api.php | 2 +- tests/Feature/VendorApiTest.php | 74 ++++++++++++ 9 files changed, 240 insertions(+), 18 deletions(-) create mode 100644 app/Helpers/Bank/Yodlee/DTO/AccountSummary.php diff --git a/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php b/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php new file mode 100644 index 000000000000..f0771dde9f71 --- /dev/null +++ b/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php @@ -0,0 +1,111 @@ +put('current_balance', (array)$properties['currentBalance']['amount'] ?? ''); + $properties->put('account_currency', (array)$properties['currentBalance']['currency'] ?? ''); + + return $properties; + } +} \ No newline at end of file diff --git a/app/Http/Requests/Vendor/StoreVendorRequest.php b/app/Http/Requests/Vendor/StoreVendorRequest.php index 777930dd70b4..2de5edeca00c 100644 --- a/app/Http/Requests/Vendor/StoreVendorRequest.php +++ b/app/Http/Requests/Vendor/StoreVendorRequest.php @@ -26,17 +26,23 @@ class StoreVendorRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', Vendor::class); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', Vendor::class); } public function rules() { + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; $rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email'; if (isset($this->number)) { - $rules['number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id); + $rules['number'] = Rule::unique('vendors')->where('company_id', $user->company()->id); } $rules['currency_id'] = 'bail|required|exists:currencies,id'; @@ -53,15 +59,20 @@ class StoreVendorRequest extends Request $rules['file'] = $this->file_validation; } + $rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id'; + return $rules; } public function prepareForValidation() { + /** @var \App\Models\User $user */ + $user = auth()->user(); + $input = $this->all(); if (!array_key_exists('currency_id', $input) || empty($input['currency_id'])) { - $input['currency_id'] = auth()->user()->company()->settings->currency_id; + $input['currency_id'] = $user->company()->settings->currency_id; } $input = $this->decodePrimaryKeys($input); diff --git a/app/Http/Requests/Vendor/UpdateVendorRequest.php b/app/Http/Requests/Vendor/UpdateVendorRequest.php index 2010b38d1d1e..867b4541c0bf 100644 --- a/app/Http/Requests/Vendor/UpdateVendorRequest.php +++ b/app/Http/Requests/Vendor/UpdateVendorRequest.php @@ -28,17 +28,21 @@ class UpdateVendorRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('edit', $this->vendor); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('edit', $this->vendor); } public function rules() { - /* Ensure we have a client name, and that all emails are unique*/ - + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules['country_id'] = 'integer'; if ($this->number) { - $rules['number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id)->ignore($this->vendor->id); + $rules['number'] = Rule::unique('vendors')->where('company_id', $user->company()->id)->ignore($this->vendor->id); } $rules['contacts.*.email'] = 'nullable|distinct'; @@ -56,6 +60,8 @@ class UpdateVendorRequest extends Request $rules['file'] = $this->file_validation; } + $rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id'; + return $rules; } diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 43ed415da681..7dfbfa60e7ce 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -51,6 +51,8 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $vendor_hash * @property string|null $public_notes * @property string|null $id_number + * @property string|null $language_id + * @property int|null $last_login * @property-read \Illuminate\Database\Eloquent\Collection $activities * @property-read int|null $activities_count * @property-read \App\Models\User|null $assigned_user @@ -140,6 +142,7 @@ class Vendor extends BaseModel 'custom_value3', 'custom_value4', 'number', + 'language_id', ]; protected $casts = [ diff --git a/app/Transformers/VendorTransformer.php b/app/Transformers/VendorTransformer.php index f2acf862afcd..bbf1bfcde77e 100644 --- a/app/Transformers/VendorTransformer.php +++ b/app/Transformers/VendorTransformer.php @@ -103,6 +103,7 @@ class VendorTransformer extends EntityTransformer 'archived_at' => (int) $vendor->deleted_at, 'created_at' => (int) $vendor->created_at, 'number' => (string) $vendor->number ?: '', + 'language_id' => (string) $vendor->language_id ?: '', ]; } } diff --git a/lang/en/texts.php b/lang/en/texts.php index de744e933230..f9d8f3821afb 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5147,6 +5147,15 @@ $LANG = array( 'intracommunity_tax_info' => 'Tax-free intra-community delivery', 'reverse_tax_info' => 'Please note that this supply is subject to reverse charge', 'currency_nicaraguan_cordoba' => 'Nicaraguan Córdoba', + 'public' => 'Public', + 'private' => 'Private', + 'image' => 'Image', + 'other' => 'Other', + 'linked_to' => 'Linked To', + 'file_saved_in_path' => 'The file has been saved in :path', + 'unlinked_transactions' => 'Successfully unlinked :count transactions', + 'unlinked_transaction' => 'Successfully unlinked transaction', + 'view_dashboard_permission' => 'Allow user to access the dashboard, data is limited to available permissions', ); return $LANG; diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php index 46bdc30e79fc..a349743843c7 100644 --- a/lang/fr_CA/texts.php +++ b/lang/fr_CA/texts.php @@ -2189,7 +2189,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'credit_total' => 'Total du crédit', 'mark_billable' => 'Marquer comme facturable', 'billed' => 'Facturé', - 'company_variables' => 'Variables de la compagnie', + 'company_variables' => 'Variables de l\'entreprise', 'client_variables' => 'Variables du client', 'invoice_variables' => 'Variables de facture', 'navigation_variables' => 'Variables de navigation', @@ -2396,6 +2396,9 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'currency_cuban_peso' => 'Cuban Peso', 'currency_bz_dollar' => 'Dollar bélizien', + 'currency_libyan_dinar' => 'Dinar libyen', + 'currency_silver_troy_ounce' => 'Once troy d\'argent', + 'currency_gold_troy_ounce' => 'Once troy d\'or', 'review_app_help' => 'Nous espérons que votre utilisation de cette application vous est agréable.
Un commentaire de votre part serait grandement apprécié!', 'writing_a_review' => 'rédiger un commentaire', @@ -3264,8 +3267,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'contact_custom_value4' => 'Valeur personnalisée du contact 4', 'assigned_to_id' => 'Assigné à ID', 'created_by_id' => 'Créé par ID', - 'add_column' => 'Ajouter colonne', - 'edit_columns' => 'Éditer colonne', + 'add_column' => 'Ajouter une colonne', + 'edit_columns' => 'Éditer les colonnes', 'to_learn_about_gogle_fonts' => 'en savoir plus sur Google Fonts', 'refund_date' => 'Date de remboursement', 'multiselect' => 'Sélection multiple', @@ -3320,7 +3323,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'item_tax_rates' => 'Taux de taxe par article', 'configure_rates' => 'Configuration des taux', 'tax_settings_rates' => 'Taux de taxe', - 'accent_color' => 'Couleur de mise en évidence', + 'accent_color' => 'Couleur d\'accent', 'comma_sparated_list' => 'Liste séparée par virgule', 'single_line_text' => 'Ligne de texte simple', 'multi_line_text' => 'Zone de texte multilignes', @@ -3415,7 +3418,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'quote_details' => 'Informations de la soumission', 'credit_details' => 'Informations de crédit', 'product_columns' => 'Colonnes produit', - 'task_columns' => 'Colonnes tâches', + 'task_columns' => 'Colonnes tâche', 'add_field' => 'Ajouter un champ', 'all_events' => 'Ajouter un événement', 'owned' => 'Propriétaire', @@ -4209,7 +4212,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'copyright' => 'Droits d\'auteur', 'user_created_user' => ':user a créé :created_user à :time', 'company_deleted' => 'Entreprise supprimée', - 'company_deleted_body' => 'La compagnie [:company] a été supprimé par :user', + 'company_deleted_body' => 'L\'entreprise [:company] a été supprimé par :user', 'back_to' => 'Retour à :url', 'stripe_connect_migration_title' => 'Connectez votre compte Stripe', 'stripe_connect_migration_desc' => 'Invoice Ninja v5 utilise Stripe Connect pour lier votre compte Stripe à Invoice Ninja. Cela fournit une couche de sécurité supplémentaire pour votre compte. Maintenant que vos données ont migré, vous devez autoriser Stripe à accepter les paiements dans la v5.

Pour ce faire, accédez à Paramètres > Paiements en ligne > Configurer les passerelles. Cliquez sur Stripe Connect, puis sous Paramètres, cliquez sur Configurer la passerelle. Cela vous amènera à Stripe pour autoriser Invoice Ninja et à votre retour, votre compte sera lié !', @@ -4478,7 +4481,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'id' => 'Id', 'convert_to' => 'Convertir en', 'client_currency' => 'Devise du client', - 'company_currency' => 'Devise de la compagnie', + 'company_currency' => 'Devise de l\'entreprise', 'custom_emails_disabled_help' => 'Il est nécessaire de souscrire à un compte payant pour personnaliser les paramètres anti-pourriels', 'upgrade_to_add_company' => 'Augmenter votre plan pour ajouter des entreprises', 'file_saved_in_downloads_folder' => 'Le fichier a été sauvegardé dans le dossier Téléchargements', @@ -4944,7 +4947,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'sync_from' => 'Sync de', 'gateway_payment_text' => 'Factures: :invoices pour :amount pour :client', 'gateway_payment_text_no_invoice' => 'Paiement sans facture d\'un montant de :amount pour le client :client', - 'click_to_variables' => 'Cliquez ici pour voir toutes les variables', + 'click_to_variables' => 'Cliquez ici pour voir toutes les variables.', 'ship_to' => 'Livrer à', 'stripe_direct_debit_details' => 'Veuillez transférer dans le compte bancaire indiqué ci-dessus.', 'branch_name' => 'Nom de succursale', @@ -5127,9 +5130,13 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'activity_10_online' => ':contact a saisi un paiement :payment pour la facture :invoice pour :client', 'activity_10_manual' => ':user a saisi un paiement :payment pour la facture :invoice pour :client', 'default_payment_type' => 'Type de paiement par défaut', + 'number_precision' => 'Précision du nombre', + 'number_precision_help' => 'Contrôle le nombre de décimales supportées dans l\'interface', + 'is_tax_exempt' => 'Exonéré de taxe', + 'drop_files_here' => 'Déposez les fichiers ici', + 'upload_files' => 'Téléverser les fichiers', ); - return $LANG; -?> \ No newline at end of file +?> diff --git a/routes/api.php b/routes/api.php index 3210f8c92e75..93a84ea8f65d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -157,7 +157,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::resource('clients', ClientController::class); // name = (clients. index / create / show / update / destroy / edit Route::put('clients/{client}/upload', [ClientController::class, 'upload'])->name('clients.upload'); Route::post('clients/{client}/purge', [ClientController::class, 'purge'])->name('clients.purge')->middleware('password_protected'); - Route::post('clients/{client}/updateTaxData', [ClientController::class, 'updateTaxData'])->name('clients.purge')->middleware('throttle:3,1'); + Route::post('clients/{client}/updateTaxData', [ClientController::class, 'updateTaxData'])->name('clients.update_tax_data')->middleware('throttle:3,1'); Route::post('clients/{client}/{mergeable_client}/merge', [ClientController::class, 'merge'])->name('clients.merge')->middleware('password_protected'); Route::post('clients/bulk', [ClientController::class, 'bulk'])->name('clients.bulk'); diff --git a/tests/Feature/VendorApiTest.php b/tests/Feature/VendorApiTest.php index 79a8800f0f86..09383ea7e6ab 100644 --- a/tests/Feature/VendorApiTest.php +++ b/tests/Feature/VendorApiTest.php @@ -29,6 +29,8 @@ class VendorApiTest extends TestCase use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -40,6 +42,9 @@ class VendorApiTest extends TestCase $this->faker = \Faker\Factory::create(); Model::reguard(); + + // $this->withoutExceptionHandling(); + } public function testVendorGetFilter() @@ -52,10 +57,79 @@ class VendorApiTest extends TestCase $response->assertStatus(200); } + + public function testAddVendorLanguage200() + { + $data = [ + 'name' => $this->faker->firstName(), + 'language_id' => 2, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/vendors', $data)->assertStatus(200); + + $arr = $response->json(); + $this->assertEquals('2', $arr['data']['language_id']); + + $id = $arr['data']['id']; + + $data = [ + 'language_id' => 3, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/vendors/{$id}", $data); + + $response->assertStatus(200); + + $arr = $response->json(); + $this->assertEquals('3', $arr['data']['language_id']); + + } + + public function testAddVendorLanguage422() + { + $data = [ + 'name' => $this->faker->firstName(), + 'language_id' => '4431', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/vendors', $data)->assertStatus(422); + + } + + + public function testAddVendorLanguage() + { + $data = [ + 'name' => $this->faker->firstName(), + 'language_id' => '1', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/vendors', $data); + + $response->assertStatus(200); + $arr = $response->json(); + + $this->assertEquals('1', $arr['data']['language_id']); + } + + public function testAddVendorToInvoice() { $data = [ 'name' => $this->faker->firstName(), + 'language_id' => '', ]; $response = $this->withHeaders([ From c807ae864aea37a46b8f69bf1c7784a25eef14d0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 10:44:29 +1000 Subject: [PATCH 08/29] Tests for Vendor Locale --- app/Models/Vendor.php | 23 ++++++++++++-------- tests/Feature/VendorApiTest.php | 38 ++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 7dfbfa60e7ce..b49cdf4502fe 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -172,12 +172,12 @@ class Vendor extends BaseModel return $this->hasMany(VendorContact::class)->where('is_primary', true); } - public function documents() + public function documents(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); } - public function assigned_user() + public function assigned_user(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); } @@ -214,12 +214,12 @@ class Vendor extends BaseModel return $this->company->timezone(); } - public function company() + public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Company::class); } - public function user() + public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(User::class)->withTrashed(); } @@ -268,24 +268,29 @@ class Vendor extends BaseModel return $this->company->settings; } - public function purchase_order_filepath($invitation) + public function purchase_order_filepath($invitation): string { $contact_key = $invitation->contact->contact_key; return $this->company->company_key.'/'.$this->vendor_hash.'/'.$contact_key.'/purchase_orders/'; } - public function locale() + public function locale(): string { - return $this->company->locale(); + return $this->language ? $this->language->locale : $this->company->locale(); } - public function country() + public function language(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Language::class); + } + + public function country(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Country::class); } - public function date_format() + public function date_format(): string { return $this->company->date_format(); } diff --git a/tests/Feature/VendorApiTest.php b/tests/Feature/VendorApiTest.php index 09383ea7e6ab..2127a996576f 100644 --- a/tests/Feature/VendorApiTest.php +++ b/tests/Feature/VendorApiTest.php @@ -42,9 +42,45 @@ class VendorApiTest extends TestCase $this->faker = \Faker\Factory::create(); Model::reguard(); + } - // $this->withoutExceptionHandling(); + public function testVendorLocale() + { + $v = \App\Models\Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id + ]); + $this->assertNotNull($v->locale()); + } + + public function testVendorLocaleEn() + { + $v = \App\Models\Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'language_id' => '1' + ]); + + $this->assertEquals('en', $v->locale()); + } + + public function testVendorLocaleEnCompanyFallback() + { + $settings = $this->company->settings; + $settings->language_id = '2'; + + $c = \App\Models\Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $v = \App\Models\Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $c->id + ]); + + $this->assertEquals('it', $v->locale()); } public function testVendorGetFilter() From a9dffb335ee53ed760a641b4e09bbf8063051b47 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 10:56:34 +1000 Subject: [PATCH 09/29] Enable Vendor Locale --- app/Http/Middleware/VendorLocale.php | 2 +- app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 2 +- app/Utils/VendorHtmlEngine.php | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Http/Middleware/VendorLocale.php b/app/Http/Middleware/VendorLocale.php index a64fc1ec41f5..abca08b9e3d7 100644 --- a/app/Http/Middleware/VendorLocale.php +++ b/app/Http/Middleware/VendorLocale.php @@ -36,7 +36,7 @@ class VendorLocale $locale = $request->input('lang'); App::setLocale($locale); } elseif (auth()->guard('vendor')->user()) { - App::setLocale(auth()->guard('vendor')->user()->company->locale()); + App::setLocale(auth()->guard('vendor')->user()->vendor->locale()); } elseif (auth()->user()) { try { App::setLocale(auth()->user()->company()->getLocale()); diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index fe6029aa2042..5c8432603a08 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -102,7 +102,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue /* Init a new copy of the translator*/ $t = app('translator'); /* Set the locale*/ - App::setLocale($this->company->locale()); + App::setLocale($this->vendor->locale()); /* Set customized translations _NOW_ */ $t->replace(Ninja::transformTranslations($this->company->settings)); diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php index fa0bff0b8deb..eb1f595584e5 100644 --- a/app/Utils/VendorHtmlEngine.php +++ b/app/Utils/VendorHtmlEngine.php @@ -112,7 +112,7 @@ class VendorHtmlEngine App::forgetInstance('translator'); $t = app('translator'); - App::setLocale($this->company->locale()); + App::setLocale($this->vendor->locale()); $t->replace(Ninja::transformTranslations($this->settings)); $data = []; @@ -126,16 +126,16 @@ class VendorHtmlEngine $data['$total_tax_values'] = ['value' => $this->totalTaxValues(), 'label' => ctrans('texts.taxes')]; $data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')]; $data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')]; - $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.date')]; - $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.due_date')]; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.due_date')]; - $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; $data['$dueDate'] = &$data['$due_date']; $data['$purchase_order.due_date'] = &$data['$due_date']; - $data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')]; + $data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')]; $data['$purchase_order.po_number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.po_number')]; $data['$poNumber'] = &$data['$purchase_order.po_number']; @@ -153,7 +153,7 @@ class VendorHtmlEngine $data['$viewButton'] = &$data['$view_link']; $data['$view_button'] = &$data['$view_link']; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; - $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.date')]; $data['$purchase_order.number'] = &$data['$number']; $data['$purchase_order.date'] = &$data['$date']; @@ -177,7 +177,7 @@ class VendorHtmlEngine $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')]; $data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')]; $data['$amount_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')]; - $data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->vendor->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; } else { if ($this->entity->status_id == 1) { $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')]; From 9b187cc6fb873b2b8a5c23717d6d90bac2d4bd0a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 11:16:27 +1000 Subject: [PATCH 10/29] Tests for account summary DTOP --- .../Bank/Yodlee/DTO/AccountSummary.php | 66 ++++---- tests/Integration/DTO/AccountSummaryTest.php | 144 ++++++++++++++++++ 2 files changed, 177 insertions(+), 33 deletions(-) create mode 100644 tests/Integration/DTO/AccountSummaryTest.php diff --git a/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php b/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php index f0771dde9f71..346496411f4a 100644 --- a/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php +++ b/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php @@ -17,94 +17,94 @@ use Spatie\LaravelData\Attributes\MapOutputName; use Illuminate\Support\Collection; /** - * {#2983 + * [ "account": [ - {#2979 + [ "CONTAINER": "bank", - "providerAccountId": 10058190, - "accountName": "Business Trans Acct", + "providerAccountId": 1005, + "accountName": "Business Acct", "accountStatus": "ACTIVE", - "accountNumber": "1011 4402", + "accountNumber": "1011", "aggregationSource": "USER", "isAsset": true, - "balance": {#2978 + "balance": [ "currency": "AUD", - "amount": 308544.98, - }, + "amount": 304.98, + ], "id": 10139315, "includeInNetWorth": true, "providerId": "3857", - "providerName": "Commonwealth Bank", + "providerName": "Bank", "isManual": false, "availableBalance": {#2966 "currency": "AUD", - "amount": 309044.98, - }, - "currentBalance": {#2982 + "amount": 304.98, + ], + "currentBalance": [ "currency": "AUD", - "amount": 308544.98, - }, + "amount": 3044.98, + ], "accountType": "CHECKING", "displayedName": "after David", "createdDate": "2023-01-10T08:29:07Z", "classification": "SMALL_BUSINESS", "lastUpdated": "2023-08-01T23:50:13Z", - "nickname": "Business Trans Acct", + "nickname": "Business ", "bankTransferCode": [ - {#2976 - "id": "062020", + [ + "id": "062", "type": "BSB", - }, + ], ], "dataset": [ - {#2971 + [ "name": "BASIC_AGG_DATA", "additionalStatus": "AVAILABLE_DATA_RETRIEVED", "updateEligibility": "ALLOW_UPDATE", "lastUpdated": "2023-08-01T23:49:53Z", "lastUpdateAttempt": "2023-08-01T23:49:53Z", "nextUpdateScheduled": "2023-08-03T14:45:14Z", - }, + ], ], - }, + ], ], - } + ]; */ class AccountSummary extends Data { public ?int $id; #[MapInputName('CONTAINER')] - public ?string $account_type; + public ?string $account_type = ''; #[MapInputName('accountName')] - public ?string $account_name; + public ?string $account_name = ''; #[MapInputName('accountStatus')] - public ?string $account_status; + public ?string $account_status = ''; #[MapInputName('accountNumber')] - public ?string $account_number; + public ?string $account_number = ''; #[MapInputName('providerAccountId')] public int $provider_account_id; #[MapInputName('providerId')] - public ?string $provider_id; + public ?string $provider_id = ''; #[MapInputName('providerName')] - public ?string $provider_name; + public ?string $provider_name = ''; - public ?string $nickname; + public ?string $nickname = ''; - public ?float $current_balance; - public ?string $account_currency; + public ?float $current_balance = 0; + public ?string $account_currency = ''; public static function prepareForPipeline(Collection $properties) : Collection { - $properties->put('current_balance', (array)$properties['currentBalance']['amount'] ?? ''); - $properties->put('account_currency', (array)$properties['currentBalance']['currency'] ?? ''); + $properties->put('current_balance', $properties['currentBalance']['amount'] ?? 0); + $properties->put('account_currency', $properties['currentBalance']['currency'] ?? 0); return $properties; } diff --git a/tests/Integration/DTO/AccountSummaryTest.php b/tests/Integration/DTO/AccountSummaryTest.php new file mode 100644 index 000000000000..d42046920369 --- /dev/null +++ b/tests/Integration/DTO/AccountSummaryTest.php @@ -0,0 +1,144 @@ +//invoiceninja.com). + * + * @link https=>//github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2021. Invoice Ninja LLC (https=>//invoiceninja.com) + * + * @license https=>//www.elastic.co/licensing/elastic-license + */ + +namespace Tests\Integration\DTO; + +use Tests\TestCase; + +/** + * @test + */ +class AccountSummaryTest extends TestCase +{ + + private $data = [ + [ + "CONTAINER"=> "bank", + "providerAccountId"=> 330, + "accountName"=> "Business Acct", + "accountStatus"=> "ACTIVE", + "accountNumber"=> "1012", + "aggregationSource"=> "USER", + "isAsset"=> true, + "balance"=> [ + "currency"=> "AUD", + "amount"=> 44.98, + ], + "id"=> 19315, + "includeInNetWorth"=> true, + "providerId"=> "3857", + "providerName"=> "Bank", + "isManual"=> false, + "availableBalance"=> [ + "currency"=> "AUD", + "amount"=> 34.98, + ], + "currentBalance"=> [ + "currency"=> "AUD", + "amount"=> 344.98, + ], + "accountType"=> "CHECKING", + "displayedName"=> "after David", + "createdDate"=> "2023-01-10T08=>29=>07Z", + "classification"=> "", + "lastUpdated"=> "2023-08-01T23=>50=>13Z", + "nickname"=> "Busines Acct", + "bankTransferCode"=> [ + [ + "id"=> "062", + "type"=> "BSB", + ], + ], + "dataset"=> [ + [ + "name"=> "BASIC_AGG_DATA", + "additionalStatus"=> "AVAILABLE_DATA_RETRIEVED", + "updateEligibility"=> "ALLOW_UPDATE", + "lastUpdated"=> "2023-08-01T23=>49=>53Z", + "lastUpdateAttempt"=> "2023-08-01T23=>49=>53Z", + "nextUpdateScheduled"=> "2023-08-03T14=>45=>14Z", + ], + ], + ] + ]; + + private $bad_data = [ + [ + "CONTAINER"=> "bank", + "providerAccountId"=> 10090, + "accountName"=> "Business Trans Acct", + // "accountStatus"=> "ACTIVE", + "accountNumber"=> "4402", + "aggregationSource"=> "USER", + "isAsset"=> true, + "balance"=> [ + "currency"=> "AUD", + "amount"=> 34.98, + ], + "id"=> 19315, + "includeInNetWorth"=> true, + "providerId"=> "37", + "providerName"=> "Bank", + "isManual"=> false, + // "availableBalance"=> [ + // "currency"=> "AUD", + // "amount"=> 7.98, + // ], + "currentBalance"=> [ + "currency"=> "AUD", + "amount"=> 344.98, + ], + "accountType"=> "CHECKING", + "displayedName"=> "after David", + "createdDate"=> "2023-01-10T08=>29=>07Z", + "classification"=> "SMALL_BUSINESS", + "lastUpdated"=> "2023-08-01T23=>50=>13Z", + "nickname"=> "Busines Acct", + "bankTransferCode"=> [ + [ + "id"=> "060", + "type"=> "BSB", + ], + ], + "dataset"=> [ + [ + "name"=> "BASIC_AGG_DATA", + "additionalStatus"=> "AVAILABLE_DATA_RETRIEVED", + "updateEligibility"=> "ALLOW_UPDATE", + "lastUpdated"=> "2023-08-01T23=>49=>53Z", + "lastUpdateAttempt"=> "2023-08-01T23=>49=>53Z", + "nextUpdateScheduled"=> "2023-08-03T14=>45=>14Z", + ], + ], + ] + ]; + + + + protected function setUp() :void + { + parent::setUp(); + } + + public function testWithBadDataTransformations() + { + $dtox = \App\Helpers\Bank\Yodlee\DTO\AccountSummary::from($this->bad_data[0]); + $this->assertEquals(19315, $dtox->id); + $this->assertEquals('', $dtox->account_status); + } + + public function testTransform() + { + $dto = \App\Helpers\Bank\Yodlee\DTO\AccountSummary::from($this->data[0]); + $this->assertEquals($dto->id, 19315); + } + +} \ No newline at end of file From 45c5e2195b163a2cc2abbb51bec6bd386aacb9db Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 12:17:37 +1000 Subject: [PATCH 11/29] Updates for yodlee account status --- .../Yodlee/Transformer/AccountTransformer.php | 44 +++++++++++- app/Helpers/Bank/Yodlee/Yodlee.php | 67 +++++++++++++++++++ ...23_08_09_224955_add_nicaragua_currency.php | 1 - 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php index 2043c865d2cd..19ff1974c64d 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -64,6 +64,7 @@ use App\Helpers\Bank\AccountTransformerInterface; class AccountTransformer implements AccountTransformerInterface { + public function transform($yodlee_account) { $data = []; @@ -93,13 +94,54 @@ class AccountTransformer implements AccountTransformerInterface $account_currency = $account->balance->currency ?? ''; } + $account_status = $account->accountStatus; + + if(property_exists($account, 'dataset')){ + $dataset = $account->dataset[0]; + $status = false; + $update = false; + + match($dataset->additionalStatus ?? ''){ + 'LOGIN_IN_PROGRESS' => $status = 'Data retrieval in progress.', + 'USER_INPUT_REQUIRED' => $status = 'Please reconnect your account, authentication required.', + 'LOGIN_SUCCESS' => $status = 'Data retrieval in progress', + 'ACCOUNT_SUMMARY_RETRIEVED' => $status = 'Account summary retrieval in progress.', + 'NEVER_INITIATED' => $status = 'Upstream working on connecting to your account.', + 'LOGIN_FAILED' => $status = 'Authentication failed, please try reauthenticating.', + 'REQUEST_TIME_OUT' => $status = 'Timeout encountered retrieving data.', + 'DATA_RETRIEVAL_FAILED' => $status = 'Login successful, but data retrieval failed.', + 'PARTIAL_DATA_RETRIEVED' => $status = 'Partial data update failed.', + 'PARTIAL_DATA_RETRIEVED_REM_SCHED' => $status = 'Partial data update failed.', + 'SUCCESS' => $status = 'All accounts added or updated successfully.', + default => $status = false + }; + + if($status){ + $account_status = $status; + } + + match($dataset->updateEligibility ?? ''){ + 'ALLOW_UPDATE' => $update = 'Account connection stable.', + 'ALLOW_UPDATE_WITH_CREDENTIALS' => $update = 'Please reconnect your account with updated credentials.', + 'DISALLOW_UPDATE' => $update = 'Update not available due to technical issues.', + default => $update = false, + }; + + if($status && $update){ + $account_status = $status . ' - ' . $update; + } + elseif($update){ + $account_status = $update; + } + + } return [ 'id' => $account->id, 'account_type' => $account->CONTAINER, // 'account_name' => $account->accountName, 'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname, - 'account_status' => $account->accountStatus, + 'account_status' => $account_status, 'account_number' => property_exists($account, 'accountNumber') ? '**** ' . substr($account?->accountNumber, -7) : '', 'provider_account_id' => $account->providerAccountId, 'provider_id' => $account->providerId, diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index fb506981fb81..f8294becd97e 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -297,4 +297,71 @@ class Yodlee 'secret' => $this->client_secret, ]; } + + /** + * updateEligibility + * + * ALLOW_UPDATE + * ALLOW_UPDATE_WITH_CREDENTIALS + * DISALLOW_UPDATE + */ + + /** + * additionalStatus + * + * LOGIN_IN_PROGRESS + * DATA_RETRIEVAL_IN_PROGRESS + * ACCT_SUMMARY_RECEIVED + * AVAILABLE_DATA_RETRIEVED + * PARTIAL_DATA_RETRIEVED + * DATA_RETRIEVAL_FAILED + * DATA_NOT_AVAILABLE + * ACCOUNT_LOCKED + * ADDL_AUTHENTICATION_REQUIRED + * BETA_SITE_DEV_IN_PROGRESS + * CREDENTIALS_UPDATE_NEEDED + * INCORRECT_CREDENTIALS + * PROPERTY_VALUE_NOT_AVAILABLE + * INVALID_ADDL_INFO_PROVIDED + * REQUEST_TIME_OUT + * SITE_BLOCKING_ERROR + * UNEXPECTED_SITE_ERROR + * SITE_NOT_SUPPORTED + * SITE_UNAVAILABLE + * TECH_ERROR + * USER_ACTION_NEEDED_AT_SITE + * SITE_SESSION_INVALIDATED + * NEW_AUTHENTICATION_REQUIRED + * DATASET_NOT_SUPPORTED + * ENROLLMENT_REQUIRED_FOR_DATASET + * CONSENT_REQUIRED + * CONSENT_EXPIRED + * CONSENT_REVOKED + * INCORRECT_OAUTH_TOKEN + * MIGRATION_IN_PROGRESS + */ + + /** + * IN_PROGRESS LOGIN_IN_PROGRESS Provider login is in progress. + * IN_PROGRESS USER_INPUT_REQUIRED Provider site requires MFA-based authentication and needs user input for login. + * IN_PROGRESS LOGIN_SUCCESS Provider login is successful. + * IN_PROGRESS ACCOUNT_SUMMARY_RETRIEVED Account summary info may not have the complete info of accounts that are available in the provider site. This depends on the sites behaviour. Account summary info may not be available at all times. + * FAILED NEVER_INITIATED The add or update provider account was not triggered due to techincal reasons. This is a rare occurrence and usually resolves quickly. + * FAILED LOGIN_FAILED Provider login failed. + * FAILED REQUEST_TIME_OUT The process timed out. + * FAILED DATA_RETRIEVAL_FAILED All accounts under the provider account failed with same or different errors, though login was successful. + * FAILED No additional status or information will be provided when there are errors other than the ones listed above. + * PARTIAL_SUCCESS PARTIAL_DATA_RETRIEVED DATA_RETRIEVAL_FAILED_PARTIALLY One/few accounts data gathered and one/few accounts failed. + * PARTIAL_SUCCESS PARTIAL_DATA_RETRIEVED_REM_SCHED DATA_RETRIEVAL_FAILED_PARTIALLY One/few accounts data gathered One/few accounts failed + * SUCCESS All accounts under the provider was added or updated successfully. + */ + + /** + * updateEligibility + * + * ALLOW_UPDATE The status indicates that the account is eligible for the next update and applies to both MFA and non-MFA accounts. For MFA-based accounts, the user may have to provide the MFA details during account refresh. + * ALLOW_UPDATE_WITH_CREDENTIALS The status indicates updating or refreshing the account by directing the user to edit the provided credentials. + * DISALLOW_UPDATE The status indicates the account is not eligible for the update or refresh process due to a site issue or a technical error. + */ + } diff --git a/database/migrations/2023_08_09_224955_add_nicaragua_currency.php b/database/migrations/2023_08_09_224955_add_nicaragua_currency.php index c7c92fc2c3e5..49671a87951f 100644 --- a/database/migrations/2023_08_09_224955_add_nicaragua_currency.php +++ b/database/migrations/2023_08_09_224955_add_nicaragua_currency.php @@ -32,7 +32,6 @@ return new class extends Migration $table->timestamp('last_login')->nullable(); }); - } /** From 59ed13122cedf989ec56c595c216f3d25c522f6a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 12:40:52 +1000 Subject: [PATCH 12/29] Add last_login timestamps for vendorcontacts and vendors --- app/Events/Vendor/VendorContactLoggedIn.php | 45 +++++++++++++++++++ app/Http/Middleware/VendorContactKeyLogin.php | 16 ++++--- .../Vendor/UpdateVendorContactLastLogin.php | 45 +++++++++++++++++++ app/Models/Vendor.php | 1 + app/Models/VendorContact.php | 1 + app/Providers/EventServiceProvider.php | 11 +++-- app/Transformers/VendorContactTransformer.php | 1 + tests/Feature/VendorApiTest.php | 36 +++++++++++++-- 8 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 app/Events/Vendor/VendorContactLoggedIn.php create mode 100644 app/Listeners/Vendor/UpdateVendorContactLastLogin.php diff --git a/app/Events/Vendor/VendorContactLoggedIn.php b/app/Events/Vendor/VendorContactLoggedIn.php new file mode 100644 index 000000000000..2422af1023a5 --- /dev/null +++ b/app/Events/Vendor/VendorContactLoggedIn.php @@ -0,0 +1,45 @@ +save(); auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); + + event(new VendorContactLoggedIn($vendor_contact, $vendor_contact->company, Ninja::eventVars())); if ($request->query('redirect') && ! empty($request->query('redirect'))) { return redirect()->to($request->query('redirect')); @@ -72,7 +76,7 @@ class VendorContactKeyLogin $vendor_contact->save(); auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); - + event(new VendorContactLoggedIn($vendor_contact, $vendor_contact->company, Ninja::eventVars())); if ($request->query('next')) { return redirect()->to($request->query('next')); } diff --git a/app/Listeners/Vendor/UpdateVendorContactLastLogin.php b/app/Listeners/Vendor/UpdateVendorContactLastLogin.php new file mode 100644 index 000000000000..272b7144b1f7 --- /dev/null +++ b/app/Listeners/Vendor/UpdateVendorContactLastLogin.php @@ -0,0 +1,45 @@ +company->db); + + $contact = $event->contact; + + $contact->last_login = now(); + $contact->vendor->last_login = now(); + + $contact->push(); + } +} diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index b49cdf4502fe..d72061bad7fa 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -152,6 +152,7 @@ class Vendor extends BaseModel 'updated_at' => 'timestamp', 'created_at' => 'timestamp', 'deleted_at' => 'timestamp', + 'last_login' => 'timestamp', ]; protected $touches = []; diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 91201af7a357..2e2da098504c 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -106,6 +106,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference 'updated_at' => 'timestamp', 'created_at' => 'timestamp', 'deleted_at' => 'timestamp', + 'last_login' => 'timestamp', ]; protected $fillable = [ diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5e8f8d60876c..d83f5602c8f3 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -70,13 +70,13 @@ use App\Events\Quote\QuoteWasRestored; use App\Events\Client\ClientWasCreated; use App\Events\Client\ClientWasDeleted; use App\Events\Client\ClientWasUpdated; -use App\Events\Design\DesignWasDeleted; -use App\Events\Design\DesignWasUpdated; use App\Events\Contact\ContactLoggedIn; use App\Events\Credit\CreditWasCreated; use App\Events\Credit\CreditWasDeleted; use App\Events\Credit\CreditWasEmailed; use App\Events\Credit\CreditWasUpdated; +use App\Events\Design\DesignWasDeleted; +use App\Events\Design\DesignWasUpdated; use App\Events\Vendor\VendorWasCreated; use App\Events\Vendor\VendorWasDeleted; use App\Events\Vendor\VendorWasUpdated; @@ -85,10 +85,10 @@ use App\Observers\SubscriptionObserver; use Illuminate\Mail\Events\MessageSent; use App\Events\Client\ClientWasArchived; use App\Events\Client\ClientWasRestored; -use App\Events\Design\DesignWasRestored; use App\Events\Credit\CreditWasArchived; use App\Events\Credit\CreditWasRestored; use App\Events\Design\DesignWasArchived; +use App\Events\Design\DesignWasRestored; use App\Events\Invoice\InvoiceWasViewed; use App\Events\Misc\InvitationWasViewed; use App\Events\Payment\PaymentWasVoided; @@ -133,6 +133,7 @@ use App\Listeners\User\UpdateUserLastLogin; use App\Events\Document\DocumentWasArchived; use App\Events\Document\DocumentWasRestored; use App\Events\Invoice\InvoiceWasMarkedSent; +use App\Events\Vendor\VendorContactLoggedIn; use App\Listeners\Quote\QuoteViewedActivity; use App\Listeners\User\ArchivedUserActivity; use App\Listeners\User\RestoredUserActivity; @@ -220,6 +221,7 @@ use App\Listeners\Invoice\InvoiceEmailFailedActivity; use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; use App\Events\PurchaseOrder\PurchaseOrderWasArchived; use App\Events\PurchaseOrder\PurchaseOrderWasRestored; +use App\Listeners\Vendor\UpdateVendorContactLastLogin; use App\Events\RecurringQuote\RecurringQuoteWasCreated; use App\Events\RecurringQuote\RecurringQuoteWasDeleted; use App\Events\RecurringQuote\RecurringQuoteWasUpdated; @@ -615,6 +617,9 @@ class EventServiceProvider extends ServiceProvider VendorWasUpdated::class => [ VendorUpdatedActivity::class, ], + VendorContactLoggedIn::class => [ + UpdateVendorContactLastLogin::class, + ], \SocialiteProviders\Manager\SocialiteWasCalled::class => [ // ... Manager won't register drivers that are not added to this listener. \SocialiteProviders\Apple\AppleExtendSocialite::class.'@handle', diff --git a/app/Transformers/VendorContactTransformer.php b/app/Transformers/VendorContactTransformer.php index d06996c0100f..39f4d60d0e6e 100644 --- a/app/Transformers/VendorContactTransformer.php +++ b/app/Transformers/VendorContactTransformer.php @@ -44,6 +44,7 @@ class VendorContactTransformer extends EntityTransformer 'custom_value3' => $vendor->custom_value3 ?: '', 'custom_value4' => $vendor->custom_value4 ?: '', 'link' => $vendor->getLoginLink(), + 'last_login' => 'timestamp', ]; } } diff --git a/tests/Feature/VendorApiTest.php b/tests/Feature/VendorApiTest.php index 2127a996576f..d103afc5c1b8 100644 --- a/tests/Feature/VendorApiTest.php +++ b/tests/Feature/VendorApiTest.php @@ -11,13 +11,15 @@ namespace Tests\Feature; +use Tests\TestCase; +use App\Utils\Ninja; +use Tests\MockAccountData; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Session; +use App\Events\Vendor\VendorContactLoggedIn; use Illuminate\Validation\ValidationException; -use Tests\MockAccountData; -use Tests\TestCase; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -44,6 +46,34 @@ class VendorApiTest extends TestCase Model::reguard(); } + public function testVendorLoggedInEvents() + { + $v = \App\Models\Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id + ]); + + $vc = \App\Models\VendorContact::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'vendor_id' => $v->id + ]); + + $this->assertNull($v->last_login); + $this->assertNull($vc->last_login); + + event(new VendorContactLoggedIn($vc, $this->company, Ninja::eventVars())); + + $this->expectsEvents([VendorContactLoggedIn::class]); + + // $vc->fresh(); + // $v->fresh(); + + // $this->assertNotNull($vc->fresh()->last_login); + // $this->assertNotNull($v->fresh()->last_login); + + } + public function testVendorLocale() { $v = \App\Models\Vendor::factory()->create([ From 345c40b144af45f2f08e77956289df683e21111f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 17:24:50 +1000 Subject: [PATCH 13/29] Add events for catching contact logins --- .../ClientPortal/InvitationController.php | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index ccb6f8f53fd4..e5007cca9a48 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -11,26 +11,27 @@ namespace App\Http\Controllers\ClientPortal; -use App\Events\Credit\CreditWasViewed; -use App\Events\Invoice\InvoiceWasViewed; -use App\Events\Misc\InvitationWasViewed; +use App\Utils\Ninja; +use App\Models\Client; +use App\Models\Payment; +use Illuminate\Support\Str; +use Illuminate\Http\Request; +use App\Models\ClientContact; +use App\Models\QuoteInvitation; +use App\Utils\Traits\MakesHash; +use App\Models\CreditInvitation; +use App\Utils\Traits\MakesDates; +use App\Jobs\Entity\CreateRawPdf; +use App\Models\InvoiceInvitation; use App\Events\Quote\QuoteWasViewed; use App\Http\Controllers\Controller; -use App\Jobs\Entity\CreateRawPdf; -use App\Models\Client; -use App\Models\ClientContact; -use App\Models\CreditInvitation; -use App\Models\InvoiceInvitation; -use App\Models\Payment; -use App\Models\PurchaseOrderInvitation; -use App\Models\QuoteInvitation; -use App\Services\ClientPortal\InstantPayment; -use App\Utils\Ninja; -use App\Utils\Traits\MakesDates; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Str; +use App\Events\Credit\CreditWasViewed; +use App\Events\Contact\ContactLoggedIn; +use App\Models\PurchaseOrderInvitation; +use App\Events\Invoice\InvoiceWasViewed; +use App\Events\Misc\InvitationWasViewed; +use App\Services\ClientPortal\InstantPayment; /** * Class InvitationController. @@ -94,6 +95,7 @@ class InvitationController extends Controller } $client_contact = $invitation->contact; + event(new ContactLoggedIn($client_contact, $client_contact->company, Ninja::eventVars())); if (empty($client_contact->email)) { $client_contact->email = Str::random(15) . "@example.com"; From e1863394ef8ff331686854755c2c0954a22c85ec Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 18:07:22 +1000 Subject: [PATCH 14/29] Support using draft credits in payments --- .../Requests/Payment/StorePaymentRequest.php | 2 - .../Credit/ValidCreditsRules.php | 6 +- tests/Feature/CreditTest.php | 2 + tests/Feature/PaymentV2Test.php | 72 +++++++++++++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index b178717caa1e..97736fda7a55 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -85,8 +85,6 @@ class StorePaymentRequest extends Request $input['amount'] = $invoices_total - $credits_total; } - // $input['is_manual'] = true; - if (! isset($input['date'])) { $input['date'] = now()->addSeconds(auth()->user()->company()->timezone()->utc_offset)->format('Y-m-d'); } diff --git a/app/Http/ValidationRules/Credit/ValidCreditsRules.php b/app/Http/ValidationRules/Credit/ValidCreditsRules.php index b0892da7330e..d02097df2159 100644 --- a/app/Http/ValidationRules/Credit/ValidCreditsRules.php +++ b/app/Http/ValidationRules/Credit/ValidCreditsRules.php @@ -64,7 +64,6 @@ class ValidCreditsRules implements Rule foreach ($this->input['credits'] as $credit) { $unique_array[] = $credit['credit_id']; - // $cred = Credit::find($this->decodePrimaryKey($credit['credit_id'])); $cred = $cred_collection->firstWhere('id', $credit['credit_id']); if (! $cred) { @@ -77,6 +76,11 @@ class ValidCreditsRules implements Rule return false; } + if($cred->status_id == Credit::STATUS_DRAFT){ + $cred->service()->markSent()->save(); + $cred = $cred->fresh(); + } + if($cred->balance < $credit['amount']) { $this->error_msg = ctrans('texts.insufficient_credit_balance'); return false; diff --git a/tests/Feature/CreditTest.php b/tests/Feature/CreditTest.php index 0e830189e813..f67aa96db9b4 100644 --- a/tests/Feature/CreditTest.php +++ b/tests/Feature/CreditTest.php @@ -27,6 +27,8 @@ class CreditTest extends TestCase use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp(): void { parent::setUp(); diff --git a/tests/Feature/PaymentV2Test.php b/tests/Feature/PaymentV2Test.php index 5f1f386ef173..3365b42adf3f 100644 --- a/tests/Feature/PaymentV2Test.php +++ b/tests/Feature/PaymentV2Test.php @@ -58,6 +58,78 @@ class PaymentV2Test extends TestCase ); } + public function testUsingDraftCreditsForPayments() + { + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'uses_inclusive_taxes' => false, + 'amount' => 20, + 'balance' => 20, + 'discount' => 0, + 'number' => uniqid("st", true), + 'line_items' => [] + ]); + + $item = InvoiceItemFactory::generateCredit(); + $item['cost'] = 20; + $item['quantity'] = 1; + + $credit = Credit::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Credit::STATUS_DRAFT, + 'uses_inclusive_taxes' => false, + 'amount' => 20, + 'balance' => 0, + 'discount' => 0, + 'number' => uniqid("st", true), + 'line_items' => [ + $item + ] + ]); + + $data = [ + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + [ + 'invoice_id' => $invoice->hashed_id, + 'amount' => 20, + ], + ], + 'credits' => [ + [ + 'credit_id' => $credit->hashed_id, + 'amount' => 20, + ], + ], + 'date' => '2020/12/12', + + ]; + + $response = null; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments?include=invoices', $data); + + $arr = $response->json(); + $response->assertStatus(200); + + $payment_id = $arr['data']['id']; + $this->assertEquals(Credit::STATUS_APPLIED, $credit->fresh()->status_id); + $this->assertEquals(Invoice::STATUS_PAID, $invoice->fresh()->status_id); + + $this->assertEquals(0, $credit->fresh()->balance); + $this->assertEquals(0, $invoice->fresh()->balance); + + } + public function testStorePaymentWithCreditsThenDeletingInvoices() { $client = Client::factory()->create(['company_id' =>$this->company->id, 'user_id' => $this->user->id, 'balance' => 20, 'paid_to_date' => 0]); From 1de6086c215b75cd963521b9eadc8e70d0e51373 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 18:30:05 +1000 Subject: [PATCH 15/29] Adjustments for Facturae 3.2.2 --- app/Services/Invoice/EInvoice/FacturaEInvoice.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Services/Invoice/EInvoice/FacturaEInvoice.php b/app/Services/Invoice/EInvoice/FacturaEInvoice.php index 9f97da9e4519..700bad3602a8 100644 --- a/app/Services/Invoice/EInvoice/FacturaEInvoice.php +++ b/app/Services/Invoice/EInvoice/FacturaEInvoice.php @@ -218,8 +218,10 @@ class FacturaEInvoice extends AbstractService private function setPoNumber(): self { $po = $this->invoice->po_number ?? ''; + $transaction_reference = (isset($this->invoice->custom_value1) && strlen($this->invoice->custom_value1) > 2) ? substr($this->invoice->custom_value1, 0, 20) : null; + $contract_reference = (isset($this->invoice->custom_value2) && strlen($this->invoice->custom_value2) > 2) ? $this->invoice->custom_value2: null; - $this->fac->setReferences($po, substr($this->invoice->custom_value1, 0, 20), $this->invoice->custom_value2); + $this->fac->setReferences($po, $transaction_reference, $contract_reference); return $this; } @@ -242,6 +244,9 @@ class FacturaEInvoice extends AbstractService private function setBillingPeriod(): self { + if(!$this->invoice->custom_value3) + return $this; + try { if (\Carbon\Carbon::createFromFormat('Y-m-d', $this->invoice->custom_value3)->format('Y-m-d') === $this->invoice->custom_value3 && \Carbon\Carbon::createFromFormat('Y-m-d', $this->invoice->custom_value4)->format('Y-m-d') === $this->invoice->custom_value4 From 87d6d3262234b4d37a5dd1719827348338f06ea7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 18:31:09 +1000 Subject: [PATCH 16/29] v5.6.29 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 030ab30ddf86..83e2d80cabbe 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.6.28 \ No newline at end of file +5.6.29 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 52ba2c49801d..eb9345acbaab 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.6.28'), - 'app_tag' => env('APP_TAG','5.6.28'), + 'app_version' => env('APP_VERSION','5.6.29'), + 'app_tag' => env('APP_TAG','5.6.29'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From 8e5fba6943124bad7c884a11fa30a03a684d0017 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Aug 2023 19:53:35 +1000 Subject: [PATCH 17/29] Updates for low permission users attempting to generate reports --- app/Http/Requests/Report/GenericReportRequest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/Report/GenericReportRequest.php b/app/Http/Requests/Report/GenericReportRequest.php index d18bb516c233..4f941b5802ad 100644 --- a/app/Http/Requests/Report/GenericReportRequest.php +++ b/app/Http/Requests/Report/GenericReportRequest.php @@ -22,7 +22,11 @@ class GenericReportRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin() || $user->hasPermission('view_reports'); + } public function rules() From 5ca41f5f8641127f8729a8c6ccd05e7058b1db6a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 08:10:21 +1000 Subject: [PATCH 18/29] Fixes for static analysis --- app/Http/Middleware/VendorContactKeyLogin.php | 5 --- .../Requests/Yodlee/YodleeAuthRequest.php | 2 +- app/Transformers/VendorContactTransformer.php | 2 +- app/Utils/TemplateEngine.php | 34 +++++++++++++------ 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/Http/Middleware/VendorContactKeyLogin.php b/app/Http/Middleware/VendorContactKeyLogin.php index 5b569bbded08..e73a32554662 100644 --- a/app/Http/Middleware/VendorContactKeyLogin.php +++ b/app/Http/Middleware/VendorContactKeyLogin.php @@ -13,14 +13,12 @@ namespace App\Http\Middleware; use Auth; use Closure; -use App\Utils\Ninja; use App\Models\Vendor; use App\Libraries\MultiDB; use Illuminate\Support\Str; use Illuminate\Http\Request; use App\Models\VendorContact; use Illuminate\Support\Facades\Cache; -use App\Events\Vednor\VendorContactLoggedIn; class VendorContactKeyLogin { @@ -59,8 +57,6 @@ class VendorContactKeyLogin auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); - event(new VendorContactLoggedIn($vendor_contact, $vendor_contact->company, Ninja::eventVars())); - if ($request->query('redirect') && ! empty($request->query('redirect'))) { return redirect()->to($request->query('redirect')); } @@ -76,7 +72,6 @@ class VendorContactKeyLogin $vendor_contact->save(); auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); - event(new VendorContactLoggedIn($vendor_contact, $vendor_contact->company, Ninja::eventVars())); if ($request->query('next')) { return redirect()->to($request->query('next')); } diff --git a/app/Http/Requests/Yodlee/YodleeAuthRequest.php b/app/Http/Requests/Yodlee/YodleeAuthRequest.php index 942139a2e25e..34b214da9f48 100644 --- a/app/Http/Requests/Yodlee/YodleeAuthRequest.php +++ b/app/Http/Requests/Yodlee/YodleeAuthRequest.php @@ -39,7 +39,7 @@ class YodleeAuthRequest extends Request return []; } - /** @var $token */ + /** @var Request $token */ public function getTokenContent() { if ($this->state) { diff --git a/app/Transformers/VendorContactTransformer.php b/app/Transformers/VendorContactTransformer.php index 39f4d60d0e6e..ca344634ce5b 100644 --- a/app/Transformers/VendorContactTransformer.php +++ b/app/Transformers/VendorContactTransformer.php @@ -44,7 +44,7 @@ class VendorContactTransformer extends EntityTransformer 'custom_value3' => $vendor->custom_value3 ?: '', 'custom_value4' => $vendor->custom_value4 ?: '', 'link' => $vendor->getLoginLink(), - 'last_login' => 'timestamp', + 'last_login' => (int)$vendor->last_login, ]; } } diff --git a/app/Utils/TemplateEngine.php b/app/Utils/TemplateEngine.php index 3cd5221d5de3..2e531daf9484 100644 --- a/app/Utils/TemplateEngine.php +++ b/app/Utils/TemplateEngine.php @@ -52,8 +52,8 @@ class TemplateEngine public $template; - /** @var Invoice | Quote | Credit | PurchaseOrder | RecurringInvoice | null $entity_obj **/ - private $entity_obj; + /** @var \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice $entity_obj **/ + private \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice $entity_obj; /** @var \App\Models\Company | \App\Models\Client | null $settings_entity **/ private $settings_entity; @@ -81,7 +81,7 @@ class TemplateEngine $this->template = $template; - $this->entity_obj = null; + // $this->entity_obj = null; $this->settings_entity = null; } @@ -99,7 +99,7 @@ class TemplateEngine { if (strlen($this->entity) > 1 && strlen($this->entity_id) > 1) { $class = 'App\Models\\' . ucfirst(Str::camel($this->entity)); - $this->entity_obj = $class::withTrashed()->where('id', $this->decodePrimaryKey($this->entity_id))->company()->first(); + $this->entity_obj = $class::query()->withTrashed()->where('id', $this->decodePrimaryKey($this->entity_id))->company()->first(); } elseif (stripos($this->template, 'quote') !== false && $quote = Quote::query()->whereHas('invitations')->withTrashed()->company()->first()) { $this->entity = 'quote'; $this->entity_obj = $quote; @@ -111,6 +111,7 @@ class TemplateEngine $this->entity_obj = $payment; } elseif ($invoice = Invoice::query()->whereHas('invitations')->withTrashed()->company()->first()) { + /** @var \App\Models\Invoice $invoice */ $this->entity_obj = $invoice; } else { $this->mockEntity(); @@ -286,6 +287,8 @@ class TemplateEngine private function mockEntity() { + $invitation = false; + if (!$this->entity && $this->template && str_contains($this->template, 'purchase_order')) { $this->entity = 'purchaseOrder'; } elseif (str_contains($this->template, 'payment')) { @@ -304,7 +307,6 @@ class TemplateEngine 'company_id' => $user->company()->id, ]); - /** @var \App\Models\ClientContact $contact */ $contact = ClientContact::factory()->create([ 'user_id' => $user->id, @@ -315,7 +317,8 @@ class TemplateEngine ]); if ($this->entity == 'payment') { - $this->entity_obj = Payment::factory()->create([ + /** @var \App\Models\Payment $payment */ + $payment = Payment::factory()->create([ 'user_id' => $user->id, 'company_id' => $user->company()->id, 'client_id' => $client->id, @@ -324,6 +327,8 @@ class TemplateEngine 'refunded' => 5, ]); + $this->entity_obj = $payment; + /** @var \App\Models\Invoice $invoice */ $invoice = Invoice::factory()->create([ 'user_id' => $user->id, @@ -349,7 +354,8 @@ class TemplateEngine } if (!$this->entity || $this->entity == 'invoice') { - $this->entity_obj = Invoice::factory()->create([ + /** @var \App\Models\Invoice $invoice */ + $invoice = Invoice::factory()->create([ 'user_id' => $user->id, 'company_id' => $user->company()->id, 'client_id' => $client->id, @@ -357,6 +363,8 @@ class TemplateEngine 'balance' => '10', ]); + $this->entity_obj = $invoice; + $invitation = InvoiceInvitation::factory()->create([ 'user_id' => $user->id, 'company_id' => $user->company()->id, @@ -366,11 +374,14 @@ class TemplateEngine } if ($this->entity == 'quote') { - $this->entity_obj = Quote::factory()->create([ + /** @var \App\Models\Quote $quote */ + $quote = Quote::factory()->create([ 'user_id' => $user->id, 'company_id' => $user->company()->id, 'client_id' => $client->id, ]); + + $this->entity_obj = $quote; $invitation = QuoteInvitation::factory()->create([ 'user_id' => $user->id, @@ -395,12 +406,15 @@ class TemplateEngine 'is_primary' => 1, 'send_email' => true, ]); - - $this->entity_obj = PurchaseOrder::factory()->create([ + + /** @var \App\Models\PurchaseOrder $purchase_order **/ + $purchase_order = PurchaseOrder::factory()->create([ 'user_id' => $user->id, 'company_id' => $user->company()->id, 'vendor_id' => $vendor->id, ]); + + $this->entity_obj = $purchase_order; /** @var \App\Models\PurchaseOrderInvitation $invitation **/ $invitation = PurchaseOrderInvitation::factory()->create([ From 63c7c0718c8f5ff5a9e98bc9137cfe6003d45442 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 11:30:11 +1000 Subject: [PATCH 19/29] Add reminder schedules to invoice transformer selectively --- app/Http/Controllers/TwilioController.php | 4 ++ app/Models/Invoice.php | 51 +++++++++++++++++++++++ app/Transformers/InvoiceTransformer.php | 9 +++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/TwilioController.php b/app/Http/Controllers/TwilioController.php index 1f26720773ca..d6bf7488d931 100644 --- a/app/Http/Controllers/TwilioController.php +++ b/app/Http/Controllers/TwilioController.php @@ -123,6 +123,10 @@ class TwilioController extends BaseController return response()->json(['message' => 'Unable to retrieve user.'], 400); } + if (!$user->phone || $user->phone == '') { + return response()->json(['message' => 'User found, but no valid phone number on file, please contact support.'], 400); + } + $sid = config('ninja.twilio_account_sid'); $token = config('ninja.twilio_auth_token'); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index f9903591b1c8..6f03027ad6af 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -745,4 +745,55 @@ class Invoice extends BaseModel return $type; } + + public function reminderSchedule(): string + { + $reminder_schedule = ''; + $settings = $this->client->getMergedSettings(); + + $send_email_enabled = ctrans('texts.send_email') . " " .ctrans('texts.enabled'); + $send_email_disabled = ctrans('texts.send_email') . " " .ctrans('texts.disabled'); + + $sends_email_1 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; + $days_1 = $settings->num_days_reminder1 . " " . ctrans('texts.days'); + $schedule_1 = ctrans("texts.{$settings->schedule_reminder1}"); //after due date etc or disabled + $label_1 = ctrans('texts.reminder1'); + + $sends_email_2 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; + $days_2 = $settings->num_days_reminder2 . " " . ctrans('texts.days'); + $schedule_2 = ctrans("texts.{$settings->schedule_reminder2}"); //after due date etc or disabled + $label_2 = ctrans('texts.reminder2'); + + $sends_email_3 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; + $days_3 = $settings->num_days_reminder3 . " " . ctrans('texts.days'); + $schedule_3 = ctrans("texts.{$settings->schedule_reminder3}"); //after due date etc or disabled + $label_3 = ctrans('texts.reminder3'); + + $sends_email_endless = $settings->enable_reminder_endless ? $send_email_enabled : $send_email_disabled; + $days_endless = \App\Models\RecurringInvoice::frequencyForKey($settings->endless_reminder_frequency_id); + $label_endless = ctrans('texts.reminder_endless'); + + if($schedule_1 == ctrans('texts.disabled') || $settings->schedule_reminder1 == 'disabled' || $settings->schedule_reminder1 == '') + $reminder_schedule .= "{$label_1}: " . ctrans('texts.disabled') ."
"; + else + $reminder_schedule .= "{$label_1}: {$days_1} {$schedule_1} [{$sends_email_1}]
"; + + if($schedule_2 == ctrans('texts.disabled') || $settings->schedule_reminder2 == 'disabled' || $settings->schedule_reminder2 == '') + $reminder_schedule .= "{$label_2}: " . ctrans('texts.disabled') ."
"; + else + $reminder_schedule .= "{$label_2}: {$days_2} {$schedule_2} [{$sends_email_2}]
"; + + if($schedule_3 == ctrans('texts.disabled') || $settings->schedule_reminder3 == 'disabled' || $settings->schedule_reminder3 == '') + $reminder_schedule .= "{$label_3}: " . ctrans('texts.disabled') ."
"; + else + $reminder_schedule .= "{$label_3}: {$days_3} {$schedule_3} [{$sends_email_3}]
"; + + if($sends_email_endless == ctrans('texts.disabled') || $settings->endless_reminder_frequency_id == '0' || $settings->endless_reminder_frequency_id == '') + $reminder_schedule .= "{$label_endless}: " . ctrans('texts.disabled') ."
"; + else + $reminder_schedule .= "{$label_endless}: {$days_endless} [{$sends_email_endless}]
"; + + + return $reminder_schedule; + } } diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index c12ae1361b87..d30ef214be3f 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -87,7 +87,7 @@ class InvoiceTransformer extends EntityTransformer public function transform(Invoice $invoice) { - return [ + $data = [ 'id' => $this->encodePrimaryKey($invoice->id), 'user_id' => $this->encodePrimaryKey($invoice->user_id), 'project_id' => $this->encodePrimaryKey($invoice->project_id), @@ -151,5 +151,12 @@ class InvoiceTransformer extends EntityTransformer 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, 'tax_info' => $invoice->tax_data ?: new \stdClass, ]; + + if (request()->has('reminder_schedule') && request()->query('reminder_schedule') == 'true') { + $data['reminder_schedule'] = (string) $invoice->reminderSchedule(); + } + + return $data; + } } From 8f1ca3f945e5a316ba36d0aaca0819d1c6ebfffe Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 11:40:21 +1000 Subject: [PATCH 20/29] v5.6.30 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 83e2d80cabbe..230cac8f9b7e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.6.29 \ No newline at end of file +5.6.30 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index eb9345acbaab..a3d19b473f7d 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.6.29'), - 'app_tag' => env('APP_TAG','5.6.29'), + 'app_version' => env('APP_VERSION','5.6.30'), + 'app_tag' => env('APP_TAG','5.6.30'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From e5d5b9d25ca1046b4e1767ba2b26aa41edbec156 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 14:18:58 +1000 Subject: [PATCH 21/29] Static analysis --- app/DataMapper/CompanySettings.php | 2 + app/Filters/PaymentFilters.php | 5 +- app/Http/Controllers/MigrationController.php | 1 + .../Controllers/OneTimeTokenController.php | 1 + .../PreviewPurchaseOrderController.php | 4 +- .../Controllers/StripeConnectController.php | 58 ++++++----------- app/Jobs/PurchaseOrder/ZipPurchaseOrders.php | 3 - app/Models/BaseModel.php | 11 +++- app/Models/Credit.php | 47 +++++++------- app/Models/CreditInvitation.php | 10 +-- app/Models/Invoice.php | 7 +- app/Models/InvoiceInvitation.php | 10 +-- app/Models/PurchaseOrder.php | 29 +++++---- app/Models/PurchaseOrderInvitation.php | 10 +-- app/Models/Quote.php | 7 +- app/Models/QuoteInvitation.php | 10 +-- app/Models/RecurringInvoice.php | 1 + app/Models/RecurringInvoiceInvitation.php | 10 +-- app/Models/User.php | 1 + app/Models/Vendor.php | 43 ++++--------- .../Jobs/PaymentIntentFailureWebhook.php | 4 +- .../Stripe/UpdatePaymentMethods.php | 27 +++++--- app/Services/Client/ClientService.php | 3 +- app/Services/Pdf/PdfMock.php | 18 +++--- app/Services/Preview/StubBuilder.php | 15 ++++- .../BankIntegrationTransformer.php | 1 + .../CompanyGatewayTransformer.php | 5 +- composer.json | 1 + composer.lock | 64 ++++++++++++++++++- phpstan.neon | 1 + .../views/auth/connect/completed.blade.php | 2 +- 31 files changed, 238 insertions(+), 173 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 91871e2ae6e3..141762d8ed91 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -820,6 +820,8 @@ class CompanySettings extends BaseSettings { $company_settings = (object) get_class_vars(self::class); + $settings = new \stdClass; + foreach ($company_settings as $key => $value) { if (! property_exists($settings, $key)) { $settings->{$key} = self::castAttribute($key, $company_settings->{$key}); diff --git a/app/Filters/PaymentFilters.php b/app/Filters/PaymentFilters.php index 92c1a7827812..38381ed786bb 100644 --- a/app/Filters/PaymentFilters.php +++ b/app/Filters/PaymentFilters.php @@ -12,6 +12,7 @@ namespace App\Filters; use App\Models\Payment; +use Illuminate\Contracts\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder; /** @@ -117,6 +118,8 @@ class PaymentFilters extends QueryFilters /** * Returns a list of payments that can be matched to bank transactions + * @param ?string $value + * @return Builder */ public function match_transactions($value = 'true'): Builder { @@ -124,7 +127,7 @@ class PaymentFilters extends QueryFilters if ($value == 'true') { return $this->builder ->where('is_deleted', 0) - ->where(function ($query) { + ->where(function (Builder $query) { $query->whereNull('transaction_id') ->orWhere("transaction_id", "") ->company(); diff --git a/app/Http/Controllers/MigrationController.php b/app/Http/Controllers/MigrationController.php index 55f8979d8737..217d29ed5e64 100644 --- a/app/Http/Controllers/MigrationController.php +++ b/app/Http/Controllers/MigrationController.php @@ -301,6 +301,7 @@ class MigrationController extends BaseController $user = auth()->user(); $company_count = $user->account->companies()->count(); + $fresh_company = false; // Look for possible existing company (based on company keys). $existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company['company_key']])->first(); diff --git a/app/Http/Controllers/OneTimeTokenController.php b/app/Http/Controllers/OneTimeTokenController.php index 2d387a0fc998..b065a50529ac 100644 --- a/app/Http/Controllers/OneTimeTokenController.php +++ b/app/Http/Controllers/OneTimeTokenController.php @@ -74,6 +74,7 @@ class OneTimeTokenController extends BaseController 'user_id' => $user->id, 'company_key'=> $user->company()->company_key, 'context' => $request->input('context'), + 'is_react' => $request->has('react') && $request->query('react') == 'true' ? true : false, ]; Cache::put($hash, $data, 3600); diff --git a/app/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php index 8ef2da0d514f..81d823cf254a 100644 --- a/app/Http/Controllers/PreviewPurchaseOrderController.php +++ b/app/Http/Controllers/PreviewPurchaseOrderController.php @@ -181,8 +181,8 @@ class PreviewPurchaseOrderController extends BaseController DB::connection(config('database.default'))->beginTransaction(); if ($request->has('entity_id')) { - /** @var \App\Models\PurchaseOrder|\Illuminate\Database\Eloquent\Builder $entity_obj **/ - $entity_obj = $class::on(config('database.default')) + /** @var \App\Models\PurchaseOrder|\Illuminate\Contracts\Database\Eloquent\Builder $entity_obj **/ + $entity_obj = \App\Models\PurchaseOrder::on(config('database.default')) ->with('vendor.company') ->where('id', $this->decodePrimaryKey($request->input('entity_id'))) ->where('company_id', $company->id) diff --git a/app/Http/Controllers/StripeConnectController.php b/app/Http/Controllers/StripeConnectController.php index 691d8602fd3a..b2e2bf70d421 100644 --- a/app/Http/Controllers/StripeConnectController.php +++ b/app/Http/Controllers/StripeConnectController.php @@ -32,7 +32,6 @@ class StripeConnectController extends BaseController */ public function initialize(InitializeStripeConnectRequest $request, string $token) { - // Should we check if company has set country in the ap? Otherwise this will fail. if (! is_array($request->getTokenContent())) { abort(400, 'Invalid token'); @@ -40,8 +39,6 @@ class StripeConnectController extends BaseController MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); - $company = Company::where('company_key', $request->getTokenContent()['company_key'])->first(); - $company_gateway = CompanyGateway::query() ->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34') ->where('company_id', $request->getCompany()->id) @@ -71,6 +68,20 @@ class StripeConnectController extends BaseController } try { + /** @class \stdClass $response + * @property string $scope + * @property string $stripe_user_id + * @property string $stripe_publishable_key + * @property string $refresh_token + * @property string $livemode + * @property string $access_token + * @property string $token_type + * @property string $stripe_user + * @property string $stripe_account + * @property string $error + */ + + /** @var \stdClass $response */ $response = \Stripe\OAuth::token([ 'grant_type' => 'authorization_code', 'code' => $request->input('code'), @@ -81,8 +92,7 @@ class StripeConnectController extends BaseController MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); - /** @var \App\Models\Company $company */ - $company = Company::where('company_key', $request->getTokenContent()['company_key'])->first(); + $company = Company::query()->where('company_key', $request->getTokenContent()['company_key'])->first(); $company_gateway = CompanyGateway::query() ->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34') @@ -97,7 +107,6 @@ class StripeConnectController extends BaseController $company_gateway->fees_and_limits = $fees_and_limits; $company_gateway->setConfig([]); $company_gateway->token_billing = 'always'; - // $company_gateway->save(); } $payload = [ @@ -116,39 +125,14 @@ class StripeConnectController extends BaseController $company_gateway->save(); // StripeWebhook::dispatch($company->company_key, $company_gateway->id); - - //response here - return view('auth.connect.completed'); - } - - private function checkAccountAlreadyLinkToEmail($company_gateway, $email) - { - $client = Client::first() ? Client::first() : new Client; - - //Pull the list of Stripe Accounts and see if we match - $accounts = $company_gateway->driver($client)->getAllConnectedAccounts()->data; - - foreach ($accounts as $account) { - if ($account['email'] == $email) { - return $account['id']; - } + if(isset($request->getTokenContent()['is_react']) && $request->getTokenContent()['is_react']) { + $redirect_uri = 'https://app.invoicing.co/#/settings/online_payments'; + } else { + $redirect_uri = 'https://invoicing.co/stripe/completed'; } - return false; + //response here + return view('auth.connect.completed', ['url' => $redirect_uri]); } - /********************************* - * Stripe OAuth - */ - - // public function initialize(InitializeStripeConnectRequest $request, string $token) - // { - - // $stripe_key = config('ninja.ninja_stripe_key'); - - // $endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_key}&scope=read_write"; - - // return redirect($endpoint); - - // } } diff --git a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php index 8d9a14a76026..ae545c66626c 100644 --- a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php +++ b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php @@ -62,9 +62,6 @@ class ZipPurchaseOrders implements ShouldQueue * Execute the job. * * @return void - * @throws \ZipStream\Exception\FileNotFoundException - * @throws \ZipStream\Exception\FileNotReadableException - * @throws \ZipStream\Exception\OverflowException */ public function handle() { diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 09f5571565df..f216635c28d1 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -37,7 +37,6 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio * @property int $assigned_user_id * @method BaseModel service() * @property \App\Models\Company $company - * @property \App\Models\Vendor $vendor * @method static BaseModel find($value) * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|Illuminate\Database\Eloquent\Relations\BelongsTo|\Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo|\App\Models\Company company() @@ -63,11 +62,15 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio * @method int companyId() * @method createInvitations() * @method Builder scopeCompany(Builder $builder) + * @method static \Illuminate\Database\Eloquent\Builder company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|\Illuminate\Database\Query\Builder withTrashed(bool $withTrashed = true) * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|\Illuminate\Database\Query\Builder onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|\Illuminate\Database\Query\Builder withoutTrashed() * @mixin \Eloquent * @mixin \Illuminate\Database\Eloquent\Builder + * + * @property \Illuminate\Support\Collection $tax_map + * @property array $total_tax_map */ class BaseModel extends Model { @@ -119,7 +122,11 @@ class BaseModel extends Model return parent::__call($method, $params); } - + /** + * @param \Illuminate\Database\Eloquent\Builder $query + * @extends \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder + */ public function scopeCompany($query): \Illuminate\Database\Eloquent\Builder { /** @var \App\Models\User $user */ diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 8a4dc2ea642d..a9a849dd74d1 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -93,29 +93,30 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; * @property string|null $reminder_last_sent * @property float $paid_to_date * @property int|null $subscription_id - * @property-read \Illuminate\Database\Eloquent\Collection $activities - * @property-read int|null $activities_count - * @property-read \App\Models\User|null $assigned_user - * @property-read \App\Models\Client $client - * @property-read \App\Models\Company $company - * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger - * @property-read int|null $company_ledger_count - * @property-read \Illuminate\Database\Eloquent\Collection $documents - * @property-read int|null $documents_count - * @property-read mixed $hashed_id - * @property-read \Illuminate\Database\Eloquent\Collection $history - * @property-read int|null $history_count - * @property-read \Illuminate\Database\Eloquent\Collection $invitations - * @property-read int|null $invitations_count - * @property-read \App\Models\Invoice|null $invoice - * @property-read \Illuminate\Database\Eloquent\Collection $invoices - * @property-read int|null $invoices_count - * @property-read \Illuminate\Database\Eloquent\Collection $payments - * @property-read int|null $payments_count - * @property-read \App\Models\Project|null $project - * @property-read \App\Models\User $user - * @property-read \App\Models\Client $client - * @property-read \App\Models\Vendor|null $vendor + * @property \Illuminate\Database\Eloquent\Collection $activities + * @property int|null $activities_count + * @property \App\Models\User|null $assigned_user + * @property \App\Models\Client $client + * @property \App\Models\Company $company + * @property \App\Models\CreditInvitation $invitation + * @property \Illuminate\Database\Eloquent\Collection $company_ledger + * @property int|null $company_ledger_count + * @property \Illuminate\Database\Eloquent\Collection $documents + * @property int|null $documents_count + * @property mixed $hashed_id + * @property \Illuminate\Database\Eloquent\Collection $history + * @property int|null $history_count + * @property \Illuminate\Database\Eloquent\Collection $invitations + * @property int|null $invitations_count + * @property \App\Models\Invoice|null $invoice + * @property \Illuminate\Database\Eloquent\Collection $invoices + * @property int|null $invoices_count + * @property \Illuminate\Database\Eloquent\Collection $payments + * @property int|null $payments_count + * @property \App\Models\Project|null $project + * @property \App\Models\User $user + * @property \App\Models\Client $client + * @property \App\Models\Vendor|null $vendor * @property-read mixed $pivot * @property-read \Illuminate\Database\Eloquent\Collection $activities * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger diff --git a/app/Models/CreditInvitation.php b/app/Models/CreditInvitation.php index 1c29cd73b75a..ca5d8a2fb27b 100644 --- a/app/Models/CreditInvitation.php +++ b/app/Models/CreditInvitation.php @@ -41,11 +41,11 @@ use Illuminate\Support\Facades\Storage; * @property int|null $deleted_at * @property string|null $signature_ip * @property string|null $email_status - * @property-read \App\Models\Company $company - * @property-read \App\Models\ClientContact $contact - * @property-read \App\Models\Credit $credit - * @property-read mixed $hashed_id - * @property-read \App\Models\User $user + * @property \App\Models\Company $company + * @property \App\Models\ClientContact $contact + * @property \App\Models\Credit $credit + * @property mixed $hashed_id + * @property \App\Models\User $user * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Database\Factories\CreditInvitationFactory factory($count = null, $state = []) diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 6f03027ad6af..b160ff0be709 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -99,12 +99,13 @@ use App\Events\Invoice\InvoiceReminderWasEmailed; * @property int $auto_bill_tries * @property bool $is_proforma * @property-read int|null $activities_count - * @property-read \App\Models\User|null $assigned_user + * @property \App\Models\User|null $assigned_user * @property \App\Models\Client $client - * @property-read \App\Models\Company $company + * @property \App\Models\InvoiceInvitation $invitation + * @property \App\Models\Company $company * @property-read int|null $company_ledger_count * @property-read int|null $credits_count - * @property-read \App\Models\Design|null $design + * @property \App\Models\Design|null $design * @property-read int|null $documents_count * @property-read \App\Models\Expense|null $expense * @property-read int|null $expenses_count diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php index bd4e24f75d1a..08513619b003 100644 --- a/app/Models/InvoiceInvitation.php +++ b/app/Models/InvoiceInvitation.php @@ -43,11 +43,11 @@ use Illuminate\Support\Facades\Storage; * @property int|null $deleted_at * @property string|null $signature_ip * @property string|null $email_status - * @property-read \App\Models\Company $company - * @property-read \App\Models\ClientContact $contact - * @property-read mixed $hashed_id - * @property-read \App\Models\Invoice $invoice - * @property-read \App\Models\User $user + * @property \App\Models\Company $company + * @property \App\Models\ClientContact $contact + * @property mixed $hashed_id + * @property \App\Models\Invoice $invoice + * @property \App\Models\User $user * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Database\Factories\InvoiceInvitationFactory factory($count = null, $state = []) diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php index 932d2ba9b935..cf4841147e6e 100644 --- a/app/Models/PurchaseOrder.php +++ b/app/Models/PurchaseOrder.php @@ -87,17 +87,17 @@ use Illuminate\Support\Facades\Storage; * @property int|null $expense_id * @property int|null $currency_id * @property-read int|null $activities_count - * @property-read \App\Models\User|null $assigned_user - * @property-read \App\Models\Client|null $client - * @property-read \App\Models\Company $company + * @property \App\Models\User|null $assigned_user + * @property \App\Models\Client|null $client + * @property \App\Models\Company $company * @property-read int|null $documents_count - * @property-read \App\Models\Expense|null $expense - * @property-read mixed $hashed_id - * @property-read \App\Models\Invoice|null $invoice - * @property-read \App\Models\Project|null $project - * @property-read \App\Models\User $user - * @property \App\Models\Vendor|null $vendor - * @method static \Illuminate\Database\Eloquent\Builder|PurchaseOrder company() + * @property \App\Models\Expense|null $expense + * @property string $hashed_id + * @property \App\Models\Invoice|null $invoice + * @property \App\Models\Project|null $project + * @property \App\Models\User $user + * @property \App\Models\Vendor $vendor + * @property \App\Models\PurchaseOrderInvitation $invitation * @method static \Illuminate\Database\Eloquent\Builder|PurchaseOrder exclude($columns) * @method static \Database\Factories\PurchaseOrderFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|PurchaseOrder filter(\App\Filters\QueryFilters $filters) @@ -110,10 +110,8 @@ use Illuminate\Support\Facades\Storage; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $history * @property-read \Illuminate\Database\Eloquent\Collection $invitations - * @property-read \Illuminate\Database\Eloquent\Collection $invoices - * @property-read \Illuminate\Database\Eloquent\Collection $payments - * @method static \Illuminate\Database\Eloquent\Builder|Task withTrashed() - * @method static \Illuminate\Database\Eloquent\Builder|Task withoutTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|PurchaseOrder withTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|PurchaseOrder withoutTrashed() * @mixin \Eloquent */ class PurchaseOrder extends BaseModel @@ -236,6 +234,9 @@ class PurchaseOrder extends BaseModel return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ public function vendor(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Vendor::class)->withTrashed(); diff --git a/app/Models/PurchaseOrderInvitation.php b/app/Models/PurchaseOrderInvitation.php index be8e8f4dee80..8599090ed286 100644 --- a/app/Models/PurchaseOrderInvitation.php +++ b/app/Models/PurchaseOrderInvitation.php @@ -40,11 +40,11 @@ use Illuminate\Support\Str; * @property int|null $updated_at * @property int|null $deleted_at * @property string|null $email_status - * @property-read \App\Models\Company $company - * @property-read \App\Models\VendorContact $contact - * @property-read mixed $hashed_id - * @property-read \App\Models\PurchaseOrder $purchase_order - * @property-read \App\Models\User $user + * @property \App\Models\Company $company + * @property \App\Models\VendorContact $contact + * @property string $hashed_id + * @property \App\Models\PurchaseOrder $purchase_order + * @property \App\Models\User $user * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Database\Factories\PurchaseOrderInvitationFactory factory($count = null, $state = []) diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 38a3443460da..551d6b3a45a2 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -91,9 +91,10 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $reminder_last_sent * @property string $paid_to_date * @property int|null $subscription_id - * @property-read \App\Models\User|null $assigned_user - * @property-read \App\Models\Client $client - * @property-read \App\Models\Company $company + * @property \App\Models\User|null $assigned_user + * @property \App\Models\Client $client + * @property \App\Models\Company $company + * @property \App\Models\QuoteInvitation $invitation * @property-read mixed $balance_due * @property-read mixed $hashed_id * @property-read mixed $total diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php index fcd20852f77c..a183d3d5b63f 100644 --- a/app/Models/QuoteInvitation.php +++ b/app/Models/QuoteInvitation.php @@ -41,11 +41,11 @@ use Illuminate\Support\Facades\Storage; * @property int|null $deleted_at * @property string|null $signature_ip * @property string|null $email_status - * @property-read \App\Models\Company $company - * @property-read \App\Models\ClientContact $contact - * @property-read mixed $hashed_id - * @property-read \App\Models\Quote $quote - * @property-read \App\Models\User $user + * @property \App\Models\Company $company + * @property \App\Models\ClientContact $contact + * @property mixed $hashed_id + * @property \App\Models\Quote $quote + * @property \App\Models\User $user * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Database\Factories\QuoteInvitationFactory factory($count = null, $state = []) diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index 248f946fe9ce..44e8c61265cf 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -91,6 +91,7 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \App\Models\User|null $assigned_user * @property-read \App\Models\Client $client * @property-read \App\Models\Company $company + * @property \App\Models\RecurringInvoiceInvitation $invitation * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read int|null $documents_count * @property-read mixed $hashed_id diff --git a/app/Models/RecurringInvoiceInvitation.php b/app/Models/RecurringInvoiceInvitation.php index 01e2c088ce73..34928a155c1d 100644 --- a/app/Models/RecurringInvoiceInvitation.php +++ b/app/Models/RecurringInvoiceInvitation.php @@ -37,11 +37,11 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string|null $viewed_date * @property string|null $opened_date * @property string|null $email_status - * @property-read \App\Models\Company $company - * @property-read \App\Models\ClientContact $contact - * @property-read mixed $hashed_id - * @property-read \App\Models\RecurringInvoice $recurring_invoice - * @property-read \App\Models\User $user + * @property \App\Models\Company $company + * @property \App\Models\ClientContact $contact + * @property string $hashed_id + * @property \App\Models\RecurringInvoice $recurring_invoice + * @property \App\Models\User $user * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder|RecurringInvoiceInvitation newModelQuery() diff --git a/app/Models/User.php b/app/Models/User.php index 4e9a2864d69b..98a11089042b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -95,6 +95,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; * @property-read \Illuminate\Database\Eloquent\Collection $company_users * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read \Illuminate\Database\Eloquent\Collection $companies * @method bool hasPermissionTo(string $permission) * @method \App\Models\Company getCompany() * @method \App\Models\Company company() diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index d72061bad7fa..a055ed7992b4 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -66,7 +66,6 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $primary_contact * @property-read int|null $primary_contact_count * @property-read \App\Models\User $user - * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Database\Factories\VendorFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Vendor filter(\App\Filters\QueryFilters $filters) @@ -75,35 +74,6 @@ use Laracasts\Presenter\PresentableTrait; * @method static \Illuminate\Database\Eloquent\Builder|Vendor onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|Vendor query() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel scope() - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereAddress1($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereAddress2($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereAssignedUserId($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCity($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCompanyId($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCountryId($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCreatedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCurrencyId($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCustomValue1($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCustomValue2($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCustomValue3($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereCustomValue4($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereDeletedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereId($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereIdNumber($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereIsDeleted($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereName($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereNumber($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor wherePhone($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor wherePostalCode($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor wherePrivateNotes($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor wherePublicNotes($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereState($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereTransactionName($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereUpdatedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereUserId($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereVatNumber($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereVendorHash($value) - * @method static \Illuminate\Database\Eloquent\Builder|Vendor whereWebsite($value) * @method static \Illuminate\Database\Eloquent\Builder|Vendor withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|Vendor withoutTrashed() * @property-read \Illuminate\Database\Eloquent\Collection $activities @@ -215,6 +185,9 @@ class Vendor extends BaseModel return $this->company->timezone(); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Company::class); @@ -225,7 +198,7 @@ class Vendor extends BaseModel return $this->belongsTo(User::class)->withTrashed(); } - public function translate_entity() + public function translate_entity(): string { return ctrans('texts.vendor'); } @@ -253,7 +226,13 @@ class Vendor extends BaseModel return $defaults; } - public function getSetting($setting) + /** + * Returns a vendor settings proxying company setting + * + * @param string $setting + * @return string + */ + public function getSetting($setting): string { if ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) { return $this->company->settings->{$setting}; diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php index 466c0c49dbf0..38e9da662e39 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php @@ -50,7 +50,7 @@ class PaymentIntentFailureWebhook implements ShouldQueue { MultiDB::findAndSetDbByCompanyKey($this->company_key); - $company = Company::where('company_key', $this->company_key)->first(); + $company = Company::query()->where('company_key', $this->company_key)->first(); foreach ($this->stripe_request as $transaction) { if (array_key_exists('payment_intent', $transaction)) { @@ -78,7 +78,7 @@ class PaymentIntentFailureWebhook implements ShouldQueue $payment->status_id = Payment::STATUS_FAILED; $payment->save(); - $payment_hash = PaymentHash::where('payment_id', $payment->id)->first(); + $payment_hash = PaymentHash::query()->where('payment_id', $payment->id)->first(); if ($payment_hash) { $error = ctrans('texts.client_payment_failure_body', [ diff --git a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php index 8cbaaf95757c..58ff84400fcc 100644 --- a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php +++ b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php @@ -208,17 +208,24 @@ class UpdatePaymentMethods switch ($type_id) { case GatewayType::CREDIT_CARD: - $payment_meta = new \stdClass; - $payment_meta->exp_month = (string) $method->card->exp_month; - $payment_meta->exp_year = (string) $method->card->exp_year; - $payment_meta->brand = (string) $method->card->brand; - $payment_meta->last4 = (string) $method->card->last4; - $payment_meta->type = GatewayType::CREDIT_CARD; - - return $payment_meta; - - break; + /** + * @class \Stripe\PaymentMethod $method + * @property \Stripe\StripeObject $card + * @class \Stripe\StripeObject $card + * @property string $exp_year + * @property string $exp_month + * @property string $brand + * @property string $last4 + */ + + $payment_meta = new \stdClass; + $payment_meta->exp_month = (string) $method->card->exp_month; + $payment_meta->exp_year = (string) $method->card->exp_year; + $payment_meta->brand = (string) $method->card->brand; + $payment_meta->last4 = (string) $method->card->last4; + $payment_meta->type = GatewayType::CREDIT_CARD; + return $payment_meta; case GatewayType::ALIPAY: case GatewayType::SOFORT: diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 81cd527c615f..66ec70ed1355 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -155,7 +155,8 @@ class ClientService return false; } - return $this->emailStatement($pdf, $statement->options); + $this->emailStatement($pdf, $statement->options); + return; } return $pdf; diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 67ceaa42d82e..3ee76fbaed5a 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -133,7 +133,7 @@ class PdfMock $entity->vendor = Vendor::factory()->make(); break; default: - # code... + $entity = false; break; } @@ -152,11 +152,12 @@ class PdfMock */ public function getMergedSettings() :object { + $settings = $this->company->settings; + match ($this->request['settings_type']) { 'group' => $settings = ClientSettings::buildClientSettings($this->company->settings, $this->request['settings']), 'client' => $settings = ClientSettings::buildClientSettings($this->company->settings, $this->request['settings']), - 'company' => $settings = (object)$this->request['settings'], - default => $settings = $this->company->settings, + 'company' => $settings = (object)$this->request['settings'] }; $settings = CompanySettings::setProperties($settings); @@ -168,9 +169,9 @@ class PdfMock /** * getTaxMap * - * @return void + * @return \Illuminate\Support\Collection */ - private function getTaxMap() + private function getTaxMap(): \Illuminate\Support\Collection { return collect([['name' => 'GST', 'total' => 10]]); } @@ -178,9 +179,9 @@ class PdfMock /** * getTotalTaxMap * - * @return void + * @return array */ - private function getTotalTaxMap() + private function getTotalTaxMap(): array { return [['name' => 'GST', 'total' => 10]]; } @@ -188,8 +189,9 @@ class PdfMock /** * getStubVariables * + * @return array */ - public function getStubVariables() + public function getStubVariables(): array { return ['values' => [ diff --git a/app/Services/Preview/StubBuilder.php b/app/Services/Preview/StubBuilder.php index 4bf2eafcca47..8d3cce3076f9 100644 --- a/app/Services/Preview/StubBuilder.php +++ b/app/Services/Preview/StubBuilder.php @@ -227,10 +227,13 @@ class StubBuilder private function createClient(): self { - $this->recipient = Client::factory()->create([ + /** @var \App\Models\Client $client */ + $client = Client::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, ]); + + $this->recipient = $client; $this->contact = ClientContact::factory()->create([ 'user_id' => $this->user->id, @@ -245,10 +248,13 @@ class StubBuilder private function createVendor(): self { - $this->recipient = Vendor::factory()->create([ + /** @var \App\Models\Vendor $vendor */ + $vendor = Vendor::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->user->company()->id, ]); + + $this->recipient = $vendor; $this->contact = VendorContact::factory()->create([ 'user_id' => $this->user->id, @@ -276,7 +282,8 @@ class StubBuilder private function createInvoice() { - $this->entity = Invoice::factory()->create([ + /** @var \App\Models\Invoice $invoice */ + $invoice = Invoice::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->recipient->id, @@ -285,6 +292,8 @@ class StubBuilder 'status_id' => Invoice::STATUS_PAID, ]); + $this->entity = $invoice; + $this->invitation = InvoiceInvitation::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, diff --git a/app/Transformers/BankIntegrationTransformer.php b/app/Transformers/BankIntegrationTransformer.php index 0f296e19ad85..c0a983ced78d 100644 --- a/app/Transformers/BankIntegrationTransformer.php +++ b/app/Transformers/BankIntegrationTransformer.php @@ -12,6 +12,7 @@ namespace App\Transformers; use App\Models\Account; +use App\Models\Company; use App\Models\BankIntegration; use App\Models\BankTransaction; use App\Utils\Traits\MakesHash; diff --git a/app/Transformers/CompanyGatewayTransformer.php b/app/Transformers/CompanyGatewayTransformer.php index d1f214086512..2c79a442a557 100644 --- a/app/Transformers/CompanyGatewayTransformer.php +++ b/app/Transformers/CompanyGatewayTransformer.php @@ -11,11 +11,12 @@ namespace App\Transformers; -use App\Models\CompanyGateway; +use stdClass; +use App\Models\Gateway; use App\Models\SystemLog; +use App\Models\CompanyGateway; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\SoftDeletes; -use stdClass; /** * Class CompanyGatewayTransformer. diff --git a/composer.json b/composer.json index 7e1bf4f4ba1d..0d071ca1da10 100644 --- a/composer.json +++ b/composer.json @@ -113,6 +113,7 @@ "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^9.5.10", "spatie/laravel-ignition": "^1.0", + "spaze/phpstan-stripe": "^3.0", "vimeo/psalm": "^4.24" }, "autoload": { diff --git a/composer.lock b/composer.lock index 4afddc900c60..6bf16dd7b2c8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "be16996524279f340c44e2f6c9a0ba53", + "content-hash": "9d7348352c913eb82fcca2e67670e1f8", "packages": [ { "name": "adrienrn/php-mimetyper", @@ -17898,6 +17898,68 @@ ], "time": "2023-01-03T19:28:04+00:00" }, + { + "name": "spaze/phpstan-stripe", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/spaze/phpstan-stripe.git", + "reference": "6e5debe4f65b15192a28bd01f01670ced8250443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spaze/phpstan-stripe/zipball/6e5debe4f65b15192a28bd01f01670ced8250443", + "reference": "6e5debe4f65b15192a28bd01f01670ced8250443", + "shasum": "" + }, + "require": { + "php": "^8.0", + "phpstan/phpstan": "^1.7" + }, + "require-dev": { + "nikic/php-parser": "^4.13", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "spaze/coding-standard": "^1.1", + "stripe/stripe-php": "^8.7|^9.9|^10.8" + }, + "suggest": { + "phpstan/extension-installer": "Allows automatic requirement of extension.neon" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Spaze\\PHPStan\\Stripe\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michal Špaček", + "email": "mail@michalspacek.cz", + "homepage": "https://www.michalspacek.cz" + } + ], + "description": "Stripe SDK extension for PHPStan", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/spaze/phpstan-stripe/issues", + "source": "https://github.com/spaze/phpstan-stripe/tree/v3.0.0" + }, + "time": "2023-03-06T00:39:29+00:00" + }, { "name": "symfony/polyfill-php81", "version": "v1.27.0", diff --git a/phpstan.neon b/phpstan.neon index 7aec499ed7bd..92bb5444400e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ includes: - ./vendor/nunomaduro/larastan/extension.neon + - ./vendor/spaze/phpstan-stripe/extension.neon parameters: level: 2 paths: diff --git a/resources/views/auth/connect/completed.blade.php b/resources/views/auth/connect/completed.blade.php index 3d9999add5c6..05ea6a8cd463 100644 --- a/resources/views/auth/connect/completed.blade.php +++ b/resources/views/auth/connect/completed.blade.php @@ -8,6 +8,6 @@

Connecting your account using Stripe has been successfully completed.

- Click here to continue. + Click here to continue. @endsection From d465fd3f2e9271da947b7184a060e2f92cc70619 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 17:14:54 +1000 Subject: [PATCH 22/29] Fixes for tests --- app/DataMapper/CompanySettings.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 141762d8ed91..5a957a8f8fcd 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -812,15 +812,13 @@ class CompanySettings extends BaseSettings * need to provide a fallback catch on old settings objects which will * set new properties to the object prior to being returned. * - * @param $settings + * @param \stdClass $settings * * @return stdClass */ public static function setProperties($settings): stdClass { $company_settings = (object) get_class_vars(self::class); - - $settings = new \stdClass; foreach ($company_settings as $key => $value) { if (! property_exists($settings, $key)) { From b08d80572815b4cb4019241b81d9c992f787ee82 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 17:58:43 +1000 Subject: [PATCH 23/29] Fixes for tests --- .../Requests/Yodlee/YodleeAuthRequest.php | 6 +++- app/Jobs/Document/ZipDocuments.php | 3 -- app/Jobs/Quote/ZipQuotes.php | 3 -- app/Models/BaseModel.php | 1 - app/Models/Client.php | 10 ++++-- app/Models/Company.php | 3 ++ app/Models/CompanyUser.php | 33 ++++--------------- app/Models/Credit.php | 9 +++++ app/Models/Expense.php | 5 ++- app/Models/GroupSetting.php | 5 ++- app/Models/Invoice.php | 31 +++++++++++++---- app/Models/Payment.php | 13 +++++--- app/Models/Quote.php | 6 ++++ app/PaymentDrivers/Stripe/ImportCustomers.php | 8 ++--- 14 files changed, 83 insertions(+), 53 deletions(-) diff --git a/app/Http/Requests/Yodlee/YodleeAuthRequest.php b/app/Http/Requests/Yodlee/YodleeAuthRequest.php index 34b214da9f48..071ac46483fd 100644 --- a/app/Http/Requests/Yodlee/YodleeAuthRequest.php +++ b/app/Http/Requests/Yodlee/YodleeAuthRequest.php @@ -17,6 +17,11 @@ use App\Models\Company; use App\Models\User; use Illuminate\Support\Facades\Cache; +/** + * @class \App\Http\Requests\Yodlee\YodleeAuthRequest + * @property string $token + * @property string $state + */ class YodleeAuthRequest extends Request { /** @@ -39,7 +44,6 @@ class YodleeAuthRequest extends Request return []; } - /** @var Request $token */ public function getTokenContent() { if ($this->state) { diff --git a/app/Jobs/Document/ZipDocuments.php b/app/Jobs/Document/ZipDocuments.php index 393aeec3bb80..d255c10640cf 100644 --- a/app/Jobs/Document/ZipDocuments.php +++ b/app/Jobs/Document/ZipDocuments.php @@ -66,9 +66,6 @@ class ZipDocuments implements ShouldQueue * Execute the job. * * @return void - * @throws \ZipStream\Exception\FileNotFoundException - * @throws \ZipStream\Exception\FileNotReadableException - * @throws \ZipStream\Exception\OverflowException */ public function handle() { diff --git a/app/Jobs/Quote/ZipQuotes.php b/app/Jobs/Quote/ZipQuotes.php index 3dd1b7f8967b..14693ac3fc41 100644 --- a/app/Jobs/Quote/ZipQuotes.php +++ b/app/Jobs/Quote/ZipQuotes.php @@ -62,9 +62,6 @@ class ZipQuotes implements ShouldQueue * Execute the job. * * @return void - * @throws \ZipStream\Exception\FileNotFoundException - * @throws \ZipStream\Exception\FileNotReadableException - * @throws \ZipStream\Exception\OverflowException */ public function handle() { diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index f216635c28d1..7f8c0d4bca69 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -124,7 +124,6 @@ class BaseModel extends Model /** * @param \Illuminate\Database\Eloquent\Builder $query - * @extends \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder */ public function scopeCompany($query): \Illuminate\Database\Eloquent\Builder diff --git a/app/Models/Client.php b/app/Models/Client.php index 9f69bfc0d1a3..2087a27186da 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -238,7 +238,10 @@ class Client extends BaseModel implements HasLocalePreference return $this->hasMany(CompanyLedger::class)->orderBy('id', 'desc'); } - public function company_ledger() + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function company_ledger(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(CompanyLedger::class, 'company_ledgerable'); } @@ -516,7 +519,10 @@ class Client extends BaseModel implements HasLocalePreference throw new \Exception('Could not find a settings object', 1); } - public function documents() :MorphMany + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function documents() :\Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); } diff --git a/app/Models/Company.php b/app/Models/Company.php index 0c8ac30a5990..9b9f12c11858 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -395,6 +395,9 @@ class Company extends BaseModel return $this->calculate_taxes && in_array($this->getSetting('country_id'), $this->tax_coverage_countries); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function documents() { return $this->morphMany(Document::class, 'documentable'); diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index ba3306808f37..1dfe71544f3e 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -44,28 +44,6 @@ use Illuminate\Database\Eloquent\Relations\Pivot; * @property-read \App\Models\User $user * @property-read \Illuminate\Database\Eloquent\Collection $users * @property-read int|null $users_count - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser authCompany() - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser newModelQuery() - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser newQuery() - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser onlyTrashed() - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser query() - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereAccountId($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereCompanyId($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereCreatedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereDeletedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereId($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereIsAdmin($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereIsLocked($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereIsOwner($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereNinjaPortalUrl($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereNotifications($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser wherePermissions($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser wherePermissionsUpdatedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereReactSettings($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereSettings($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereSlackWebhookUrl($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereUpdatedAt($value) - * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereUserId($value) * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser withoutTrashed() * @property-read \Illuminate\Database\Eloquent\Collection $token @@ -130,18 +108,18 @@ class CompanyUser extends Pivot /** * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function user_pivot() + public function user_pivot(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(User::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } /** * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function company_pivot() + public function company_pivot(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(Company::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } @@ -156,6 +134,9 @@ class CompanyUser extends Pivot return $this->belongsTo(Company::class); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function users(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(User::class)->withTrashed(); diff --git a/app/Models/Credit.php b/app/Models/Credit.php index a9a849dd74d1..d7d5e91d869f 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -270,6 +270,9 @@ class Credit extends BaseModel return $this->belongsTo(Invoice::class); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function company_ledger(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(CompanyLedger::class, 'company_ledgerable'); @@ -289,11 +292,17 @@ class Credit extends BaseModel return $this->belongsToMany(Invoice::class)->using(Paymentable::class); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ public function payments(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphToMany(Payment::class, 'paymentable'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function documents(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); diff --git a/app/Models/Expense.php b/app/Models/Expense.php index d60802bc2338..3ebe98b30c8b 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -150,7 +150,10 @@ class Expense extends BaseModel return self::class; } - public function documents() + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function documents(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); } diff --git a/app/Models/GroupSetting.php b/app/Models/GroupSetting.php index fb6b52b6513d..1a8bd5f093f5 100644 --- a/app/Models/GroupSetting.php +++ b/app/Models/GroupSetting.php @@ -102,7 +102,10 @@ class GroupSetting extends StaticModel return $this->hasMany(Client::class, 'id', 'group_settings_id'); } - public function documents() + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function documents(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index b160ff0be709..687cde690ebc 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -31,6 +31,7 @@ use App\Helpers\Invoice\InvoiceSumInclusive; use App\Utils\Traits\Invoice\ActionsInvoice; use Illuminate\Database\Eloquent\SoftDeletes; use App\Events\Invoice\InvoiceReminderWasEmailed; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; /** * App\Models\Invoice @@ -302,16 +303,25 @@ class Invoice extends BaseModel return $this->belongsTo(Subscription::class)->withTrashed(); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function documents(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ public function payments(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphToMany(Payment::class, 'paymentable')->withTrashed()->withPivot('amount', 'refunded')->withTimestamps(); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function company_ledger(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(CompanyLedger::class, 'company_ledgerable'); @@ -322,32 +332,41 @@ class Invoice extends BaseModel return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50); } - public function history() + /** + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function history(): \Illuminate\Database\Eloquent\Relations\HasManyThrough { return $this->hasManyThrough(Backup::class, Activity::class); } - public function credits() + public function credits(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Credit::class); } - public function tasks() + public function tasks(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Task::class); } - public function task() + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function task(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(Task::class); } - public function expenses() + public function expenses(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Expense::class); } - public function expense() + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function expense(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(Expense::class); } diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 79f4cfa5a87e..3773e8237d99 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -216,21 +216,24 @@ class Payment extends BaseModel } /** - * @return MorphedByMany + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ - public function invoices() + public function invoices(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Invoice::class, 'paymentable')->withTrashed()->withPivot('amount', 'refunded')->withTimestamps(); } /** - * @return MorphedByMany + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ - public function credits() + public function credits(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Credit::class, 'paymentable')->withTrashed()->withPivot('amount', 'refunded')->withTimestamps(); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function company_ledger(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(CompanyLedger::class, 'company_ledgerable'); @@ -266,7 +269,7 @@ class Payment extends BaseModel return $this->belongsTo(Project::class); } - public function translatedType() + public function translatedType(): string { if (! $this->type_id) { return ''; diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 551d6b3a45a2..3ab63980e771 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -227,6 +227,9 @@ class Quote extends BaseModel return $this->belongsTo(Vendor::class); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ public function history(): \Illuminate\Database\Eloquent\Relations\HasManyThrough { return $this->hasManyThrough(Backup::class, Activity::class); @@ -267,6 +270,9 @@ class Quote extends BaseModel return $this->hasMany(QuoteInvitation::class); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ public function documents(): \Illuminate\Database\Eloquent\Relations\MorphMany { return $this->morphMany(Document::class, 'documentable'); diff --git a/app/PaymentDrivers/Stripe/ImportCustomers.php b/app/PaymentDrivers/Stripe/ImportCustomers.php index 387544bdb2c8..9d80b3b3a588 100644 --- a/app/PaymentDrivers/Stripe/ImportCustomers.php +++ b/app/PaymentDrivers/Stripe/ImportCustomers.php @@ -80,7 +80,7 @@ class ImportCustomers { $account = $this->stripe->company_gateway->company->account; - if (Ninja::isHosted() && ! $account->isPaidHostedClient() && Client::where('company_id', $this->stripe->company_gateway->company_id)->count() > config('ninja.quotas.free.clients')) { + if (Ninja::isHosted() && ! $account->isPaidHostedClient() && Client::query()->where('company_id', $this->stripe->company_gateway->company_id)->count() > config('ninja.quotas.free.clients')) { return; } @@ -115,7 +115,7 @@ class ImportCustomers $client->phone = $customer->address->phone ? $customer->phone : ''; if ($customer->address->country) { - $country = Country::where('iso_3166_2', $customer->address->country)->first(); + $country = Country::query()->where('iso_3166_2', $customer->address->country)->first(); if ($country) { $client->country_id = $country->id; @@ -124,7 +124,7 @@ class ImportCustomers } if ($customer->currency) { - $currency = Currency::where('code', $customer->currency)->first(); + $currency = Currency::query()->where('code', $customer->currency)->first(); if ($currency) { $settings = $client->settings; @@ -209,7 +209,7 @@ class ImportCustomers // nlog(count($searchResults)); if (count($searchResults) == 1) { - $cgt = ClientGatewayToken::where('gateway_customer_reference', $searchResults->data[0]->id)->where('company_id', $this->stripe->company_gateway->company->id)->exists(); + $cgt = ClientGatewayToken::query()->where('gateway_customer_reference', $searchResults->data[0]->id)->where('company_id', $this->stripe->company_gateway->company->id)->exists(); if (! $cgt) { nlog('customer '.$searchResults->data[0]->id.' does not exist.'); From b4d48c96d474349507f69c80c040405a5b1a033c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 18:04:41 +1000 Subject: [PATCH 24/29] Updates for static analysis --- app/Models/CompanyUser.php | 62 +++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index 1dfe71544f3e..101aeb0c9e31 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -13,6 +13,8 @@ namespace App\Models; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\Pivot; +use Awobaz\Compoships\Exceptions\InvalidUsageException; +use Awobaz\Compoships\Database\Eloquent\Relations\HasMany; /** * App\Models\CompanyUser @@ -44,6 +46,28 @@ use Illuminate\Database\Eloquent\Relations\Pivot; * @property-read \App\Models\User $user * @property-read \Illuminate\Database\Eloquent\Collection $users * @property-read int|null $users_count + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser authCompany() + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser query() + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereAccountId($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereCompanyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereIsAdmin($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereIsLocked($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereIsOwner($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereNinjaPortalUrl($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereNotifications($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser wherePermissions($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser wherePermissionsUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereReactSettings($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereSettings($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereSlackWebhookUrl($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser whereUserId($value) * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|CompanyUser withoutTrashed() * @property-read \Illuminate\Database\Eloquent\Collection $token @@ -107,53 +131,63 @@ class CompanyUser extends Pivot } /** - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Awobaz\Compoships\Database\Eloquent\Relations\HasOne */ - public function user_pivot(): \Illuminate\Database\Eloquent\Relations\HasOne + + public function user_pivot() { return $this->hasOne(User::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } /** - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Awobaz\Compoships\Database\Eloquent\Relations\HasOne */ - public function company_pivot(): \Illuminate\Database\Eloquent\Relations\HasOne + public function company_pivot() { return $this->hasOne(Company::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() { return $this->belongsTo(User::class)->withTrashed(); } - public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function company() { return $this->belongsTo(Company::class); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function users(): \Illuminate\Database\Eloquent\Relations\HasMany + public function users() { return $this->hasMany(User::class)->withTrashed(); } - /*todo monitor this function - may fail under certain conditions*/ + /** + * @return HasMany + */ public function token() { return $this->hasMany(CompanyToken::class, 'user_id', 'user_id'); } - public function tokens(): \Illuminate\Database\Eloquent\Relations\HasMany + /** + * @return HasMany + */ + public function tokens() { return $this->hasMany(CompanyToken::class, 'user_id', 'user_id'); } - public function scopeAuthCompany($query) + public function scopeAuthCompany($query): \Illuminate\Database\Eloquent\Builder { /** @var \App\Models\User $user */ $user = auth()->user(); @@ -168,7 +202,7 @@ class CompanyUser extends Pivot * * @return bool */ - public function portalType():bool + public function portalType(): bool { return isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link; } From 542a4108927f16d4cd8b913c17b17ae011862c10 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 18:07:57 +1000 Subject: [PATCH 25/29] Updates for static analysis --- app/Models/CompanyUser.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index 101aeb0c9e31..9f57099d7a6c 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -131,16 +131,15 @@ class CompanyUser extends Pivot } /** - * @return \Awobaz\Compoships\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function user_pivot() { return $this->hasOne(User::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } /** - * @return \Awobaz\Compoships\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function company_pivot() { From c293b743738d75c1ce1bac567fc5f1e738379a09 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 11 Aug 2023 18:23:59 +1000 Subject: [PATCH 26/29] fixes for tests --- app/Models/CompanyUser.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index 9f57099d7a6c..347365a63ddb 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -131,17 +131,17 @@ class CompanyUser extends Pivot } /** - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function user_pivot() + public function user_pivot(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(User::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasOne + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function company_pivot() + public function company_pivot(): \Illuminate\Database\Eloquent\Relations\HasOne { return $this->hasOne(Company::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating'); } From b2f0e04e78a00aa86b9828555984d9c25431ceac Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 12 Aug 2023 12:40:41 +1000 Subject: [PATCH 27/29] Fixes for tests --- tests/Feature/UserTest.php | 63 ++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index b79644f586a7..083dc0a58c24 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -38,6 +38,8 @@ class UserTest extends TestCase private $default_email = 'attach@gmail.com'; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -50,7 +52,7 @@ class UserTest extends TestCase Model::reguard(); - $this->withoutExceptionHandling(); + // $this->withoutExceptionHandling(); $this->withoutMiddleware( ThrottleRequests::class, @@ -58,10 +60,9 @@ class UserTest extends TestCase ); } - public function testUserAttemptingtToDeleteThemselves() + private function mockAccout() { - $account = Account::factory()->create([ 'hosted_client_count' => 1000, 'hosted_company_count' => 1000, @@ -102,17 +103,67 @@ class UserTest extends TestCase $company_token->token = $token; $company_token->is_system = true; + + } + + public function testUserAttemptingtToDeleteThemselves() + { + + $account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $account->num_users = 3; + $account->save(); + + $user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + 'password' => \Illuminate\Support\Facades\Hash::make('ALongAndBriliantPassword'), + ]); + + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + + $company = Company::factory()->create([ + 'account_id' => $account->id, + 'settings' => $settings, + ]); + + + $cu = CompanyUserFactory::create($user->id, $company->id, $account->id); + $cu->is_owner = true; + $cu->is_admin = true; + $cu->is_locked = false; + $cu->save(); + + $token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken(); + $company_token->user_id = $user->id; + $company_token->company_id = $company->id; + $company_token->account_id = $account->id; + $company_token->name = 'test token'; + $company_token->token = $token; + $company_token->is_system = true; + $company_token->save(); + $data = [ 'ids' => [$user->hashed_id], - ]; + ]; $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $token, 'X-API-PASSWORD' => 'ALongAndBriliantPassword', - ])->postJson('/api/v1/users/bulk?action=dete', $data) - ->assertStatus(403); + ])->postJson('/api/v1/users/bulk?action=delete', $data); + nlog($response); + + $response->assertStatus(401); } From a847bdb0edd52fc068256141b7bf894f3dce3daf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 12 Aug 2023 12:46:46 +1000 Subject: [PATCH 28/29] Fixes for tests --- tests/Feature/UserTest.php | 65 +++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index 083dc0a58c24..595195ce6f7f 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -60,7 +60,7 @@ class UserTest extends TestCase ); } - private function mockAccout() + private function mockAccount() { $account = Account::factory()->create([ @@ -75,6 +75,7 @@ class UserTest extends TestCase 'account_id' => $this->account->id, 'confirmation_code' => 'xyz123', 'email' => $this->faker->unique()->safeEmail(), + 'password' => \Illuminate\Support\Facades\Hash::make('ALongAndBriliantPassword'), ]); $settings = CompanySettings::defaults(); @@ -102,6 +103,68 @@ class UserTest extends TestCase $company_token->name = 'test token'; $company_token->token = $token; $company_token->is_system = true; + $company_token->save(); + + return $company_token; + + } + + public function testUserResponse() + { + $company_token = $this->mockAccount(); + + $data = [ + 'first_name' => 'hey', + 'last_name' => 'you', + 'email' => 'normal_user@gmail.com', + 'company_user' => [ + 'is_admin' => true, + 'is_owner' => false, + 'permissions' => 'create_client,create_invoice', + ], + 'phone' => null, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->post('/api/v1/users?include=company_user', $data); + + $response->assertStatus(200); + + $user = $response->json(); + $user_id = $user['data']['id']; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->get('/api/v1/users', $data); + + + $response->assertStatus(200); + $arr = $response->json(); + + $this->assertCount(2, $arr['data']); + + //archive the user we just created: + + $data = [ + 'action' => 'archive', + 'ids' => [$user_id], + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->postJson('/api/v1/users/bulk', $data); + + $response->assertStatus(200); + + $this->assertCount(1, $response->json()['data']); } From 0f931fe1a8fdc0309374a165b6548674a113b783 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 12 Aug 2023 16:21:06 +1000 Subject: [PATCH 29/29] Tests for users --- app/Filters/UserFilters.php | 3 +- tests/Feature/UserTest.php | 60 +++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/app/Filters/UserFilters.php b/app/Filters/UserFilters.php index 12a26d27a6cf..3989ec7450ed 100644 --- a/app/Filters/UserFilters.php +++ b/app/Filters/UserFilters.php @@ -127,8 +127,7 @@ class UserFilters extends QueryFilters $user_array = $this->transformKeys(explode(',', $user_id)); return $this->builder->where(function ($query) use ($user_array) { - $query->whereNotIn('id', $user_array) - ->where('account_id', auth()->user()->account_id); + $query->whereNotIn('id', $user_array); }); } } diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index 595195ce6f7f..4872dd7478a3 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -142,29 +142,63 @@ class UserTest extends TestCase 'X-API-PASSWORD' => 'ALongAndBriliantPassword', ])->get('/api/v1/users', $data); - $response->assertStatus(200); $arr = $response->json(); $this->assertCount(2, $arr['data']); - //archive the user we just created: + //archive the user we just created: - $data = [ - 'action' => 'archive', - 'ids' => [$user_id], - ]; + $data = [ + 'action' => 'archive', + 'ids' => [$user_id], + ]; + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->postJson('/api/v1/users/bulk', $data); - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $company_token->token, - 'X-API-PASSWORD' => 'ALongAndBriliantPassword', - ])->postJson('/api/v1/users/bulk', $data); + $response->assertStatus(200); - $response->assertStatus(200); + $this->assertCount(1, $response->json()['data']); - $this->assertCount(1, $response->json()['data']); + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->get("/api/v1/users?without={$company_token->user->hashed_id}"); + + $response->assertStatus(200); + $this->assertCount(1, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=active"); + + $response->assertStatus(200); + $this->assertCount(0, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=archived"); + + $response->assertStatus(200); + $this->assertCount(1, $response->json()['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $company_token->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=deleted"); + + $response->assertStatus(200); + $this->assertCount(0, $response->json()['data']); }