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 @@
+
+
+
+
{!! 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