From a32a9a00156c75914d0020002f63d09b531618bd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 09:22:16 +1100 Subject: [PATCH 01/16] Working on white label licensing --- app/Http/Livewire/BillingPortalPurchase.php | 34 ++++++++++++------- .../Subscription/SubscriptionService.php | 27 ++++++++++++--- lang/en/texts.php | 2 +- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index 8b7f79a5628f..289dc7bdd39e 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -11,22 +11,23 @@ namespace App\Http\Livewire; -use App\DataMapper\ClientSettings; +use App\Utils\Ninja; +use App\Models\Client; +use App\Models\Invoice; +use Livewire\Component; +use App\Libraries\MultiDB; +use Illuminate\Support\Str; +use App\Models\Subscription; +use App\Models\ClientContact; use App\Factory\ClientFactory; use App\Jobs\Mail\NinjaMailerJob; +use App\DataMapper\ClientSettings; use App\Jobs\Mail\NinjaMailerObject; -use App\Libraries\MultiDB; -use App\Mail\ContactPasswordlessLogin; -use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Invoice; -use App\Models\Subscription; -use App\Repositories\ClientContactRepository; -use App\Repositories\ClientRepository; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Str; -use Livewire\Component; +use App\Mail\ContactPasswordlessLogin; +use App\Repositories\ClientRepository; +use App\Repositories\ClientContactRepository; class BillingPortalPurchase extends Component { @@ -168,7 +169,7 @@ class BillingPortalPurchase extends Component /** * Instance of company. * - * @var Company + * @var \App\Models\Company */ public $company; @@ -396,12 +397,19 @@ class BillingPortalPurchase extends Component ->adjustInventory() ->save(); + $context = 'purchase'; + + // if(Ninja::isHosted() && $this->subscription->service()->recurring_products()->first()->product_key == 'whitelabel') { + if($this->subscription->service()->recurring_products()->first()?->product_key == 'whitelabel') { + $context = 'whitelabel'; + } + Cache::put($this->hash, [ 'subscription_id' => $this->subscription->hashed_id, 'email' => $this->email ?? $this->contact->email, 'client_id' => $this->contact->client->hashed_id, 'invoice_id' => $this->invoice->hashed_id, - 'context' => 'purchase', + 'context' => $context, 'campaign' => $this->campaign, ], now()->addMinutes(60)); diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index c0a6292b9bd3..32c5514f1f10 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -169,9 +169,24 @@ class SubscriptionService //send license to the user. $invoice = $payment_hash->fee_invoice; $license_key = Str::uuid()->toString(); - $invoice->public_notes = $license_key; - $invoice->save(); + $invoice->footer = ctrans('texts.white_label_body',['license_key' => $license_key]); + + $recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id); + + $recurring_invoice_repo = new RecurringInvoiceRepository(); + $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); + $recurring_invoice->auto_bill = $this->subscription->auto_bill; + + /* Start the recurring service */ + $recurring_invoice->service() + ->start() + ->save(); + + //update the invoice and attach to the recurring invoice!!!!! + $invoice->recurring_id = $recurring_invoice->id; + $invoice->is_proforma = false; $invoice->service()->touchPdf(); + $invoice->save(); $contact = $invoice->client->contacts()->whereNotNull('email')->first(); @@ -183,16 +198,20 @@ class SubscriptionService $license->is_claimed = 1; $license->transaction_reference = $payment_hash?->payment?->transaction_reference ?: ' '; $license->product_id = self::WHITE_LABEL; + $license->recurring_invoice_id = $recurring_invoice->id; $license->save(); + $invitation = $invoice->invitations()->first(); + $email_object = new EmailObject; - $email_object->to = $contact->email; + $email_object->to = [$contact->email]; $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; $email_object->client_contact_id = $contact->id; - $email_object->invitation_key = $invoice->invitations()->first()->invitation_key; + $email_object->invitation_key = $invitation->invitation_key; + $email_object->invitation_id = $invitation->id; $email_object->entity_id = $invoice->id; $email_object->entity_class = Invoice::class; $email_object->user_id = $invoice->user_id; diff --git a/lang/en/texts.php b/lang/en/texts.php index f9e2a1d082a8..2040cdcbc138 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5014,7 +5014,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. \n\n Your license key is: \n\n :license_key', ); From d45a8df218e7111f4cfa480fd6d696798438f62d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 09:24:22 +1100 Subject: [PATCH 02/16] Whitelabel --- lang/en/texts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index 2040cdcbc138..33e29e69b563 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5014,7 +5014,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. \n\n Your license key is: \n\n :license_key', + 'white_label_body' => 'Thank you for purchasing a white label license.

Your license key is:

:license_key', ); From f13ba7d9618d1b68c3f5bf8674335f00685681a8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 13:01:32 +1100 Subject: [PATCH 03/16] Plan Tests --- app/Http/Controllers/LicenseController.php | 60 ++++++++++++++++++- app/Http/Livewire/BillingPortalPurchase.php | 5 +- .../Subscription/SubscriptionService.php | 4 +- tests/Feature/Ninja/PlanTest.php | 37 +++++++++--- 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php index 88e04574d928..72543ef1f1c7 100644 --- a/app/Http/Controllers/LicenseController.php +++ b/app/Http/Controllers/LicenseController.php @@ -11,11 +11,13 @@ namespace App\Http\Controllers; +use stdClass; +use Carbon\Carbon; use App\Models\Account; use App\Utils\CurlUtils; -use Carbon\Carbon; use Illuminate\Http\Response; -use stdClass; +use Illuminate\Support\Facades\Http; +use Illuminate\Http\Request; class LicenseController extends BaseController { @@ -78,7 +80,7 @@ class LicenseController extends BaseController * ), * ) */ - public function index() + public function indexx() { $this->checkLicense(); @@ -147,6 +149,57 @@ class LicenseController extends BaseController return response()->json($error, 400); } + public function index(Request $request) + { + $this->checkLicense(); + + /* Catch claim license requests */ + if (config('ninja.environment') == 'selfhost' && request()->has('license_key')) { + + $response = Http::get( config('ninja.license_url')."/claim_license", [ + 'license_key' => $request->input('license_key'), + 'product_id' => 3, + ]); + + if($response->successful()) { + + $payload = $response->json(); + + $account = auth()->user()->account; + + $account->plan_term = Account::PLAN_TERM_YEARLY; + $account->plan_expires = Carbon::parse($payload?->expires)->format('Y-m-d'); + $account->plan = Account::PLAN_WHITE_LABEL; + $account->save(); + + $error = [ + 'message' => trans('texts.bought_white_label'), + 'errors' => new \stdClass, + ]; + + return response()->json($error, 200); + }else { + + $error = [ + 'message' => trans('texts.white_label_license_error'), + 'errors' => new stdClass, + ]; + + return response()->json($error, 400); + } + + } + + $error = [ + 'message' => ctrans('texts.invoice_license_or_environment', ['environment' => config('ninja.environment')]), + 'errors' => new stdClass, + ]; + + return response()->json($error, 400); + + } + + private function checkLicense() { $account = auth()->user()->account; @@ -156,5 +209,6 @@ class LicenseController extends BaseController $account->plan_expires = null; $account->save(); } + } } diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index 289dc7bdd39e..b0df3019ca18 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -28,6 +28,7 @@ use Illuminate\Support\Facades\Cache; use App\Mail\ContactPasswordlessLogin; use App\Repositories\ClientRepository; use App\Repositories\ClientContactRepository; +use App\Services\Subscription\SubscriptionService; class BillingPortalPurchase extends Component { @@ -399,8 +400,8 @@ class BillingPortalPurchase extends Component $context = 'purchase'; - // if(Ninja::isHosted() && $this->subscription->service()->recurring_products()->first()->product_key == 'whitelabel') { - if($this->subscription->service()->recurring_products()->first()?->product_key == 'whitelabel') { + // if(Ninja::isHosted() && $this->subscription->service()->recurring_products()->first()?->id == SubscriptionService::WHITE_LABEL) { + if(Ninja::isHosted() && $this->subscription->service()->recurring_products()->first()?->product_key == 'whitelabel') { $context = 'whitelabel'; } diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 32c5514f1f10..2bc4060aadb0 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -58,7 +58,7 @@ class SubscriptionService /** @var subscription */ private $subscription; - private const WHITE_LABEL = 4316; + public const WHITE_LABEL = 4316; private float $credit_payments = 0; @@ -203,7 +203,7 @@ class SubscriptionService $license->save(); $invitation = $invoice->invitations()->first(); - + $email_object = new EmailObject; $email_object->to = [$contact->email]; $email_object->subject = ctrans('texts.white_label_link') . " " .ctrans('texts.payment_subject'); diff --git a/tests/Feature/Ninja/PlanTest.php b/tests/Feature/Ninja/PlanTest.php index 1253166cc250..20f15a333cdb 100644 --- a/tests/Feature/Ninja/PlanTest.php +++ b/tests/Feature/Ninja/PlanTest.php @@ -11,16 +11,18 @@ namespace Tests\Feature\Ninja; -use App\Factory\SubscriptionFactory; -use App\Models\Account; -use App\Models\RecurringInvoice; -use App\Utils\Traits\MakesHash; use Carbon\Carbon; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; use Tests\TestCase; +use App\Models\Account; +use App\Models\License; +use Tests\MockAccountData; +use App\Utils\Traits\MakesHash; +use App\Models\RecurringInvoice; +use App\Factory\SubscriptionFactory; +use Illuminate\Support\Facades\Http; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Session; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -87,4 +89,23 @@ class PlanTest extends TestCase $this->assertEquals($date->addMonthNoOverflow()->startOfDay(), $next_date->startOfDay()); } + + public function testLicense() + { + $license = new License; + $license->license_key = "1234"; + $license->product_id = "3"; + $license->email = 'test@gmail.com'; + $license->is_claimed = 1; + $license->save(); + + $license->fresh(); + + $response = $this->get("/claim_license?license_key=1234&product_id=3") + ->assertStatus(200); + + $response = $this->get("/claim_license?license_key=12345&product_id=3") + ->assertStatus(400); + + } } From 22eb28d138d12587c16c3734159ceeae9cca96e9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 13:19:43 +1100 Subject: [PATCH 04/16] White label license --- app/Http/Controllers/LicenseController.php | 7 ++++--- lang/en/texts.php | 2 +- routes/api.php | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php index 72543ef1f1c7..7a70aa2cb92e 100644 --- a/app/Http/Controllers/LicenseController.php +++ b/app/Http/Controllers/LicenseController.php @@ -149,14 +149,15 @@ class LicenseController extends BaseController return response()->json($error, 400); } - public function index(Request $request) + public function v5ClaimLicense(Request $request) { $this->checkLicense(); /* Catch claim license requests */ if (config('ninja.environment') == 'selfhost' && request()->has('license_key')) { - $response = Http::get( config('ninja.license_url')."/claim_license", [ + $response = Http::get( "http://ninja.test:8000/claim_license", [ + // $response = Http::get( "https://invoicing.co/claim_license", [ 'license_key' => $request->input('license_key'), 'product_id' => 3, ]); @@ -168,7 +169,7 @@ class LicenseController extends BaseController $account = auth()->user()->account; $account->plan_term = Account::PLAN_TERM_YEARLY; - $account->plan_expires = Carbon::parse($payload?->expires)->format('Y-m-d'); + $account->plan_expires = Carbon::parse($payload['expires'])->addYear()->format('Y-m-d'); $account->plan = Account::PLAN_WHITE_LABEL; $account->save(); diff --git a/lang/en/texts.php b/lang/en/texts.php index 33e29e69b563..20c7c4ee9335 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2251,7 +2251,7 @@ $LANG = array( 'invalid_file' => 'Invalid file type', 'add_documents_to_invoice' => 'Add Documents to Invoice', 'mark_expense_paid' => 'Mark paid', - 'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.', + 'white_label_license_error' => 'Failed to validate the license, either expired or excessive activations. Email contact@invoiceninja.com for more information.', 'plan_price' => 'Plan Price', 'wrong_confirmation' => 'Incorrect confirmation code', 'oauth_taken' => 'The account is already registered', diff --git a/routes/api.php b/routes/api.php index 4d8342adfbf8..daf2db3a84a1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -136,6 +136,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('charts/chart_summary', [ChartController::class, 'chart_summary'])->name('chart.chart_summary'); Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index'); + Route::post('v5_claim_license', [LicenseController::class, 'v5ClaimLicense'])->name('license.v5_claim_license'); Route::resource('clients', ClientController::class); // name = (clients. index / create / show / update / destroy / edit Route::put('clients/{client}/upload', [ClientController::class, 'upload'])->name('clients.upload'); From ad0f06f73a9bd2b49f51bcec79e518724bbeb4c8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 13:21:07 +1100 Subject: [PATCH 05/16] License tests --- app/Http/Controllers/LicenseController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php index 7a70aa2cb92e..851f5281b418 100644 --- a/app/Http/Controllers/LicenseController.php +++ b/app/Http/Controllers/LicenseController.php @@ -156,8 +156,8 @@ class LicenseController extends BaseController /* Catch claim license requests */ if (config('ninja.environment') == 'selfhost' && request()->has('license_key')) { - $response = Http::get( "http://ninja.test:8000/claim_license", [ - // $response = Http::get( "https://invoicing.co/claim_license", [ + // $response = Http::get( "http://ninja.test:8000/claim_license", [ + $response = Http::get( "https://invoicing.co/claim_license", [ 'license_key' => $request->input('license_key'), 'product_id' => 3, ]); From 8ee294703a496cb2ce734ea3cdaf98fea0146a8a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 13:24:14 +1100 Subject: [PATCH 06/16] License tests --- tests/Feature/Ninja/PlanTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Feature/Ninja/PlanTest.php b/tests/Feature/Ninja/PlanTest.php index 20f15a333cdb..12e27a6a8e01 100644 --- a/tests/Feature/Ninja/PlanTest.php +++ b/tests/Feature/Ninja/PlanTest.php @@ -92,6 +92,8 @@ class PlanTest extends TestCase public function testLicense() { + $this->markTestSkipped(); + $license = new License; $license->license_key = "1234"; $license->product_id = "3"; From 19104ec0d557aada2b7fb5ced0efe9bbb700aaf9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 14:01:07 +1100 Subject: [PATCH 07/16] Add functionality to keep payment_balance up to date. : --- .../Payment/PaymentBalanceActivity.php | 49 ++ .../Payment/PaymentEmailFailureActivity.php | 2 - .../Payment/PaymentEmailedActivity.php | 1 - app/Providers/EventServiceProvider.php | 471 +++++++++--------- app/Services/Client/ClientService.php | 18 + 5 files changed, 305 insertions(+), 236 deletions(-) create mode 100644 app/Listeners/Payment/PaymentBalanceActivity.php diff --git a/app/Listeners/Payment/PaymentBalanceActivity.php b/app/Listeners/Payment/PaymentBalanceActivity.php new file mode 100644 index 000000000000..23d021e1c12c --- /dev/null +++ b/app/Listeners/Payment/PaymentBalanceActivity.php @@ -0,0 +1,49 @@ +company->db); + + $event->payment->client->service()->updatePaymentBalance(); + + } + + public function middleware($event): array + { + return [(new WithoutOverlapping($event->payment->client->id))]; + } +} diff --git a/app/Listeners/Payment/PaymentEmailFailureActivity.php b/app/Listeners/Payment/PaymentEmailFailureActivity.php index c478d013ccdb..bcf9ca95983c 100644 --- a/app/Listeners/Payment/PaymentEmailFailureActivity.php +++ b/app/Listeners/Payment/PaymentEmailFailureActivity.php @@ -19,8 +19,6 @@ class PaymentEmailFailureActivity implements ShouldQueue { use UserNotifies; - public $delay = 5; - /** * Create the event listener. * diff --git a/app/Listeners/Payment/PaymentEmailedActivity.php b/app/Listeners/Payment/PaymentEmailedActivity.php index 52ee83b3feaa..af6701c4d486 100644 --- a/app/Listeners/Payment/PaymentEmailedActivity.php +++ b/app/Listeners/Payment/PaymentEmailedActivity.php @@ -17,7 +17,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; class PaymentEmailedActivity implements ShouldQueue { - public $delay = 5; use UserNotifies; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a9434ea7f937..a672bafc963b 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -11,259 +11,258 @@ namespace App\Providers; -use App\Events\Account\AccountCreated; -use App\Events\Client\ClientWasArchived; -use App\Events\Client\ClientWasCreated; -use App\Events\Client\ClientWasDeleted; -use App\Events\Client\ClientWasRestored; -use App\Events\Client\ClientWasUpdated; -use App\Events\Client\DesignWasDeleted; -use App\Events\Client\DesignWasRestored; -use App\Events\Client\DesignWasUpdated; -use App\Events\Company\CompanyDocumentsDeleted; -use App\Events\Contact\ContactLoggedIn; -use App\Events\Credit\CreditWasArchived; -use App\Events\Credit\CreditWasCreated; -use App\Events\Credit\CreditWasDeleted; -use App\Events\Credit\CreditWasEmailed; -use App\Events\Credit\CreditWasEmailedAndFailed; -use App\Events\Credit\CreditWasMarkedSent; -use App\Events\Credit\CreditWasRestored; -use App\Events\Credit\CreditWasUpdated; -use App\Events\Credit\CreditWasViewed; -use App\Events\Design\DesignWasArchived; -use App\Events\Expense\ExpenseWasArchived; -use App\Events\Expense\ExpenseWasCreated; -use App\Events\Expense\ExpenseWasDeleted; -use App\Events\Expense\ExpenseWasRestored; -use App\Events\Expense\ExpenseWasUpdated; -use App\Events\Invoice\InvoiceReminderWasEmailed; -use App\Events\Invoice\InvoiceWasArchived; -use App\Events\Invoice\InvoiceWasCancelled; -use App\Events\Invoice\InvoiceWasCreated; -use App\Events\Invoice\InvoiceWasDeleted; -use App\Events\Invoice\InvoiceWasEmailed; -use App\Events\Invoice\InvoiceWasEmailedAndFailed; -use App\Events\Invoice\InvoiceWasMarkedSent; -use App\Events\Invoice\InvoiceWasPaid; -use App\Events\Invoice\InvoiceWasRestored; -use App\Events\Invoice\InvoiceWasReversed; -use App\Events\Invoice\InvoiceWasUpdated; -use App\Events\Invoice\InvoiceWasViewed; -use App\Events\Misc\InvitationWasViewed; -use App\Events\Payment\PaymentWasArchived; -use App\Events\Payment\PaymentWasCreated; -use App\Events\Payment\PaymentWasDeleted; -use App\Events\Payment\PaymentWasEmailed; -use App\Events\Payment\PaymentWasEmailedAndFailed; -use App\Events\Payment\PaymentWasRefunded; -use App\Events\Payment\PaymentWasRestored; -use App\Events\Payment\PaymentWasUpdated; -use App\Events\Payment\PaymentWasVoided; -use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; -use App\Events\PurchaseOrder\PurchaseOrderWasArchived; -use App\Events\PurchaseOrder\PurchaseOrderWasCreated; -use App\Events\PurchaseOrder\PurchaseOrderWasDeleted; -use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; -use App\Events\PurchaseOrder\PurchaseOrderWasRestored; -use App\Events\PurchaseOrder\PurchaseOrderWasUpdated; -use App\Events\PurchaseOrder\PurchaseOrderWasViewed; -use App\Events\Quote\QuoteWasApproved; -use App\Events\Quote\QuoteWasArchived; -use App\Events\Quote\QuoteWasCreated; -use App\Events\Quote\QuoteWasDeleted; -use App\Events\Quote\QuoteWasEmailed; -use App\Events\Quote\QuoteWasRestored; -use App\Events\Quote\QuoteWasUpdated; -use App\Events\Quote\QuoteWasViewed; -use App\Events\RecurringExpense\RecurringExpenseWasArchived; -use App\Events\RecurringExpense\RecurringExpenseWasCreated; -use App\Events\RecurringExpense\RecurringExpenseWasDeleted; -use App\Events\RecurringExpense\RecurringExpenseWasRestored; -use App\Events\RecurringExpense\RecurringExpenseWasUpdated; -use App\Events\RecurringInvoice\RecurringInvoiceWasArchived; -use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; -use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted; -use App\Events\RecurringInvoice\RecurringInvoiceWasRestored; -use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; -use App\Events\RecurringQuote\RecurringQuoteWasArchived; -use App\Events\RecurringQuote\RecurringQuoteWasCreated; -use App\Events\RecurringQuote\RecurringQuoteWasDeleted; -use App\Events\RecurringQuote\RecurringQuoteWasRestored; -use App\Events\RecurringQuote\RecurringQuoteWasUpdated; -use App\Events\Subscription\SubscriptionWasArchived; -use App\Events\Subscription\SubscriptionWasCreated; -use App\Events\Subscription\SubscriptionWasDeleted; -use App\Events\Subscription\SubscriptionWasRestored; -use App\Events\Subscription\SubscriptionWasUpdated; -use App\Events\Task\TaskWasArchived; -use App\Events\Task\TaskWasCreated; -use App\Events\Task\TaskWasDeleted; -use App\Events\Task\TaskWasRestored; -use App\Events\Task\TaskWasUpdated; -use App\Events\User\UserLoggedIn; -use App\Events\User\UserWasArchived; -use App\Events\User\UserWasCreated; -use App\Events\User\UserWasDeleted; -use App\Events\User\UserWasRestored; -use App\Events\User\UserWasUpdated; -use App\Events\Vendor\VendorWasArchived; -use App\Events\Vendor\VendorWasCreated; -use App\Events\Vendor\VendorWasDeleted; -use App\Events\Vendor\VendorWasRestored; -use App\Events\Vendor\VendorWasUpdated; -use App\Listeners\Activity\ArchivedClientActivity; -use App\Listeners\Activity\ClientUpdatedActivity; -use App\Listeners\Activity\CreatedClientActivity; -use App\Listeners\Activity\CreatedCreditActivity; -use App\Listeners\Activity\CreatedExpenseActivity; -use App\Listeners\Activity\CreatedQuoteActivity; -use App\Listeners\Activity\CreatedSubscriptionActivity; -use App\Listeners\Activity\CreatedTaskActivity; -use App\Listeners\Activity\CreatedVendorActivity; -use App\Listeners\Activity\CreditArchivedActivity; -use App\Listeners\Activity\DeleteClientActivity; -use App\Listeners\Activity\DeleteCreditActivity; -use App\Listeners\Activity\ExpenseArchivedActivity; -use App\Listeners\Activity\ExpenseDeletedActivity; -use App\Listeners\Activity\ExpenseRestoredActivity; -use App\Listeners\Activity\ExpenseUpdatedActivity; -use App\Listeners\Activity\PaymentArchivedActivity; -use App\Listeners\Activity\PaymentCreatedActivity; -use App\Listeners\Activity\PaymentDeletedActivity; -use App\Listeners\Activity\PaymentRefundedActivity; -use App\Listeners\Activity\PaymentUpdatedActivity; -use App\Listeners\Activity\PaymentVoidedActivity; -use App\Listeners\Activity\QuoteUpdatedActivity; -use App\Listeners\Activity\RestoreClientActivity; -use App\Listeners\Activity\SubscriptionArchivedActivity; -use App\Listeners\Activity\SubscriptionDeletedActivity; -use App\Listeners\Activity\SubscriptionRestoredActivity; -use App\Listeners\Activity\SubscriptionUpdatedActivity; -use App\Listeners\Activity\TaskArchivedActivity; -use App\Listeners\Activity\TaskDeletedActivity; -use App\Listeners\Activity\TaskRestoredActivity; -use App\Listeners\Activity\TaskUpdatedActivity; -use App\Listeners\Activity\UpdatedCreditActivity; -use App\Listeners\Activity\VendorArchivedActivity; -use App\Listeners\Activity\VendorDeletedActivity; -use App\Listeners\Activity\VendorRestoredActivity; -use App\Listeners\Activity\VendorUpdatedActivity; -use App\Listeners\Contact\UpdateContactLastLogin; -use App\Listeners\Credit\CreditCreatedNotification; -use App\Listeners\Credit\CreditEmailedNotification; -use App\Listeners\Credit\CreditRestoredActivity; -use App\Listeners\Credit\CreditViewedActivity; -use App\Listeners\Document\DeleteCompanyDocuments; -use App\Listeners\Invoice\CreateInvoiceActivity; -use App\Listeners\Invoice\CreateInvoicePdf; -use App\Listeners\Invoice\InvoiceArchivedActivity; -use App\Listeners\Invoice\InvoiceCancelledActivity; -use App\Listeners\Invoice\InvoiceCreatedNotification; -use App\Listeners\Invoice\InvoiceDeletedActivity; -use App\Listeners\Invoice\InvoiceEmailActivity; -use App\Listeners\Invoice\InvoiceEmailedNotification; -use App\Listeners\Invoice\InvoiceEmailFailedActivity; -use App\Listeners\Invoice\InvoiceFailedEmailNotification; -use App\Listeners\Invoice\InvoicePaidActivity; -use App\Listeners\Invoice\InvoiceReminderEmailActivity; -use App\Listeners\Invoice\InvoiceRestoredActivity; -use App\Listeners\Invoice\InvoiceReversedActivity; -use App\Listeners\Invoice\InvoiceViewedActivity; -use App\Listeners\Invoice\UpdateInvoiceActivity; -use App\Listeners\Mail\MailSentListener; -use App\Listeners\Misc\InvitationViewedListener; -use App\Listeners\Payment\PaymentEmailedActivity; -use App\Listeners\Payment\PaymentEmailFailureActivity; -use App\Listeners\Payment\PaymentNotification; -use App\Listeners\Payment\PaymentRestoredActivity; -use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener; -use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener; -use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification; -use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity; -use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity; -use App\Listeners\Quote\QuoteApprovedActivity; -use App\Listeners\Quote\QuoteApprovedNotification; -use App\Listeners\Quote\QuoteApprovedWebhook; -use App\Listeners\Quote\QuoteArchivedActivity; -use App\Listeners\Quote\QuoteCreatedNotification; -use App\Listeners\Quote\QuoteDeletedActivity; -use App\Listeners\Quote\QuoteEmailActivity; -use App\Listeners\Quote\QuoteEmailedNotification; -use App\Listeners\Quote\QuoteRestoredActivity; -use App\Listeners\Quote\QuoteViewedActivity; -use App\Listeners\Quote\ReachWorkflowSettings; -use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity; -use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity; -use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity; -use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity; -use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity; -use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity; -use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity; -use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity; -use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity; -use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity; -use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity; -use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity; -use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity; -use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity; -use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity; -use App\Listeners\SendVerificationNotification; -use App\Listeners\User\ArchivedUserActivity; -use App\Listeners\User\CreatedUserActivity; -use App\Listeners\User\DeletedUserActivity; -use App\Listeners\User\RestoredUserActivity; -use App\Listeners\User\UpdatedUserActivity; -use App\Listeners\User\UpdateUserLastLogin; -use App\Models\Account; +use App\Models\Task; +use App\Models\User; +use App\Models\Quote; use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyGateway; -use App\Models\CompanyToken; use App\Models\Credit; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\Company; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; use App\Models\Product; use App\Models\Project; use App\Models\Proposal; -use App\Models\PurchaseOrder; -use App\Models\Quote; +use App\Models\CompanyToken; use App\Models\Subscription; -use App\Models\Task; -use App\Models\User; -use App\Models\Vendor; +use App\Models\ClientContact; +use App\Models\PurchaseOrder; use App\Models\VendorContact; -use App\Observers\AccountObserver; -use App\Observers\ClientContactObserver; +use App\Models\CompanyGateway; +use App\Observers\TaskObserver; +use App\Observers\UserObserver; +use App\Observers\QuoteObserver; +use App\Events\User\UserLoggedIn; use App\Observers\ClientObserver; -use App\Observers\CompanyGatewayObserver; -use App\Observers\CompanyObserver; -use App\Observers\CompanyTokenObserver; use App\Observers\CreditObserver; +use App\Observers\VendorObserver; +use App\Observers\AccountObserver; +use App\Observers\CompanyObserver; use App\Observers\ExpenseObserver; use App\Observers\InvoiceObserver; use App\Observers\PaymentObserver; use App\Observers\ProductObserver; use App\Observers\ProjectObserver; +use App\Events\Task\TaskWasCreated; +use App\Events\Task\TaskWasDeleted; +use App\Events\Task\TaskWasUpdated; +use App\Events\User\UserWasCreated; +use App\Events\User\UserWasDeleted; +use App\Events\User\UserWasUpdated; use App\Observers\ProposalObserver; -use App\Observers\PurchaseOrderObserver; -use App\Observers\QuoteObserver; +use App\Events\Quote\QuoteWasViewed; +use App\Events\Task\TaskWasArchived; +use App\Events\Task\TaskWasRestored; +use App\Events\User\UserWasArchived; +use App\Events\User\UserWasRestored; +use App\Events\Quote\QuoteWasCreated; +use App\Events\Quote\QuoteWasDeleted; +use App\Events\Quote\QuoteWasEmailed; +use App\Events\Quote\QuoteWasUpdated; +use App\Events\Account\AccountCreated; +use App\Events\Credit\CreditWasViewed; +use App\Events\Invoice\InvoiceWasPaid; +use App\Events\Quote\QuoteWasApproved; +use App\Events\Quote\QuoteWasArchived; +use App\Events\Quote\QuoteWasRestored; +use App\Events\Client\ClientWasCreated; +use App\Events\Client\ClientWasDeleted; +use App\Events\Client\ClientWasUpdated; +use App\Events\Client\DesignWasDeleted; +use App\Events\Client\DesignWasUpdated; +use App\Events\Contact\ContactLoggedIn; +use App\Events\Credit\CreditWasCreated; +use App\Events\Credit\CreditWasDeleted; +use App\Events\Credit\CreditWasEmailed; +use App\Events\Credit\CreditWasUpdated; +use App\Events\Vendor\VendorWasCreated; +use App\Events\Vendor\VendorWasDeleted; +use App\Events\Vendor\VendorWasUpdated; +use App\Observers\CompanyTokenObserver; use App\Observers\SubscriptionObserver; -use App\Observers\TaskObserver; -use App\Observers\UserObserver; -use App\Observers\VendorContactObserver; -use App\Observers\VendorObserver; -use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; -use Illuminate\Mail\Events\MessageSending; use Illuminate\Mail\Events\MessageSent; +use App\Events\Client\ClientWasArchived; +use App\Events\Client\ClientWasRestored; +use App\Events\Client\DesignWasRestored; +use App\Events\Credit\CreditWasArchived; +use App\Events\Credit\CreditWasRestored; +use App\Events\Design\DesignWasArchived; +use App\Events\Invoice\InvoiceWasViewed; +use App\Events\Misc\InvitationWasViewed; +use App\Events\Payment\PaymentWasVoided; +use App\Events\Vendor\VendorWasArchived; +use App\Events\Vendor\VendorWasRestored; +use App\Listeners\Mail\MailSentListener; +use App\Observers\ClientContactObserver; +use App\Observers\PurchaseOrderObserver; +use App\Observers\VendorContactObserver; +use App\Events\Expense\ExpenseWasCreated; +use App\Events\Expense\ExpenseWasDeleted; +use App\Events\Expense\ExpenseWasUpdated; +use App\Events\Invoice\InvoiceWasCreated; +use App\Events\Invoice\InvoiceWasDeleted; +use App\Events\Invoice\InvoiceWasEmailed; +use App\Events\Invoice\InvoiceWasUpdated; +use App\Events\Payment\PaymentWasCreated; +use App\Events\Payment\PaymentWasDeleted; +use App\Events\Payment\PaymentWasEmailed; +use App\Events\Payment\PaymentWasUpdated; +use App\Observers\CompanyGatewayObserver; +use App\Events\Credit\CreditWasMarkedSent; +use App\Events\Expense\ExpenseWasArchived; +use App\Events\Expense\ExpenseWasRestored; +use App\Events\Invoice\InvoiceWasArchived; +use App\Events\Invoice\InvoiceWasRestored; +use App\Events\Invoice\InvoiceWasReversed; +use App\Events\Payment\PaymentWasArchived; +use App\Events\Payment\PaymentWasRefunded; +use App\Events\Payment\PaymentWasRestored; +use Illuminate\Mail\Events\MessageSending; +use App\Events\Invoice\InvoiceWasCancelled; +use App\Listeners\Invoice\CreateInvoicePdf; +use App\Listeners\Quote\QuoteEmailActivity; +use App\Listeners\User\CreatedUserActivity; +use App\Listeners\User\DeletedUserActivity; +use App\Listeners\User\UpdatedUserActivity; +use App\Listeners\User\UpdateUserLastLogin; +use App\Events\Invoice\InvoiceWasMarkedSent; +use App\Listeners\Quote\QuoteViewedActivity; +use App\Listeners\User\ArchivedUserActivity; +use App\Listeners\User\RestoredUserActivity; +use App\Listeners\Quote\QuoteApprovedWebhook; +use App\Listeners\Quote\QuoteDeletedActivity; +use App\Listeners\Credit\CreditViewedActivity; +use App\Listeners\Invoice\InvoicePaidActivity; +use App\Listeners\Payment\PaymentNotification; +use App\Listeners\Quote\QuoteApprovedActivity; +use App\Listeners\Quote\QuoteArchivedActivity; +use App\Listeners\Quote\QuoteRestoredActivity; +use App\Listeners\Quote\ReachWorkflowSettings; +use App\Events\Company\CompanyDocumentsDeleted; +use App\Listeners\Activity\CreatedTaskActivity; +use App\Listeners\Activity\TaskDeletedActivity; +use App\Listeners\Activity\TaskUpdatedActivity; +use App\Listeners\Invoice\InvoiceEmailActivity; +use App\Listeners\SendVerificationNotification; +use App\Events\Credit\CreditWasEmailedAndFailed; +use App\Listeners\Activity\CreatedQuoteActivity; +use App\Listeners\Activity\DeleteClientActivity; +use App\Listeners\Activity\DeleteCreditActivity; +use App\Listeners\Activity\QuoteUpdatedActivity; +use App\Listeners\Activity\TaskArchivedActivity; +use App\Listeners\Activity\TaskRestoredActivity; +use App\Listeners\Credit\CreditRestoredActivity; +use App\Listeners\Invoice\CreateInvoiceActivity; +use App\Listeners\Invoice\InvoiceViewedActivity; +use App\Listeners\Invoice\UpdateInvoiceActivity; +use App\Listeners\Misc\InvitationViewedListener; +use App\Events\Invoice\InvoiceReminderWasEmailed; +use App\Listeners\Activity\ClientUpdatedActivity; +use App\Listeners\Activity\CreatedClientActivity; +use App\Listeners\Activity\CreatedCreditActivity; +use App\Listeners\Activity\CreatedVendorActivity; +use App\Listeners\Activity\PaymentVoidedActivity; +use App\Listeners\Activity\RestoreClientActivity; +use App\Listeners\Activity\UpdatedCreditActivity; +use App\Listeners\Activity\VendorDeletedActivity; +use App\Listeners\Activity\VendorUpdatedActivity; +use App\Listeners\Contact\UpdateContactLastLogin; +use App\Listeners\Invoice\InvoiceDeletedActivity; +use App\Listeners\Payment\PaymentBalanceActivity; +use App\Listeners\Quote\QuoteCreatedNotification; +use App\Listeners\Quote\QuoteEmailedNotification; +use App\Events\Invoice\InvoiceWasEmailedAndFailed; +use App\Events\Payment\PaymentWasEmailedAndFailed; +use App\Listeners\Activity\ArchivedClientActivity; +use App\Listeners\Activity\CreatedExpenseActivity; +use App\Listeners\Activity\CreditArchivedActivity; +use App\Listeners\Activity\ExpenseDeletedActivity; +use App\Listeners\Activity\ExpenseUpdatedActivity; +use App\Listeners\Activity\PaymentCreatedActivity; +use App\Listeners\Activity\PaymentDeletedActivity; +use App\Listeners\Activity\PaymentUpdatedActivity; +use App\Listeners\Activity\VendorArchivedActivity; +use App\Listeners\Activity\VendorRestoredActivity; +use App\Listeners\Document\DeleteCompanyDocuments; +use App\Listeners\Invoice\InvoiceArchivedActivity; +use App\Listeners\Invoice\InvoiceRestoredActivity; +use App\Listeners\Invoice\InvoiceReversedActivity; +use App\Listeners\Payment\PaymentRestoredActivity; +use App\Listeners\Quote\QuoteApprovedNotification; +use App\Events\Subscription\SubscriptionWasCreated; +use App\Events\Subscription\SubscriptionWasDeleted; +use App\Events\Subscription\SubscriptionWasUpdated; +use App\Listeners\Activity\ExpenseArchivedActivity; +use App\Listeners\Activity\ExpenseRestoredActivity; +use App\Listeners\Activity\PaymentArchivedActivity; +use App\Listeners\Activity\PaymentRefundedActivity; +use App\Listeners\Credit\CreditCreatedNotification; +use App\Listeners\Credit\CreditEmailedNotification; +use App\Listeners\Invoice\InvoiceCancelledActivity; +use App\Events\PurchaseOrder\PurchaseOrderWasViewed; +use App\Events\Subscription\SubscriptionWasArchived; +use App\Events\Subscription\SubscriptionWasRestored; +use App\Events\PurchaseOrder\PurchaseOrderWasCreated; +use App\Events\PurchaseOrder\PurchaseOrderWasDeleted; +use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; +use App\Events\PurchaseOrder\PurchaseOrderWasUpdated; +use App\Listeners\Invoice\InvoiceCreatedNotification; +use App\Listeners\Invoice\InvoiceEmailedNotification; +use App\Listeners\Invoice\InvoiceEmailFailedActivity; +use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; +use App\Events\PurchaseOrder\PurchaseOrderWasArchived; +use App\Events\PurchaseOrder\PurchaseOrderWasRestored; +use App\Events\RecurringQuote\RecurringQuoteWasCreated; +use App\Events\RecurringQuote\RecurringQuoteWasDeleted; +use App\Events\RecurringQuote\RecurringQuoteWasUpdated; +use App\Listeners\Activity\CreatedSubscriptionActivity; +use App\Listeners\Activity\SubscriptionDeletedActivity; +use App\Listeners\Activity\SubscriptionUpdatedActivity; +use App\Listeners\Invoice\InvoiceReminderEmailActivity; +use App\Events\RecurringQuote\RecurringQuoteWasArchived; +use App\Events\RecurringQuote\RecurringQuoteWasRestored; +use App\Listeners\Activity\SubscriptionArchivedActivity; +use App\Listeners\Activity\SubscriptionRestoredActivity; +use App\Listeners\Invoice\InvoiceFailedEmailNotification; +use App\Events\RecurringExpense\RecurringExpenseWasCreated; +use App\Events\RecurringExpense\RecurringExpenseWasDeleted; +use App\Events\RecurringExpense\RecurringExpenseWasUpdated; +use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; +use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted; +use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; +use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; +use App\Events\RecurringExpense\RecurringExpenseWasArchived; +use App\Events\RecurringExpense\RecurringExpenseWasRestored; +use App\Events\RecurringInvoice\RecurringInvoiceWasArchived; +use App\Events\RecurringInvoice\RecurringInvoiceWasRestored; +use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity; +use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener; +use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener; +use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity; +use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity; +use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity; +use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity; +use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity; +use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification; +use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity; +use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity; +use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity; +use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity; +use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity; +use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity; +use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity; +use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity; +use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity; +use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity; +use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { @@ -305,24 +304,30 @@ class EventServiceProvider extends ServiceProvider PaymentWasCreated::class => [ PaymentCreatedActivity::class, PaymentNotification::class, + PaymentBalanceActivity::class, ], PaymentWasDeleted::class => [ PaymentDeletedActivity::class, + PaymentBalanceActivity::class, ], PaymentWasArchived::class => [ PaymentArchivedActivity::class, ], PaymentWasUpdated::class => [ PaymentUpdatedActivity::class, + PaymentBalanceActivity::class, ], PaymentWasRefunded::class => [ PaymentRefundedActivity::class, + PaymentBalanceActivity::class, ], PaymentWasVoided::class => [ PaymentVoidedActivity::class, + PaymentBalanceActivity::class, ], PaymentWasRestored::class => [ PaymentRestoredActivity::class, + PaymentBalanceActivity::class, ], // Clients ClientWasCreated::class => [ diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 5a88bf755bc0..dd5d944285e5 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -14,6 +14,7 @@ namespace App\Services\Client; use App\Utils\Number; use App\Models\Client; use App\Models\Credit; +use App\Models\Payment; use App\Services\Email\Email; use App\Utils\Traits\MakesDates; use Illuminate\Support\Facades\DB; @@ -75,6 +76,23 @@ class ClientService return $this; } + public function updatePaymentBalance() + { + $amount = Payment::where('client_id', $this->client->id) + ->where('is_deleted', 0) + ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) + ->sum(DB::Raw('amount - refunded - applied')); + + DB::connection(config('database.default'))->transaction(function () use ($amount) { + $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); + $this->client->payment_balance = $amount; + $this->client->saveQuietly(); + }, 2); + + return $this; + } + + public function adjustCreditBalance(float $amount) { $this->client->credit_balance += $amount; From 8e34d0ed0d146dc9d46f480946b0758c862ce6ea Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 16:37:17 +1100 Subject: [PATCH 08/16] Updates for RandomDataSeeder --- composer.lock | 76 ++++++------ database/seeders/RandomDataSeeder.php | 167 ++++++++++++++++++-------- 2 files changed, 152 insertions(+), 91 deletions(-) diff --git a/composer.lock b/composer.lock index d2bf544f4253..c450d418938f 100644 --- a/composer.lock +++ b/composer.lock @@ -380,16 +380,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.261.6", + "version": "3.261.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "63b03ec821473861af58439f1a35173e904d0f8c" + "reference": "9c38c82b7d3fb2b15957e71cae3957450e131ed8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/63b03ec821473861af58439f1a35173e904d0f8c", - "reference": "63b03ec821473861af58439f1a35173e904d0f8c", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9c38c82b7d3fb2b15957e71cae3957450e131ed8", + "reference": "9c38c82b7d3fb2b15957e71cae3957450e131ed8", "shasum": "" }, "require": { @@ -468,9 +468,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.261.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.261.8" }, - "time": "2023-03-07T19:21:12+00:00" + "time": "2023-03-09T19:23:27+00:00" }, { "name": "bacon/bacon-qr-code", @@ -2171,16 +2171,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.289.1", + "version": "v0.289.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "df7e6cbab08f60509b3f360d8286c194ad2930e2" + "reference": "937f83a927db2d09db7eebb69ce2ac4114559bd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/df7e6cbab08f60509b3f360d8286c194ad2930e2", - "reference": "df7e6cbab08f60509b3f360d8286c194ad2930e2", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/937f83a927db2d09db7eebb69ce2ac4114559bd7", + "reference": "937f83a927db2d09db7eebb69ce2ac4114559bd7", "shasum": "" }, "require": { @@ -2209,9 +2209,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.289.1" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.289.0" }, - "time": "2023-03-01T17:20:18+00:00" + "time": "2023-02-26T01:10:11+00:00" }, { "name": "google/auth", @@ -2605,16 +2605,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.3", + "version": "2.4.4", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "67c26b443f348a51926030c83481b85718457d3d" + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", - "reference": "67c26b443f348a51926030c83481b85718457d3d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", "shasum": "" }, "require": { @@ -2704,7 +2704,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.3" + "source": "https://github.com/guzzle/psr7/tree/2.4.4" }, "funding": [ { @@ -2720,7 +2720,7 @@ "type": "tidelift" } ], - "time": "2022-10-26T14:07:24+00:00" + "time": "2023-03-09T13:19:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -14427,16 +14427,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -14474,7 +14474,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -14482,7 +14482,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "netresearch/jsonmapper", @@ -15020,16 +15020,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.5", + "version": "1.10.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "1fb6f494d82455151ecf15c5c191923f5d84324e" + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1fb6f494d82455151ecf15c5c191923f5d84324e", - "reference": "1fb6f494d82455151ecf15c5c191923f5d84324e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", "shasum": "" }, "require": { @@ -15059,7 +15059,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.5" + "source": "https://github.com/phpstan/phpstan/tree/1.10.6" }, "funding": [ { @@ -15075,7 +15075,7 @@ "type": "tidelift" } ], - "time": "2023-03-07T16:48:45+00:00" + "time": "2023-03-09T16:55:12+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15397,16 +15397,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.4", + "version": "9.6.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", - "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", "shasum": "" }, "require": { @@ -15439,8 +15439,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -15479,7 +15479,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" }, "funding": [ { @@ -15495,7 +15495,7 @@ "type": "tidelift" } ], - "time": "2023-02-27T13:06:37+00:00" + "time": "2023-03-09T06:34:10+00:00" }, { "name": "sebastian/cli-parser", diff --git a/database/seeders/RandomDataSeeder.php b/database/seeders/RandomDataSeeder.php index 60fbf0707813..2790b34bba05 100644 --- a/database/seeders/RandomDataSeeder.php +++ b/database/seeders/RandomDataSeeder.php @@ -11,37 +11,41 @@ namespace Database\Seeders; -use App\DataMapper\ClientSettings; -use App\DataMapper\CompanySettings; -use App\Events\Payment\PaymentWasCreated; -use App\Helpers\Invoice\InvoiceSum; -use App\Helpers\Invoice\InvoiceSumInclusive; -use App\Models\Account; +use App\Models\User; +use App\Utils\Ninja; +use App\Models\Quote; use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyGateway; -use App\Models\CompanyToken; use App\Models\Credit; -use App\Models\GroupSetting; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\Company; use App\Models\Invoice; use App\Models\Payment; +use App\Models\Product; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\Product; -use App\Models\Quote; -use App\Models\RecurringInvoice; -use App\Models\User; -use App\Repositories\CreditRepository; -use App\Repositories\InvoiceRepository; -use App\Repositories\QuoteRepository; -use App\Utils\Ninja; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; +use App\Models\CompanyToken; +use App\Models\GroupSetting; +use App\Models\ClientContact; +use App\Models\VendorContact; +use App\Models\CompanyGateway; +use Illuminate\Database\Seeder; +use App\Models\RecurringInvoice; +use App\DataMapper\ClientSettings; +use App\DataMapper\CompanySettings; +use App\Helpers\Invoice\InvoiceSum; +use Illuminate\Support\Facades\Hash; +use App\Repositories\QuoteRepository; +use Illuminate\Support\Facades\Cache; +use App\Repositories\CreditRepository; +use Illuminate\Support\Facades\Schema; +use App\Repositories\InvoiceRepository; +use Illuminate\Database\Eloquent\Model; +use App\Events\Payment\PaymentWasCreated; +use App\Helpers\Invoice\InvoiceSumInclusive; +use App\Models\BankIntegration; +use App\Models\BankTransaction; class RandomDataSeeder extends Seeder { @@ -117,6 +121,58 @@ class RandomDataSeeder extends Seeder 'settings' => null, ]); + + $permission_users = [ + 'permissions', + 'products', + 'invoices', + 'quotes', + 'clients', + 'vendors', + 'tasks', + 'expenses', + 'projects', + 'credits', + 'payments', + 'bank_transactions', + 'purchase_orders', + ]; + + foreach ($permission_users as $p_user) { + nlog($p_user); + $user = User::firstOrNew([ + 'email' => "{$p_user}@example.com", + ]); + + $user->first_name = ucfirst($p_user); + $user->last_name = 'Example'; + $user->password = Hash::make('password'); + $user->account_id = $account->id; + $user->email_verified_at = now(); + $user->save(); + + $company_token = CompanyToken::create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'account_id' => $account->id, + 'name' => 'test token', + 'token' => \Illuminate\Support\Str::random(64), + ]); + + $user->companies()->attach($company->id, [ + 'account_id' => $account->id, + 'is_owner' => 0, + 'is_admin' => 0, + 'is_locked' => 0, + 'notifications' => CompanySettings::notificationDefaults(), + 'permissions' => '', + 'settings' => null, + ]); + + $user = null; + } + + $user = User::firstOrNew([ 'email' => 'user@example.com', ]); @@ -147,35 +203,6 @@ class RandomDataSeeder extends Seeder ]); - $user = User::firstOrNew([ - 'email' => 'permissions@example.com', - ]); - - $user->first_name = 'Permissions'; - $user->last_name = 'Example'; - $user->password = Hash::make('password'); - $user->account_id = $account->id; - $user->email_verified_at = now(); - $user->save(); - - $company_token = CompanyToken::create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'account_id' => $account->id, - 'name' => 'test token', - 'token' => \Illuminate\Support\Str::random(64), - ]); - - $user->companies()->attach($company->id, [ - 'account_id' => $account->id, - 'is_owner' => 0, - 'is_admin' => 0, - 'is_locked' => 0, - 'notifications' => CompanySettings::notificationDefaults(), - 'permissions' => '', - 'settings' => null, - ]); - $client = Client::factory()->create([ 'user_id' => $user->id, @@ -195,6 +222,28 @@ class RandomDataSeeder extends Seeder 'password' => Hash::make('password'), ]); + + + $vendor = Vendor::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'name' => 'cypress' + ]); + + $vendor->number = $vendor->getNextVendorNumber($vendor); + $vendor->save(); + + VendorContact::factory()->create([ + 'user_id' => $user->id, + 'vendor_id' => $vendor->id, + 'company_id' => $company->id, + 'is_primary' => 1, + 'email' => 'cypress_vendor@example.com', + 'password' => Hash::make('password'), + ]); + + + /* Product Factory */ Product::factory()->count(2)->create(['user_id' => $user->id, 'company_id' => $company->id]); @@ -304,6 +353,18 @@ class RandomDataSeeder extends Seeder 'name' => 'Default Client Settings', ]); + $bi = BankIntegration::factory()->create([ + 'account_id' => $account->id, + 'user_id' => $user->id, + 'company_id' => $company->id, + ]); + + BankTransaction::factory()->create([ + 'bank_integration_id' => $bi->id, + 'user_id' => $user->id, + 'company_id' => $company->id, + ]); + if (config('ninja.testvars.stripe')) { $cg = new CompanyGateway; $cg->company_id = $company->id; From e62f6ebba4fc020a8b6edbf5e5222b18fcb3c2a7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 17:06:42 +1100 Subject: [PATCH 09/16] Minor fixes for seeder --- database/seeders/RandomDataSeeder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/database/seeders/RandomDataSeeder.php b/database/seeders/RandomDataSeeder.php index 2790b34bba05..a2ff960ea9ea 100644 --- a/database/seeders/RandomDataSeeder.php +++ b/database/seeders/RandomDataSeeder.php @@ -121,7 +121,6 @@ class RandomDataSeeder extends Seeder 'settings' => null, ]); - $permission_users = [ 'permissions', 'products', From 3286ff52eee3f62aa36a8caf6899763c8805a84b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 17:38:30 +1100 Subject: [PATCH 10/16] Tests for dynamic payment type test --- app/Models/PaymentType.php | 42 +++++++++++++++++++++++++++ database/seeders/RandomDataSeeder.php | 2 +- lang/en/texts.php | 3 +- tests/Unit/PaymentTypeTest.php | 40 +++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/PaymentTypeTest.php diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index 298d17e53e33..e6222ba941c4 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -74,6 +74,48 @@ class PaymentType extends StaticModel const KLARNA = 47; const Interac_E_Transfer = 48; + public array $type_names = [ + self::CREDIT => 'payment_type_Credit', + self::ACH => 'payment_type_ACH', + self::VISA => 'payment_type_Visa Card', + self::MASTERCARD => 'payment_type_MasterCard', + self::AMERICAN_EXPRESS => 'payment_type_American Express', + self::DISCOVER => 'payment_type_Discover Card', + self::DINERS => 'payment_type_Diners Card', + self::EUROCARD => 'payment_type_EuroCard', + self::NOVA => 'payment_type_Nova', + self::CREDIT_CARD_OTHER => 'payment_type_Credit Card Other', + self::PAYPAL => 'payment_type_PayPal', + self::CHECK => 'payment_type_Check', + self::CARTE_BLANCHE => 'payment_type_Carte Blanche', + self::UNIONPAY => 'payment_type_UnionPay', + self::JCB => 'payment_type_JCB', + self::LASER => 'payment_type_Laser', + self::MAESTRO => 'payment_type_Maestro', + self::SOLO => 'payment_type_Solo', + self::SWITCH => 'payment_type_Switch', + self::ALIPAY => 'payment_type_Alipay', + self::SOFORT => 'payment_type_Sofort', + self::SEPA => 'payment_type_SEPA', + self::GOCARDLESS => 'payment_type_GoCardless', + self::CRYPTO => 'payment_type_Crypto', + self::MOLLIE_BANK_TRANSFER => 'payment_type_Mollie Bank Transfer', + self::KBC => 'payment_type_KBC/CBC', + self::BANCONTACT => 'payment_type_Bancontact', + self::IDEAL => 'payment_type_iDEAL', + self::HOSTED_PAGE => 'payment_type_Hosted Page', + self::GIROPAY => 'payment_type_GiroPay', + self::PRZELEWY24 => 'payment_type_Przelewy24', + self::EPS => 'payment_type_EPS', + self::DIRECT_DEBIT => 'payment_type_Direct Debit', + self::BECS => 'payment_type_BECS', + self::ACSS => 'payment_type_ACSS', + self::INSTANT_BANK_PAY => 'payment_type_Instant Bank Pay', + self::FPX => 'fpx', + self::KLARNA => 'payment_type_Klarna', + self::Interac_E_Transfer => 'payment_type_Interac E Transfer', + ]; + public static function parseCardType($cardName) { $cardTypes = [ diff --git a/database/seeders/RandomDataSeeder.php b/database/seeders/RandomDataSeeder.php index a2ff960ea9ea..502f0987c253 100644 --- a/database/seeders/RandomDataSeeder.php +++ b/database/seeders/RandomDataSeeder.php @@ -138,7 +138,7 @@ class RandomDataSeeder extends Seeder ]; foreach ($permission_users as $p_user) { - nlog($p_user); + $user = User::firstOrNew([ 'email' => "{$p_user}@example.com", ]); diff --git a/lang/en/texts.php b/lang/en/texts.php index 20c7c4ee9335..3534284820ee 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -4852,7 +4852,6 @@ $LANG = array( 'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.', 'expense_paid_report' => 'Expensed reporting', 'expense_paid_report_help' => 'Turn on for reporting all expenses, turn off for reporting only paid expenses', - 'payment_type_Klarna' => 'Klarna', 'online_payment_email_help' => 'Send an email when an online payment is made', 'manual_payment_email_help' => 'Send an email when manually entering a payment', 'mark_paid_payment_email_help' => 'Send an email when marking an invoice as paid', @@ -5015,6 +5014,8 @@ $LANG = array( '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', + 'payment_type_Klarna' => 'Klarna', + 'payment_type_Interac E Transfer' => 'Interac E Transfer', ); diff --git a/tests/Unit/PaymentTypeTest.php b/tests/Unit/PaymentTypeTest.php new file mode 100644 index 000000000000..92440ad31efd --- /dev/null +++ b/tests/Unit/PaymentTypeTest.php @@ -0,0 +1,40 @@ +type_names as $type) + {nlog($type); + $this->assertTrue(Lang::has("texts.{$type}")); + } + } +} From ab6dfc7ff5c114a0fceca294a42810d63d5cef98 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Mar 2023 17:49:43 +1100 Subject: [PATCH 11/16] Ensure payment types are returned --- app/Models/Payment.php | 7 +++++-- app/Models/PaymentType.php | 8 ++++++++ app/Services/Pdf/PdfBuilder.php | 4 ++-- app/Services/PdfMaker/Design.php | 2 +- resources/views/portal/ninja2020/payments/show.blade.php | 6 +++--- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 1a6e79830707..b6f8296a4c3d 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -299,11 +299,14 @@ class Payment extends BaseModel public function translatedType() { - if (! $this->type) { + if (! $this->type_id) { return ''; } - return ctrans('texts.payment_type_'.$this->type->name); + $pt = new PaymentType(); + + return $pt->name($this->type_id); + } public function gateway_type() diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index e6222ba941c4..9170b3d53d21 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -148,4 +148,12 @@ class PaymentType extends StaticModel return self::CREDIT_CARD_OTHER; } } + + public function name($id) + { + if(isset($this->type_names[$id])) + return ctrans("texts.".$this->type_names[$id]); + + return ctrans('texts.manual_entry'); + } } diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index c57fef889f33..eb2feebc3bf7 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -244,7 +244,7 @@ class PdfBuilder $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')]; + $element['elements'][] = ['element' => 'td', 'content' => $payment->translatedType()]; $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($payment->pivot->amount) ?: ' ']; $tbody[] = $element; @@ -279,7 +279,7 @@ class PdfBuilder return [ ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))], - ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->type ? $payment->type->name : ctrans('texts.manual_entry'))], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())], ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], ]; } diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index a2d4927a6ebd..7bc942eb51b5 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -601,7 +601,7 @@ class Design extends BaseDesign $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->client->date_format(), $this->client->locale()) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')]; + $element['elements'][] = ['element' => 'td', 'content' => $payment->translatedType()]; $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->pivot->amount, $this->client) ?: ' ']; $tbody[] = $element; diff --git a/resources/views/portal/ninja2020/payments/show.blade.php b/resources/views/portal/ninja2020/payments/show.blade.php index 546f55a89dee..80c443d700ad 100644 --- a/resources/views/portal/ninja2020/payments/show.blade.php +++ b/resources/views/portal/ninja2020/payments/show.blade.php @@ -49,16 +49,16 @@ @endif - @if(!empty($payment->type?->name) && !is_null($payment->type?->name)) +
{{ ctrans('texts.method') }}
- {{ $payment->type?->name }} + {{ $payment->translatedType() }}
- @endif + @if(!empty($payment->formattedAmount()) && !is_null($payment->formattedAmount()))
From 0a6cd028b87688cf3cefc243d2eacf89b7296e41 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Mar 2023 09:19:52 +1100 Subject: [PATCH 12/16] Add webp to file types --- app/Http/Requests/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 5553feddf077..789d723938c7 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -20,7 +20,7 @@ class Request extends FormRequest use MakesHash; use RuntimeFormRequest; - protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp|max:20000'; /** * Get the validation rules that apply to the request. * From a23c44c0b81af4979928170160d888900500a93d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Mar 2023 10:50:47 +1100 Subject: [PATCH 13/16] Updates for user --- app/Models/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index 55065731c875..fc57697d5e10 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -182,8 +182,8 @@ class User extends Authenticatable implements MustVerifyEmail 'accepted_terms_version', 'oauth_user_id', 'oauth_provider_id', - // 'oauth_user_token', - // 'oauth_user_refresh_token', + 'oauth_user_token', + 'oauth_user_refresh_token', 'custom_value1', 'custom_value2', 'custom_value3', From eeb87dc5cb4879301355190f3d1dcc98bc10195f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Mar 2023 12:26:56 +1100 Subject: [PATCH 14/16] Fixes for deleting partial payment payments on a single invoice --- .../Requests/Payment/StorePaymentRequest.php | 1 - .../PaymentAppliedValidAmount.php | 26 ++++++++++++++++--- app/Services/Payment/DeletePayment.php | 13 ++++++---- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index f31e1cf8d3ff..d796a082454d 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -70,7 +70,6 @@ class StorePaymentRequest extends Request if (isset($input['credits']) && is_array($input['credits']) !== false) { foreach ($input['credits'] as $key => $value) { if (array_key_exists('credit_id', $input['credits'][$key])) { - // $input['credits'][$key]['credit_id'] = $value['credit_id']; $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); $credits_total += $value['amount']; diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php index 332bcc97e210..dcb0c6d11b26 100644 --- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -11,6 +11,7 @@ namespace App\Http\ValidationRules; +use App\Models\Invoice; use App\Models\Payment; use App\Utils\Traits\MakesHash; use Illuminate\Contracts\Validation\Rule; @@ -22,6 +23,8 @@ class PaymentAppliedValidAmount implements Rule { use MakesHash; + private $message; + /** * @param string $attribute * @param mixed $value @@ -29,6 +32,8 @@ class PaymentAppliedValidAmount implements Rule */ public function passes($attribute, $value) { + $this->message = ctrans('texts.insufficient_applied_amount_remaining'); + return $this->calculateAmounts(); } @@ -37,12 +42,13 @@ class PaymentAppliedValidAmount implements Rule */ public function message() { - return ctrans('texts.insufficient_applied_amount_remaining'); + return $this->message; } private function calculateAmounts() :bool { $payment = Payment::withTrashed()->whereId($this->decodePrimaryKey(request()->segment(4)))->company()->first(); + $inv_collection = Invoice::withTrashed()->whereIn('id', array_column(request()->input('invoices'), 'invoice_id'))->get(); if (! $payment) { return false; @@ -71,10 +77,24 @@ class PaymentAppliedValidAmount implements Rule if (request()->input('invoices') && is_array(request()->input('invoices'))) { foreach (request()->input('invoices') as $invoice) { $invoice_amounts += $invoice['amount']; + + $inv = $inv_collection->firstWhere('id', $invoice['invoice_id']); + + if($inv->balance < $invoice['amount']) { + + // $this->message = ctrans('texts.insufficient_applied_amount_remaining'); + $this->message = 'Amount cannot be greater than invoice balance'; + + return false; + } + } } - // nlog("{round($payment_amounts,3)} >= {round($invoice_amounts,3)}"); - return round($payment_amounts, 3) >= round($invoice_amounts, 3); + if(round($payment_amounts, 3) >= round($invoice_amounts, 3)) { + return true; + } + + return false; } } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index b327287fcb60..eec109408172 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -74,9 +74,11 @@ class DeletePayment if ($this->payment->invoices()->exists()) { $this->payment->invoices()->each(function ($paymentable_invoice) { + $net_deletable = $paymentable_invoice->pivot->amount - $paymentable_invoice->pivot->refunded; $this->_paid_to_date_deleted += $net_deletable; + $paymentable_invoice = $paymentable_invoice->fresh(); nlog("net deletable amount - refunded = {$net_deletable}"); @@ -92,17 +94,18 @@ class DeletePayment ->updateInvoiceBalance($net_deletable, "Adjusting invoice {$paymentable_invoice->number} due to deletion of Payment {$this->payment->number}") ->save(); - $client = $this->payment - ->client - ->service() - ->updateBalanceAndPaidToDate($net_deletable, $net_deletable*-1) - ->save(); + $this->payment + ->client + ->service() + ->updateBalanceAndPaidToDate($net_deletable, $net_deletable*-1) + ->save(); if ($paymentable_invoice->balance == $paymentable_invoice->amount) { $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); } else { $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); } + } else { $paymentable_invoice->restore(); $paymentable_invoice->service() From d919134bb900d3e7981b55a515fd32e8fa36dcad Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Mar 2023 14:25:18 +1100 Subject: [PATCH 15/16] Fixes for payment tests --- app/Http/Requests/Payment/UpdatePaymentRequest.php | 3 ++- .../ValidationRules/PaymentAppliedValidAmount.php | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 8f24eb3f6518..1d87ef164542 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -36,7 +36,7 @@ class UpdatePaymentRequest extends Request public function rules() { $rules = [ - 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule($this->all())], + 'invoices' => ['array', new PaymentAppliedValidAmount($this->all()), new ValidCreditsPresentRule($this->all())], 'invoices.*.invoice_id' => 'distinct', ]; @@ -87,6 +87,7 @@ class UpdatePaymentRequest extends Request } } } + $this->replace($input); } diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php index dcb0c6d11b26..24c5dbe09e47 100644 --- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -25,6 +25,10 @@ class PaymentAppliedValidAmount implements Rule private $message; + public function __construct(private array $input) + { + $this->input = $input; + } /** * @param string $attribute * @param mixed $value @@ -48,8 +52,8 @@ class PaymentAppliedValidAmount implements Rule private function calculateAmounts() :bool { $payment = Payment::withTrashed()->whereId($this->decodePrimaryKey(request()->segment(4)))->company()->first(); - $inv_collection = Invoice::withTrashed()->whereIn('id', array_column(request()->input('invoices'), 'invoice_id'))->get(); - + $inv_collection = Invoice::withTrashed()->whereIn('id', array_column($this->input['invoices'], 'invoice_id'))->get(); + if (! $payment) { return false; } @@ -74,15 +78,15 @@ class PaymentAppliedValidAmount implements Rule } } - if (request()->input('invoices') && is_array(request()->input('invoices'))) { - foreach (request()->input('invoices') as $invoice) { + if (isset($this->input['invoices']) && is_array($this->input['invoices'])) { + foreach ($this->input['invoices'] as $invoice) { $invoice_amounts += $invoice['amount']; $inv = $inv_collection->firstWhere('id', $invoice['invoice_id']); + nlog($inv); if($inv->balance < $invoice['amount']) { - // $this->message = ctrans('texts.insufficient_applied_amount_remaining'); $this->message = 'Amount cannot be greater than invoice balance'; return false; From 8bddb49e6c0671aaabecacdd0b62955f89488f20 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Mar 2023 14:26:13 +1100 Subject: [PATCH 16/16] Fixes for payment tests --- app/Http/ValidationRules/PaymentAppliedValidAmount.php | 1 - tests/Feature/PaymentTest.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php index 24c5dbe09e47..88432bd8f2a8 100644 --- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -83,7 +83,6 @@ class PaymentAppliedValidAmount implements Rule $invoice_amounts += $invoice['amount']; $inv = $inv_collection->firstWhere('id', $invoice['invoice_id']); - nlog($inv); if($inv->balance < $invoice['amount']) { diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index f1e5c17c5972..48c10689645d 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -1392,8 +1392,6 @@ class PaymentTest extends TestCase $payment = Payment::find($this->decodePrimaryKey($payment_id)); - // nlog($payment); - $this->assertNotNull($payment); $this->assertNotNull($payment->invoices()); $this->assertEquals(1, $payment->invoices()->count());