From 616646400335f50c1e98ce2044f68475cbd79fba Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 20 Jan 2016 10:41:57 +0200 Subject: [PATCH] Merge: Make automatic conversion of quote to invoice optional (#636) --- app/Http/Controllers/AccountController.php | 1 + app/Http/Controllers/InvoiceController.php | 2 +- .../Controllers/PublicClientController.php | 5 +- app/Http/Controllers/QuoteController.php | 2 +- app/Http/routes.php | 5 +- app/Models/Invoice.php | 8 ++++ app/Services/InvoiceService.php | 41 +++++++++++++---- ..._17_155725_add_quote_to_invoice_option.php | 46 +++++++++++++++++++ database/seeds/ConstantsSeeder.php | 6 --- database/seeds/DatabaseSeeder.php | 35 +++++++------- database/seeds/InvoiceStatusSeeder.php | 38 +++++++++++++++ public/js/built.js | 6 ++- public/js/script.js | 7 +-- resources/lang/en/texts.php | 8 +++- resources/lang/nl/texts.php | 2 + .../views/accounts/invoice_settings.blade.php | 23 ++++++++-- 16 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 database/migrations/2016_01_17_155725_add_quote_to_invoice_option.php create mode 100644 database/seeds/InvoiceStatusSeeder.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index ddf43bb33050..697c100129e5 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -662,6 +662,7 @@ class AccountController extends BaseController $account->invoice_terms = Input::get('invoice_terms'); $account->invoice_footer = Input::get('invoice_footer'); $account->quote_terms = Input::get('quote_terms'); + $account->auto_convert_quote = Input::get('auto_convert_quote'); if (Input::has('recurring_hour')) { $account->recurring_hour = Input::get('recurring_hour'); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 1d165d0de9cb..68abcc92f2c3 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -475,7 +475,7 @@ class InvoiceController extends BaseController public function convertQuote($publicId) { $invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail(); - $clone = $this->invoiceService->approveQuote($invoice); + $clone = $this->invoiceService->convertQuote($invoice); Session::flash('message', trans('texts.converted_to_invoice')); return Redirect::to('invoices/'.$clone->public_id); diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index aabd2fdf1652..834a89beac59 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -97,6 +97,9 @@ class PublicClientController extends BaseController if ($invoice->due_date) { $showApprove = time() < strtotime($invoice->due_date); } + if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) { + $showApprove = false; + } // Checkout.com requires first getting a payment token $checkoutComToken = false; @@ -341,4 +344,4 @@ class PublicClientController extends BaseController return $invitation; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 7c13b51f8336..47b5c1a338e4 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -131,7 +131,7 @@ class QuoteController extends BaseController if ($action == 'convert') { $invoice = Invoice::with('invoice_items')->scope($ids)->firstOrFail(); - $clone = $this->invoiceService->approveQuote($invoice); + $clone = $this->invoiceService->convertQuote($invoice); Session::flash('message', trans('texts.converted_to_invoice')); return Redirect::to('invoices/'.$clone->public_id); diff --git a/app/Http/routes.php b/app/Http/routes.php index a5e903eb3cf8..e31d463bfb03 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -368,8 +368,9 @@ if (!defined('CONTACT_EMAIL')) { define('INVOICE_STATUS_DRAFT', 1); define('INVOICE_STATUS_SENT', 2); define('INVOICE_STATUS_VIEWED', 3); - define('INVOICE_STATUS_PARTIAL', 4); - define('INVOICE_STATUS_PAID', 5); + define('INVOICE_STATUS_APPROVED', 4); + define('INVOICE_STATUS_PARTIAL', 5); + define('INVOICE_STATUS_PAID', 6); define('PAYMENT_TYPE_CREDIT', 1); define('CUSTOM_DESIGN', 11); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 3d01216629cf..5fb63c00d806 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -252,6 +252,14 @@ class Invoice extends EntityModel implements BalanceAffecting } } + public function markApproved() + { + if ($this->is_quote) { + $this->invoice_status_id = INVOICE_STATUS_APPROVED; + $this->save(); + } + } + public function updateBalances($balanceAdjustment, $partial = 0) { if ($this->is_deleted) { diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index 664180e5a95f..8648f315f6cf 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -1,5 +1,6 @@ invoiceRepo->cloneInvoice($quote, $quote->id); + if (!$invitation) { + return $invoice; + } + + foreach ($invoice->invitations as $invoiceInvitation) { + if ($invitation->contact_id == $invoiceInvitation->contact_id) { + return $invoiceInvitation->invitation_key; + } + } + } + public function approveQuote($quote, $invitation = null) { + $account = Auth::user()->account; if (!$quote->is_quote || $quote->quote_invoice_id) { return null; } - $invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id); + if ($account->auto_convert_quote) { + $invoice = $this->convertQuote($quote, $invitation); + + event(new QuoteInvitationWasApproved($quote, $invoice, $invitation)); - if (!$invitation) { return $invoice; - } + } else { + $quote->markApproved(); - event(new QuoteInvitationWasApproved($quote, $invoice, $invitation)); - - foreach ($invoice->invitations as $invoiceInvitation) { - if ($invitation->contact_id == $invoiceInvitation->contact_id) { - return $invoiceInvitation->invitation_key; + event(new QuoteInvitationWasApproved($quote, null, $invitation)); + + foreach ($quote->invitations as $invoiceInvitation) { + if ($invitation->contact_id == $invoiceInvitation->contact_id) { + return $invoiceInvitation->invitation_key; + } } } } @@ -227,6 +247,9 @@ class InvoiceService extends BaseService case INVOICE_STATUS_VIEWED: $class = 'warning'; break; + case INVOICE_STATUS_APPROVED: + $class = 'success'; + break; case INVOICE_STATUS_PARTIAL: $class = 'primary'; break; @@ -237,4 +260,4 @@ class InvoiceService extends BaseService return "

$label

"; } -} \ No newline at end of file +} diff --git a/database/migrations/2016_01_17_155725_add_quote_to_invoice_option.php b/database/migrations/2016_01_17_155725_add_quote_to_invoice_option.php new file mode 100644 index 000000000000..f3183ad8f2b5 --- /dev/null +++ b/database/migrations/2016_01_17_155725_add_quote_to_invoice_option.php @@ -0,0 +1,46 @@ +boolean('auto_convert_quote')->default(true); + }); + + // we need to create the last status to resolve a foreign key constraint + DB::table('invoice_statuses')->insert([ + 'id' => 6, + 'name' => 'Paid' + ]); + + DB::table('invoices') + ->whereIn('invoice_status_id', [4, 5]) + ->increment('invoice_status_id'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function (Blueprint $table) { + $table->dropColumn('auto_convert_quote'); + }); + + DB::table('invoices') + ->whereIn('invoice_status_id', [5, 6]) + ->decrement('invoice_status_id'); + } +} diff --git a/database/seeds/ConstantsSeeder.php b/database/seeds/ConstantsSeeder.php index 66ff33e47b47..8bef4c105342 100644 --- a/database/seeds/ConstantsSeeder.php +++ b/database/seeds/ConstantsSeeder.php @@ -50,12 +50,6 @@ class ConstantsSeeder extends Seeder Theme::create(array('name' => 'united')); Theme::create(array('name' => 'yeti')); - InvoiceStatus::create(array('name' => 'Draft')); - InvoiceStatus::create(array('name' => 'Sent')); - InvoiceStatus::create(array('name' => 'Viewed')); - InvoiceStatus::create(array('name' => 'Partial')); - InvoiceStatus::create(array('name' => 'Paid')); - Frequency::create(array('name' => 'Weekly')); Frequency::create(array('name' => 'Two weeks')); Frequency::create(array('name' => 'Four weeks')); diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 47cc52a9a713..f808c5a3802e 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -1,23 +1,24 @@ command->info('Running DatabaseSeeder'); - /** - * Run the database seeds. - * - * @return void - */ - public function run() - { - $this->command->info('Running DatabaseSeeder'); + Eloquent::unguard(); - Eloquent::unguard(); - - $this->call('ConstantsSeeder'); - $this->call('CountriesSeeder'); - $this->call('PaymentLibrariesSeeder'); + $this->call('ConstantsSeeder'); + $this->call('CountriesSeeder'); + $this->call('PaymentLibrariesSeeder'); $this->call('FontsSeeder'); $this->call('BanksSeeder'); - } - -} \ No newline at end of file + $this->call('FontsSeeder'); + $this->call('InvoiceStatusSeeder'); + } +} diff --git a/database/seeds/InvoiceStatusSeeder.php b/database/seeds/InvoiceStatusSeeder.php new file mode 100644 index 000000000000..2b551ae7eace --- /dev/null +++ b/database/seeds/InvoiceStatusSeeder.php @@ -0,0 +1,38 @@ +createInvoiceStatuses(); + + Eloquent::reguard(); + } + + private function createInvoiceStatuses() + { + $statuses = [ + ['id' => '1', 'name' => 'Draft'], + ['id' => '2', 'name' => 'Sent'], + ['id' => '3', 'name' => 'Viewed'], + ['id' => '4', 'name' => 'Approved'], + ['id' => '5', 'name' => 'Partial'], + ['id' => '6', 'name' => 'Paid'], + ]; + + foreach ($statuses as $status) { + $record = InvoiceStatus::find($status['id']); + if ($record) { + $record->name = $status['name']; + $record->save(); + } else { + InvoiceStatus::create($status); + } + } + } +} + diff --git a/public/js/built.js b/public/js/built.js index 4331c8699121..a26e6f108f0e 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -30394,8 +30394,9 @@ var CONSTS = {}; CONSTS.INVOICE_STATUS_DRAFT = 1; CONSTS.INVOICE_STATUS_SENT = 2; CONSTS.INVOICE_STATUS_VIEWED = 3; -CONSTS.INVOICE_STATUS_PARTIAL = 4; -CONSTS.INVOICE_STATUS_PAID = 5; +CONSTS.INVOICE_STATUS_APPROVED = 4; +CONSTS.INVOICE_STATUS_PARTIAL = 5; +CONSTS.INVOICE_STATUS_PAID = 6; $.fn.datepicker.defaults.autoclose = true; $.fn.datepicker.defaults.todayHighlight = true; @@ -30870,6 +30871,7 @@ function actionListHandler() { } }); } + var NINJA = NINJA || {}; NINJA.TEMPLATES = { diff --git a/public/js/script.js b/public/js/script.js index 8749599e59b9..3857feae5f00 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -516,8 +516,9 @@ var CONSTS = {}; CONSTS.INVOICE_STATUS_DRAFT = 1; CONSTS.INVOICE_STATUS_SENT = 2; CONSTS.INVOICE_STATUS_VIEWED = 3; -CONSTS.INVOICE_STATUS_PARTIAL = 4; -CONSTS.INVOICE_STATUS_PAID = 5; +CONSTS.INVOICE_STATUS_APPROVED = 4; +CONSTS.INVOICE_STATUS_PARTIAL = 5; +CONSTS.INVOICE_STATUS_PAID = 6; $.fn.datepicker.defaults.autoclose = true; $.fn.datepicker.defaults.todayHighlight = true; @@ -991,4 +992,4 @@ function actionListHandler() { $(this).closest('tr').find('.tr-status').show(); } }); -} \ No newline at end of file +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index ddec22ce8012..11653195376e 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1054,5 +1054,9 @@ return array( 'username' => 'Username', 'account_number' => 'Account Number', 'bank_account_error' => 'Failed to retreive account details, please check your credentials.', - -); \ No newline at end of file + 'status_approved' => 'Approved', + 'quote_settings' => 'Quote Settings', + 'auto_convert_quote' => 'Auto convert quote', + 'auto_convert_quote_help' => 'When a client approves a quote automatically convert it to an invoice.', + +); diff --git a/resources/lang/nl/texts.php b/resources/lang/nl/texts.php index 0a133e9fadce..6a6316a9f286 100644 --- a/resources/lang/nl/texts.php +++ b/resources/lang/nl/texts.php @@ -390,6 +390,7 @@ return array( 'notification_quote_viewed_subject' => 'Offerte :invoice is bekeken door :client', 'notification_quote_sent' => 'Klant :client heeft offerte :invoice voor :amount per email ontvangen.', 'notification_quote_viewed' => 'Klant :client heeft offerte :invoice voor :amount bekeken.', + 'auto_convert_quote' => 'Offerte automatisch omzetten in factuur als deze goed gekeurd wordt', 'session_expired' => 'Uw sessie is verlopen.', @@ -757,6 +758,7 @@ return array( 'status_draft' => 'Concept', 'status_sent' => 'Verstuurd', 'status_viewed' => 'Bekeken', + 'status_approved' => 'Goedgekeurd', 'status_partial' => 'Gedeeltelijk', 'status_paid' => 'Betaald', 'show_line_item_tax' => 'BTW-tarieven per regel tonen', diff --git a/resources/views/accounts/invoice_settings.blade.php b/resources/views/accounts/invoice_settings.blade.php index 6a8e18e8a38f..af9bdefadae9 100644 --- a/resources/views/accounts/invoice_settings.blade.php +++ b/resources/views/accounts/invoice_settings.blade.php @@ -20,10 +20,11 @@ @parent @include('accounts.nav', ['selected' => ACCOUNT_INVOICE_SETTINGS, 'advanced' => true]) - {!! Former::open()->rules(['iframe_url' => 'url'])->addClass('warn-on-exit') !!} - {{ Former::populate($account) }} - {{ Former::populateField('custom_invoice_taxes1', intval($account->custom_invoice_taxes1)) }} - {{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }} + {!! Former::open()->rules(['iframe_url' => 'url'])->addClass('warn-on-exit') !!} + {{ Former::populate($account) }} + {{ Former::populateField('auto_convert_quote', intval($account->auto_convert_quote)) }} + {{ Former::populateField('custom_invoice_taxes1', intval($account->custom_invoice_taxes1)) }} + {{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }} {{ Former::populateField('share_counter', intval($account->share_counter)) }}
@@ -97,6 +98,7 @@
+
@@ -172,6 +174,17 @@
+
+
+

{!! trans('texts.quote_settings') !!}

+
+
+ {!! Former::checkbox('auto_convert_quote') + ->text(trans('texts.enable')) + ->blockHelp(trans('texts.auto_convert_quote_help')) !!} +
+
+

{!! trans('texts.default_messages') !!}

@@ -304,4 +317,4 @@ @section('onReady') $('#custom_invoice_label1').focus(); -@stop \ No newline at end of file +@stop