From ff17e3eb67126add2b344692a8dd6ca53e7148ca Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 19 Nov 2019 21:23:56 +1100 Subject: [PATCH] Payment deletes (#3079) * Add amount to paymentable tables to enable reversing payments gracefully * Create Test Data artisan comannd * Delete Payments + Fixes for company settings persistence --- app/Console/Commands/CreateTestData.php | 220 ++++++++++++++++++ app/Http/Controllers/PaymentController.php | 5 +- .../Payment/DestroyPaymentRequest.php | 14 ++ app/Jobs/Invoice/MarkInvoicePaid.php | 6 +- app/Jobs/Invoice/ReverseInvoicePayment.php | 73 ++++++ app/Jobs/Invoice/UpdateInvoicePayment.php | 15 +- .../Activity/PaymentDeletedActivity.php | 65 ++++++ app/Models/Payment.php | 2 +- app/Models/Paymentable.php | 23 ++ app/Providers/EventServiceProvider.php | 5 + app/Repositories/InvoiceRepository.php | 2 +- app/Utils/Traits/CompanySettingsSaver.php | 13 +- .../2014_10_13_000000_create_users_table.php | 2 + 13 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 app/Console/Commands/CreateTestData.php create mode 100644 app/Jobs/Invoice/ReverseInvoicePayment.php create mode 100644 app/Listeners/Activity/PaymentDeletedActivity.php create mode 100644 app/Models/Paymentable.php diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php new file mode 100644 index 000000000000..0e00124ad72e --- /dev/null +++ b/app/Console/Commands/CreateTestData.php @@ -0,0 +1,220 @@ +invoice_repo = $invoice_repo; + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $this->info(date('r').' Running CreateTestData...'); + $this->count = $this->argument('count'); + + $this->info('Warming up cache'); + + $this->warmCache(); + + + $this->info('Creating Account and Company'); + + $account = factory(\App\Models\Account::class)->create(); + $company = factory(\App\Models\Company::class)->create([ + 'account_id' => $account->id, + 'domain' => 'ninja.test:8000', + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $user = User::whereEmail('user@example.com')->first(); + + if(!$user) + { + $user = factory(\App\Models\User::class)->create([ + // 'account_id' => $account->id, + 'email' => 'user@example.com', + 'confirmation_code' => $this->createDbHash(config('database.default')) + ]); + } + + $token = \Illuminate\Support\Str::random(64); + + $company_token = CompanyToken::create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'account_id' => $account->id, + 'name' => 'test token', + 'token' => $token, + ]); + + $user->companies()->attach($company->id, [ + 'account_id' => $account->id, + 'is_owner' => 1, + 'is_admin' => 1, + 'is_locked' => 0, + 'permissions' => json_encode([]), + 'settings' => json_encode(DefaultSettings::userSettings()), + ]); + + + $this->info('Creating '.$this->count. ' clients'); + + + for($x=0; $x<$this->count; $x++) { + $z = $x+1; + $this->info("Creating client # ".$z); + + $this->createClient($company, $user); + } + } + + private function createClient($company, $user) + { + $client = ClientFactory::create($company->id, $user->id); + $client->save(); + + $y = $this->count * rand(1,5); + + $this->info("Creating {$y} invoices"); + + for($x=0; $x<$y; $x++){ + + $this->createInvoice($client); + } + } + + private function createInvoice($client) + { + $invoice = InvoiceFactory::create($client->company->id,$client->user->id);//stub the company and user_id + $invoice->client_id = $client->id; + + $invoice->line_items = $this->buildLineItems(); + $invoice->uses_inclusive_Taxes = false; + + $invoice->save(); + + $invoice_calc = new InvoiceSum($invoice); + $invoice_calc->build(); + + $invoice = $invoice_calc->getInvoice(); + + $invoice->save(); + + event(new CreateInvoiceInvitation($invoice)); + + UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance); + + $this->invoice_repo->markSent($invoice); + + event(new InvoiceWasMarkedSent($invoice)); + + if(rand(0, 1)) { + + $payment = PaymentFactory::create($client->company->id, $client->user->id); + $payment->payment_date = now(); + $payment->client_id = $client->id; + $payment->amount = $invoice->balance; + $payment->transaction_reference = rand(0,500); + $payment->payment_type_id = PaymentType::CREDIT_CARD_OTHER; + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->save(); + + $payment->invoices()->save($invoice); + + event(new PaymentWasCreated($payment)); + + UpdateInvoicePayment::dispatchNow($payment); + } + } + + private function buildLineItems() + { + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost =10; + + $line_items[] = $item; + + return $line_items; + + } + + private function warmCache() + { + /* Warm up the cache !*/ + $cached_tables = config('ninja.cached_tables'); + + foreach ($cached_tables as $name => $class) { + if (! Cache::has($name)) { + // check that the table exists in case the migration is pending + if (! Schema::hasTable((new $class())->getTable())) { + continue; + } + if ($name == 'payment_terms') { + $orderBy = 'num_days'; + } elseif ($name == 'fonts') { + $orderBy = 'sort_order'; + } elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) { + $orderBy = 'name'; + } else { + $orderBy = 'id'; + } + $tableData = $class::orderBy($orderBy)->get(); + if ($tableData->count()) { + Cache::forever($name, $tableData); + } + } + } + } +} diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index e83d6204e8a8..bcca69716c1c 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -22,6 +22,7 @@ use App\Http\Requests\Payment\ShowPaymentRequest; use App\Http\Requests\Payment\StorePaymentRequest; use App\Http\Requests\Payment\UpdatePaymentRequest; use App\Jobs\Entity\ActionEntity; +use App\Jobs\Invoice\ReverseInvoicePayment; use App\Models\Payment; use App\Repositories\BaseRepository; use App\Repositories\PaymentRepository; @@ -483,10 +484,12 @@ class PaymentController extends BaseController public function destroy(DestroyPaymentRequest $request, Payment $payment) { + ReverseInvoicePayment::dispatchNow($payment); + $payment->is_deleted = true; $payment->delete(); - return response()->json([], 200); + return $this->itemResponse($payment); } diff --git a/app/Http/Requests/Payment/DestroyPaymentRequest.php b/app/Http/Requests/Payment/DestroyPaymentRequest.php index 7e2fc3d6f5f6..a83499206fab 100644 --- a/app/Http/Requests/Payment/DestroyPaymentRequest.php +++ b/app/Http/Requests/Payment/DestroyPaymentRequest.php @@ -27,4 +27,18 @@ class DestroyPaymentRequest extends Request return auth()->user()->can('edit', $this->payment); } + // public function rules() + // { + // return [ + // 'deletable' + // ]; + // } + + // public function messages() + // { + // return [ + // 'deletable' => 'Payment cannot be deleted', + // ]; + + // } } \ No newline at end of file diff --git a/app/Jobs/Invoice/MarkInvoicePaid.php b/app/Jobs/Invoice/MarkInvoicePaid.php index 349dc38f4e65..c153f726a1ab 100644 --- a/app/Jobs/Invoice/MarkInvoicePaid.php +++ b/app/Jobs/Invoice/MarkInvoicePaid.php @@ -59,8 +59,10 @@ class MarkInvoicePaid implements ShouldQueue $payment->transaction_reference = ctrans('texts.manual_entry'); /* Create a payment relationship to the invoice entity */ $payment->save(); - $payment->invoices()->save($this->invoice); - $payment->save(); + + $payment->invoices()->attach($this->invoice->id,[ + 'amount' => $payment->amount + ]); $this->invoice->updateBalance($payment->amount*-1); diff --git a/app/Jobs/Invoice/ReverseInvoicePayment.php b/app/Jobs/Invoice/ReverseInvoicePayment.php new file mode 100644 index 000000000000..c2564a5e4263 --- /dev/null +++ b/app/Jobs/Invoice/ReverseInvoicePayment.php @@ -0,0 +1,73 @@ +payment = $payment; + } + + /** + * Handle the event. + * + * @param object $event + * @return void + */ + public function handle() + { + + $invoices = $this->payment->invoices()->get(); + $client = $this->payment->client; + + $invoices->each(function($invoice){ + + if($invoice->pivot->amount > 0) + { + $invoice->status_id = Invoice::STATUS_SENT; + $invoice->balance = $invoice->pivot->amount; + $invoice->save(); + } + + }); + + UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($this->payment->amount)); + + UpdateClientBalance::dispatchNow($client, $this->payment->amount); + + + } + +} \ No newline at end of file diff --git a/app/Jobs/Invoice/UpdateInvoicePayment.php b/app/Jobs/Invoice/UpdateInvoicePayment.php index c489bf5e28fb..9d33d49a6ff5 100644 --- a/app/Jobs/Invoice/UpdateInvoicePayment.php +++ b/app/Jobs/Invoice/UpdateInvoicePayment.php @@ -58,9 +58,14 @@ class UpdateInvoicePayment implements ShouldQueue $invoices->each(function ($invoice){ UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($invoice->balance*-1)); + + $invoice->pivot->amount = $invoice->balance; + $invoice->pivot->save(); + $invoice->clearPartial(); $invoice->updateBalance($invoice->balance*-1); - + + UpdateClientBalance::dispatchNow($this->payment->client, $invoice->balance*-1); }); @@ -91,6 +96,10 @@ class UpdateInvoicePayment implements ShouldQueue if($invoice->hasPartial()) { UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($invoice->partial*-1)); + + $invoice->pivot->amount = $invoice->partial; + $invoice->pivot->save(); + $invoice->updateBalance($invoice->partial*-1); $invoice->clearPartial(); $invoice->setDueDate(); @@ -102,6 +111,10 @@ class UpdateInvoicePayment implements ShouldQueue else { UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($invoice->balance*-1)); + + $invoice->pivot->amount = $invoice->balance; + $invoice->pivot->save(); + $invoice->clearPartial(); $invoice->updateBalance($invoice->balance*-1); diff --git a/app/Listeners/Activity/PaymentDeletedActivity.php b/app/Listeners/Activity/PaymentDeletedActivity.php new file mode 100644 index 000000000000..29d2e9bdf537 --- /dev/null +++ b/app/Listeners/Activity/PaymentDeletedActivity.php @@ -0,0 +1,65 @@ +activityRepo = $activityRepo; + } + + /** + * Handle the event. + * + * @param object $event + * @return void + */ + public function handle($event) + { + $payment = $event->payment; + + $invoices = $payment->invoices; + + $fields = new \stdClass; + + $fields->payment_id = $payment->id; + $fields->user_id = $payment->user_id; + $fields->company_id = $payment->company_id; + $fields->activity_type_id = Activity::DELETE_PAYMENT; + + + foreach($invoices as $invoice) //todo we may need to add additional logic if in the future we apply payments to other entity Types, not just invoices + { + + $fields->invoice_id = $invoice->id; + + $this->activityRepo->save($fields, $invoice); + } + + if( count( $invoices ) == 0 ) + $this->activityRepo->save($fields, $payment); + } +} diff --git a/app/Models/Payment.php b/app/Models/Payment.php index dc83b320c28f..ab39ae141fde 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -91,7 +91,7 @@ class Payment extends BaseModel public function invoices() { - return $this->morphedByMany(Invoice::class, 'paymentable'); + return $this->morphedByMany(Invoice::class, 'paymentable')->withPivot('amount'); } public function company_ledger() diff --git a/app/Models/Paymentable.php b/app/Models/Paymentable.php new file mode 100644 index 000000000000..6ebcef077ee0 --- /dev/null +++ b/app/Models/Paymentable.php @@ -0,0 +1,23 @@ + [ + PaymentDeletedActivity::class + ], 'App\Events\ClientWasArchived' => [ 'App\Listeners\ActivityListener@archivedClient', ], diff --git a/app/Repositories/InvoiceRepository.php b/app/Repositories/InvoiceRepository.php index 6c91e86582a8..b96e94993741 100644 --- a/app/Repositories/InvoiceRepository.php +++ b/app/Repositories/InvoiceRepository.php @@ -157,7 +157,7 @@ class InvoiceRepository extends BaseRepository */ $invoice = ApplyInvoiceNumber::dispatchNow($invoice, $invoice->client->getMergedSettings()); - UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $this->balance); + UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance); return $invoice; diff --git a/app/Utils/Traits/CompanySettingsSaver.php b/app/Utils/Traits/CompanySettingsSaver.php index e92f7ce27198..4e56ad912abb 100644 --- a/app/Utils/Traits/CompanySettingsSaver.php +++ b/app/Utils/Traits/CompanySettingsSaver.php @@ -43,12 +43,17 @@ trait CompanySettingsSaver $company_settings = CompanySettings::defaults(); //Iterate and set CURRENT settings - foreach($this->settings as $key => $value) - $company_settings->{$key} = $value; + // foreach($this->settings as $key => $value) + // $company_settings->{$key} = $value; //Iterate and set NEW settings - foreach($settings as $key => $value) - $company_settings->{$key} = $value; + foreach($settings as $key => $value) { + + if(is_null($settings->{$key})) + $company_settings->{$key} = ''; + else + $company_settings->{$key} = $value; + } $entity->settings = $company_settings; $entity->save(); diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index 9f977bfc050d..392394e56f15 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -761,8 +761,10 @@ class CreateUsersTable extends Migration }); Schema::create('paymentables', function ($table) { //allows multiple invoices to one payment + // $table->increments('id'); $table->unsignedInteger('payment_id'); $table->unsignedInteger('paymentable_id'); + $table->decimal('amount', 16, 4)->default(0); $table->string('paymentable_type'); });