1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-05 18:52:44 +01:00

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
This commit is contained in:
David Bomba 2019-11-19 21:23:56 +11:00 committed by GitHub
parent 1c4b05c212
commit ff17e3eb67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 435 additions and 10 deletions

View File

@ -0,0 +1,220 @@
<?php
namespace App\Console\Commands;
use App\DataMapper\DefaultSettings;
use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\ClientFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\PaymentFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Invoice\UpdateInvoicePayment;
use App\Listeners\Invoice\CreateInvoiceInvitation;
use App\Models\CompanyToken;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\User;
use App\Repositories\InvoiceRepository;
use App\Utils\Traits\MakesHash;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
class CreateTestData extends Command
{
use MakesHash;
/**
* @var string
*/
protected $description = 'Create Test Data';
/**
* @var string
*/
protected $signature = 'ninja:create-test-data {count=1}';
protected $invoice_repo;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(InvoiceRepository $invoice_repo)
{
parent::__construct();
$this->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);
}
}
}
}
}

View File

@ -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);
}

View File

@ -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',
// ];
// }
}

View File

@ -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);

View File

@ -0,0 +1,73 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Invoice;
use App\Jobs\Client\UpdateClientBalance;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Company\UpdateCompanyLedgerWithPayment;
use App\Jobs\Util\SystemLogger;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Utils\Traits\SystemLogTrait;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ReverseInvoicePayment implements ShouldQueue
{
use SystemLogTrait, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $payment;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(Payment $payment)
{
$this->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);
}
}

View File

@ -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);

View File

@ -0,0 +1,65 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Activity;
use App\Models\Activity;
use App\Models\Invoice;
use App\Models\Payment;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class PaymentDeletedActivity implements ShouldQueue
{
protected $activityRepo;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(ActivityRepository $activityRepo)
{
$this->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);
}
}

View File

@ -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()

View File

@ -0,0 +1,23 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class Paymentable extends Pivot
{
// protected $guarded = ['id'];
// public $incrementing = true;
}

View File

@ -17,10 +17,12 @@ use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Events\Payment\PaymentWasCreated;
use App\Events\Payment\PaymentWasDeleted;
use App\Events\User\UserLoggedIn;
use App\Events\User\UserWasCreated;
use App\Listeners\Activity\CreatedClientActivity;
use App\Listeners\Activity\PaymentCreatedActivity;
use App\Listeners\Activity\PaymentDeletedActivity;
use App\Listeners\Contact\UpdateContactLastLogin;
use App\Listeners\Invoice\CreateInvoiceActivity;
use App\Listeners\Invoice\CreateInvoiceInvitation;
@ -59,6 +61,9 @@ class EventServiceProvider extends ServiceProvider
//UpdateInvoicePayment::class,
UpdateInvoiceInvitations::class,
],
PaymentWasDeleted::class => [
PaymentDeletedActivity::class
],
'App\Events\ClientWasArchived' => [
'App\Listeners\ActivityListener@archivedClient',
],

View File

@ -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;

View File

@ -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();

View File

@ -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');
});