From 726ba91368d54a07bfd30a399e43366b6bc62173 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 13:06:03 +1100 Subject: [PATCH 01/26] Add in company columns for SMTP configuration --- .../Requests/Company/StoreCompanyRequest.php | 21 ++++++++++-- .../Requests/Company/UpdateCompanyRequest.php | 28 ++++++++++++--- app/Models/Company.php | 16 +++++++++ app/Repositories/CompanyRepository.php | 9 +++++ app/Transformers/CompanyTransformer.php | 7 ++++ .../2024_02_16_011055_smtp_configuration.php | 34 +++++++++++++++++++ tests/Feature/CompanyTest.php | 9 +++++ 7 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 database/migrations/2024_02_16_011055_smtp_configuration.php diff --git a/app/Http/Requests/Company/StoreCompanyRequest.php b/app/Http/Requests/Company/StoreCompanyRequest.php index acab4c2a502e..c9aae8b611be 100644 --- a/app/Http/Requests/Company/StoreCompanyRequest.php +++ b/app/Http/Requests/Company/StoreCompanyRequest.php @@ -56,6 +56,12 @@ class StoreCompanyRequest extends Request } } + $rules['smtp_host'] = 'sometimes|string'; + $rules['smtp_port'] = 'sometimes|string'; + $rules['smtp_encryption'] = 'sometimes|string'; + $rules['smtp_local_domain'] = 'sometimes|string'; + // $rules['smtp_verify_peer'] = 'sometimes|in:true,false'; + return $rules; } @@ -67,11 +73,11 @@ class StoreCompanyRequest extends Request $input['name'] = 'Untitled Company'; } - if (array_key_exists('google_analytics_url', $input)) { + if (isset($input['google_analytics_url'])) { $input['google_analytics_key'] = $input['google_analytics_url']; } - if (array_key_exists('portal_domain', $input)) { + if (isset($input['portal_domain'])) { $input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/"); } @@ -79,6 +85,17 @@ class StoreCompanyRequest extends Request $input['subdomain'] = MultiDB::randomSubdomainGenerator(); } + if(isset($input['smtp_username']) && strlen(str_replace("*", "", $input['smtp_username'])) < 2) { + unset($input['smtp_username']); + } + + if(isset($input['smtp_password']) && strlen(str_replace("*", "", $input['smtp_password'])) < 2) { + unset($input['smtp_password']); + } + + if(isset($input['smtp_verify_peer']) && is_string($input['smtp_verify_peer'])) + $input['smtp_verify_peer'] == 'true' ? true : false; + $this->replace($input); } } diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index b5329ecb43e2..5b716aacf21c 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -57,8 +57,14 @@ class UpdateCompanyRequest extends Request $rules['matomo_id'] = 'nullable|integer'; $rules['e_invoice_certificate_passphrase'] = 'sometimes|nullable'; $rules['e_invoice_certificate'] = 'sometimes|nullable|file|mimes:p12,pfx,pem,cer,crt,der,txt,p7b,spc,bin'; - // $rules['client_registration_fields'] = 'array'; + $rules['smtp_host'] = 'sometimes|string'; + $rules['smtp_port'] = 'sometimes|string'; + $rules['smtp_encryption'] = 'sometimes|string'; + $rules['smtp_local_domain'] = 'sometimes|string'; + // $rules['smtp_verify_peer'] = 'sometimes|string'; + + if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) { $rules['portal_domain'] = 'bail|nullable|sometimes|url'; } @@ -74,23 +80,35 @@ class UpdateCompanyRequest extends Request { $input = $this->all(); - if (array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) { + if (isset($input['portal_domain']) && strlen($input['portal_domain']) > 1) { $input['portal_domain'] = $this->addScheme($input['portal_domain']); $input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/"); } - if (array_key_exists('settings', $input)) { + if (isset($input['settings'])) { $input['settings'] = (array)$this->filterSaveableSettings($input['settings']); } - if(array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) { + if(isset($input['subdomain']) && $this->company->subdomain == $input['subdomain']) { unset($input['subdomain']); } - if(array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) { + if(isset($input['e_invoice_certificate_passphrase']) && empty($input['e_invoice_certificate_passphrase'])) { unset($input['e_invoice_certificate_passphrase']); } + if(isset($input['smtp_username']) && strlen(str_replace("*","", $input['smtp_username'])) < 2) { + unset($input['smtp_username']); + } + + if(isset($input['smtp_password']) && strlen(str_replace("*", "", $input['smtp_password'])) < 2) { + unset($input['smtp_password']); + } + + if(isset($input['smtp_verify_peer']) && is_string($input['smtp_verify_peer'])) { + $input['smtp_verify_peer'] == 'true' ? true : false; + } + $this->replace($input); } diff --git a/app/Models/Company.php b/app/Models/Company.php index ea2e2915ae15..4e0ba74eea52 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -112,6 +112,13 @@ use Laracasts\Presenter\PresentableTrait; * @property int $notify_vendor_when_paid * @property int $invoice_task_hours * @property int $deleted_at + * @property string smtp_username + * @property string smtp_password + * @property string smtp_host + * @property string smtp_port + * @property string smtp_encryption + * @property string smtp_local_domain + * @property boolean smtp_verify_peer * @property-read \App\Models\Account $account * @property-read \Illuminate\Database\Eloquent\Collection $activities * @property-read int|null $activities_count @@ -352,12 +359,19 @@ class Company extends BaseModel 'calculate_taxes', 'tax_data', 'e_invoice_certificate_passphrase', + 'smtp_host', + 'smtp_port', + 'smtp_encryption', + 'smtp_local_domain', + 'smtp_verify_peer', ]; protected $hidden = [ 'id', 'db', 'ip', + 'smtp_username', + 'smtp_password', ]; protected $casts = [ @@ -372,6 +386,8 @@ class Company extends BaseModel 'tax_data' => 'object', 'origin_tax_data' => 'object', 'e_invoice_certificate_passphrase' => EncryptedCast::class, + 'smtp_username' => 'encrypted', + 'smtp_password' => 'encrypted', ]; protected $with = []; diff --git a/app/Repositories/CompanyRepository.php b/app/Repositories/CompanyRepository.php index da42bd02c97f..96babe4146b7 100644 --- a/app/Repositories/CompanyRepository.php +++ b/app/Repositories/CompanyRepository.php @@ -39,6 +39,7 @@ class CompanyRepository extends BaseRepository $company->fill($data); + // nlog($data); /** Only required to handle v4 migration workloads */ if(Ninja::isHosted() && $company->isDirty('is_disabled') && !$company->is_disabled) { Ninja::triggerForwarding($company->company_key, $company->owner()->email); @@ -48,6 +49,14 @@ class CompanyRepository extends BaseRepository $company->saveSettings($data['settings'], $company); } + if(isset($data['smtp_username'])) { + $company->smtp_username = $data['smtp_username']; + } + + if(isset($data['smtp_password'])) { + $company->smtp_password = $data['smtp_password']; + } + $company->save(); return $company; diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index a52cb317b094..e8941e1111a0 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -204,6 +204,13 @@ class CompanyTransformer extends EntityTransformer 'invoice_task_project_header' => (bool) $company->invoice_task_project_header, 'invoice_task_item_description' => (bool) $company->invoice_task_item_description, 'origin_tax_data' => $company->origin_tax_data ?: new \stdClass(), + 'smtp_host' => (string)$company->smtp_host ?? '', + 'smtp_port' => (string)$company->smtp_port ?? '', + 'smtp_encryption' => (string)$company->smtp_encryption ?? 'tls', + 'smtp_username' => $company->smtp_username ? '********' : '', + 'smtp_password' => $company->smtp_password ? '********' : '', + 'smtp_local_domain' => (string)$company->smtp_local_domain ?? '', + 'smtp_verify_peer' => (bool)$company->smtp_verify_peer, ]; } diff --git a/database/migrations/2024_02_16_011055_smtp_configuration.php b/database/migrations/2024_02_16_011055_smtp_configuration.php new file mode 100644 index 000000000000..38e7ea54e53b --- /dev/null +++ b/database/migrations/2024_02_16_011055_smtp_configuration.php @@ -0,0 +1,34 @@ +string('smtp_host')->nullable(); + $table->unsignedInteger('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->string('smtp_local_domain')->nullable(); + $table->boolean('smtp_verify_peer')->default(0); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/tests/Feature/CompanyTest.php b/tests/Feature/CompanyTest.php index 91e412812951..a9c02ef83045 100644 --- a/tests/Feature/CompanyTest.php +++ b/tests/Feature/CompanyTest.php @@ -50,6 +50,15 @@ class CompanyTest extends TestCase $this->makeTestData(); } + public function testEnsureStrReplace() + { + $x = '**********'; + + $new_string = str_replace("*", "", $x); + + $this->assertEquals(0, strlen($new_string)); + } + public function testCompanyTaxInit() { TaxRate::query()->delete(); From 998dd1c16c66b41f42b434c6f5b7c89d64596c7c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 13:38:22 +1100 Subject: [PATCH 02/26] Add Smtp check route --- .../ConnectedAccountController.php | 33 ++-- app/Http/Controllers/SmtpController.php | 63 +++++++ .../Requests/Company/UpdateCompanyRequest.php | 4 +- app/Models/Company.php | 14 +- routes/api.php | 163 +++++++++--------- 5 files changed, 174 insertions(+), 103 deletions(-) create mode 100644 app/Http/Controllers/SmtpController.php diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php index 9fc1376cff49..c588d58a9020 100644 --- a/app/Http/Controllers/ConnectedAccountController.php +++ b/app/Http/Controllers/ConnectedAccountController.php @@ -169,13 +169,16 @@ class ConnectedAccountController extends BaseController 'email_verified_at' => now(), ]; - auth()->user()->update($connected_account); - auth()->user()->email_verified_at = now(); - auth()->user()->save(); + /** @var \App\Models\User $logged_in_user */ + $logged_in_user = auth()->user(); - $this->setLoginCache(auth()->user()); + $logged_in_user->update($connected_account); + $logged_in_user->email_verified_at = now(); + $logged_in_user->save(); - return $this->itemResponse(auth()->user()); + $this->setLoginCache($logged_in_user); + + return $this->itemResponse($logged_in_user); } return response() @@ -214,20 +217,22 @@ class ConnectedAccountController extends BaseController // 'email_verified_at' =>now(), ]; - if (auth()->user()->email != $google->harvestEmail($user)) { + /** @var \App\Models\User $logged_in_user */ + $logged_in_user = auth()->user(); + + if ($logged_in_user->email != $google->harvestEmail($user)) { return response()->json(['message' => 'Primary Email differs to OAuth email. Emails must match.'], 400); } - auth()->user()->update($connected_account); - auth()->user()->email_verified_at = now(); - auth()->user()->oauth_user_token = $token; - auth()->user()->oauth_user_refresh_token = $refresh_token; + $logged_in_user->update($connected_account); + $logged_in_user->email_verified_at = now(); + $logged_in_user->oauth_user_token = $token; + $logged_in_user->oauth_user_refresh_token = $refresh_token; + $logged_in_user->save(); - auth()->user()->save(); + $this->activateGmail($logged_in_user); - $this->activateGmail(auth()->user()); - - return $this->itemResponse(auth()->user()); + return $this->itemResponse($logged_in_user); } return response() diff --git a/app/Http/Controllers/SmtpController.php b/app/Http/Controllers/SmtpController.php new file mode 100644 index 000000000000..2b800fb96d4d --- /dev/null +++ b/app/Http/Controllers/SmtpController.php @@ -0,0 +1,63 @@ +user(); + $company = $user->company(); + + + + config([ + 'mail.mailers.smtp' => [ + 'transport' => 'smtp', + 'host' => $request->input('smtp_host', $company->smtp_host), + 'port' => $request->input('smtp_port', $company->smtp_port), + 'username' => $request->input('smtp_username', $company->smtp_username), + 'password' => $request->input('smtp_password', $company->smtp_password), + 'encryption' => $request->input('smtp_encryption', $company->smtp_encryption ?? 'tls'), + 'local_domain' => $request->input('smtp_local_domain', strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null), + 'verify_peer' => $request->input('verify_peer', $company->smtp_verify_peer ?? true), + 'timeout' => 5, + ], + ]); + + (new \Illuminate\Mail\MailServiceProvider(app()))->register(); + + try { + Mail::to($user->email, $user->present()->name())->send(new TestMailServer('Email Server Works!', strlen($company->settings->custom_sending_email) > 1 ? $company->settings->custom_sending_email : $user->email)); + } catch (\Exception $e) { + app('mail.manager')->forgetMailers(); + return response()->json(['message' => $e->getMessage()], 400); + } + + app('mail.manager')->forgetMailers(); + + return response()->json(['message' => 'Ok'], 200); + + } + +} \ No newline at end of file diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index 5b716aacf21c..0482fa0aadb5 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -60,8 +60,8 @@ class UpdateCompanyRequest extends Request $rules['smtp_host'] = 'sometimes|string'; $rules['smtp_port'] = 'sometimes|string'; - $rules['smtp_encryption'] = 'sometimes|string'; - $rules['smtp_local_domain'] = 'sometimes|string'; + $rules['smtp_encryption'] = 'sometimes|string|nullable'; + $rules['smtp_local_domain'] = 'sometimes|string|nullable'; // $rules['smtp_verify_peer'] = 'sometimes|string'; diff --git a/app/Models/Company.php b/app/Models/Company.php index 4e0ba74eea52..f9bd44bd630d 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -112,13 +112,13 @@ use Laracasts\Presenter\PresentableTrait; * @property int $notify_vendor_when_paid * @property int $invoice_task_hours * @property int $deleted_at - * @property string smtp_username - * @property string smtp_password - * @property string smtp_host - * @property string smtp_port - * @property string smtp_encryption - * @property string smtp_local_domain - * @property boolean smtp_verify_peer + * @property string $smtp_username + * @property string $smtp_password + * @property string $smtp_host + * @property string $smtp_port + * @property string $smtp_encryption + * @property string $smtp_local_domain + * @property boolean $smtp_verify_peer * @property-read \App\Models\Account $account * @property-read \Illuminate\Database\Eloquent\Collection $activities * @property-read int|null $activities_count diff --git a/routes/api.php b/routes/api.php index 904885c59174..311360ae66d8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,113 +10,114 @@ | is assigned the "api" middleware group. Enjoy building your API! | */ -use App\Http\Controllers\AccountController; -use App\Http\Controllers\ActivityController; -use App\Http\Controllers\Auth\ForgotPasswordController; -use App\Http\Controllers\Auth\LoginController; -use App\Http\Controllers\Auth\PasswordTimeoutController; -use App\Http\Controllers\Bank\NordigenController; -use App\Http\Controllers\Bank\YodleeController; -use App\Http\Controllers\BankIntegrationController; -use App\Http\Controllers\BankTransactionController; -use App\Http\Controllers\BankTransactionRuleController; +use Illuminate\Support\Facades\Route; use App\Http\Controllers\BaseController; +use App\Http\Controllers\PingController; +use App\Http\Controllers\SmtpController; +use App\Http\Controllers\TaskController; +use App\Http\Controllers\UserController; use App\Http\Controllers\ChartController; +use App\Http\Controllers\EmailController; +use App\Http\Controllers\QuoteController; +use App\Http\Controllers\TokenController; use App\Http\Controllers\ClientController; -use App\Http\Controllers\ClientGatewayTokenController; -use App\Http\Controllers\ClientStatementController; -use App\Http\Controllers\CompanyController; -use App\Http\Controllers\CompanyGatewayController; -use App\Http\Controllers\CompanyLedgerController; -use App\Http\Controllers\CompanyUserController; -use App\Http\Controllers\ConnectedAccountController; use App\Http\Controllers\CreditController; use App\Http\Controllers\DesignController; -use App\Http\Controllers\DocumentController; -use App\Http\Controllers\EmailController; -use App\Http\Controllers\EmailHistoryController; -use App\Http\Controllers\ExpenseCategoryController; -use App\Http\Controllers\ExpenseController; use App\Http\Controllers\ExportController; use App\Http\Controllers\FilterController; -use App\Http\Controllers\GroupSettingController; -use App\Http\Controllers\HostedMigrationController; use App\Http\Controllers\ImportController; -use App\Http\Controllers\ImportJsonController; -use App\Http\Controllers\InAppPurchase\AppleController; +use App\Http\Controllers\LogoutController; +use App\Http\Controllers\SearchController; +use App\Http\Controllers\StaticController; +use App\Http\Controllers\StripeController; +use App\Http\Controllers\TwilioController; +use App\Http\Controllers\VendorController; +use App\Http\Controllers\AccountController; +use App\Http\Controllers\CompanyController; +use App\Http\Controllers\ExpenseController; use App\Http\Controllers\InvoiceController; use App\Http\Controllers\LicenseController; -use App\Http\Controllers\LogoutController; -use App\Http\Controllers\MailgunWebhookController; -use App\Http\Controllers\MigrationController; -use App\Http\Controllers\OneTimeTokenController; use App\Http\Controllers\PaymentController; -use App\Http\Controllers\PaymentNotificationWebhookController; -use App\Http\Controllers\PaymentTermController; -use App\Http\Controllers\PaymentWebhookController; -use App\Http\Controllers\PingController; -use App\Http\Controllers\PostMarkController; use App\Http\Controllers\PreviewController; -use App\Http\Controllers\PreviewPurchaseOrderController; use App\Http\Controllers\ProductController; use App\Http\Controllers\ProjectController; -use App\Http\Controllers\ProtectedDownloadController; +use App\Http\Controllers\TaxRateController; +use App\Http\Controllers\WebCronController; +use App\Http\Controllers\WebhookController; +use App\Http\Controllers\ActivityController; +use App\Http\Controllers\DocumentController; +use App\Http\Controllers\PostMarkController; +use App\Http\Controllers\TemplateController; +use App\Http\Controllers\MigrationController; +use App\Http\Controllers\SchedulerController; +use App\Http\Controllers\SubdomainController; +use App\Http\Controllers\SystemLogController; +use App\Http\Controllers\TwoFactorController; +use App\Http\Controllers\Auth\LoginController; +use App\Http\Controllers\ImportJsonController; +use App\Http\Controllers\SelfUpdateController; +use App\Http\Controllers\TaskStatusController; +use App\Http\Controllers\Bank\YodleeController; +use App\Http\Controllers\CompanyUserController; +use App\Http\Controllers\PaymentTermController; +use App\PaymentDrivers\PayPalPPCPPaymentDriver; +use App\Http\Controllers\EmailHistoryController; +use App\Http\Controllers\GroupSettingController; +use App\Http\Controllers\OneTimeTokenController; +use App\Http\Controllers\SubscriptionController; +use App\Http\Controllers\Bank\NordigenController; +use App\Http\Controllers\CompanyLedgerController; use App\Http\Controllers\PurchaseOrderController; -use App\Http\Controllers\QuoteController; +use App\Http\Controllers\TaskSchedulerController; +use App\Http\Controllers\CompanyGatewayController; +use App\Http\Controllers\MailgunWebhookController; +use App\Http\Controllers\PaymentWebhookController; +use App\Http\Controllers\RecurringQuoteController; +use App\Http\Controllers\BankIntegrationController; +use App\Http\Controllers\BankTransactionController; +use App\Http\Controllers\ClientStatementController; +use App\Http\Controllers\ExpenseCategoryController; +use App\Http\Controllers\HostedMigrationController; +use App\Http\Controllers\TemplatePreviewController; +use App\Http\Controllers\ConnectedAccountController; use App\Http\Controllers\RecurringExpenseController; use App\Http\Controllers\RecurringInvoiceController; -use App\Http\Controllers\RecurringQuoteController; -use App\Http\Controllers\Reports\ActivityReportController; -use App\Http\Controllers\Reports\ARDetailReportController; -use App\Http\Controllers\Reports\ARSummaryReportController; -use App\Http\Controllers\Reports\ClientBalanceReportController; -use App\Http\Controllers\Reports\ClientContactReportController; +use App\Http\Controllers\ProtectedDownloadController; +use App\Http\Controllers\ClientGatewayTokenController; +use App\Http\Controllers\Reports\TaskReportController; +use App\Http\Controllers\Auth\ForgotPasswordController; +use App\Http\Controllers\BankTransactionRuleController; +use App\Http\Controllers\InAppPurchase\AppleController; +use App\Http\Controllers\Reports\QuoteReportController; +use App\Http\Controllers\Auth\PasswordTimeoutController; +use App\Http\Controllers\PreviewPurchaseOrderController; use App\Http\Controllers\Reports\ClientReportController; -use App\Http\Controllers\Reports\ClientSalesReportController; use App\Http\Controllers\Reports\CreditReportController; -use App\Http\Controllers\Reports\DocumentReportController; +use App\Http\Controllers\Reports\ReportExportController; +use App\Http\Controllers\Reports\VendorReportController; use App\Http\Controllers\Reports\ExpenseReportController; -use App\Http\Controllers\Reports\InvoiceItemReportController; use App\Http\Controllers\Reports\InvoiceReportController; use App\Http\Controllers\Reports\PaymentReportController; use App\Http\Controllers\Reports\ProductReportController; -use App\Http\Controllers\Reports\ProductSalesReportController; use App\Http\Controllers\Reports\ProfitAndLossController; -use App\Http\Controllers\Reports\PurchaseOrderItemReportController; -use App\Http\Controllers\Reports\PurchaseOrderReportController; -use App\Http\Controllers\Reports\QuoteItemReportController; -use App\Http\Controllers\Reports\QuoteReportController; -use App\Http\Controllers\Reports\RecurringInvoiceReportController; -use App\Http\Controllers\Reports\ReportExportController; use App\Http\Controllers\Reports\ReportPreviewController; -use App\Http\Controllers\Reports\TaskReportController; -use App\Http\Controllers\Reports\TaxSummaryReportController; +use App\Http\Controllers\Reports\ActivityReportController; +use App\Http\Controllers\Reports\ARDetailReportController; +use App\Http\Controllers\Reports\DocumentReportController; +use App\Http\Controllers\Reports\ARSummaryReportController; +use App\Http\Controllers\Reports\QuoteItemReportController; use App\Http\Controllers\Reports\UserSalesReportController; -use App\Http\Controllers\Reports\VendorReportController; -use App\Http\Controllers\SchedulerController; -use App\Http\Controllers\SearchController; -use App\Http\Controllers\SelfUpdateController; -use App\Http\Controllers\StaticController; -use App\Http\Controllers\StripeController; -use App\Http\Controllers\SubdomainController; -use App\Http\Controllers\SubscriptionController; +use App\Http\Controllers\Reports\TaxSummaryReportController; use App\Http\Controllers\Support\Messages\SendingController; -use App\Http\Controllers\SystemLogController; -use App\Http\Controllers\TaskController; -use App\Http\Controllers\TaskSchedulerController; -use App\Http\Controllers\TaskStatusController; -use App\Http\Controllers\TaxRateController; -use App\Http\Controllers\TemplateController; -use App\Http\Controllers\TemplatePreviewController; -use App\Http\Controllers\TokenController; -use App\Http\Controllers\TwilioController; -use App\Http\Controllers\TwoFactorController; -use App\Http\Controllers\UserController; -use App\Http\Controllers\VendorController; -use App\Http\Controllers\WebCronController; -use App\Http\Controllers\WebhookController; -use App\PaymentDrivers\PayPalPPCPPaymentDriver; -use Illuminate\Support\Facades\Route; +use App\Http\Controllers\Reports\ClientSalesReportController; +use App\Http\Controllers\Reports\InvoiceItemReportController; +use App\Http\Controllers\PaymentNotificationWebhookController; +use App\Http\Controllers\Reports\ProductSalesReportController; +use App\Http\Controllers\Reports\ClientBalanceReportController; +use App\Http\Controllers\Reports\ClientContactReportController; +use App\Http\Controllers\Reports\PurchaseOrderReportController; +use App\Http\Controllers\Reports\RecurringInvoiceReportController; +use App\Http\Controllers\Reports\PurchaseOrderItemReportController; Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () { Route::post('api/v1/signup', [AccountController::class, 'store'])->name('signup.submit'); @@ -392,6 +393,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] // Route::post('hooks', [SubscriptionController::class, 'subscribe'])->name('hooks.subscribe'); // Route::delete('hooks/{subscription_id}', [SubscriptionController::class, 'unsubscribe'])->name('hooks.unsubscribe'); + Route::post('smtp/check', [SmtpController::class, 'check'])->name('smtp.check')->middleware('throttle:10,1'); + Route::post('stripe/update_payment_methods', [StripeController::class, 'update'])->middleware('password_protected')->name('stripe.update'); Route::post('stripe/import_customers', [StripeController::class, 'import'])->middleware('password_protected')->name('stripe.import'); From ce2885ed3713d4da13f601ee202e45f03aaf267b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 13:45:56 +1100 Subject: [PATCH 03/26] Adjustments for test mail server --- resources/views/email/support/message.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/email/support/message.blade.php b/resources/views/email/support/message.blade.php index 332f9888e252..ea8449647a5d 100644 --- a/resources/views/email/support/message.blade.php +++ b/resources/views/email/support/message.blade.php @@ -1,4 +1,4 @@ -@component('email.template.admin', ['settings' => $settings, 'logo' => $logo ?? 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) +@component('email.template.admin', ['settings' => $settings, 'logo' => $logo ?? 'https://pdf.invoicing.co/favicon-v2.png']) {{-- Body --}} {!! $support_message !!} From 2f8f9ffce86f30e3bb01c1bf7bfdec4fdf410b5e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 13:48:40 +1100 Subject: [PATCH 04/26] Fixes for tests --- app/Http/Requests/Company/StoreCompanyRequest.php | 9 ++++++--- app/Http/Requests/Company/UpdateCompanyRequest.php | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/Http/Requests/Company/StoreCompanyRequest.php b/app/Http/Requests/Company/StoreCompanyRequest.php index c9aae8b611be..29dac543e06d 100644 --- a/app/Http/Requests/Company/StoreCompanyRequest.php +++ b/app/Http/Requests/Company/StoreCompanyRequest.php @@ -56,10 +56,13 @@ class StoreCompanyRequest extends Request } } - $rules['smtp_host'] = 'sometimes|string'; - $rules['smtp_port'] = 'sometimes|string'; + $rules['smtp_host'] = 'sometimes|string|nullable'; + $rules['smtp_port'] = 'sometimes|string|nullable'; $rules['smtp_encryption'] = 'sometimes|string'; - $rules['smtp_local_domain'] = 'sometimes|string'; + $rules['smtp_local_domain'] = 'sometimes|string|nullable'; + $rules['smtp_encryption'] = 'sometimes|string|nullable'; + $rules['smtp_local_domain'] = 'sometimes|string|nullable'; + // $rules['smtp_verify_peer'] = 'sometimes|in:true,false'; return $rules; diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index 0482fa0aadb5..949400997c6e 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -58,8 +58,8 @@ class UpdateCompanyRequest extends Request $rules['e_invoice_certificate_passphrase'] = 'sometimes|nullable'; $rules['e_invoice_certificate'] = 'sometimes|nullable|file|mimes:p12,pfx,pem,cer,crt,der,txt,p7b,spc,bin'; - $rules['smtp_host'] = 'sometimes|string'; - $rules['smtp_port'] = 'sometimes|string'; + $rules['smtp_host'] = 'sometimes|string|nullable'; + $rules['smtp_port'] = 'sometimes|string|nullable'; $rules['smtp_encryption'] = 'sometimes|string|nullable'; $rules['smtp_local_domain'] = 'sometimes|string|nullable'; // $rules['smtp_verify_peer'] = 'sometimes|string'; From 6445e518dc935054a6c37f2a2c61d2e169ed91eb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 14:28:57 +1100 Subject: [PATCH 05/26] Fixes for parse float --- app/Import/Transformer/BaseTransformer.php | 11 ++++------- app/Utils/Number.php | 12 +++++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index ee9b137dcff5..81d38ba75e75 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -315,14 +315,11 @@ class BaseTransformer public function getFloat($data, $field) { if (array_key_exists($field, $data)) { - //$number = preg_replace('/[^0-9-.]+/', '', $data[$field]); return Number::parseFloat($data[$field]); - } else { - //$number = 0; - return 0; - } - - // return Number::parseFloat($number); + } + + return 0; + } /** diff --git a/app/Utils/Number.php b/app/Utils/Number.php index 67568d6da81b..2fa7e64a462a 100644 --- a/app/Utils/Number.php +++ b/app/Utils/Number.php @@ -95,6 +95,14 @@ class Number */ public static function parseFloat($value) { + if(!$value) + return 0; + + $multiplier = false; + + if(substr($value, 0,1) == '-') + $multiplier = -1; + // convert "," to "." $s = str_replace(',', '.', $value); @@ -108,7 +116,9 @@ class Number // remove all separators from first part and keep the end $s = str_replace('.', '', substr($s, 0, -3)).substr($s, -3); - // return float + if($multiplier) + $s = floatval($s)*-1; + return (float) $s; } From 0394d98d593ed6631cd4d6c17404573f60fe83bf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 14:53:39 +1100 Subject: [PATCH 06/26] Add Hyvor writer to prod composer file --- composer.json | 4 +- composer.lock | 206 +++++++++++++++++++++++++------------------------- 2 files changed, 105 insertions(+), 105 deletions(-) diff --git a/composer.json b/composer.json index b24086a877bd..1b9dc59adcc5 100644 --- a/composer.json +++ b/composer.json @@ -101,7 +101,8 @@ "twilio/sdk": "^6.40", "webpatser/laravel-countries": "dev-master#75992ad", "wepay/php-sdk": "^0.3", - "wildbit/postmark-php": "^4.0" + "wildbit/postmark-php": "^4.0", + "hyvor/php-json-exporter": "^0.0.3" }, "require-dev": { "php": "^8.1|^8.2", @@ -112,7 +113,6 @@ "fakerphp/faker": "^1.14", "filp/whoops": "^2.7", "friendsofphp/php-cs-fixer": "^3.14", - "hyvor/php-json-exporter": "^0.0.3", "laracasts/cypress": "^3.0", "larastan/larastan": "^2", "mockery/mockery": "^1.4.4", diff --git a/composer.lock b/composer.lock index 4d08ab1ceac6..10fcdc0750f5 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": "7a990a24b596bd1ab52a5f829fcc7e27", + "content-hash": "dc9142e4af116b98de0ac6310297dba6", "packages": [ { "name": "afosto/yaac", @@ -1343,16 +1343,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.298.9", + "version": "3.299.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "db225c3a1c5dabfbb5071349cfb7e4c396c3d9ec" + "reference": "76d1165568d949b430a87eaa42f6dbc4b6705b6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/db225c3a1c5dabfbb5071349cfb7e4c396c3d9ec", - "reference": "db225c3a1c5dabfbb5071349cfb7e4c396c3d9ec", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/76d1165568d949b430a87eaa42f6dbc4b6705b6f", + "reference": "76d1165568d949b430a87eaa42f6dbc4b6705b6f", "shasum": "" }, "require": { @@ -1432,9 +1432,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.298.9" + "source": "https://github.com/aws/aws-sdk-php/tree/3.299.0" }, - "time": "2024-02-13T19:08:16+00:00" + "time": "2024-02-15T19:08:34+00:00" }, { "name": "bacon/bacon-qr-code", @@ -2265,16 +2265,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.1", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2" + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c9ea252cdce4da324ede3d6c5913dd89f769afd2", - "reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb", + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb", "shasum": "" }, "require": { @@ -2358,7 +2358,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.1" + "source": "https://github.com/doctrine/dbal/tree/3.8.2" }, "funding": [ { @@ -2374,7 +2374,7 @@ "type": "tidelift" } ], - "time": "2024-02-03T17:33:49+00:00" + "time": "2024-02-12T18:36:36+00:00" }, { "name": "doctrine/deprecations", @@ -4500,6 +4500,50 @@ }, "time": "2021-07-21T13:50:14+00:00" }, + { + "name": "hyvor/php-json-exporter", + "version": "0.0.3", + "source": { + "type": "git", + "url": "https://github.com/hyvor/php-json-exporter.git", + "reference": "9fade1856135deaa2d0fc9b3065949019703471d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hyvor/php-json-exporter/zipball/9fade1856135deaa2d0fc9b3065949019703471d", + "reference": "9fade1856135deaa2d0fc9b3065949019703471d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "pestphp/pest": "^1.22", + "phpstan/phpstan": "^1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Hyvor\\JsonExporter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Supun", + "email": "supun@hyvor.com" + } + ], + "description": "Export large datasets to a JSON file without memory exhaustion", + "support": { + "issues": "https://github.com/hyvor/php-json-exporter/issues", + "source": "https://github.com/hyvor/php-json-exporter/tree/0.0.3" + }, + "time": "2023-04-11T15:12:18+00:00" + }, { "name": "imdhemy/appstore-iap", "version": "1.6.0", @@ -7384,41 +7428,41 @@ }, { "name": "moneyphp/money", - "version": "v4.4.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "5e60aebf09f709dd4ea16bf85e66d65301c0d172" + "reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/5e60aebf09f709dd4ea16bf85e66d65301c0d172", - "reference": "5e60aebf09f709dd4ea16bf85e66d65301c0d172", + "url": "https://api.github.com/repos/moneyphp/money/zipball/a1daa7daf159b4044e3d0c34c41fe2be5860e850", + "reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850", "shasum": "" }, "require": { "ext-bcmath": "*", "ext-filter": "*", "ext-json": "*", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "cache/taggable-cache": "^1.1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/instantiator": "^1.4.0", + "doctrine/coding-standard": "^12.0", + "doctrine/instantiator": "^1.5.0 || ^2.0", "ext-gmp": "*", "ext-intl": "*", - "florianv/exchanger": "^2.6.3", + "florianv/exchanger": "^2.8.1", "florianv/swap": "^4.3.0", - "moneyphp/crypto-currencies": "^1.0.0", - "moneyphp/iso-currencies": "^3.2.1", - "php-http/message": "^1.11.0", - "php-http/mock-client": "^1.4.1", + "moneyphp/crypto-currencies": "^1.1.0", + "moneyphp/iso-currencies": "^3.4", + "php-http/message": "^1.16.0", + "php-http/mock-client": "^1.6.0", "phpbench/phpbench": "^1.2.5", - "phpunit/phpunit": "^9.5.4", + "phpunit/phpunit": "^10.5.9", "psalm/plugin-phpunit": "^0.18.4", "psr/cache": "^1.0.1 || ^2.0 || ^3.0", - "vimeo/psalm": "~5.15.0" + "vimeo/psalm": "~5.20.0" }, "suggest": { "ext-gmp": "Calculate without integer limits", @@ -7466,9 +7510,9 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.4.0" + "source": "https://github.com/moneyphp/money/tree/v4.5.0" }, - "time": "2024-01-24T08:29:16+00:00" + "time": "2024-02-15T19:47:21+00:00" }, { "name": "monolog/monolog", @@ -15832,16 +15876,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.10.4", + "version": "v3.10.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "09d3dc77d7dc1b063e3728a6029c39ee0fbebf1d" + "reference": "d1a48965f2b25a6cec2eea07d719b568a37c9a88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/09d3dc77d7dc1b063e3728a6029c39ee0fbebf1d", - "reference": "09d3dc77d7dc1b063e3728a6029c39ee0fbebf1d", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/d1a48965f2b25a6cec2eea07d719b568a37c9a88", + "reference": "d1a48965f2b25a6cec2eea07d719b568a37c9a88", "shasum": "" }, "require": { @@ -15900,7 +15944,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.4" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.5" }, "funding": [ { @@ -15912,43 +15956,43 @@ "type": "github" } ], - "time": "2024-02-14T08:52:12+00:00" + "time": "2024-02-15T10:45:45+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.14.0", + "version": "v2.15.1", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "485c756f6cff408d6b273274c5e86112c3973d98" + "reference": "77831852bb7bc54f287246d32eb91274eaf87f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/485c756f6cff408d6b273274c5e86112c3973d98", - "reference": "485c756f6cff408d6b273274c5e86112c3973d98", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/77831852bb7bc54f287246d32eb91274eaf87f8b", + "reference": "77831852bb7bc54f287246d32eb91274eaf87f8b", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/class-map-generator": "^1.0", - "doctrine/dbal": "^2.6 || ^3", + "doctrine/dbal": "^2.6 || ^3.1.4", "ext-json": "*", - "illuminate/console": "^8 || ^9 || ^10", - "illuminate/filesystem": "^8 || ^9 || ^10", - "illuminate/support": "^8 || ^9 || ^10", + "illuminate/console": "^9 || ^10", + "illuminate/filesystem": "^9 || ^10", + "illuminate/support": "^9 || ^10", "nikic/php-parser": "^4.18 || ^5", - "php": "^7.3 || ^8.0", + "php": "^8.0", "phpdocumentor/type-resolver": "^1.1.0" }, "require-dev": { "ext-pdo_sqlite": "*", - "friendsofphp/php-cs-fixer": "^2", - "illuminate/config": "^8 || ^9 || ^10", - "illuminate/view": "^8 || ^9 || ^10", + "friendsofphp/php-cs-fixer": "^3", + "illuminate/config": "^9 || ^10", + "illuminate/view": "^9 || ^10", "mockery/mockery": "^1.4", - "orchestra/testbench": "^6 || ^7 || ^8", - "phpunit/phpunit": "^8.5 || ^9", - "spatie/phpunit-snapshot-assertions": "^3 || ^4", + "orchestra/testbench": "^7 || ^8", + "phpunit/phpunit": "^9", + "spatie/phpunit-snapshot-assertions": "^4", "vimeo/psalm": "^5.4" }, "suggest": { @@ -15957,7 +16001,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.14-dev" + "dev-master": "2.15-dev" }, "laravel": { "providers": [ @@ -15994,7 +16038,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.14.0" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.15.1" }, "funding": [ { @@ -16006,7 +16050,7 @@ "type": "github" } ], - "time": "2024-02-05T08:16:36+00:00" + "time": "2024-02-15T14:23:20+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -16780,50 +16824,6 @@ }, "time": "2020-07-09T08:09:16+00:00" }, - { - "name": "hyvor/php-json-exporter", - "version": "0.0.3", - "source": { - "type": "git", - "url": "https://github.com/hyvor/php-json-exporter.git", - "reference": "9fade1856135deaa2d0fc9b3065949019703471d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hyvor/php-json-exporter/zipball/9fade1856135deaa2d0fc9b3065949019703471d", - "reference": "9fade1856135deaa2d0fc9b3065949019703471d", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "pestphp/pest": "^1.22", - "phpstan/phpstan": "^1.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Hyvor\\JsonExporter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Supun", - "email": "supun@hyvor.com" - } - ], - "description": "Export large datasets to a JSON file without memory exhaustion", - "support": { - "issues": "https://github.com/hyvor/php-json-exporter/issues", - "source": "https://github.com/hyvor/php-json-exporter/tree/0.0.3" - }, - "time": "2023-04-11T15:12:18+00:00" - }, { "name": "laracasts/cypress", "version": "3.0.1", @@ -16986,16 +16986,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.20.1", + "version": "v1.20.2", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "06ebf922ccedfa4cc43015825697ee8c1fb80f7e" + "reference": "484625c23a4fa4f303617f29fcacd42951c9c01d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/06ebf922ccedfa4cc43015825697ee8c1fb80f7e", - "reference": "06ebf922ccedfa4cc43015825697ee8c1fb80f7e", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/484625c23a4fa4f303617f29fcacd42951c9c01d", + "reference": "484625c23a4fa4f303617f29fcacd42951c9c01d", "shasum": "" }, "require": { @@ -17046,9 +17046,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.20.1" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.20.2" }, - "time": "2024-02-13T19:03:14+00:00" + "time": "2024-02-15T10:49:09+00:00" }, { "name": "mockery/mockery", From ceb4708b5d91de4da0ec792a8b49610673c3ec79 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 16 Feb 2024 18:33:45 +1100 Subject: [PATCH 07/26] White label license server --- app/Models/License.php | 25 +++++++++++++++++-- .../Subscription/SubscriptionService.php | 3 ++- lang/en/texts.php | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/Models/License.php b/app/Models/License.php index d2af140651e3..d7af06bb61e9 100644 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -17,7 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * App\Models\License * * @property int $id - * @property int|null $created_at + * @property \Carbon\Carbon $created_at * @property int|null $updated_at * @property int|null $deleted_at * @property string|null $first_name @@ -28,6 +28,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string|null $transaction_reference * @property int|null $product_id * @property int|null $recurring_invoice_id + * @property-read \App\Models\RecurringInvoice $recurring_invoice * @method static \Illuminate\Database\Eloquent\Builder|StaticModel company() * @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder|License newModelQuery() @@ -53,4 +54,24 @@ use Illuminate\Database\Eloquent\SoftDeletes; class License extends StaticModel { use SoftDeletes; -} + + protected $casts = [ + 'created_at' => 'date', + ]; + + public function expiry(): string + { + return $this->created_at->addYear()->format('Y-m-d'); + } + + public function recurring_invoice() + { + return $this->belongsTo(RecurringInvoice::class); + } + + public function url() + { + $contact = $this->recurring_invoice->client->contacts()->where('email', $this->email)->first(); + + } +} \ No newline at end of file diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 3d9d21c1b3a5..cdfc7b430b6f 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -47,6 +47,7 @@ use App\Utils\Traits\SubscriptionHooker; use Carbon\Carbon; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Str; +use Illuminate\Mail\Mailables\Address; class SubscriptionService { @@ -208,7 +209,7 @@ class SubscriptionService $invitation = $invoice->invitations()->first(); $email_object = new EmailObject(); - $email_object->to = [$contact->email]; + $email_object->to = [new Address($contact->email, $contact->present()->name())]; $email_object->subject = ctrans('texts.white_label_link') . " " .ctrans('texts.payment_subject'); $email_object->body = ctrans('texts.white_label_body', ['license_key' => $license_key]); $email_object->client_id = $invoice->client_id; diff --git a/lang/en/texts.php b/lang/en/texts.php index 3e995d821340..8b6027093966 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -4925,7 +4925,7 @@ $lang = array( 'no_assigned_tasks' => 'No billable tasks for this project', 'authorization_failure' => 'Insufficient permissions to perform this action', 'authorization_sms_failure' => 'Please verify your account to send emails.', - 'white_label_body' => 'Thank you for purchasing a white label license.

