Recurring invoices no longer prevent invoice numbers from being sequential

This commit is contained in:
Hillel Coren 2015-08-10 18:48:41 +03:00
parent 07b3fdb30c
commit 78bf49cd19
30 changed files with 261 additions and 149 deletions

View File

@ -7,6 +7,7 @@ use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Invitation;
@ -16,12 +17,14 @@ class SendRecurringInvoices extends Command
protected $name = 'ninja:send-invoices';
protected $description = 'Send recurring invoices';
protected $mailer;
protected $invoiceRepo;
public function __construct(Mailer $mailer)
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
}
public function fire()
@ -34,74 +37,10 @@ class SendRecurringInvoices extends Command
$this->info(count($invoices).' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) {
if ($recurInvoice->client->deleted_at) {
continue;
}
if (!$recurInvoice->user->confirmed) {
continue;
}
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
if (!$recurInvoice->shouldSendToday()) {
continue;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1;
$invoice->custom_value2 = $recurInvoice->custom_value2;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
if ($invoice->client->payment_terms != 0) {
$days = $invoice->client->payment_terms;
if ($days == -1) {
$days = 0;
}
$invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d');
}
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem) {
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invoice->invitations()->save($invitation);
}
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
$this->invoiceRepo->createRecurringInvoice($recurInvoice);
$this->mailer->sendInvoice($invoice);
$recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
$recurInvoice->save();
}
$this->info('Done');

View File

@ -280,7 +280,7 @@ class InvoiceController extends BaseController
$method = 'POST';
$url = "{$entityType}s";
} else {
Utils::trackViewed($invoice->invoice_number.' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT';
$url = "{$entityType}s/{$publicId}";
}
@ -335,6 +335,7 @@ class InvoiceController extends BaseController
'url' => $url,
'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client,
'isRecurring' => $invoice->is_recurring,
'actions' => $actions);
$data = array_merge($data, self::getViewModel());
@ -358,10 +359,10 @@ class InvoiceController extends BaseController
return View::make('invoices.edit', $data);
}
public function create($clientPublicId = 0)
public function create($clientPublicId = 0, $isRecurring = false)
{
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber();
$invoiceNumber = $isRecurring ? microtime(true) : Auth::user()->account->getNextInvoiceNumber();
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
@ -375,12 +376,18 @@ class InvoiceController extends BaseController
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
'isRecurring' => $isRecurring,
'client' => $client);
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
public function createRecurring($clientPublicId = 0)
{
return self::create($clientPublicId, true);
}
private static function getViewModel()
{
$recurringHelp = '';
@ -510,7 +517,16 @@ class InvoiceController extends BaseController
return $this->convertQuote($publicId);
} elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
$response = $this->mailer->sendInvoice($invoice);
if ($invoice->is_recurring) {
if ($invoice->shouldSendToday()) {
$invoice = $this->invoiceRepo->createRecurringInvoice($invoice);
$response = $this->mailer->sendInvoice($invoice);
} else {
$response = trans('texts.recurring_too_soon');
}
} else {
$response = $this->mailer->sendInvoice($invoice);
}
if ($response === true) {
$message = trans("texts.emailed_{$entityType}");
Session::flash('message', $message);

View File

@ -158,7 +158,8 @@ class QuoteController extends BaseController
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::getDesigns(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'isRecurring' => false,
];
}

View File

@ -1,6 +1,5 @@
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
@ -132,7 +131,6 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('recurring_invoices', 'InvoiceController@recurringIndex');
Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable'));
Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory');
@ -141,6 +139,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::resource('invoices', 'InvoiceController');
Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable'));
Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk');

View File

@ -83,10 +83,6 @@ class Client extends EntityModel
return $this->name;
}
if (!$this->contacts || !count($this->contacts)) {
$this->load('contacts');
}
$contact = $this->contacts()->first();
return $contact->getDisplayName();

View File

