Refactor for invoice calculations, implementing testing for Invoice Invitation creation

This commit is contained in:
David Bomba 2019-04-24 15:18:48 +10:00
parent 572d368da7
commit e37c6912b1
14 changed files with 189 additions and 44 deletions

View File

@ -12,7 +12,7 @@ class InvoiceFactory
public static function create(int $company_id, int $user_id) :\stdClass public static function create(int $company_id, int $user_id) :\stdClass
{ {
$invoice = new \stdClass; $invoice = new \stdClass;
$invoice->invoice_status_id = Invoice::STATUS_DRAFT; $invoice->status_id = Invoice::STATUS_DRAFT;
$invoice->invoice_number = ''; $invoice->invoice_number = '';
$invoice->discount = 0; $invoice->discount = 0;
$invoice->is_amount_discount = true; $invoice->is_amount_discount = true;
@ -41,7 +41,7 @@ class InvoiceFactory
$invoice->partial = 0; $invoice->partial = 0;
$invoice->user_id = $user_id; $invoice->user_id = $user_id;
$invoice->company_id = $company_id; $invoice->company_id = $company_id;
return $invoice; return $invoice;
} }
} }

View File

@ -46,13 +46,12 @@ class InvoiceCalc
* *
* @param \App\Models\Invoice $invoice The invoice * @param \App\Models\Invoice $invoice The invoice
*/ */
public function __construct($invoice) public function __construct($invoice, $settings)
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
$this->settings = $settings;
$this->settings = $invoice->settings;
$this->tax_map = new Collection; $this->tax_map = new Collection;
} }

View File

@ -4,11 +4,14 @@ namespace App\Listeners\Invoice;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class CreateInvoiceInvitations class CreateInvoiceInvitations
{ {
use MakesHash;
/** /**
* Create the event listener. * Create the event listener.
* *
@ -20,7 +23,8 @@ class CreateInvoiceInvitations
} }
/** /**
* Handle the event. * Handle the creation of invitations for an invoice.
* We only ever create one invitation per contact.
* *
* @param object $event * @param object $event
* @return void * @return void
@ -34,9 +38,15 @@ class CreateInvoiceInvitations
$contacts->each(function ($contact) use($invoice) { $contacts->each(function ($contact) use($invoice) {
InvoiceInvitation::create([ $i = InvoiceInvitation::firstOrCreate([
'client_contact_id' => $contact->id,
]); 'invoice_id' => $invoice->id
],
[
'company_id' => $invoice->company_id,
'user_id' => $invoice->user_id,
'invitation_key' => $this->createDbHash($invoice->company->db),
]);
}); });

View File

@ -44,4 +44,9 @@ class Invoice extends BaseModel
{ {
return $this->hasMany(InvoiceInvitation::class); return $this->hasMany(InvoiceInvitation::class);
} }
public function client()
{
return $this->belongsTo(Client::class);
}
} }

View File

@ -10,6 +10,10 @@ class InvoiceInvitation extends BaseModel
use MakesDates; use MakesDates;
protected $guarded = [
'id',
];
/** /**
* @return mixed * @return mixed
*/ */

View File

@ -6,6 +6,7 @@ use App\Events\Client\ClientWasCreated;
use App\Events\Invoice\InvoiceWasMarkedSent; use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Events\User\UserCreated; use App\Events\User\UserCreated;
use App\Listeners\Client\CreatedClientActivity; use App\Listeners\Client\CreatedClientActivity;
use App\Listeners\Invoice\CreateInvoiceInvitations;
use App\Listeners\SendVerificationNotification; use App\Listeners\SendVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@ -43,8 +44,8 @@ class EventServiceProvider extends ServiceProvider
//Invoices //Invoices
[ [
InvoiceWasMarkedSent::class => [ InvoiceWasMarkedSent::class => [
CreateInvoiceInvitations::class CreateInvoiceInvitations::class,
] ]
], ],
]; ];

View File

