diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 9ec3fea97e..2435f70119 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -1065,6 +1065,7 @@ class AccountController extends BaseController $account->quote_terms = Input::get('quote_terms'); $account->auto_convert_quote = Input::get('auto_convert_quote'); $account->auto_archive_quote = Input::get('auto_archive_quote'); + $account->require_approve_quote = Input::get('require_approve_quote'); $account->allow_approve_expired_quote = Input::get('allow_approve_expired_quote'); $account->auto_archive_invoice = Input::get('auto_archive_invoice'); $account->auto_email_invoice = Input::get('auto_email_invoice'); diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index e1f27d331f..4bf1c92849 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -146,13 +146,14 @@ class ClientPortalController extends BaseController } } - $showApprove = $invoice->quote_invoice_id ? false : true; + $showApprove = ($invoice->isQuote() && $account->require_approve_quote) ? true: false; if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) { $showApprove = false; } $data += [ 'account' => $account, + 'approveRequired' => $account->require_approve_quote, 'showApprove' => $showApprove, 'showBreadcrumbs' => false, 'invoice' => $invoice->hidePrivateFields(), diff --git a/app/Models/Account.php b/app/Models/Account.php index 008c99e7e9..2bfd0916db 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -109,6 +109,8 @@ class Account extends Eloquent 'body_font_id', 'auto_convert_quote', 'auto_archive_quote', + 'require_approve_quote', + 'allow_approve_expired_quote', 'auto_archive_invoice', 'auto_email_invoice', 'all_pages_footer', diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 334eb49a7c..9e5e1c55c8 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -542,6 +542,20 @@ class Invoice extends EntityModel implements BalanceAffecting }); } + /** + * @return Invitation|null + */ + public function invitationByContactId(int $contactId) + { + foreach ($this->invitations as $invitation) { + if ($invitation->contact_id === $contactId) { + return $invitation; + } + } + + return null; + } + /** * @param $typeId * @@ -787,7 +801,15 @@ class Invoice extends EntityModel implements BalanceAffecting public function canBePaid() { - return ! $this->isPaid() && ! $this->is_deleted && $this->isStandard(); + // already paid or deleted + if ($this->isPaid() || $this->is_deleted) return false; + + // if quote approve is required, them only standard invoices can be paid + if ($this->account->require_approve_quote) { + return $this->isStandard(); + } + + return true; } public static function calcStatusLabel($status, $class, $entityType, $quoteInvoiceId) diff --git a/app/Ninja/PaymentDrivers/BasePaymentDriver.php b/app/Ninja/PaymentDrivers/BasePaymentDriver.php index 9e672a56fb..9bbc232dec 100644 --- a/app/Ninja/PaymentDrivers/BasePaymentDriver.php +++ b/app/Ninja/PaymentDrivers/BasePaymentDriver.php @@ -698,6 +698,14 @@ class BasePaymentDriver if (! $invoice->canBePaid()) { return false; } + + // check if invoice is quote and if is, them convert it + if($invoice->isQuote()) { + $invoiceService = app('App\Services\InvoiceService'); + $invoice = $invoiceService->convertQuote($invoice); + $invitation = $invoice->invitationByContactId($invitation->contact_id); + } + $invoice->markSentIfUnsent(); $payment = Payment::createNew($invitation); diff --git a/database/migrations/2019_04_29_073828_add_require_approve_quote.php b/database/migrations/2019_04_29_073828_add_require_approve_quote.php new file mode 100644 index 0000000000..8a728bde40 --- /dev/null +++ b/database/migrations/2019_04_29_073828_add_require_approve_quote.php @@ -0,0 +1,32 @@ +boolean('require_approve_quote')->default(1); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function ($table) { + $table->dropColumn('require_approve_quote'); + }); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 4842af52ad..f223fc96cc 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2842,6 +2842,8 @@ $LANG = array( 'auto_archive_invoice_help' => 'Automatically archive invoices when they are paid.', 'auto_archive_quote' => 'Auto Archive', 'auto_archive_quote_help' => 'Automatically archive quotes when they are converted.', + 'require_approve_quote' => 'Require approve quote', + 'require_approve_quote_help' => 'Require clients to approve quotes.', 'allow_approve_expired_quote' => 'Allow approve expired quote', 'allow_approve_expired_quote_help' => 'Allow clients to approve expired quotes.', 'invoice_workflow' => 'Invoice Workflow', diff --git a/resources/views/accounts/invoice_settings.blade.php b/resources/views/accounts/invoice_settings.blade.php index e79e4a325a..9148a94ab5 100644 --- a/resources/views/accounts/invoice_settings.blade.php +++ b/resources/views/accounts/invoice_settings.blade.php @@ -405,16 +405,21 @@
- {!! Former::checkbox('auto_convert_quote') - ->text(trans('texts.enable')) - ->blockHelp(trans('texts.auto_convert_quote_help')) - ->value(1) !!} - {!! Former::checkbox('auto_archive_quote') ->text(trans('texts.enable')) ->blockHelp(trans('texts.auto_archive_quote_help')) ->value(1) !!} + {!! Former::checkbox('require_approve_quote') + ->text(trans('texts.enable')) + ->blockHelp(trans('texts.require_approve_quote_help')) + ->value(1) !!} + + {!! Former::checkbox('auto_convert_quote') + ->text(trans('texts.enable')) + ->blockHelp(trans('texts.auto_convert_quote_help')) + ->value(1) !!} + {!! Former::checkbox('allow_approve_expired_quote') ->text(trans('texts.enable')) ->blockHelp(trans('texts.allow_approve_expired_quote_help')) @@ -631,6 +636,7 @@ onClientNumberEnabled(); onCreditNumberEnabled(); onResetFrequencyChange(); + updateCheckboxes(); $('#reset_counter_date').datepicker('update', '{{ Utils::fromSqlDate($account->reset_counter_date) ?: 'new Date()' }}'); $('.reset_counter_date_group .input-group-addon').click(function() { @@ -642,6 +648,14 @@ @endif }); + $('#require_approve_quote').change(updateCheckboxes); + + function updateCheckboxes() { + var checked = $('#require_approve_quote').is(':checked'); + $('#auto_convert_quote').prop('disabled', ! checked); + $('#allow_approve_expired_quote').prop('disabled', ! checked); + } + diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index adf73710f9..0de6442bbb 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -160,13 +160,16 @@ @include($partialView) @else
- @if ($invoice->isQuote()) + @if ($invoice->isQuote() && $approveRequired) {!! Button::normal(trans('texts.download'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}   @if ($showApprove) {!! Button::success(trans('texts.approve'))->withAttributes(['id' => 'approveButton', 'onclick' => 'onApproveClick()', 'class' => 'require-authorization'])->large() !!} @elseif ($invoiceLink = $invoice->getInvoiceLinkForQuote($contact->id)) {!! Button::success(trans('texts.view_invoice'))->asLinkTo($invoiceLink)->large() !!} @endif + @elseif ($invoice->isQuote() && $invoiceLink = $invoice->getInvoiceLinkForQuote($contact->id)) + {!! Button::normal(trans('texts.download'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!} + {!! Button::success(trans('texts.view_invoice'))->asLinkTo($invoiceLink)->large() !!} @elseif ( ! $invoice->canBePaid()) {!! Button::normal(trans('texts.download'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!} @elseif ($invoice->client->account->isGatewayConfigured() && floatval($invoice->balance) && !$invoice->is_recurring)