@ -44,7 +44,7 @@ class EntityModel extends Eloquent
public function getActivityKey()
{
return '[' . $this->getEntityType().':'.$this->public_id.':'.$this->getName() . ']';
return '[' . $this->getEntityType().':'.$this->public_id.':'.$this->getDisplayName() . ']';
}
/*
@ -83,6 +83,11 @@ class EntityModel extends Eloquent
return $this->public_id;
}
public function getDisplayName()
{
return $this->getName();
}
// Remap ids to public_ids and show name
public function toPublicArray()
{

View File

@ -48,6 +48,11 @@ class Invoice extends EntityModel
return $this->belongsTo('App\Models\Invoice');
}
public function recurring_invoices()
{
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
}
public function invitations()
{
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -55,7 +60,7 @@ class Invoice extends EntityModel
public function getName()
{
return $this->invoice_number;
return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
}
public function getFileName()
@ -258,7 +263,9 @@ class Invoice extends EntityModel
}
Invoice::creating(function ($invoice) {
$invoice->account->incrementCounter($invoice->is_quote);
if (!$invoice->is_recurring) {
$invoice->account->incrementCounter($invoice->is_quote);
}
});
Invoice::created(function ($invoice) {

View File

@ -1,11 +1,12 @@
<?php namespace App\Ninja\Repositories;
use Carbon;
use Utils;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Invitation;
use App\Models\Product;
use App\Models\Task;
use Utils;
class InvoiceRepository
{
@ -552,4 +553,76 @@ class InvoiceRepository
->select(['public_id', 'invoice_number'])
->get();
}
public function createRecurringInvoice($recurInvoice)
{
$recurInvoice->load('account.timezone', 'invoice_items', 'client', 'user');
if ($recurInvoice->client->deleted_at) {
return false;
}
if (!$recurInvoice->user->confirmed) {
return false;
}
if (!$recurInvoice->shouldSendToday()) {
return false;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1;
$invoice->custom_value2 = $recurInvoice->custom_value2;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
if ($invoice->client->payment_terms != 0) {
$days = $invoice->client->payment_terms;
if ($days == -1) {
$days = 0;
}
$invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d');
}
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem) {
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invoice->invitations()->save($invitation);
}
$recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
$recurInvoice->save();
return $invoice;
}
}

View File

@ -40,10 +40,13 @@ class AppServiceProvider extends ServiceProvider {
<ul class="dropdown-menu" id="menu1">
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>';
if ($type == ENTITY_INVOICE && Auth::user()->isPro()) {
$str .= '<li class="divider"></li>
if ($type == ENTITY_INVOICE) {
$str .= '<li><a href="'.URL::to('recurring_invoices/create').'">'.trans("texts.new_recurring_invoice").'</a></li>';
if (Auth::user()->isPro()) {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>
<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';
}
} else if ($type == ENTITY_CLIENT) {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li>

View File

@ -19,7 +19,7 @@ class AddPartialAmountToInvoices extends Migration {
Schema::table('accounts', function($table)
{
$table->boolean('utf8_invoices')->default(false);
$table->boolean('utf8_invoices')->default(true);
$table->boolean('auto_wrap')->default(false);
$table->string('subdomain')->nullable();
});

View File

@ -31736,7 +31736,7 @@ NINJA.invoiceLines = function(invoice) {
return NINJA.prepareDataTable(grid, 'invoiceItems');
}
NINJA.subtotals = function(invoice, removeBalance)
NINJA.subtotals = function(invoice, hideBalance)
{
if (!invoice) {
return;
@ -31774,7 +31774,7 @@ NINJA.subtotals = function(invoice, removeBalance)
data.push([{text:invoiceLabels.paid_to_date}, {text:formatMoney(paid, invoice.client.currency_id)}]);
}
if (!removeBalance) {
if (!hideBalance) {
var isPartial = NINJA.parseFloat(invoice.partial);
data.push([
{text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']},

View File

@ -262,7 +262,7 @@ NINJA.invoiceLines = function(invoice) {
return NINJA.prepareDataTable(grid, 'invoiceItems');
}
NINJA.subtotals = function(invoice, removeBalance)
NINJA.subtotals = function(invoice, hideBalance)
{
if (!invoice) {
return;
@ -300,7 +300,7 @@ NINJA.subtotals = function(invoice, removeBalance)
data.push([{text:invoiceLabels.paid_to_date}, {text:formatMoney(paid, invoice.client.currency_id)}]);
}
if (!removeBalance) {
if (!hideBalance) {
var isPartial = NINJA.parseFloat(invoice.partial);
data.push([
{text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']},

View File

@ -707,7 +707,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -745,6 +744,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -697,7 +697,6 @@ return array(
'or' => 'oder',
'email_error' => 'Es gab ein Problem beim Senden dieses E-Mails.',
'created_by_recurring' => 'Erzeugt durch die wiederkehrende Rechnung :invoice',
'confirm_recurring_timing' => 'Beachten Sie: E-Mails werden zu Beginn der Stunde gesendet.',
'old_browser' => 'Bitte verwenden Sie einen <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">neueren Browser</a>',
'payment_terms_help' => 'Setzt das Standardfälligkeitssdatum',
@ -735,5 +734,11 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -705,7 +705,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -744,7 +743,11 @@ return array(
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -677,7 +677,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -715,5 +714,11 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -706,7 +706,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -744,6 +743,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -698,7 +698,6 @@ return array(
'or' => 'ou',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -736,6 +735,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -699,7 +699,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -737,5 +736,11 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -701,7 +701,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -739,6 +738,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -708,7 +708,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -746,6 +745,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -706,7 +706,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -744,6 +743,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -701,7 +701,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -739,6 +738,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -701,7 +701,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -739,5 +738,11 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -704,7 +704,6 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
@ -742,6 +741,12 @@ return array(
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice',
'created_by_invoice' => 'Created by :invoice',
);

View File

@ -17,8 +17,12 @@
@if ($invoice && $invoice->id)
<ol class="breadcrumb">
<li>{!! link_to(($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'), trans('texts.' . ($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'))) !!}</li>
<li class='active'>{{ $invoice->invoice_number }}</li>
@if ($isRecurring)
<li>{!! link_to('invoices', trans('texts.recurring_invoice')) !!}</li>
@else
<li>{!! link_to(($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'), trans('texts.' . ($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'))) !!}</li>
<li class='active'>{{ $invoice->invoice_number }}</li>
@endif
</ol>
@endif
@ -79,53 +83,41 @@
<div class="col-md-4" id="col_2">
<div data-bind="visible: !is_recurring()">
{!! Former::text('invoice_date')->data_bind("datePicker: invoice_date, valueUpdate: 'afterkeydown'")->label(trans("texts.{$entityType}_date"))
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'invoice_date\')"></i>') !!}
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('invoice_date') !!}
{!! Former::text('due_date')->data_bind("datePicker: due_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'due_date\')"></i>') !!}
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('due_date') !!}
{!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown'")->onchange('onPartialChange()')
->rel('tooltip')->data_toggle('tooltip')->data_placement('bottom')->title(trans('texts.partial_value')) !!}
</div>
@if ($entityType == ENTITY_INVOICE)
<div data-bind="visible: is_recurring" style="display: none">
{!! Former::select('frequency_id')->options($frequencies)->data_bind("value: frequency_id") !!}
{!! Former::select('frequency_id')->options($frequencies)->data_bind("value: frequency_id")
->appendIcon('question-sign')->addGroupClass('frequency_id') !!}
{!! Former::text('start_date')->data_bind("datePicker: start_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'start_date\')"></i>') !!}
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('start_date') !!}
{!! Former::text('end_date')->data_bind("datePicker: end_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'end_date\')"></i>') !!}
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('end_date') !!}
</div>
@if ($invoice && $invoice->recurring_invoice)
<div class="pull-right" style="padding-top: 6px">
{!! trans('texts.created_by_recurring', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, $invoice->recurring_invoice->invoice_number)]) !!}
{!! trans('texts.created_by_invoice', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, trans('texts.recurring_invoice'))]) !!}
</div>
@else
<div data-bind="visible: invoice_status_id() === 0">
<div class="form-group">
<label for="" class="control-label col-lg-4 col-sm-4">
{{ trans('texts.recurring') }}
</label>
<div class="col-lg-8 col-sm-8">
<div class="checkbox">
<label for="recurring" class="">
<input onclick="onRecurringEnabled()" data-bind="checked: is_recurring" id="recurring" type="checkbox" name="recurring" value="1">{{ trans('texts.enable') }} &nbsp;&nbsp;
<a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> {{ trans('texts.learn_more') }}</a>
</label>
</div>
</div>
@elseif ($invoice && $invoice->last_sent_date)
<div class="pull-right" style="padding-top: 6px">
{!! trans('texts.last_invoice_sent', [
'date' => link_to('/invoices/'.$invoice->recurring_invoices->last()->public_id, Utils::dateToString($invoice->last_sent_date))
]) !!}
</div>
@if ($invoice && $invoice->last_sent_date)
<div class="pull-right">
{{ trans('texts.last_invoice_sent', ['date' => Utils::dateToString($invoice->last_sent_date)]) }}
</div>
@endif
</div>
@endif
@endif
@endif
</div>
<div class="col-md-4" id="col_2">
{!! Former::text('invoice_number')->label(trans("texts.{$entityType}_number_short"))->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!}
@if (!$isRecurring)
{!! Former::text('invoice_number')->label(trans("texts.{$entityType}_number_short"))->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!}
@endif
{!! Former::text('po_number')->label(trans('texts.po_number_short'))->data_bind("value: po_number, valueUpdate: 'afterkeydown'") !!}
{!! Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'")
->addGroupClass('discount-group')->type('number')->min('0')->step('any')->append(
@ -334,10 +326,7 @@
@if (!$invoice || (!$invoice->trashed() && !$invoice->client->trashed()))
{!! Button::success(trans("texts.save_{$entityType}"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onSaveClick()'))->appendIcon(Icon::create('floppy-disk')) !!}
@if (!$invoice || ($invoice && !$invoice->is_recurring))
{!! Button::info(trans("texts.email_{$entityType}"))->withAttributes(array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->appendIcon(Icon::create('send')) !!}
@endif
{!! Button::info(trans("texts.email_{$entityType}"))->withAttributes(array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->appendIcon(Icon::create('send')) !!}
@if ($invoice && $invoice->id)
{!! DropdownButton::normal(trans('texts.more_actions'))
@ -604,6 +593,20 @@
}, 1);
});
$('.frequency_id .input-group-addon').click(function() {
showLearnMore();
});
var fields = ['invoice_date', 'due_date', 'start_date', 'end_date'];
for (var i=0; i<fields.length; i++) {
var field = fields[i];
(function (_field) {
$('.' + _field + ' .input-group-addon').click(function() {
toggleDatePicker(_field);
});
})(field);
}
@if ($client || $invoice || count($clients) == 0)
$('#invoice_number').focus();
@else
@ -688,6 +691,10 @@
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
if (invoice.is_recurring) {
invoice.invoice_number = '0000';
}
@if (!$invoice)
if (!invoice.terms) {
invoice.terms = wordWrapText('{!! str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) !!}', 300);
@ -1115,10 +1122,10 @@
var self = this;
this.client = ko.observable(data ? false : new ClientModel());
self.account = {!! $account !!};
this.id = ko.observable('');
self.id = ko.observable('');
self.discount = ko.observable('');
self.is_amount_discount = ko.observable(0);
self.frequency_id = ko.observable('');
self.frequency_id = ko.observable(4); // default to monthly
self.terms = ko.observable('');
self.default_terms = ko.observable({{ !$invoice && $account->invoice_terms ? 'true' : 'false' }} ? wordWrapText('{!! str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) !!}', 300) : '');
self.set_default_terms = ko.observable(false);
@ -1134,7 +1141,7 @@
self.end_date = ko.observable('');
self.tax_name = ko.observable();
self.tax_rate = ko.observable();
self.is_recurring = ko.observable(false);
self.is_recurring = ko.observable({{ $isRecurring ? 'true' : 'false' }});
self.invoice_status_id = ko.observable(0);
self.invoice_items = ko.observableArray();
self.amount = ko.observable(0);

View File

@ -65,13 +65,13 @@
"$notesAndTerms",
{
"table": {
"widths": ["*", "auto"],
"widths": ["*", "40%"],
"body": "$subtotals"
},
"layout": {
"hLineWidth": "$none",
"vLineWidth": "$none",
"paddingLeft": "$amount:34",
"paddingLeft": "$amount:8",
"paddingRight": "$amount:8",
"paddingTop": "$amount:4",
"paddingBottom": "$amount:4"

View File

@ -30,7 +30,7 @@
"table": {
"body": "$invoiceDetails"
},
"margin": [0, 4, 12, 4],
"margin": [0, 4, 12, 4],
"layout": "noBorders"
},
{
@ -74,7 +74,7 @@
"$notesAndTerms",
{
"table": {
"widths": ["*", "auto"],
"widths": ["*", "40%"],
"body": "$subtotals"
},
"layout": {
@ -114,6 +114,9 @@
"color": "$primaryColor:#37a3c6",
"bold": true
},
"invoiceDetails": {
"margin": [0, 0, 8, 0]
},
"accountDetails": {
"margin": [0, 2, 0, 2]
},
@ -134,10 +137,10 @@
"bold": true
},
"balanceDueLabel": {
"fontSize": "$fontSizeLargest"
"fontSize": "$fontSizeLarger"
},
"balanceDue": {
"fontSize": "$fontSizeLargest",
"fontSize": "$fontSizeLarger",
"color": "$primaryColor:#37a3c6"
},
"invoiceNumber": {
@ -145,7 +148,7 @@
},
"tableHeader": {
"bold": true,
"fontSize": "$fontSizeLargest"
"fontSize": "$fontSizeLarger"
},
"invoiceLineItemsTable": {
"margin": [0, 16, 0, 16]

View File

@ -37,7 +37,7 @@
"$notesAndTerms",
{
"table": {
"widths": ["*", "auto"],
"widths": ["*", "40%"],
"body": "$subtotalsWithoutBalance"
},
"layout": {

View File

@ -66,7 +66,7 @@
{
"style": "subtotals",
"table": {
"widths": ["*", "auto"],
"widths": ["*", "40%"],
"body": "$subtotals"
},
"layout": {