mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Merge: Make automatic conversion of quote to invoice optional (#636)
This commit is contained in:
parent
bf778aa616
commit
6166464003
@ -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');
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Services;
|
||||
|
||||
use Auth;
|
||||
use Utils;
|
||||
use URL;
|
||||
use App\Services\BaseService;
|
||||
@ -62,23 +63,42 @@ class InvoiceService extends BaseService
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
public function convertQuote($quote, $invitation = null)
|
||||
{
|
||||
$invoice = $this->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 "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddQuoteToInvoiceOption extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* Make the conversion of a quote into an invoice automatically after a client approves optional.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
}
|
@ -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'));
|
||||
|
@ -1,23 +1,24 @@
|
||||
<?php
|
||||
|
||||
class DatabaseSeeder extends Seeder {
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
|
||||
}
|
||||
$this->call('FontsSeeder');
|
||||
$this->call('InvoiceStatusSeeder');
|
||||
}
|
||||
}
|
||||
|
38
database/seeds/InvoiceStatusSeeder.php
Normal file
38
database/seeds/InvoiceStatusSeeder.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InvoiceStatus;
|
||||
|
||||
class InvoiceStatusSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
Eloquent::unguard();
|
||||
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1054,5 +1054,9 @@ return array(
|
||||
'username' => 'Username',
|
||||
'account_number' => 'Account Number',
|
||||
'bank_account_error' => 'Failed to retreive account details, please check your credentials.',
|
||||
|
||||
);
|
||||
'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.',
|
||||
|
||||
);
|
||||
|
@ -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' => '<b>BTW-tarieven per regel</b> tonen',
|
||||
|
@ -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)) }}
|
||||
|
||||
<div class="panel panel-default">
|
||||
@ -97,6 +98,7 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
@ -172,6 +174,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.quote_settings') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body form-padding-right">
|
||||
{!! Former::checkbox('auto_convert_quote')
|
||||
->text(trans('texts.enable'))
|
||||
->blockHelp(trans('texts.auto_convert_quote_help')) !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.default_messages') !!}</h3>
|
||||
@ -304,4 +317,4 @@
|
||||
|
||||
@section('onReady')
|
||||
$('#custom_invoice_label1').focus();
|
||||
@stop
|
||||
@stop
|
||||
|
Loading…
Reference in New Issue
Block a user