@ -19,7 +19,7 @@ class InvoiceTransformer extends EntityTransformer
* @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true)
* @SWG\Property(property="is_deleted", type="boolean", example=false, readOnly=true) * @SWG\Property(property="is_deleted", type="boolean", example=false, readOnly=true)
* @SWG\Property(property="client_id", type="integer", example=1) * @SWG\Property(property="client_id", type="integer", example=1)
* @SWG\Property(property="invoice_status_id", type="integer", example=1, readOnly=true) * @SWG\Property(property="status_id", type="integer", example=1, readOnly=true)
* @SWG\Property(property="invoice_number", type="string", example="0001") * @SWG\Property(property="invoice_number", type="string", example="0001")
* @SWG\Property(property="discount", type="number", format="float", example=10) * @SWG\Property(property="discount", type="number", format="float", example=10)
* @SWG\Property(property="po_number", type="string", example="0001") * @SWG\Property(property="po_number", type="string", example="0001")
@ -122,7 +122,7 @@ class InvoiceTransformer extends EntityTransformer
'amount' => (float) $invoice->amount, 'amount' => (float) $invoice->amount,
'balance' => (float) $invoice->balance, 'balance' => (float) $invoice->balance,
'client_id' => (int) $invoice->client_id, 'client_id' => (int) $invoice->client_id,
'invoice_status_id' => (int) ($invoice->invoice_status_id ?: 1), 'status_id' => (int) ($invoice->status_id ?: 1),
'updated_at' => $invoice->updated_at, 'updated_at' => $invoice->updated_at,
'archived_at' => $invoice->deleted_at, 'archived_at' => $invoice->deleted_at,
'invoice_number' => $invoice->invoice_number, 'invoice_number' => $invoice->invoice_number,

View File

