From e37c6912b188b8d510c46cd3a66fab13e0dbfad1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 24 Apr 2019 15:18:48 +1000 Subject: [PATCH] Refactor for invoice calculations, implementing testing for Invoice Invitation creation --- app/Factory/InvoiceFactory.php | 4 +- app/Helpers/Invoice/InvoiceCalc.php | 7 +- .../Invoice/CreateInvoiceInvitations.php | 18 ++- app/Models/Invoice.php | 5 + app/Models/InvoiceInvitation.php | 4 + app/Providers/EventServiceProvider.php | 5 +- app/Transformers/InvoiceTransformer.php | 4 +- database/factories/InvoiceFactory.php | 3 +- .../2014_10_13_000000_create_users_table.php | 10 +- .../assets/js/vue-i18n-locales.generated.js | 2 +- resources/lang/en/texts.php | 2 +- tests/Feature/InvitationTest.php | 132 ++++++++++++++++++ tests/Feature/InvoiceTest.php | 2 +- tests/Unit/InvoiceTest.php | 35 ++--- 14 files changed, 189 insertions(+), 44 deletions(-) create mode 100644 tests/Feature/InvitationTest.php diff --git a/app/Factory/InvoiceFactory.php b/app/Factory/InvoiceFactory.php index 7652bbdec9..c2a69aaac0 100644 --- a/app/Factory/InvoiceFactory.php +++ b/app/Factory/InvoiceFactory.php @@ -12,7 +12,7 @@ class InvoiceFactory public static function create(int $company_id, int $user_id) :\stdClass { $invoice = new \stdClass; - $invoice->invoice_status_id = Invoice::STATUS_DRAFT; + $invoice->status_id = Invoice::STATUS_DRAFT; $invoice->invoice_number = ''; $invoice->discount = 0; $invoice->is_amount_discount = true; @@ -41,7 +41,7 @@ class InvoiceFactory $invoice->partial = 0; $invoice->user_id = $user_id; $invoice->company_id = $company_id; - + return $invoice; } } diff --git a/app/Helpers/Invoice/InvoiceCalc.php b/app/Helpers/Invoice/InvoiceCalc.php index c46adb22dc..cda224b07c 100644 --- a/app/Helpers/Invoice/InvoiceCalc.php +++ b/app/Helpers/Invoice/InvoiceCalc.php @@ -46,13 +46,12 @@ class InvoiceCalc * * @param \App\Models\Invoice $invoice The invoice */ - public function __construct($invoice) + public function __construct($invoice, $settings) { $this->invoice = $invoice; - - $this->settings = $invoice->settings; - + $this->settings = $settings; + $this->tax_map = new Collection; } diff --git a/app/Listeners/Invoice/CreateInvoiceInvitations.php b/app/Listeners/Invoice/CreateInvoiceInvitations.php index 8780351cc6..39eb12f3d2 100644 --- a/app/Listeners/Invoice/CreateInvoiceInvitations.php +++ b/app/Listeners/Invoice/CreateInvoiceInvitations.php @@ -4,11 +4,14 @@ namespace App\Listeners\Invoice; use App\Models\ClientContact; use App\Models\InvoiceInvitation; +use App\Utils\Traits\MakesHash; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Support\Facades\Log; class CreateInvoiceInvitations { + use MakesHash; /** * 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 * @return void @@ -34,9 +38,15 @@ class CreateInvoiceInvitations $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), + ]); }); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 2cbf3b9b12..93295a1d73 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -44,4 +44,9 @@ class Invoice extends BaseModel { return $this->hasMany(InvoiceInvitation::class); } + + public function client() + { + return $this->belongsTo(Client::class); + } } diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php index 903814dcb5..be418513ad 100644 --- a/app/Models/InvoiceInvitation.php +++ b/app/Models/InvoiceInvitation.php @@ -10,6 +10,10 @@ class InvoiceInvitation extends BaseModel use MakesDates; + protected $guarded = [ + 'id', + ]; + /** * @return mixed */ diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index d8e0bbdd0a..7ac5153918 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -6,6 +6,7 @@ use App\Events\Client\ClientWasCreated; use App\Events\Invoice\InvoiceWasMarkedSent; use App\Events\User\UserCreated; use App\Listeners\Client\CreatedClientActivity; +use App\Listeners\Invoice\CreateInvoiceInvitations; use App\Listeners\SendVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -43,8 +44,8 @@ class EventServiceProvider extends ServiceProvider //Invoices [ - InvoiceWasMarkedSent::class => [ - CreateInvoiceInvitations::class + InvoiceWasMarkedSent::class => [ + CreateInvoiceInvitations::class, ] ], ]; diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index a2f0ab0f1f..10158ef623 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -19,7 +19,7 @@ class InvoiceTransformer extends EntityTransformer * @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="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="discount", type="number", format="float", example=10) * @SWG\Property(property="po_number", type="string", example="0001") @@ -122,7 +122,7 @@ class InvoiceTransformer extends EntityTransformer 'amount' => (float) $invoice->amount, 'balance' => (float) $invoice->balance, '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, 'archived_at' => $invoice->deleted_at, 'invoice_number' => $invoice->invoice_number, diff --git a/database/factories/InvoiceFactory.php b/database/factories/InvoiceFactory.php index a98de9e98f..fade39a775 100644 --- a/database/factories/InvoiceFactory.php +++ b/database/factories/InvoiceFactory.php @@ -6,7 +6,7 @@ use Faker\Generator as Faker; $factory->define(App\Models\Invoice::class, function (Faker $faker) { return [ - 'invoice_status_id' => App\Models\Invoice::STATUS_PAID, + 'status_id' => App\Models\Invoice::STATUS_DRAFT, 'invoice_number' => $faker->text(256), 'discount' => $faker->numberBetween(1,10), 'is_amount_discount' => $faker->boolean(), @@ -24,6 +24,5 @@ $factory->define(App\Models\Invoice::class, function (Faker $faker) { 'due_date' => $faker->date(), 'line_items' => false, 'backup' => '', - 'settings' => ClientSettings::buildClientSettings(new CompanySettings(CompanySettings::defaults()), new CompanySettings(ClientSettings::defaults())) ]; }); \ No newline at end of file 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 b3136af0f0..eb8850e251 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -341,7 +341,7 @@ class CreateUsersTable extends Migration $t->unsignedInteger('client_id')->index(); $t->unsignedInteger('user_id'); $t->unsignedInteger('company_id')->index(); - $t->unsignedInteger('invoice_status_id'); + $t->unsignedInteger('status_id'); $t->string('invoice_number'); $t->float('discount'); @@ -520,11 +520,11 @@ class CreateUsersTable extends Migration $t->string('message_id')->nullable(); $t->text('email_error'); $t->text('signature_base64'); - $t->timestamp('signature_date')->nullable(); + $t->date('signature_date')->nullable(); - $t->timestamp('sent_date')->nullable(); - $t->timestamp('viewed_date')->nullable(); - $t->timestamp('opened_date')->nullable(); + $t->date('sent_date')->nullable(); + $t->date('viewed_date')->nullable(); + $t->date('opened_date')->nullable(); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade'); diff --git a/resources/assets/js/vue-i18n-locales.generated.js b/resources/assets/js/vue-i18n-locales.generated.js index 8c1c55d386..435d89a1a3 100644 --- a/resources/assets/js/vue-i18n-locales.generated.js +++ b/resources/assets/js/vue-i18n-locales.generated.js @@ -16904,7 +16904,7 @@ export default { "add_item": "Add Item", "total_amount": "Total Amount", "pdf": "PDF", - "invoice_status_id": "Invoice Status", + "status_id": "Invoice Status", "click_plus_to_add_item": "Click + to add an item", "count_selected": "{count} selected", "dismiss": "Dismiss", diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 56f13f57d0..46be7cf5c0 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2901,7 +2901,7 @@ $LANG = array( 'add_item' => 'Add Item', 'total_amount' => 'Total Amount', 'pdf' => 'PDF', - 'invoice_status_id' => 'Invoice Status', + 'status_id' => 'Invoice Status', 'click_plus_to_add_item' => 'Click + to add an item', 'count_selected' => ':count selected', 'dismiss' => 'Dismiss', diff --git a/tests/Feature/InvitationTest.php b/tests/Feature/InvitationTest.php new file mode 100644 index 0000000000..94e393889e --- /dev/null +++ b/tests/Feature/InvitationTest.php @@ -0,0 +1,132 @@ +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); + + + + } +} diff --git a/tests/Feature/InvoiceTest.php b/tests/Feature/InvoiceTest.php index 568b60007e..55835c7c9a 100644 --- a/tests/Feature/InvoiceTest.php +++ b/tests/Feature/InvoiceTest.php @@ -169,7 +169,7 @@ class InvoiceTest extends TestCase $response->assertStatus(200); $invoice_update = [ - 'invoice_status_id' => Invoice::STATUS_PAID + 'status_id' => Invoice::STATUS_PAID ]; $response = $this->withHeaders([ diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index d7ea3e15c3..6ed1038f3e 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -27,21 +27,16 @@ class InvoiceTest extends TestCase $this->invoice = InvoiceFactory::create(1,1);//stub the company and user_id $this->invoice->line_items = $this->buildLineItems(); + + $this->settings = $this->invoice->settings; - $this->invoice->settings = $this->buildSettings(); - $this->invoice_calc = new InvoiceCalc($this->invoice); - } + $this->settings->custom_taxes1 = true; + $this->settings->custom_taxes2 = true; + $this->settings->inclusive_taxes = true; + $this->settings->precision = 2; - private function buildSettings() - { - $settings = new \stdClass; - $settings->custom_taxes1 = true; - $settings->custom_taxes2 = true; - $settings->inclusive_taxes = true; - $settings->precision = 2; - - return $settings; + $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings); } private function buildLineItems() @@ -118,9 +113,9 @@ class InvoiceTest extends TestCase $this->invoice->custom_value1 = 5; $this->invoice->tax_name1 = 'GST'; $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(); @@ -133,7 +128,7 @@ class InvoiceTest extends TestCase 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->custom_value1 = 5; @@ -141,7 +136,7 @@ class InvoiceTest extends TestCase $this->invoice->tax_rate1 = 10; $this->invoice->tax_name2 = 'GST'; $this->invoice->tax_rate2 = 10; - $this->invoice->settings->inclusive_taxes = false; + $this->settings->inclusive_taxes = false; $this->invoice_calc->build(); @@ -173,11 +168,11 @@ class InvoiceTest extends TestCase $line_items[] = $item; $this->invoice->line_items = $line_items; - $this->invoice->settings->inclusive_taxes = true; + $this->settings->inclusive_taxes = true; $this->invoice->discount = 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->assertEquals($this->invoice_calc->getSubTotal(), 20); @@ -215,8 +210,8 @@ class InvoiceTest extends TestCase $this->invoice->tax_name2 = 'GST'; $this->invoice->tax_rate2 = 10; - $this->invoice->settings->inclusive_taxes = false; - $this->invoice_calc = new InvoiceCalc($this->invoice); + $this->settings->inclusive_taxes = false; + $this->invoice_calc = new InvoiceCalc($this->invoice, $this->settings); $this->invoice_calc->build(); $this->assertEquals($this->invoice_calc->getSubTotal(), 20);