Your license key is:

:license_key', + 'white_label_body' => 'Thank you for purchasing a white label license.

Your license key is:

:license_key

You can manage your license here: https://invoiceninja.invoicing.co/client/login', 'payment_type_Klarna' => 'Klarna', 'payment_type_Interac E Transfer' => 'Interac E Transfer', 'xinvoice_payable' => 'Payable within :payeddue days net until :paydate', From 478dbe5c4a7dd286f3dff0a4529aced15f4a7bb6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 05:46:26 +1100 Subject: [PATCH 08/26] Improvements for validation --- .../Requests/Client/StoreClientRequest.php | 1 + .../Requests/Client/UpdateClientRequest.php | 2 + tests/Feature/ClientApiTest.php | 72 +++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index 66aa798b0ad7..46b8bf9a2a96 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -94,6 +94,7 @@ class StoreClientRequest extends Request $rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other'; + $rules['documents'] = 'bail|sometimes|array'; return $rules; } diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index b891731cbd23..26ae1f517d57 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -84,6 +84,8 @@ class UpdateClientRequest extends Request //'regex:/[@$!%*#?&.]/', // must contain a special character ]; + $rules['documents'] = 'bail|sometimes|array'; + return $rules; } diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 882fd3cdd459..0d08a8642b10 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -59,6 +59,78 @@ class ClientApiTest extends TestCase Model::reguard(); } + public function testDocumentValidation() + { + $data = [ + 'name' => 'name of client', + 'documents' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients",$data) + ->assertStatus(200); + + } + + public function testDocumentValidationFails() + { + $data = [ + 'name' => 'name of client', + 'documents' => 'wut', + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients", $data) + ->assertStatus(422); + + $data = [ + 'name' => 'name of client', + 'documents' => null, + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients", $data) + ->assertStatus(422); + + } + + public function testDocumentValidationPutFails() + { + $data = [ + 'name' => 'name of client', + 'documents' => 'wut', + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/clients/{$this->client->hashed_id}", $data) + ->assertStatus(422); + + $data = [ + 'name' => 'name of client', + 'documents' => null, + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/clients/{$this->client->hashed_id}", $data) + ->assertStatus(422); + + $data = [ + 'name' => 'name of client', + 'documents' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/clients/{$this->client->hashed_id}", $data) + ->assertStatus(200); + + } + public function testClientDocumentQuery() { From a5fd1dc5ea9a1f3b0bfe2a51c14d5be136a2cd65 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 05:57:15 +1100 Subject: [PATCH 09/26] Improvements for validation --- .../Requests/Client/StoreClientRequest.php | 4 ++- .../Requests/Client/UpdateClientRequest.php | 4 +-- .../Requests/Credit/StoreCreditRequest.php | 2 ++ .../Requests/Credit/UpdateCreditRequest.php | 2 ++ .../Requests/Expense/StoreExpenseRequest.php | 1 + .../Requests/Expense/UpdateExpenseRequest.php | 25 +++++++++++++------ .../Requests/Invoice/StoreInvoiceRequest.php | 3 +++ .../Requests/Invoice/UpdateInvoiceRequest.php | 3 +++ .../Requests/Payment/StorePaymentRequest.php | 2 ++ .../Requests/Payment/UpdatePaymentRequest.php | 2 ++ .../Requests/Product/StoreProductRequest.php | 2 ++ .../Requests/Product/UpdateProductRequest.php | 8 +++++- .../Requests/Project/StoreProjectRequest.php | 2 ++ .../Requests/Project/UpdateProjectRequest.php | 2 ++ .../StorePurchaseOrderRequest.php | 2 ++ .../UpdatePurchaseOrderRequest.php | 2 ++ app/Http/Requests/Quote/StoreQuoteRequest.php | 5 ++-- .../Requests/Quote/UpdateQuoteRequest.php | 2 ++ .../StoreRecurringInvoiceRequest.php | 2 ++ .../UpdateRecurringInvoiceRequest.php | 2 ++ app/Http/Requests/Task/StoreTaskRequest.php | 2 ++ app/Http/Requests/Task/UpdateTaskRequest.php | 2 ++ .../Requests/Vendor/StoreVendorRequest.php | 2 ++ .../Requests/Vendor/UpdateVendorRequest.php | 2 ++ 24 files changed, 71 insertions(+), 14 deletions(-) diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index 46b8bf9a2a96..30697ab9b09f 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -49,6 +49,9 @@ class StoreClientRequest extends Request } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; } + else { + $rules['documents'] = 'bail|sometimes|array'; + } if ($this->file('file') && is_array($this->file('file'))) { $rules['file.*'] = $this->file_validation; @@ -94,7 +97,6 @@ class StoreClientRequest extends Request $rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other'; - $rules['documents'] = 'bail|sometimes|array'; return $rules; } diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index 26ae1f517d57..11b8c4a60ffc 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -53,6 +53,8 @@ class UpdateClientRequest extends Request $rules['file.*'] = $this->file_validation; } elseif ($this->file('file')) { $rules['file'] = $this->file_validation; + } else { + $rules['documents'] = 'bail|sometimes|array'; } $rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000'; @@ -84,8 +86,6 @@ class UpdateClientRequest extends Request //'regex:/[@$!%*#?&.]/', // must contain a special character ]; - $rules['documents'] = 'bail|sometimes|array'; - return $rules; } diff --git a/app/Http/Requests/Credit/StoreCreditRequest.php b/app/Http/Requests/Credit/StoreCreditRequest.php index 8d01aefc95af..c24005063d68 100644 --- a/app/Http/Requests/Credit/StoreCreditRequest.php +++ b/app/Http/Requests/Credit/StoreCreditRequest.php @@ -50,6 +50,8 @@ class StoreCreditRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Credit/UpdateCreditRequest.php b/app/Http/Requests/Credit/UpdateCreditRequest.php index 5ec021bc6e1c..7c0c3adc0274 100644 --- a/app/Http/Requests/Credit/UpdateCreditRequest.php +++ b/app/Http/Requests/Credit/UpdateCreditRequest.php @@ -52,6 +52,8 @@ class UpdateCreditRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Expense/StoreExpenseRequest.php b/app/Http/Requests/Expense/StoreExpenseRequest.php index 9bab8f25b360..72c2e7506850 100644 --- a/app/Http/Requests/Expense/StoreExpenseRequest.php +++ b/app/Http/Requests/Expense/StoreExpenseRequest.php @@ -52,6 +52,7 @@ class StoreExpenseRequest extends Request $rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0'; $rules['payment_date'] = 'bail|nullable|sometimes|date:Y-m-d'; $rules['date'] = 'bail|sometimes|date:Y-m-d'; + $rules['documents'] = 'bail|sometimes|array'; return $this->globalRules($rules); } diff --git a/app/Http/Requests/Expense/UpdateExpenseRequest.php b/app/Http/Requests/Expense/UpdateExpenseRequest.php index 961a15709d61..bece7242b0eb 100644 --- a/app/Http/Requests/Expense/UpdateExpenseRequest.php +++ b/app/Http/Requests/Expense/UpdateExpenseRequest.php @@ -29,25 +29,32 @@ class UpdateExpenseRequest extends Request */ public function authorize(): bool { - return auth()->user()->can('edit', $this->expense); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('edit', $this->expense); } public function rules() { + /** @var \App\Models\User $user */ + $user = auth()->user(); + /* Ensure we have a client name, and that all emails are unique*/ $rules = []; if (isset($this->number)) { - $rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id); + $rules['number'] = Rule::unique('expenses')->where('company_id', $user->company()->id)->ignore($this->expense->id); } if ($this->client_id) { - $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; + $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.$user->company()->id; } - $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; - $rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.auth()->user()->company()->id; - $rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.auth()->user()->company()->id; + $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0'; + $rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.$user->company()->id; + $rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.$user->company()->id; + $rules['documents'] = 'bail|sometimes|array'; return $this->globalRules($rules); @@ -55,6 +62,10 @@ class UpdateExpenseRequest extends Request public function prepareForValidation() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $input = $this->all(); $input = $this->decodePrimaryKeys($input); @@ -64,7 +75,7 @@ class UpdateExpenseRequest extends Request } if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) { - $input['currency_id'] = (string) auth()->user()->company()->settings->currency_id; + $input['currency_id'] = (string) $user->company()->settings->currency_id; } /* Ensure the project is related */ diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index acd2ad0655f2..018a1d7c7b36 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -47,6 +47,8 @@ class StoreInvoiceRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { @@ -76,6 +78,7 @@ class StoreInvoiceRequest extends Request $rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0'; $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date']; + return $rules; } diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index ad4d584ae689..501c2bab31d3 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -49,6 +49,8 @@ class UpdateInvoiceRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { @@ -77,6 +79,7 @@ class UpdateInvoiceRequest extends Request $rules['partial'] = 'bail|sometimes|nullable|numeric'; $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date']; + return $rules; } diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 361d9e07f556..25b3b43500c6 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -126,6 +126,8 @@ class StorePaymentRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 208f0445f0ab..52a3c29d895b 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -55,6 +55,8 @@ class UpdatePaymentRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Product/StoreProductRequest.php b/app/Http/Requests/Product/StoreProductRequest.php index 9619815078d6..c53ad07edbea 100644 --- a/app/Http/Requests/Product/StoreProductRequest.php +++ b/app/Http/Requests/Product/StoreProductRequest.php @@ -35,6 +35,8 @@ class StoreProductRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Product/UpdateProductRequest.php b/app/Http/Requests/Product/UpdateProductRequest.php index 1434945dcc7d..0a76f6a4ab08 100644 --- a/app/Http/Requests/Product/UpdateProductRequest.php +++ b/app/Http/Requests/Product/UpdateProductRequest.php @@ -25,7 +25,11 @@ class UpdateProductRequest extends Request */ public function authorize(): bool { - return auth()->user()->can('edit', $this->product); + + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('edit', $this->product); } public function rules() @@ -34,6 +38,8 @@ class UpdateProductRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Project/StoreProjectRequest.php b/app/Http/Requests/Project/StoreProjectRequest.php index b57cb6ffa806..8f8e4760df0c 100644 --- a/app/Http/Requests/Project/StoreProjectRequest.php +++ b/app/Http/Requests/Project/StoreProjectRequest.php @@ -53,6 +53,8 @@ class StoreProjectRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index fd5ea29a3407..e68c90383790 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -49,6 +49,8 @@ class UpdateProjectRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php index e08db93727a5..91564eab092d 100644 --- a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php @@ -57,6 +57,8 @@ class StorePurchaseOrderRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + } else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php index ce9ce0f93841..66984cd29a51 100644 --- a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php @@ -59,6 +59,8 @@ class UpdatePurchaseOrderRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Quote/StoreQuoteRequest.php b/app/Http/Requests/Quote/StoreQuoteRequest.php index 50094a262809..3e0a498610cc 100644 --- a/app/Http/Requests/Quote/StoreQuoteRequest.php +++ b/app/Http/Requests/Quote/StoreQuoteRequest.php @@ -49,6 +49,8 @@ class StoreQuoteRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { @@ -59,11 +61,8 @@ class StoreQuoteRequest extends Request $rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)]; $rules['discount'] = 'sometimes|numeric'; - $rules['is_amount_discount'] = ['boolean']; $rules['exchange_rate'] = 'bail|sometimes|numeric'; - - // $rules['number'] = new UniqueQuoteNumberRule($this->all()); $rules['line_items'] = 'array'; return $rules; diff --git a/app/Http/Requests/Quote/UpdateQuoteRequest.php b/app/Http/Requests/Quote/UpdateQuoteRequest.php index 94dc735a59a0..4644e5af691c 100644 --- a/app/Http/Requests/Quote/UpdateQuoteRequest.php +++ b/app/Http/Requests/Quote/UpdateQuoteRequest.php @@ -46,6 +46,8 @@ class UpdateQuoteRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index af9a45673330..7d69a2587e1d 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -49,6 +49,8 @@ class StoreRecurringInvoiceRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index 8800ab04a0a4..f59edc91d61a 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -48,6 +48,8 @@ class UpdateRecurringInvoiceRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php index df09bf3ae0bd..e5fee49132a0 100644 --- a/app/Http/Requests/Task/StoreTaskRequest.php +++ b/app/Http/Requests/Task/StoreTaskRequest.php @@ -82,6 +82,8 @@ class StoreTaskRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index 0b367b6283d2..299905d8cd58 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -88,6 +88,8 @@ class UpdateTaskRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Vendor/StoreVendorRequest.php b/app/Http/Requests/Vendor/StoreVendorRequest.php index 5ad488e2a64d..7cb6fc14f193 100644 --- a/app/Http/Requests/Vendor/StoreVendorRequest.php +++ b/app/Http/Requests/Vendor/StoreVendorRequest.php @@ -64,6 +64,8 @@ class StoreVendorRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { diff --git a/app/Http/Requests/Vendor/UpdateVendorRequest.php b/app/Http/Requests/Vendor/UpdateVendorRequest.php index cf2d5a884f74..b06b861adfe6 100644 --- a/app/Http/Requests/Vendor/UpdateVendorRequest.php +++ b/app/Http/Requests/Vendor/UpdateVendorRequest.php @@ -65,6 +65,8 @@ class UpdateVendorRequest extends Request $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { $rules['documents'] = $this->file_validation; + }else { + $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { From c1d809ffd06ec7e39e773049d9b6b558e1385d1f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 06:04:38 +1100 Subject: [PATCH 10/26] Updates for company factory --- app/Factory/CompanyFactory.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Factory/CompanyFactory.php b/app/Factory/CompanyFactory.php index 32a9313b9ac2..afe5a6b52ca6 100644 --- a/app/Factory/CompanyFactory.php +++ b/app/Factory/CompanyFactory.php @@ -49,6 +49,14 @@ class CompanyFactory $company->markdown_enabled = false; $company->tax_data = new TaxModel(); $company->first_month_of_year = 1; + $company->smtp_encryption = 'tls'; + $company->smtp_host = ''; + $company->smtp_local_domain = ''; + $company->smtp_password = ''; + $company->smtp_port = ''; + $company->smtp_username = ''; + $company->smtp_verify_peer = true; + return $company; } } From 4e32ac6f1a5558ea3f54f7c1ea74667cf764cf2b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 06:39:33 +1100 Subject: [PATCH 11/26] Add form request for SMTP checks --- app/Http/Controllers/SmtpController.php | 3 +- app/Http/Requests/Smtp/CheckSmtpRequest.php | 54 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 app/Http/Requests/Smtp/CheckSmtpRequest.php diff --git a/app/Http/Controllers/SmtpController.php b/app/Http/Controllers/SmtpController.php index 2b800fb96d4d..5588c668edb0 100644 --- a/app/Http/Controllers/SmtpController.php +++ b/app/Http/Controllers/SmtpController.php @@ -11,6 +11,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\Smtp\CheckSmtpRequest; use App\Mail\TestMailServer; use Illuminate\Http\Request; use Illuminate\Support\Facades\Mail; @@ -23,7 +24,7 @@ class SmtpController extends BaseController parent::__construct(); } - public function check(Request $request) + public function check(CheckSmtpRequest $request) { /** @var \App\Models\User $user */ $user = auth()->user(); diff --git a/app/Http/Requests/Smtp/CheckSmtpRequest.php b/app/Http/Requests/Smtp/CheckSmtpRequest.php new file mode 100644 index 000000000000..d4a4c22914af --- /dev/null +++ b/app/Http/Requests/Smtp/CheckSmtpRequest.php @@ -0,0 +1,54 @@ +user(); + + return $user->isAdmin(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + ]; + } + + public function prepareForValidation() + { + $input = $this->input(); + + if(isset($input['smtp_username']) && $input['smtp_username'] == '********') + unset($input['smtp_username']); + + if(isset($input['smtp_password'])&& $input['smtp_password'] == '********') + unset($input['smtp_password']); + + $this->replace($input); + } +} From 04314538d876294ef6d231b773d0563b3d5b659f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 17:44:16 +1100 Subject: [PATCH 12/26] Minor fixes for types --- app/Services/Template/TemplateService.php | 5 +- composer.lock | 90 +++++++++++------------ 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index da5c7bf0e3b9..9ab4eaa25d35 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -711,7 +711,10 @@ class TemplateService private function getPaymentRefundActivity(Payment $payment): array { - return collect($payment->refund_meta ?? []) + if(!is_array($payment->refund_meta)) + return []; + + return collect($payment->refund_meta) ->map(function ($refund) use ($payment) { $date = \Carbon\Carbon::parse($refund['date'])->addSeconds($payment->client->timezone_offset()); diff --git a/composer.lock b/composer.lock index 10fcdc0750f5..473b8697effe 100644 --- a/composer.lock +++ b/composer.lock @@ -141,16 +141,16 @@ }, { "name": "amphp/byte-stream", - "version": "v2.1.0", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "0a4b0e80dad92c75e6131f8ad253919211540338" + "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/0a4b0e80dad92c75e6131f8ad253919211540338", - "reference": "0a4b0e80dad92c75e6131f8ad253919211540338", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/daa00f2efdbd71565bf64ffefa89e37542addf93", + "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93", "shasum": "" }, "require": { @@ -166,7 +166,7 @@ "amphp/php-cs-fixer-config": "^2", "amphp/phpunit-util": "^3", "phpunit/phpunit": "^9", - "psalm/phar": "^5.4" + "psalm/phar": "5.22.1" }, "type": "library", "autoload": { @@ -204,7 +204,7 @@ ], "support": { "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v2.1.0" + "source": "https://github.com/amphp/byte-stream/tree/v2.1.1" }, "funding": [ { @@ -212,7 +212,7 @@ "type": "github" } ], - "time": "2023-11-19T14:34:16+00:00" + "time": "2024-02-17T04:49:38+00:00" }, { "name": "amphp/cache", @@ -581,16 +581,16 @@ }, { "name": "amphp/process", - "version": "v2.0.1", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/amphp/process.git", - "reference": "a65d3bc1f36ef12d44df42a68f0f0643183f1052" + "reference": "a79dc87100be857db2c4bbfd5369585a6d1e658c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/process/zipball/a65d3bc1f36ef12d44df42a68f0f0643183f1052", - "reference": "a65d3bc1f36ef12d44df42a68f0f0643183f1052", + "url": "https://api.github.com/repos/amphp/process/zipball/a79dc87100be857db2c4bbfd5369585a6d1e658c", + "reference": "a79dc87100be857db2c4bbfd5369585a6d1e658c", "shasum": "" }, "require": { @@ -637,7 +637,7 @@ "homepage": "https://amphp.org/process", "support": { "issues": "https://github.com/amphp/process/issues", - "source": "https://github.com/amphp/process/tree/v2.0.1" + "source": "https://github.com/amphp/process/tree/v2.0.2" }, "funding": [ { @@ -645,7 +645,7 @@ "type": "github" } ], - "time": "2023-01-15T16:00:57+00:00" + "time": "2024-02-13T20:38:21+00:00" }, { "name": "amphp/serialization", @@ -1343,16 +1343,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.299.0", + "version": "3.299.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "76d1165568d949b430a87eaa42f6dbc4b6705b6f" + "reference": "a0f87b8e8bfb9afd0ffd702fcda556b465eee457" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/76d1165568d949b430a87eaa42f6dbc4b6705b6f", - "reference": "76d1165568d949b430a87eaa42f6dbc4b6705b6f", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a0f87b8e8bfb9afd0ffd702fcda556b465eee457", + "reference": "a0f87b8e8bfb9afd0ffd702fcda556b465eee457", "shasum": "" }, "require": { @@ -1432,9 +1432,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.299.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.299.1" }, - "time": "2024-02-15T19:08:34+00:00" + "time": "2024-02-16T19:08:34+00:00" }, { "name": "bacon/bacon-qr-code", @@ -4650,16 +4650,16 @@ }, { "name": "imdhemy/laravel-purchases", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/imdhemy/laravel-in-app-purchases.git", - "reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90" + "reference": "014c189795704bea88260a4054b02d80b9bbc3ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90", - "reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90", + "url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/014c189795704bea88260a4054b02d80b9bbc3ac", + "reference": "014c189795704bea88260a4054b02d80b9bbc3ac", "shasum": "" }, "require": { @@ -4715,7 +4715,7 @@ ], "support": { "issues": "https://github.com/imdhemy/laravel-in-app-purchases/issues", - "source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.9.1" + "source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.10.0" }, "funding": [ { @@ -4723,7 +4723,7 @@ "type": "github" } ], - "time": "2023-12-15T10:35:56+00:00" + "time": "2024-02-17T05:58:30+00:00" }, { "name": "intervention/image", @@ -4873,7 +4873,7 @@ }, { "name": "invoiceninja/ubl_invoice", - "version": "v2.0.1", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/invoiceninja/UBL_invoice.git", @@ -4931,7 +4931,7 @@ "xml invoice" ], "support": { - "source": "https://github.com/invoiceninja/UBL_invoice/tree/v2.0.1" + "source": "https://github.com/invoiceninja/UBL_invoice/tree/v2.0.2" }, "time": "2024-02-05T02:16:14+00:00" }, @@ -5754,16 +5754,16 @@ }, { "name": "laravel/socialite", - "version": "v5.12.0", + "version": "v5.12.1", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "ffeeb2cdf723b4c88b25479968e2d3a61a83dbe5" + "reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/ffeeb2cdf723b4c88b25479968e2d3a61a83dbe5", - "reference": "ffeeb2cdf723b4c88b25479968e2d3a61a83dbe5", + "url": "https://api.github.com/repos/laravel/socialite/zipball/7dae1b072573809f32ab6dcf4aebb57c8b3e8acf", + "reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf", "shasum": "" }, "require": { @@ -5820,7 +5820,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-02-11T18:29:55+00:00" + "time": "2024-02-16T08:58:20+00:00" }, { "name": "laravel/tinker", @@ -11845,38 +11845,38 @@ }, { "name": "spatie/php-structure-discoverer", - "version": "2.0.1", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/spatie/php-structure-discoverer.git", - "reference": "d2e4e6cba962ce2a058ea415a123dd84b37765ac" + "reference": "f5b3c935dda89d6c382b27e3caf348fa80bcfa88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/d2e4e6cba962ce2a058ea415a123dd84b37765ac", - "reference": "d2e4e6cba962ce2a058ea415a123dd84b37765ac", + "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/f5b3c935dda89d6c382b27e3caf348fa80bcfa88", + "reference": "f5b3c935dda89d6c382b27e3caf348fa80bcfa88", "shasum": "" }, "require": { "amphp/amp": "^v3.0", "amphp/parallel": "^2.2", - "illuminate/collections": "^9.30|^10.0", + "illuminate/collections": "^10.0|^11.0", "php": "^8.1", "spatie/laravel-package-tools": "^1.4.3", "symfony/finder": "^6.0|^7.0" }, "require-dev": { - "illuminate/console": "^9.30|^10.0", + "illuminate/console": "^10.0|^11.0", "laravel/pint": "^1.0", - "nunomaduro/collision": "^6.0", + "nunomaduro/collision": "^7.0|^8.0", "nunomaduro/larastan": "^2.0.1", - "orchestra/testbench": "^7.0|^8.0", - "pestphp/pest": "^1.21", - "pestphp/pest-plugin-laravel": "^1.1", + "orchestra/testbench": "^7.0|^8.0|^9.0", + "pestphp/pest": "^2.0", + "pestphp/pest-plugin-laravel": "^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^9.5|^10.0", "spatie/laravel-ray": "^1.26" }, "type": "library", @@ -11913,7 +11913,7 @@ ], "support": { "issues": "https://github.com/spatie/php-structure-discoverer/issues", - "source": "https://github.com/spatie/php-structure-discoverer/tree/2.0.1" + "source": "https://github.com/spatie/php-structure-discoverer/tree/2.1.0" }, "funding": [ { @@ -11921,7 +11921,7 @@ "type": "github" } ], - "time": "2024-01-08T21:01:26+00:00" + "time": "2024-02-16T12:42:24+00:00" }, { "name": "sprain/swiss-qr-bill", From fdca196215f5b44bf0ce5965c8620f369ef97f35 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 18:17:33 +1100 Subject: [PATCH 13/26] Improve PDF Mock --- app/Services/Pdf/PdfMock.php | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index c39fc6f2632b..d6fb4e5aa53c 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -204,9 +204,9 @@ class PdfMock [ '$client.shipping_postal_code' => '46420', '$client.billing_postal_code' => '11243', - '$company.city_state_postal' => 'Beveley Hills, CA, 90210', - '$company.postal_city_state' => 'CA', - '$company.postal_city' => '90210, CA', + '$company.city_state_postal' => "{$this->settings->city}, {$this->settings->state}, {$this->settings->postal_code}", + '$company.postal_city_state' => "{$this->settings->postal_code}, {$this->settings->city}, {$this->settings->state}", + '$company.postal_city' => "{$this->settings->postal_code}, {$this->settings->state}", '$product.gross_line_total' => '100', '$client.classification' => 'Individual', '$company.classification' => 'Business', @@ -262,9 +262,9 @@ class PdfMock '$company.id_number' => $this->settings->id_number, '$invoice.po_number' => 'PO12345', '$invoice_total_raw' => 0.0, - '$postal_city_state' => '11243 Aufderharchester, North Carolina', + '$postal_city_state' => "{$this->settings->postal_code}, {$this->settings->city}, {$this->settings->state}", '$client.vat_number' => '975977515', - '$city_state_postal' => 'Aufderharchester, North Carolina 11243', + '$city_state_postal' => "{$this->settings->city}, {$this->settings->state}, {$this->settings->postal_code}", '$contact.full_name' => 'Benedict Eichmann', '$contact.last_name' => 'Eichmann', '$company.country_2' => 'US', @@ -275,7 +275,7 @@ class PdfMock '$statement_amount' => '', '$task.description' => '', '$product.discount' => '', - '$entity_issued_to' => 'Bob JOnes', + '$entity_issued_to' => 'Bob Jones', '$assigned_to_user' => '', '$product.quantity' => '', '$total_tax_labels' => '', @@ -303,10 +303,10 @@ class PdfMock '$invoice.custom2' => 'custom value', '$invoice.custom3' => 'custom value', '$invoice.custom4' => 'custom value', - '$company.custom1' => 'custom value', - '$company.custom2' => 'custom value', - '$company.custom3' => 'custom value', - '$company.custom4' => 'custom value', + '$company.custom1' => $this->company->custom_value1, + '$company.custom2' => $this->company->custom_value2, + '$company.custom3' => $this->company->custom_value3, + '$company.custom4' => $this->company->custom_value4, '$quote.po_number' => 'PO12345', '$company.website' => $this->settings->website, '$balance_due_raw' => '0.00', @@ -317,8 +317,8 @@ class PdfMock '$user.first_name' => 'Derrick Monahan DDS', '$created_by_user' => 'Derrick Monahan DDS Erna Wunsch', '$client.currency' => 'USD', - '$company.country' => 'United States', - '$company.address' => 'Christiansen Garden
70218 Lori Station Suite 529
New Loy, Delaware 29359
United States
Phone: 1-240-886-2233
Email: immanuel53@example.net
', + '$company.country' => $this->company->country()?->name ?? 'USA', + '$company.address' => $this->company->present()->address(), '$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg', '$task.tax_name1' => '', '$task.tax_name2' => '', @@ -450,15 +450,15 @@ class PdfMock '$task.tax' => '', '$discount' => '$0.00', '$subtotal' => '$0.00', - '$company1' => 'custom value', - '$company2' => 'custom value', - '$company3' => 'custom value', - '$company4' => 'custom value', + '$company1' => $this->company->custom_value1, + '$company2' => $this->company->custom_value2, + '$company3' => $this->company->custom_value3, + '$company4' => $this->company->custom_value4, '$due_date' => '2022-01-01', '$poNumber' => 'PO-123456', '$quote_no' => '0029', - '$address2' => '63993 Aiyana View', - '$address1' => '8447', + '$address2' => $this->settings->address2, + '$address1' => $this->settings->address1, '$viewLink' => 'View Invoice', '$autoBill' => 'This invoice will automatically be billed to your credit card on file on the due date.', '$view_url' => 'http://ninja.test:8000/client/invoice/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', @@ -477,7 +477,7 @@ class PdfMock '$country' => 'United States', '$contact' => 'Benedict Eichmann', '$app_url' => 'http://ninja.test:8000', - '$website' => 'http://www.parisian.org/', + '$website' => $this->settings->website, '$entity' => '', '$thanks' => 'Thanks!', '$amount' => '$30.00', From 1263e6a1db92a92cb817b627d63dd4f4adb76ebd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 18:24:26 +1100 Subject: [PATCH 14/26] Add company setting --- app/DataMapper/CompanySettings.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 3079a50988c3..d2241f95b2f5 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -495,7 +495,10 @@ class CompanySettings extends BaseSettings public $show_pdfhtml_on_mobile = true; + public $use_unapplied_payment = 'off'; //always, option, off //@implemented + public static $casts = [ + 'use_unapplied_payment' => 'string', 'show_pdfhtml_on_mobile' => 'bool', 'payment_email_all_contacts' => 'bool', 'statement_design_id' => 'string', From 32b82f5bfe6d695a7a5afdf4a90f553948a2a4ba Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 18:41:40 +1100 Subject: [PATCH 15/26] Working on adding unapplied payments into autobill sequence --- app/Services/Invoice/AutoBillInvoice.php | 87 +++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 73eac355eefe..1091d1874825 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -31,6 +31,8 @@ class AutoBillInvoice extends AbstractService private Client $client; private array $used_credit = []; + + private array $used_unapplied = []; /*Specific variable for partial payments */ private bool $is_partial_amount = false; @@ -66,6 +68,10 @@ class AutoBillInvoice extends AbstractService $this->applyCreditPayment(); } + if($this->client->getSetting('use_unapplied_payment') != 'off') { + $this->applyUnappliedPayment(); + } + //If this returns true, it means a partial invoice amount was paid as a credit and there is no further balance payable if ($this->is_partial_amount && $this->invoice->partial == 0) { return; @@ -162,6 +168,11 @@ class AutoBillInvoice extends AbstractService } } + private function finalizePaymentUsingUnapplied() + { + + } + /** * If the credits on file cover the invoice amount * the we create a matching payment using credits only @@ -243,6 +254,80 @@ class AutoBillInvoice extends AbstractService ->setCalculatedStatus() ->save(); } + + /** + * If the client has unapplied payments on file + * we will use these prior to charging a + * payment method on file. + * + * This needs to be wrapped in a transaction. + * + * @return self + */ + private function applyUnappliedPayment(): self + { + $unapplied_payments = Payment::query()->where('client_id', $this->client->id) + ->where('status_id', Payment::STATUS_COMPLETED) + ->where('is_deleted', false) + ->where('amount', '>', 'applied') + ->where('amount', '>', 0) + ->orderBy('created_at') + ->get(); + + $available_unapplied_balance = $unapplied_payments->sum('amount') - $unapplied_payments->sum('applied'); + + nlog("available unapplied balance = {$available_unapplied_balance}"); + + if ((int) $available_unapplied_balance == 0) { + return $this; + } + + if ($this->invoice->partial > 0) { + $this->is_partial_amount = true; + } + + $this->used_unapplied = []; + + foreach ($unapplied_payments as $key => $payment) { + $payment_balance = $payment->amount - $payment->applied; + if ($this->is_partial_amount) { + //more than needed + if ($payment_balance > $this->invoice->partial) { + $this->used_unapplied[$key]['payment_id'] = $payment->id; + $this->used_unapplied[$key]['amount'] = $this->invoice->partial; + $this->invoice->balance -= $this->invoice->partial; + $this->invoice->paid_to_date += $this->invoice->partial; + $this->invoice->partial = 0; + break; + } else { + $this->used_unapplied[$key]['payment_id'] = $payment->id; + $this->used_unapplied[$key]['amount'] = $payment_balance; + $this->invoice->partial -= $payment_balance; + $this->invoice->balance -= $payment_balance; + $this->invoice->paid_to_date += $payment_balance; + } + } else { + //more than needed + if ($payment_balance > $this->invoice->balance) { + $this->used_unapplied[$key]['payment_id'] = $payment->id; + $this->used_unapplied[$key]['amount'] = $this->invoice->balance; + $this->invoice->paid_to_date += $this->invoice->balance; + $this->invoice->balance = 0; + + break; + } else { + $this->used_unapplied[$key]['payment_id'] = $payment->id; + $this->used_unapplied[$key]['amount'] = $payment_balance; + $this->invoice->balance -= $payment_balance; + $this->invoice->paid_to_date += $payment_balance; + } + } + } + + $this->finalizePaymentUsingUnapplied(); + + return $this; + } /** * Applies credits to a payment prior to push @@ -260,7 +345,7 @@ class AutoBillInvoice extends AbstractService $available_credit_balance = $available_credits->sum('balance'); - info("available credit balance = {$available_credit_balance}"); + nlog("available credit balance = {$available_credit_balance}"); if ((int) $available_credit_balance == 0) { return $this; From 66fd68cf9356f14a5d6ccfd5b063196a473b1690 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 19:03:20 +1100 Subject: [PATCH 16/26] Refactor for payment processing --- app/Services/Invoice/AutoBillInvoice.php | 48 +++++++++--------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 1091d1874825..dd0da025ab99 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -22,6 +22,8 @@ use App\Models\Invoice; use App\Models\Payment; use App\Models\PaymentHash; use App\Models\PaymentType; +use App\Repositories\CreditRepository; +use App\Repositories\PaymentRepository; use App\Services\AbstractService; use App\Utils\Ninja; use Illuminate\Support\Str; @@ -32,8 +34,6 @@ class AutoBillInvoice extends AbstractService private array $used_credit = []; - private array $used_unapplied = []; - /*Specific variable for partial payments */ private bool $is_partial_amount = false; @@ -168,11 +168,6 @@ class AutoBillInvoice extends AbstractService } } - private function finalizePaymentUsingUnapplied() - { - - } - /** * If the credits on file cover the invoice amount * the we create a matching payment using credits only @@ -228,8 +223,6 @@ class AutoBillInvoice extends AbstractService ->client ->service() ->updateBalanceAndPaidToDate($amount * -1, $amount) - // ->updateBalance($amount * -1) - // ->updatePaidToDate($amount) ->adjustCreditBalance($amount * -1) ->save(); @@ -286,45 +279,40 @@ class AutoBillInvoice extends AbstractService $this->is_partial_amount = true; } - $this->used_unapplied = []; + $payment_repo = new PaymentRepository(new CreditRepository()); + foreach ($unapplied_payments as $key => $payment) { $payment_balance = $payment->amount - $payment->applied; + if ($this->is_partial_amount) { //more than needed if ($payment_balance > $this->invoice->partial) { - $this->used_unapplied[$key]['payment_id'] = $payment->id; - $this->used_unapplied[$key]['amount'] = $this->invoice->partial; - $this->invoice->balance -= $this->invoice->partial; - $this->invoice->paid_to_date += $this->invoice->partial; - $this->invoice->partial = 0; + $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->partial]]]; + $payment_repo->save($payload, $payment); break; } else { - $this->used_unapplied[$key]['payment_id'] = $payment->id; - $this->used_unapplied[$key]['amount'] = $payment_balance; - $this->invoice->partial -= $payment_balance; - $this->invoice->balance -= $payment_balance; - $this->invoice->paid_to_date += $payment_balance; + $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; + $payment_repo->save($payload, $payment); } } else { //more than needed if ($payment_balance > $this->invoice->balance) { - $this->used_unapplied[$key]['payment_id'] = $payment->id; - $this->used_unapplied[$key]['amount'] = $this->invoice->balance; - $this->invoice->paid_to_date += $this->invoice->balance; - $this->invoice->balance = 0; + + $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->balance]]]; + $payment_repo->save($payload, $payment); break; } else { - $this->used_unapplied[$key]['payment_id'] = $payment->id; - $this->used_unapplied[$key]['amount'] = $payment_balance; - $this->invoice->balance -= $payment_balance; - $this->invoice->paid_to_date += $payment_balance; + + $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; + $payment_repo->save($payload, $payment); + } } - } - $this->finalizePaymentUsingUnapplied(); + $this->invoice = $this->invoice->fresh(); + } return $this; } From 97accc814249d433a850bbf4deb6de742c496af9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 19:04:41 +1100 Subject: [PATCH 17/26] Refactor for payment processing --- app/Services/Invoice/AutoBillInvoice.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index dd0da025ab99..3c864a6a88f3 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -288,24 +288,24 @@ class AutoBillInvoice extends AbstractService if ($this->is_partial_amount) { //more than needed if ($payment_balance > $this->invoice->partial) { - $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->partial]]]; + $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->partial]]]; $payment_repo->save($payload, $payment); break; } else { - $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; + $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; $payment_repo->save($payload, $payment); } } else { //more than needed if ($payment_balance > $this->invoice->balance) { - $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->balance]]]; + $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->balance]]]; $payment_repo->save($payload, $payment); break; } else { - $payload = ['invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; + $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; $payment_repo->save($payload, $payment); } From 6974841921c78b716ba7ce059974fe1323b16e80 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 17 Feb 2024 19:08:20 +1100 Subject: [PATCH 18/26] Refactor for payment processing --- app/Services/Invoice/AutoBillInvoice.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 3c864a6a88f3..22eacad77639 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -279,7 +279,6 @@ class AutoBillInvoice extends AbstractService $this->is_partial_amount = true; } - $payment_repo = new PaymentRepository(new CreditRepository()); foreach ($unapplied_payments as $key => $payment) { @@ -296,7 +295,7 @@ class AutoBillInvoice extends AbstractService $payment_repo->save($payload, $payment); } } else { - //more than needed + //more than needed if ($payment_balance > $this->invoice->balance) { $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->balance]]]; @@ -312,6 +311,10 @@ class AutoBillInvoice extends AbstractService } $this->invoice = $this->invoice->fresh(); + + if((int)$this->invoice->balance == 0) { + return $this; + } } return $this; From 3006f5882bb708a27a487b6fe0efa036c70630f0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 11:19:59 +1100 Subject: [PATCH 19/26] Checks for routes --- app/Models/Document.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Models/Document.php b/app/Models/Document.php index e393ef3c4129..061210041ff8 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -176,7 +176,11 @@ class Document extends BaseModel public function generateRoute($absolute = false) { + try{ return route('api.documents.show', ['document' => $this->hashed_id]).'/download'; + }catch(\Exception $e){ + return ''; + } } public function deleteFile() From 5a14b85eeaea276bbea30eb13cfdb158c603a3db Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 11:21:04 +1100 Subject: [PATCH 20/26] fixes for html entities --- app/Utils/Traits/CleanLineItems.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Utils/Traits/CleanLineItems.php b/app/Utils/Traits/CleanLineItems.php index 9cb96b913664..6ff730bfc520 100644 --- a/app/Utils/Traits/CleanLineItems.php +++ b/app/Utils/Traits/CleanLineItems.php @@ -75,11 +75,11 @@ trait CleanLineItems } if(isset($item['notes'])) { - $item['notes'] = str_replace(" Date: Sun, 18 Feb 2024 15:06:20 +1100 Subject: [PATCH 21/26] Tests for using Unapplied Payments --- app/Services/Invoice/AutoBillInvoice.php | 16 +- .../Payments/AutoUnappliedPaymentTest.php | 180 ++++++++++++++++++ 2 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 tests/Feature/Payments/AutoUnappliedPaymentTest.php diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 22eacad77639..549efee15b1d 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -68,7 +68,9 @@ class AutoBillInvoice extends AbstractService $this->applyCreditPayment(); } + nlog($this->client->getSetting('use_unapplied_payment')); if($this->client->getSetting('use_unapplied_payment') != 'off') { + nlog("meeeeeeerp"); $this->applyUnappliedPayment(); } @@ -182,9 +184,6 @@ class AutoBillInvoice extends AbstractService $payment->amount = 0; $payment->applied = 0; - - // $payment->amount = $amount; - // $payment->applied = $amount; $payment->client_id = $this->invoice->client_id; $payment->currency_id = $this->invoice->client->getSetting('currency_id'); $payment->date = now()->addSeconds($this->invoice->company->utc_offset())->format('Y-m-d'); @@ -259,7 +258,8 @@ class AutoBillInvoice extends AbstractService */ private function applyUnappliedPayment(): self { - $unapplied_payments = Payment::query()->where('client_id', $this->client->id) + $unapplied_payments = Payment::query() + ->where('client_id', $this->client->id) ->where('status_id', Payment::STATUS_COMPLETED) ->where('is_deleted', false) ->where('amount', '>', 'applied') @@ -408,14 +408,6 @@ class AutoBillInvoice extends AbstractService })->orderBy('is_default', 'DESC') ->get(); - // $gateway_tokens = $this->client - // ->gateway_tokens() - // ->whereHas('gateway', function ($query) { - // $query->where('is_deleted', 0) - // ->where('deleted_at', null); - // })->orderBy('is_default', 'DESC') - // ->get(); - $filtered_gateways = $gateway_tokens->filter(function ($gateway_token) use ($amount) { $company_gateway = $gateway_token->gateway; diff --git a/tests/Feature/Payments/AutoUnappliedPaymentTest.php b/tests/Feature/Payments/AutoUnappliedPaymentTest.php new file mode 100644 index 000000000000..96e3a967a22f --- /dev/null +++ b/tests/Feature/Payments/AutoUnappliedPaymentTest.php @@ -0,0 +1,180 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + // $this->withoutExceptionHandling(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } + + public function testUnappliedPaymentsAreEnabled() + { + + $settings = ClientSettings::defaults(); + $settings->use_unapplied_payment = 'always'; + + $client = Client::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'settings' => $settings, + ]); + + $this->assertEquals('always', $client->settings->use_unapplied_payment); + + $invoice = Invoice::factory()->for($client)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'auto_bill_enabled' => true, + 'client_id' => $client->id, + ]); + + $invoice = $invoice->calc()->getInvoice(); + + $payment = Payment::factory()->for($client)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'amount' => 100, + 'applied' => 0, + 'refunded' => 0, + 'status_id' => Payment::STATUS_COMPLETED, + 'is_deleted' => 0, + ]); + + $invoice->service()->markSent()->save(); + + $this->assertGreaterThan(0, $invoice->balance); + + nlog($invoice->balance); + + try{ + $invoice->service()->autoBill()->save(); + } + catch(\Exception $e){ + + } + + $invoice = $invoice->fresh(); + $payment = $payment->fresh(); + + nlog($invoice->toArray()); + nlog($payment->toArray()); + + $this->assertEquals($payment->applied, $invoice->paid_to_date); + $this->assertGreaterThan(2, $invoice->status_id); + $this->assertGreaterThan(0, $payment->applied); + + // $this->assertEquals(Invoice::STATUS_PAID, $invoice->status_id); + // $this->assertEquals(0, $invoice->balance); + + } + + + public function testUnappliedPaymentsAreDisabled() + { + + $settings = ClientSettings::defaults(); + $settings->use_unapplied_payment = 'off'; + + $client = Client::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'settings' => $settings, + ]); + + $this->assertEquals('off', $client->settings->use_unapplied_payment); + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'auto_bill_enabled' => true, + 'status_id' => 2 + ]); + $invoice = $invoice->calc()->getInvoice(); + $invoice_balance = $invoice->balance; + + $payment = Payment::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'amount' => 100, + 'applied' => 0, + 'refunded' => 0, + 'status_id' => Payment::STATUS_COMPLETED + ]); + + $invoice->service()->markSent()->save(); + + $this->assertGreaterThan(0, $invoice->balance); + + try { + $invoice->service()->autoBill()->save(); + } + catch(\Exception $e) { + + } + + $invoice = $invoice->fresh(); + $payment = $payment->fresh(); + + $this->assertEquals($invoice_balance, $invoice->balance); + $this->assertEquals(0, $payment->applied); + $this->assertEquals(2, $invoice->status_id); + $this->assertEquals(0, $invoice->paid_to_date); + $this->assertEquals($invoice->amount, $invoice->balance); + + // $this->assertEquals($payment->applied, $invoice->paid_to_date); + // $this->assertEquals(2, $invoice->status_id); + + + } + +} \ No newline at end of file From 22793dbe520f441d75902602dc3adc4c07e88de1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 15:11:01 +1100 Subject: [PATCH 22/26] v5.8.36 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 261553830a70..96c215198db3 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.8.25 \ No newline at end of file +5.8.26 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index c71db78427aa..860ad8ef153e 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -17,8 +17,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.8.25'), - 'app_tag' => env('APP_TAG', '5.8.25'), + 'app_version' => env('APP_VERSION', '5.8.26'), + 'app_tag' => env('APP_TAG', '5.8.26'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), From e2287c6d5701873e60b4dcd44e3fbd59ab7c1927 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 15:32:49 +1100 Subject: [PATCH 23/26] Fixes for tests --- app/Services/Invoice/AutoBillInvoice.php | 10 ++++++---- tests/TestCase.php | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 549efee15b1d..50ce340e88c4 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -68,17 +68,17 @@ class AutoBillInvoice extends AbstractService $this->applyCreditPayment(); } - nlog($this->client->getSetting('use_unapplied_payment')); if($this->client->getSetting('use_unapplied_payment') != 'off') { - nlog("meeeeeeerp"); $this->applyUnappliedPayment(); } //If this returns true, it means a partial invoice amount was paid as a credit and there is no further balance payable - if ($this->is_partial_amount && $this->invoice->partial == 0) { + if (($this->is_partial_amount && $this->invoice->partial == 0) || (int)$this->invoice->balance == 0) { return; } + nlog($this->invoice->toArray()); + $amount = 0; $invoice_total = 0; @@ -262,11 +262,13 @@ class AutoBillInvoice extends AbstractService ->where('client_id', $this->client->id) ->where('status_id', Payment::STATUS_COMPLETED) ->where('is_deleted', false) - ->where('amount', '>', 'applied') + ->whereColumn('amount', '>', 'applied') ->where('amount', '>', 0) ->orderBy('created_at') ->get(); + nlog($unapplied_payments->pluck("id")); + $available_unapplied_balance = $unapplied_payments->sum('amount') - $unapplied_payments->sum('applied'); nlog("available unapplied balance = {$available_unapplied_balance}"); diff --git a/tests/TestCase.php b/tests/TestCase.php index 2932d4a69d65..ffa0f237e183 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,19 @@ namespace Tests; +use App\Utils\Traits\AppSetup; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { use CreatesApplication; + use AppSetup; + + protected function setUp() :void + { + parent::setUp(); + + $this->buildCache(true); + + } } From 6c7df568dc65e292cc65f69f60fc85484dacdd79 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 16:19:09 +1100 Subject: [PATCH 24/26] Ensure port is int for SMTP --- app/Http/Controllers/BaseController.php | 3 ++- app/Models/Project.php | 4 ++++ app/Models/Task.php | 4 ++++ app/Services/Invoice/AutoBillInvoice.php | 19 +++++++++---------- app/Transformers/CompanyTransformer.php | 2 +- app/Transformers/ProjectTransformer.php | 5 ++++- app/Transformers/TaskTransformer.php | 7 +++---- 7 files changed, 27 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 43e72c20051b..03a5eb4e07ff 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -140,6 +140,7 @@ class BaseController extends Controller 'company.quotes.invitations.company', 'company.quotes.documents', 'company.tasks.documents', + // 'company.tasks.project', 'company.subscriptions', 'company.tax_rates', 'company.tokens_hashed', @@ -458,7 +459,7 @@ class BaseController extends Controller } }, 'company.tasks' => function ($query) use ($updated_at, $user) { - $query->where('updated_at', '>=', $updated_at)->with('documents'); + $query->where('updated_at', '>=', $updated_at)->with('project','documents'); if (! $user->hasPermission('view_task')) { $query->whereNested(function ($query) use ($user) { diff --git a/app/Models/Project.php b/app/Models/Project.php index be9784d0a83a..ca00a95d8ddc 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -75,6 +75,10 @@ class Project extends BaseModel 'number', ]; + protected $with = [ + 'documents', + ]; + public function getEntityType() { return self::class; diff --git a/app/Models/Task.php b/app/Models/Task.php index 16903f6fff62..f1716b15a870 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -131,6 +131,10 @@ class Task extends BaseModel 'deleted_at' => 'timestamp', ]; + protected $with = [ + // 'project', + ]; + protected $touches = []; public function getEntityType() diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 50ce340e88c4..b0d81c5c9af5 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -77,8 +77,6 @@ class AutoBillInvoice extends AbstractService return; } - nlog($this->invoice->toArray()); - $amount = 0; $invoice_total = 0; @@ -236,9 +234,7 @@ class AutoBillInvoice extends AbstractService //if we have paid the invoice in full using credits, then we need to fire the event if($this->invoice->balance == 0) { - event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars())); - } return $this->invoice @@ -267,8 +263,6 @@ class AutoBillInvoice extends AbstractService ->orderBy('created_at') ->get(); - nlog($unapplied_payments->pluck("id")); - $available_unapplied_balance = $unapplied_payments->sum('amount') - $unapplied_payments->sum('applied'); nlog("available unapplied balance = {$available_unapplied_balance}"); @@ -291,7 +285,10 @@ class AutoBillInvoice extends AbstractService if ($payment_balance > $this->invoice->partial) { $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->partial]]]; $payment_repo->save($payload, $payment); - break; + + $this->invoice = $this->invoice->fresh(); + + return $this; } else { $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; $payment_repo->save($payload, $payment); @@ -303,7 +300,10 @@ class AutoBillInvoice extends AbstractService $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->balance]]]; $payment_repo->save($payload, $payment); - break; + $this->invoice = $this->invoice->fresh(); + + return $this; + } else { $payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]]; @@ -312,9 +312,8 @@ class AutoBillInvoice extends AbstractService } } - $this->invoice = $this->invoice->fresh(); - if((int)$this->invoice->balance == 0) { + event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars())); return $this; } } diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index e8941e1111a0..9b838f542fe5 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -205,7 +205,7 @@ class CompanyTransformer extends EntityTransformer 'invoice_task_item_description' => (bool) $company->invoice_task_item_description, 'origin_tax_data' => $company->origin_tax_data ?: new \stdClass(), 'smtp_host' => (string)$company->smtp_host ?? '', - 'smtp_port' => (string)$company->smtp_port ?? '', + 'smtp_port' => (int)$company->smtp_port ?? 25, 'smtp_encryption' => (string)$company->smtp_encryption ?? 'tls', 'smtp_username' => $company->smtp_username ? '********' : '', 'smtp_password' => $company->smtp_password ? '********' : '', diff --git a/app/Transformers/ProjectTransformer.php b/app/Transformers/ProjectTransformer.php index 729552b58e69..6f66dd23f15c 100644 --- a/app/Transformers/ProjectTransformer.php +++ b/app/Transformers/ProjectTransformer.php @@ -41,7 +41,10 @@ class ProjectTransformer extends EntityTransformer { $transformer = new DocumentTransformer($this->serializer); - return $this->includeCollection($project->documents, $transformer, Document::class); + if($project->documents) + return $this->includeCollection($project->documents, $transformer, Document::class); + + return null; } public function includeClient(Project $project): \League\Fractal\Resource\Item diff --git a/app/Transformers/TaskTransformer.php b/app/Transformers/TaskTransformer.php index 131789b32ff6..1bfcf64e9b51 100644 --- a/app/Transformers/TaskTransformer.php +++ b/app/Transformers/TaskTransformer.php @@ -100,11 +100,10 @@ class TaskTransformer extends EntityTransformer { $transformer = new ProjectTransformer($this->serializer); - if (!$task->project) { - return null; - } + if ($task->project) + return $this->includeItem($task->project, $transformer, Project::class); - return $this->includeItem($task->project, $transformer, Project::class); + return null; } public function transform(Task $task) From 26a7e7369490c61bf59c136126979ca0f07d228c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 16:29:16 +1100 Subject: [PATCH 25/26] Adjustments for eager loading --- app/Http/Controllers/BaseController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 03a5eb4e07ff..b0abfcf207f0 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -797,7 +797,7 @@ class BaseController extends Controller } }, 'company.tasks' => function ($query) use ($created_at, $user) { - $query->where('created_at', '>=', $created_at)->with('documents'); + $query->where('created_at', '>=', $created_at)->with('project.documents','documents'); if (! $user->hasPermission('view_task')) { $query->whereNested(function ($query) use ($user) { From 9d5e11a0c4f1ad41eec31b4ee76ce7c2f6dca429 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 18 Feb 2024 17:07:21 +1100 Subject: [PATCH 26/26] Enforce columns that we can sort on --- app/Filters/PaymentFilters.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Filters/PaymentFilters.php b/app/Filters/PaymentFilters.php index 038bc114375c..5c0b733f9398 100644 --- a/app/Filters/PaymentFilters.php +++ b/app/Filters/PaymentFilters.php @@ -12,8 +12,9 @@ namespace App\Filters; use App\Models\Payment; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Eloquent\Builder; /** * PaymentFilters. @@ -163,7 +164,7 @@ class PaymentFilters extends QueryFilters { $sort_col = explode('|', $sort); - if (!is_array($sort_col) || count($sort_col) != 2) { + if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col, Schema::getColumnListing('payments'))) { return $this->builder; }