@ -6,7 +6,7 @@ use Faker\Generator as Faker;
$factory->define(App\Models\Invoice::class, function (Faker $faker) { $factory->define(App\Models\Invoice::class, function (Faker $faker) {
return [ return [
'invoice_status_id' => App\Models\Invoice::STATUS_PAID, 'status_id' => App\Models\Invoice::STATUS_DRAFT,
'invoice_number' => $faker->text(256), 'invoice_number' => $faker->text(256),
'discount' => $faker->numberBetween(1,10), 'discount' => $faker->numberBetween(1,10),
'is_amount_discount' => $faker->boolean(), 'is_amount_discount' => $faker->boolean(),
@ -24,6 +24,5 @@ $factory->define(App\Models\Invoice::class, function (Faker $faker) {
'due_date' => $faker->date(), 'due_date' => $faker->date(),
'line_items' => false, 'line_items' => false,
'backup' => '', 'backup' => '',
'settings' => ClientSettings::buildClientSettings(new CompanySettings(CompanySettings::defaults()), new CompanySettings(ClientSettings::defaults()))
]; ];
}); });

View File

@ -341,7 +341,7 @@ class CreateUsersTable extends Migration
$t->unsignedInteger('client_id')->index(); $t->unsignedInteger('client_id')->index();
$t->unsignedInteger('user_id'); $t->unsignedInteger('user_id');
$t->unsignedInteger('company_id')->index(); $t->unsignedInteger('company_id')->index();
$t->unsignedInteger('invoice_status_id'); $t->unsignedInteger('status_id');
$t->string('invoice_number'); $t->string('invoice_number');
$t->float('discount'); $t->float('discount');
@ -520,11 +520,11 @@ class CreateUsersTable extends Migration
$t->string('message_id')->nullable(); $t->string('message_id')->nullable();
$t->text('email_error'); $t->text('email_error');
$t->text('signature_base64'); $t->text('signature_base64');
$t->timestamp('signature_date')->nullable(); $t->date('signature_date')->nullable();
$t->timestamp('sent_date')->nullable(); $t->date('sent_date')->nullable();
$t->timestamp('viewed_date')->nullable(); $t->date('viewed_date')->nullable();
$t->timestamp('opened_date')->nullable(); $t->date('opened_date')->nullable();
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade'); $t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade');

View File

@ -16904,7 +16904,7 @@ export default {
"add_item": "Add Item", "add_item": "Add Item",
"total_amount": "Total Amount", "total_amount": "Total Amount",
"pdf": "PDF", "pdf": "PDF",
"invoice_status_id": "Invoice Status", "status_id": "Invoice Status",
"click_plus_to_add_item": "Click + to add an item", "click_plus_to_add_item": "Click + to add an item",
"count_selected": "{count} selected", "count_selected": "{count} selected",
"dismiss": "Dismiss", "dismiss": "Dismiss",

View File

@ -2901,7 +2901,7 @@ $LANG = array(
'add_item' => 'Add Item', 'add_item' => 'Add Item',
'total_amount' => 'Total Amount', 'total_amount' => 'Total Amount',
'pdf' => 'PDF', 'pdf' => 'PDF',
'invoice_status_id' => 'Invoice Status', 'status_id' => 'Invoice Status',
'click_plus_to_add_item' => 'Click + to add an item', 'click_plus_to_add_item' => 'Click + to add an item',
'count_selected' => ':count selected', 'count_selected' => ':count selected',
'dismiss' => 'Dismiss', 'dismiss' => 'Dismiss',

View File

@ -0,0 +1,132 @@
<?php
namespace Feature;
use App\DataMapper\ClientSettings;
use App\DataMapper\DefaultSettings;
use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Jobs\Account\CreateAccount;
use App\Listeners\Invoice\CreateInvoiceInvitations;
use App\Models\Account;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\User;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\UserSessionAttributes;
use Faker\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Session;
use Tests\TestCase;
/**
* @test
* @covers App\Listeners\Invoice\CreateInvoiceInvitations
*/
class InvitationTest extends TestCase
{
use DatabaseTransactions;
use MakesHash;
public function setUp()
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
}
public function testInvoiceCreationAfterInvoiceMarkedSent()
{
$account = factory(\App\Models\Account::class)->create();
$company = factory(\App\Models\Company::class)->create([
'account_id' => $account->id,
]);
$account->default_company_id = $company->id;
$account->save();
$user = factory(\App\Models\User::class)->create([
'account_id' => $account->id,
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
$userPermissions = collect([
'view_invoice',
'view_client',
'edit_client',
'edit_invoice',
'create_invoice',
'create_client'
]);
$userSettings = DefaultSettings::userSettings();
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'permissions' => $userPermissions->toJson(),
'settings' => json_encode($userSettings),
'is_locked' => 0,
]);
factory(\App\Models\Client::class)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id,
'is_primary' => 1
]);
factory(\App\Models\ClientContact::class,2)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id
]);
});
$client = Client::whereUserId($user->id)->whereCompanyId($company->id)->first();
factory(\App\Models\Invoice::class,5)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'settings' => ClientSettings::buildClientSettings($company->settings, $client->settings)]);
$invoice = Invoice::whereUserId($user->id)->whereCompanyId($company->id)->whereClientId($client->id)->first();
$this->assertNotNull($invoice);
$this->assertNotNull($invoice->client);
$this->assertNotNull($invoice->client->primary_contact);
$arr[] = $invoice->client->primary_contact->first()->id;
$settings = $invoice->settings;
$settings->invoice_email_list = implode(",",$arr);
$invoice->settings = $settings;
$invoice->save();
$listener = new CreateInvoiceInvitations();
$listener->handle(new InvoiceWasMarkedSent($invoice));
$i = InvoiceInvitation::whereClientContactId($invoice->client->primary_contact->first()->id)->whereInvoiceId($invoice->id)->first();
$this->assertNotNull($i);
$this->assertEquals($i->invoice_id, $invoice->id);
}
}

View File

@ -169,7 +169,7 @@ class InvoiceTest extends TestCase
$response->assertStatus(200); $response->assertStatus(200);
$invoice_update = [ $invoice_update = [
'invoice_status_id' => Invoice::STATUS_PAID 'status_id' => Invoice::STATUS_PAID
]; ];
$response = $this->withHeaders([ $response = $this->withHeaders([

View File

@ -27,21 +27,16 @@ class InvoiceTest extends TestCase
$this->invoice = InvoiceFactory::create(1,1);//stub the company and user_id $this->invoice = InvoiceFactory::create(1,1);//stub the company and user_id
$this->invoice->line_items = $this->buildLineItems(); $this->invoice->line_items = $this->buildLineItems();
$this->settings = $this->invoice->settings;
$this->invoice->settings = $this->buildSettings(); $this->settings->custom_taxes1 = true;
$this->invoice_calc = new InvoiceCalc($this->invoice); $this->settings->custom_taxes2 = true;
} $this->settings->inclusive_taxes = true;
$this->settings->precision = 2;
private function buildSettings() $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings);
{
$settings = new \stdClass;
$settings->custom_taxes1 = true;
$settings->custom_taxes2 = true;
$settings->inclusive_taxes = true;
$settings->precision = 2;
return $settings;
} }
private function buildLineItems() private function buildLineItems()
@ -118,9 +113,9 @@ class InvoiceTest extends TestCase
$this->invoice->custom_value1 = 5; $this->invoice->custom_value1 = 5;
$this->invoice->tax_name1 = 'GST'; $this->invoice->tax_name1 = 'GST';
$this->invoice->tax_rate1 = 10; $this->invoice->tax_rate1 = 10;
$this->invoice->settings->inclusive_taxes = false; $this->settings->inclusive_taxes = false;
$this->invoice_calc = new InvoiceCalc($this->invoice); $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings);
$this->invoice_calc->build(); $this->invoice_calc->build();
@ -133,7 +128,7 @@ class InvoiceTest extends TestCase
public function testInvoiceTotalsWithDiscountWithSurchargeWithDoubleExclusiveTax() public function testInvoiceTotalsWithDiscountWithSurchargeWithDoubleExclusiveTax()
{ {
$this->invoice_calc = new InvoiceCalc($this->invoice); $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings);
$this->invoice->discount = 5; $this->invoice->discount = 5;
$this->invoice->custom_value1 = 5; $this->invoice->custom_value1 = 5;
@ -141,7 +136,7 @@ class InvoiceTest extends TestCase
$this->invoice->tax_rate1 = 10; $this->invoice->tax_rate1 = 10;
$this->invoice->tax_name2 = 'GST'; $this->invoice->tax_name2 = 'GST';
$this->invoice->tax_rate2 = 10; $this->invoice->tax_rate2 = 10;
$this->invoice->settings->inclusive_taxes = false; $this->settings->inclusive_taxes = false;
$this->invoice_calc->build(); $this->invoice_calc->build();
@ -173,11 +168,11 @@ class InvoiceTest extends TestCase
$line_items[] = $item; $line_items[] = $item;
$this->invoice->line_items = $line_items; $this->invoice->line_items = $line_items;
$this->invoice->settings->inclusive_taxes = true; $this->settings->inclusive_taxes = true;
$this->invoice->discount = 0; $this->invoice->discount = 0;
$this->invoice->custom_value1 = 0; $this->invoice->custom_value1 = 0;
$this->invoice_calc = new InvoiceCalc($this->invoice); $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings);
$this->invoice_calc->build(); $this->invoice_calc->build();
$this->assertEquals($this->invoice_calc->getSubTotal(), 20); $this->assertEquals($this->invoice_calc->getSubTotal(), 20);
@ -215,8 +210,8 @@ class InvoiceTest extends TestCase
$this->invoice->tax_name2 = 'GST'; $this->invoice->tax_name2 = 'GST';
$this->invoice->tax_rate2 = 10; $this->invoice->tax_rate2 = 10;
$this->invoice->settings->inclusive_taxes = false; $this->settings->inclusive_taxes = false;
$this->invoice_calc = new InvoiceCalc($this->invoice); $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings);
$this->invoice_calc->build(); $this->invoice_calc->build();
$this->assertEquals($this->invoice_calc->getSubTotal(), 20); $this->assertEquals($this->invoice_calc->getSubTotal(), 20);