Added custom invoice fields

This commit is contained in:
Hillel Coren 2014-07-20 13:38:42 +03:00
parent 4620a06a9b
commit 4b177e160d
16 changed files with 391 additions and 79 deletions

View File

@ -4,6 +4,6 @@ return array(
//'TAG_MANAGER_KEY' => '', //'TAG_MANAGER_KEY' => '',
//'ANALYTICS_KEY' => '', //'ANALYTICS_KEY' => '',
'NINJA_PROD' => true, //'NINJA_PROD' => true,
); );

View File

@ -273,12 +273,16 @@ class AccountController extends \BaseController {
if (Auth::user()->account->isPro()) if (Auth::user()->account->isPro())
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$account->custom_label1 = Input::get('custom_label1'); $account->custom_label1 = trim(Input::get('custom_label1'));
$account->custom_value1 = Input::get('custom_value1'); $account->custom_value1 = trim(Input::get('custom_value1'));
$account->custom_label2 = Input::get('custom_label2'); $account->custom_label2 = trim(Input::get('custom_label2'));
$account->custom_value2 = Input::get('custom_value2'); $account->custom_value2 = trim(Input::get('custom_value2'));
$account->custom_client_label1 = Input::get('custom_client_label1'); $account->custom_client_label1 = trim(Input::get('custom_client_label1'));
$account->custom_client_label2 = Input::get('custom_client_label2'); $account->custom_client_label2 = trim(Input::get('custom_client_label2'));
$account->custom_invoice_label1 = trim(Input::get('custom_invoice_label1'));
$account->custom_invoice_label2 = trim(Input::get('custom_invoice_label2'));
$account->custom_invoice_taxes1 = Input::get('custom_invoice_taxes1') ? true : false;
$account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false;
$account->save(); $account->save();
Session::flash('message', trans('texts.updated_settings')); Session::flash('message', trans('texts.updated_settings'));
@ -292,6 +296,8 @@ class AccountController extends \BaseController {
if (Auth::user()->account->isPro()) if (Auth::user()->account->isPro())
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$account->hide_quantity = Input::get('hide_quantity') ? true : false;
$account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false;
$account->primary_color = Input::get('primary_color');// ? Input::get('primary_color') : null; $account->primary_color = Input::get('primary_color');// ? Input::get('primary_color') : null;
$account->secondary_color = Input::get('secondary_color');// ? Input::get('secondary_color') : null; $account->secondary_color = Input::get('secondary_color');// ? Input::get('secondary_color') : null;
$account->save(); $account->save();

View File

@ -244,8 +244,8 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('country_id')->nullable(); $t->unsignedInteger('country_id')->nullable();
$t->string('work_phone'); $t->string('work_phone');
$t->text('private_notes'); $t->text('private_notes');
$t->decimal('balance', 13, 4); $t->decimal('balance', 13, 2);
$t->decimal('paid_to_date', 13, 4); $t->decimal('paid_to_date', 13, 2);
$t->timestamp('last_login')->nullable(); $t->timestamp('last_login')->nullable();
$t->string('website'); $t->string('website');
$t->unsignedInteger('industry_id')->nullable(); $t->unsignedInteger('industry_id')->nullable();
@ -326,10 +326,10 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('recurring_invoice_id')->index()->nullable(); $t->unsignedInteger('recurring_invoice_id')->index()->nullable();
$t->string('tax_name'); $t->string('tax_name');
$t->decimal('tax_rate', 13, 4); $t->decimal('tax_rate', 13, 2);
$t->decimal('amount', 13, 4); $t->decimal('amount', 13, 2);
$t->decimal('balance', 13, 4); $t->decimal('balance', 13, 2);
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('account_id')->references('id')->on('accounts');
@ -375,7 +375,7 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes(); $t->softDeletes();
$t->string('name'); $t->string('name');
$t->decimal('rate', 13, 4); $t->decimal('rate', 13, 2);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
@ -394,8 +394,8 @@ class ConfideSetupUsersTable extends Migration {
$t->string('product_key'); $t->string('product_key');
$t->text('notes'); $t->text('notes');
$t->decimal('cost', 13, 4); $t->decimal('cost', 13, 2);
$t->decimal('qty', 13, 4); $t->decimal('qty', 13, 2);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
@ -417,11 +417,11 @@ class ConfideSetupUsersTable extends Migration {
$t->string('product_key'); $t->string('product_key');
$t->text('notes'); $t->text('notes');
$t->decimal('cost', 13, 4); $t->decimal('cost', 13, 2);
$t->decimal('qty', 13, 4); $t->decimal('qty', 13, 2);
$t->string('tax_name'); $t->string('tax_name');
$t->decimal('tax_rate', 13, 4); $t->decimal('tax_rate', 13, 2);
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->foreign('product_id')->references('id')->on('products'); $t->foreign('product_id')->references('id')->on('products');
@ -446,7 +446,7 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_deleted'); $t->boolean('is_deleted');
$t->decimal('amount', 13, 4); $t->decimal('amount', 13, 2);
$t->date('payment_date'); $t->date('payment_date');
$t->string('transaction_reference'); $t->string('transaction_reference');
$t->string('payer_id'); $t->string('payer_id');
@ -473,8 +473,8 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_deleted'); $t->boolean('is_deleted');
$t->decimal('amount', 13, 4); $t->decimal('amount', 13, 2);
$t->decimal('balance', 13, 4); $t->decimal('balance', 13, 2);
$t->date('credit_date')->nullable(); $t->date('credit_date')->nullable();
$t->string('credit_number'); $t->string('credit_number');
$t->text('private_notes'); $t->text('private_notes');
@ -504,8 +504,8 @@ class ConfideSetupUsersTable extends Migration {
$t->text('message'); $t->text('message');
$t->text('json_backup'); $t->text('json_backup');
$t->integer('activity_type_id'); $t->integer('activity_type_id');
$t->decimal('adjustment', 13, 4); $t->decimal('adjustment', 13, 2);
$t->decimal('balance', 13, 4); $t->decimal('balance', 13, 2);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');

View File

@ -0,0 +1,66 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SupportHidingQuantity extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->boolean('hide_quantity');
$table->boolean('hide_paid_to_date');
$table->string('custom_invoice_label1');
$table->string('custom_invoice_label2');
$table->boolean('custom_invoice_taxes1');
$table->boolean('custom_invoice_taxes2');
});
Schema::table('invoices', function($table)
{
$table->decimal('custom_value1', 13, 2);
$table->decimal('custom_value2', 13, 2);
$table->boolean('custom_taxes1');
$table->boolean('custom_taxes2');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('hide_quantity');
$table->dropColumn('hide_paid_to_date');
$table->dropColumn('custom_invoice_label1');
$table->dropColumn('custom_invoice_label2');
$table->dropColumn('custom_invoice_taxes1');
$table->dropColumn('custom_invoice_taxes2');
});
Schema::table('invoices', function($table)
{
$table->dropColumn('custom_value1');
$table->dropColumn('custom_value2');
$table->dropColumn('custom_taxes1');
$table->dropColumn('custom_taxes2');
});
}
}

View File

@ -69,13 +69,17 @@ App::after(function($request, $response)
Route::filter('auth', function() Route::filter('auth', function()
{ {
if (Auth::guest()) { if (Auth::guest())
if(Utils::isNinja()) { {
return Redirect::guest('/'); if (Utils::isNinja() || Account::count() == 0)
} else { {
return Redirect::guest('/login'); return Redirect::guest('/');
} }
else
{
return Redirect::guest('/login');
} }
}
}); });

View File

@ -45,7 +45,7 @@ return array(
'quantity' => 'Menge', 'quantity' => 'Menge',
'line_total' => 'Summe', 'line_total' => 'Summe',
'subtotal' => 'Zwischensumme', 'subtotal' => 'Zwischensumme',
'paid_to_date' => 'Zahlungsdatum', 'paid_to_date' => 'Bereits gezahlt',
'balance_due' => 'Rechnungsbetrag', 'balance_due' => 'Rechnungsbetrag',
'invoice_design_id' => 'Design', 'invoice_design_id' => 'Design',
'terms' => 'Bedingungen', 'terms' => 'Bedingungen',

View File

@ -397,6 +397,14 @@ return array(
'session_expired' => 'Your session has expired.', 'session_expired' => 'Your session has expired.',
'invoice_fields' => 'Invoice Fields',
'invoice_options' => 'Invoice Options',
'hide_quantity' => 'Hide quantity',
'hide_quantity_help' => 'All line items will have a quantity of one',
'hide_paid_to_date' => 'Hide paid to date',
'hide_paid_to_date_help' => 'Hide until a payment is made',
'charge_taxes' => 'Charge taxes',
); );

View File

@ -82,7 +82,11 @@ class Invoice extends EntityModel
'account', 'account',
'invoice_design_id', 'invoice_design_id',
'is_pro', 'is_pro',
'is_quote']); 'is_quote',
'custom_value1',
'custom_value2',
'custom_taxes1',
'custom_taxes2']);
$this->client->setVisible([ $this->client->setVisible([
'name', 'name',
@ -117,7 +121,9 @@ class Invoice extends EntityModel
'custom_client_label1', 'custom_client_label1',
'custom_client_label2', 'custom_client_label2',
'primary_color', 'primary_color',
'secondary_color']); 'secondary_color',
'hide_quantity',
'hide_paid_to_date']);
foreach ($this->invoice_items as $invoiceItem) foreach ($this->invoice_items as $invoiceItem)
{ {

View File

@ -237,7 +237,7 @@ class InvoiceRepository
foreach ($data['invoice_items'] as $item) foreach ($data['invoice_items'] as $item)
{ {
if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes) if (!$item->cost && !$item->product_key && !$item->notes)
{ {
continue; continue;
} }
@ -261,9 +261,30 @@ class InvoiceRepository
$total *= (100 - $invoice->discount) / 100; $total *= (100 - $invoice->discount) / 100;
} }
$invoice->custom_value1 = $data['custom_value1'];
$invoice->custom_value2 = $data['custom_value2'];
$invoice->custom_taxes1 = $data['custom_taxes1'] ? true : false;
$invoice->custom_taxes2 = $data['custom_taxes2'] ? true : false;
// custom fields charged taxes
if ($invoice->custom_value1 && $invoice->custom_taxes1) {
$total += $invoice->custom_value1;
}
if ($invoice->custom_value2 && $invoice->custom_taxes2) {
$total += $invoice->custom_value2;
}
$total += $total * $invoice->tax_rate / 100; $total += $total * $invoice->tax_rate / 100;
$total = round($total, 2); $total = round($total, 2);
// custom fields not charged taxes
if ($invoice->custom_value1 && !$invoice->custom_taxes1) {
$total += $invoice->custom_value1;
}
if ($invoice->custom_value2 && !$invoice->custom_taxes2) {
$total += $invoice->custom_value2;
}
if ($publicId) if ($publicId)
{ {
$invoice->balance = $total - ($invoice->amount - $invoice->balance); $invoice->balance = $total - ($invoice->amount - $invoice->balance);
@ -280,7 +301,7 @@ class InvoiceRepository
foreach ($data['invoice_items'] as $item) foreach ($data['invoice_items'] as $item)
{ {
if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes) if (!$item->cost && !$item->product_key && !$item->notes)
{ {
continue; continue;
} }

View File

@ -6,6 +6,20 @@
{{ Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') }} {{ Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') }}
{{ Former::populate($account) }} {{ Former::populate($account) }}
{{ Former::populateField('custom_invoice_taxes1', intval($account->custom_invoice_taxes1)) }}
{{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }}
{{ Former::legend('invoice_fields') }}
{{ Former::text('custom_invoice_label1')->label(trans('texts.field_label'))
->append(Former::checkbox('custom_invoice_taxes1')->raw() . ' ' . trans('texts.charge_taxes')) }}
{{ Former::text('custom_invoice_label2')->label(trans('texts.field_label'))
->append(Former::checkbox('custom_invoice_taxes2')->raw() . ' ' . trans('texts.charge_taxes')) }}
<p>&nbsp;</p>
{{ Former::legend('client_fields') }}
{{ Former::text('custom_client_label1')->label(trans('texts.field_label')) }}
{{ Former::text('custom_client_label2')->label(trans('texts.field_label')) }}
<p>&nbsp;</p>
{{ Former::legend('company_fields') }} {{ Former::legend('company_fields') }}
{{ Former::text('custom_label1')->label(trans('texts.field_label')) }} {{ Former::text('custom_label1')->label(trans('texts.field_label')) }}
@ -14,10 +28,6 @@
{{ Former::text('custom_label2')->label(trans('texts.field_label')) }} {{ Former::text('custom_label2')->label(trans('texts.field_label')) }}
{{ Former::text('custom_value2')->label(trans('texts.field_value')) }} {{ Former::text('custom_value2')->label(trans('texts.field_value')) }}
{{ Former::legend('client_fields') }}
{{ Former::text('custom_client_label1')->label(trans('texts.field_label')) }}
{{ Former::text('custom_client_label2')->label(trans('texts.field_label')) }}
@if (Auth::user()->isPro()) @if (Auth::user()->isPro())
{{ Former::actions( Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk') ) }} {{ Former::actions( Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk') ) }}
@else @else

View File

@ -6,6 +6,13 @@
{{ Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') }} {{ Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') }}
{{ Former::populate($account) }} {{ Former::populate($account) }}
{{ Former::populateField('hide_quantity', intval($account->hide_quantity)) }}
{{ Former::populateField('hide_paid_to_date', intval($account->hide_paid_to_date)) }}
{{ Former::legend('invoice_options') }}
{{ Former::checkbox('hide_quantity')->text(trans('texts.hide_quantity_help')) }}
{{ Former::checkbox('hide_paid_to_date')->text(trans('texts.hide_paid_to_date_help')) }}
<p>&nbsp;</p>
{{ Former::legend('invoice_design') }} {{ Former::legend('invoice_design') }}
{{ Former::text('primary_color') }} {{ Former::text('primary_color') }}

View File

@ -119,7 +119,7 @@
<th style="min-width:160px">{{ trans('texts.item') }}</th> <th style="min-width:160px">{{ trans('texts.item') }}</th>
<th style="width:100%">{{ trans('texts.description') }}</th> <th style="width:100%">{{ trans('texts.description') }}</th>
<th style="min-width:120px">{{ trans('texts.unit_cost') }}</th> <th style="min-width:120px">{{ trans('texts.unit_cost') }}</th>
<th style="min-width:120px">{{ trans('texts.quantity') }}</th> <th style="{{ $account->hide_quantity ? 'display:none' : 'min-width:120px' }}">{{ trans('texts.quantity') }}</th>
<th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th> <th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th>
<th style="min-width:120px;">{{ trans('texts.line_total') }}</th> <th style="min-width:120px;">{{ trans('texts.line_total') }}</th>
<th style="min-width:32px;" class="hide-border"></th> <th style="min-width:32px;" class="hide-border"></th>
@ -140,7 +140,7 @@
<td> <td>
<input onkeyup="onItemChange()" data-bind="value: prettyCost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//> <input onkeyup="onItemChange()" data-bind="value: prettyCost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//>
</td> </td>
<td> <td style="{{ $account->hide_quantity ? 'display:none' : '' }}">
<input onkeyup="onItemChange()" data-bind="value: prettyQty, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//> <input onkeyup="onItemChange()" data-bind="value: prettyQty, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control"//>
</td> </td>
<td style="display:none;" data-bind="visible: $root.invoice_item_taxes.show"> <td style="display:none;" data-bind="visible: $root.invoice_item_taxes.show">
@ -154,10 +154,12 @@
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td class="hide-border"/> <td class="hide-border"/>
<td colspan="2" rowspan="5"> <td colspan="2" rowspan="6" style="vertical-align:top">
<br/> <br/>
{{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'") {{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
->label(false)->placeholder(trans('texts.note_to_client'))->style('resize: none') }} ->label(false)->placeholder(trans('texts.note_to_client'))->style('resize: none') }}
@ -169,35 +171,82 @@
</label> </label>
</td> </td>
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2">{{ trans('texts.subtotal') }}</td> <td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.subtotal') }}</td>
<td style="text-align: right"><span data-bind="text: totals.subtotal"/></td> <td style="text-align: right"><span data-bind="text: totals.subtotal"/></td>
</tr> </tr>
<tr style="display:none" data-bind="visible: discount() > 0"> <tr style="display:none" data-bind="visible: discount() > 0">
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2">{{ trans('texts.discount') }}</td> <td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.discount') }}</td>
<td style="text-align: right"><span data-bind="text: totals.discounted"/></td> <td style="text-align: right"><span data-bind="text: totals.discounted"/></td>
</tr> </tr>
@if (($account->custom_invoice_label1 || ($invoice && floatval($invoice->custom_value1)) != 0) && $account->custom_invoice_taxes1)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ $account->custom_invoice_label1 }}</td>
<td style="text-align: right"><input class="form-control" data-bind="value: custom_value1, valueUpdate: 'afterkeydown'"/></td>
</tr>
@endif
@if (($account->custom_invoice_label2 || ($invoice && floatval($invoice->custom_value2)) != 0) && $account->custom_invoice_taxes2)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ $account->custom_invoice_label2 }}</td>
<td style="text-align: right"><input class="form-control" data-bind="value: custom_value2, valueUpdate: 'afterkeydown'"/></td>
</tr>
@endif
<tr style="display:none" data-bind="visible: $root.invoice_taxes.show"> <tr style="display:none" data-bind="visible: $root.invoice_taxes.show">
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td>{{ trans('texts.tax') }}</td> @if (!$account->hide_quantity)
<td>{{ trans('texts.tax') }}</td>
@endif
<td style="min-width:120px"><select class="form-control" style="width:100%" data-bind="value: tax, options: $root.tax_rates, optionsText: 'displayName'"></select></td> <td style="min-width:120px"><select class="form-control" style="width:100%" data-bind="value: tax, options: $root.tax_rates, optionsText: 'displayName'"></select></td>
<td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td> <td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td>
</tr> </tr>
@if (($account->custom_invoice_label1 || ($invoice && floatval($invoice->custom_value1)) != 0) && !$account->custom_invoice_taxes1)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ $account->custom_invoice_label1 }}</td>
<td style="text-align: right"><input class="form-control" data-bind="value: custom_value1, valueUpdate: 'afterkeydown'"/></td>
</tr>
@endif
@if (($account->custom_invoice_label2 || ($invoice && floatval($invoice->custom_value2)) != 0) && !$account->custom_invoice_taxes2)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ $account->custom_invoice_label2 }}</td>
<td style="text-align: right"><input class="form-control" data-bind="value: custom_value2, valueUpdate: 'afterkeydown'"/></td>
</tr>
@endif
@if (!$account->hide_paid_to_date)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.paid_to_date') }}</td>
<td style="text-align: right" data-bind="text: totals.paidToDate"></td>
</tr>
@endif
<tr> <tr>
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2">{{ trans('texts.paid_to_date') }}</td> <td colspan="{{ $account->hide_quantity ? 1 : 2 }}"><b>{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }}</b></td>
<td style="text-align: right" data-bind="text: totals.paidToDate"></td>
</tr>
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2"><b>{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }}</b></td>
<td style="text-align: right"><span data-bind="text: totals.total"/></td> <td style="text-align: right"><span data-bind="text: totals.total"/></td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
@ -1017,6 +1066,11 @@
self.balance = ko.observable(0); self.balance = ko.observable(0);
self.invoice_design_id = ko.observable({{ $account->invoice_design_id }}); self.invoice_design_id = ko.observable({{ $account->invoice_design_id }});
self.custom_value1 = ko.observable(0);
self.custom_value2 = ko.observable(0);
self.custom_taxes1 = ko.observable(false);
self.custom_taxes2 = ko.observable(false);
self.mapping = { self.mapping = {
'client': { 'client': {
create: function(options) { create: function(options) {
@ -1037,6 +1091,9 @@
self.addItem = function() { self.addItem = function() {
var itemModel = new ItemModel(); var itemModel = new ItemModel();
@if ($account->hide_quantity)
itemModel.qty(1);
@endif
self.invoice_items.push(itemModel); self.invoice_items.push(itemModel);
applyComboboxListeners(); applyComboboxListeners();
} }
@ -1044,6 +1101,8 @@
if (data) { if (data) {
ko.mapping.fromJS(data, self.mapping, self); ko.mapping.fromJS(data, self.mapping, self);
self.is_recurring(parseInt(data.is_recurring)); self.is_recurring(parseInt(data.is_recurring));
self.is_recurring(parseInt(data.is_recurring) == 1);
self.is_recurring(parseInt(data.is_recurring) == 1);
} else { } else {
self.addItem(); self.addItem();
} }
@ -1125,12 +1184,24 @@
}); });
self.totals.taxAmount = ko.computed(function() { self.totals.taxAmount = ko.computed(function() {
var total = self.totals.rawSubtotal(); var total = self.totals.rawSubtotal();
var discount = parseFloat(self.discount()); var discount = parseFloat(self.discount());
if (discount > 0) { if (discount > 0) {
total = roundToTwo(total * ((100 - discount)/100)); total = roundToTwo(total * ((100 - discount)/100));
} }
var customValue1 = roundToTwo(self.custom_value1());
var customValue2 = roundToTwo(self.custom_value2());
var customTaxes1 = self.custom_taxes1() == 1;
var customTaxes2 = self.custom_taxes2() == 1;
if (customValue1 && customTaxes1) {
total = NINJA.parseFloat(total) + customValue1;
}
if (customValue2 && customTaxes2) {
total = NINJA.parseFloat(total) + customValue2;
}
var taxRate = parseFloat(self.tax_rate()); var taxRate = parseFloat(self.tax_rate());
if (taxRate > 0) { if (taxRate > 0) {
@ -1158,11 +1229,30 @@
total = roundToTwo(total * ((100 - discount)/100)); total = roundToTwo(total * ((100 - discount)/100));
} }
var customValue1 = roundToTwo(self.custom_value1());
var customValue2 = roundToTwo(self.custom_value2());
var customTaxes1 = self.custom_taxes1() == 1;
var customTaxes2 = self.custom_taxes2() == 1;
if (customValue1 && customTaxes1) {
total = NINJA.parseFloat(total) + customValue1;
}
if (customValue2 && customTaxes2) {
total = NINJA.parseFloat(total) + customValue2;
}
var taxRate = parseFloat(self.tax_rate()); var taxRate = parseFloat(self.tax_rate());
if (taxRate > 0) { if (taxRate > 0) {
total = NINJA.parseFloat(total) + roundToTwo((total * (taxRate/100))); total = NINJA.parseFloat(total) + roundToTwo((total * (taxRate/100)));
} }
if (customValue1 && !customTaxes1) {
total = NINJA.parseFloat(total) + customValue1;
}
if (customValue2 && !customTaxes2) {
total = NINJA.parseFloat(total) + customValue2;
}
var paid = self.totals.rawPaidToDate(); var paid = self.totals.rawPaidToDate();
if (paid > 0) { if (paid > 0) {
total -= paid; total -= paid;
@ -1428,7 +1518,7 @@
} }
this.isEmpty = function() { this.isEmpty = function() {
return !self.product_key() && !self.notes() && !self.cost() && !self.qty(); return !self.product_key() && !self.notes() && !self.cost() && (!self.qty() || {{ $account->hide_quantity ? 'true' : 'false' }});
} }
this.onSelect = function(){ this.onSelect = function(){
@ -1508,6 +1598,9 @@
} }
model.invoice().addItem(); model.invoice().addItem();
//model.addTaxRate(); //model.addTaxRate();
@else
model.invoice().custom_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }});
model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? 'true' : 'false' }});
@endif @endif
// Add the first tax rate for new invoices // Add the first tax rate for new invoices
//if(model.invoice_taxes() && model.tax_rates().length > 0) { //if(model.invoice_taxes() && model.tax_rates().length > 0) {
@ -1524,7 +1617,10 @@
} }
onTaxRateChange(); onTaxRateChange();
// display blank instead of '0'
if (!model.invoice().discount()) model.invoice().discount(''); if (!model.invoice().discount()) model.invoice().discount('');
if (!model.invoice().custom_value1()) model.invoice().custom_value1('');
if (!model.invoice().custom_value2()) model.invoice().custom_value2('');
ko.applyBindings(model); ko.applyBindings(model);
onItemChange(); onItemChange();

View File

@ -2269,6 +2269,12 @@ border-bottom-width: 1px;
.dashboard .table-striped>tbody>tr>td:first-child { padding-left: 15px; } .dashboard .table-striped>tbody>tr>td:first-child { padding-left: 15px; }
.dashboard .table-striped>thead>tr>th:first-child { padding-left: 15px; } .dashboard .table-striped>thead>tr>th:first-child { padding-left: 15px; }
.invoice-table tfoot input {
text-align: right;
}
/*********************************************** /***********************************************
New/edit invoice page New/edit invoice page
************************************************/ ************************************************/

View File

@ -47256,14 +47256,32 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
return; return;
} }
//var taxTitle = 'Tax ' + getInvoiceTaxRate(invoice) + '%';
var data = [ var data = [
{'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)}, {'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)},
{'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}, {'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}
{'tax': invoice.tax_amount > 0 ? formatMoney(invoice.tax_amount, invoice.client.currency_id) : false},
{'paid_to_date': formatMoney(invoice.amount - invoice.balance, invoice.client.currency_id)}
]; ];
if (NINJA.parseFloat(invoice.custom_value1) && NINJA.parseFloat(invoice.custom_taxes1)) {
data.push({'custom_invoice_label1': formatMoney(invoice.custom_value1, invoice.client.currency_id) })
}
if (NINJA.parseFloat(invoice.custom_value2) && NINJA.parseFloat(invoice.custom_taxes2)) {
data.push({'custom_invoice_label2': formatMoney(invoice.custom_value2, invoice.client.currency_id) })
}
data.push({'tax': invoice.tax_amount > 0 ? formatMoney(invoice.tax_amount, invoice.client.currency_id) : false});
if (NINJA.parseFloat(invoice.custom_value1) && !NINJA.parseFloat(invoice.custom_taxes1)) {
data.push({'custom_invoice_label1': formatMoney(invoice.custom_value1, invoice.client.currency_id) })
}
if (NINJA.parseFloat(invoice.custom_value2) && !NINJA.parseFloat(invoice.custom_taxes2)) {
data.push({'custom_invoice_label2': formatMoney(invoice.custom_value2, invoice.client.currency_id) })
}
var paid = invoice.amount - invoice.balance;
if (invoice.account.hide_paid_to_date === '0' || paid) {
data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)});
}
return displayGrid(doc, invoice, data, 300, y, layout, true, 550, rightAlignTitleX) + 10; return displayGrid(doc, invoice, data, 300, y, layout, true, 550, rightAlignTitleX) + 10;
} }
@ -47410,11 +47428,18 @@ function calculateAmounts(invoice) {
invoice.subtotal_amount = total; invoice.subtotal_amount = total;
if (invoice.discount > 0) { if (invoice.discount > 0) {
var discount = roundToTwo(total * (invoice.discount/100)); var discount = roundToTwo(total * (invoice.discount/100));
total -= discount; total -= discount;
} }
// custom fields with taxes
if (NINJA.parseFloat(invoice.custom_value1) && NINJA.parseFloat(invoice.custom_taxes1)) {
total += roundToTwo(invoice.custom_value1);
}
if (NINJA.parseFloat(invoice.custom_value2) && NINJA.parseFloat(invoice.custom_taxes2)) {
total += roundToTwo(invoice.custom_value2);
}
var tax = 0; var tax = 0;
if (invoice.tax && parseFloat(invoice.tax.rate)) { if (invoice.tax && parseFloat(invoice.tax.rate)) {
tax = parseFloat(invoice.tax.rate); tax = parseFloat(invoice.tax.rate);
@ -47427,9 +47452,17 @@ function calculateAmounts(invoice) {
total = parseFloat(total) + parseFloat(tax); total = parseFloat(total) + parseFloat(tax);
} }
// custom fields w/o with taxes
if (NINJA.parseFloat(invoice.custom_value1) && !NINJA.parseFloat(invoice.custom_taxes1)) {
total += roundToTwo(invoice.custom_value1);
}
if (NINJA.parseFloat(invoice.custom_value2) && !NINJA.parseFloat(invoice.custom_taxes2)) {
total += roundToTwo(invoice.custom_value2);
}
invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.tax_amount = tax;
invoice.discount_amount = discount; invoice.discount_amount = discount;
invoice.tax_amount = tax;
invoice.has_taxes = hasTaxes; invoice.has_taxes = hasTaxes;
return invoice; return invoice;
@ -47455,7 +47488,9 @@ function displayInvoiceHeader(doc, invoice, layout) {
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item); doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item);
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description); doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description);
doc.text(costX, layout.tableTop, invoiceLabels.unit_cost); doc.text(costX, layout.tableTop, invoiceLabels.unit_cost);
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity); if (invoice.account.hide_quantity === '0') {
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity);
}
doc.text(totalX, layout.tableTop, invoiceLabels.line_total); doc.text(totalX, layout.tableTop, invoiceLabels.line_total);
if (invoice.has_taxes) if (invoice.has_taxes)
@ -47470,8 +47505,9 @@ function displayInvoiceItems(doc, invoice, layout) {
var line = 1; var line = 1;
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1; var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
var tableTop = layout.tableTop; var tableTop = layout.tableTop;
var hideQuantity = invoice.account.hide_quantity === '1';
doc.setFontSize(8); doc.setFontSize(8);
for (var i=0; i<invoice.invoice_items.length; i++) { for (var i=0; i<invoice.invoice_items.length; i++) {
@ -47507,7 +47543,7 @@ function displayInvoiceItems(doc, invoice, layout) {
} }
// show at most one blank line // show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !qty && !notes && !productKey) { if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue; continue;
} }
shownItem = true; shownItem = true;
@ -47581,7 +47617,9 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.text(layout.descriptionLeft, y+2, notes); doc.text(layout.descriptionLeft, y+2, notes);
doc.text(costX, y+2, cost); doc.text(costX, y+2, cost);
doc.text(qtyX, y+2, qty); if (!hideQuantity) {
doc.text(qtyX, y+2, qty);
}
doc.text(totalX, y+2, lineTotal); doc.text(totalX, y+2, lineTotal);
if (tax) { if (tax) {
@ -47838,7 +47876,7 @@ function toggleDatePicker(field) {
$('#'+field).datepicker('show'); $('#'+field).datepicker('show');
} }
function roundToTwo(num, toString) { function roundToTwo(num, toString) {
var val = +(Math.round(num + "e+2") + "e-2"); var val = +(Math.round(num + "e+2") + "e-2");
return toString ? val.toFixed(2) : val; return toString ? val.toFixed(2) : val;
} }

View File

@ -494,6 +494,12 @@ border-bottom-width: 1px;
.dashboard .table-striped>tbody>tr>td:first-child { padding-left: 15px; } .dashboard .table-striped>tbody>tr>td:first-child { padding-left: 15px; }
.dashboard .table-striped>thead>tr>th:first-child { padding-left: 15px; } .dashboard .table-striped>thead>tr>th:first-child { padding-left: 15px; }
.invoice-table tfoot input {
text-align: right;
}
/*********************************************** /***********************************************
New/edit invoice page New/edit invoice page
************************************************/ ************************************************/

View File

@ -1331,14 +1331,32 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
return; return;
} }
//var taxTitle = 'Tax ' + getInvoiceTaxRate(invoice) + '%';
var data = [ var data = [
{'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)}, {'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)},
{'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}, {'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}
{'tax': invoice.tax_amount > 0 ? formatMoney(invoice.tax_amount, invoice.client.currency_id) : false},
{'paid_to_date': formatMoney(invoice.amount - invoice.balance, invoice.client.currency_id)}
]; ];
if (NINJA.parseFloat(invoice.custom_value1) && NINJA.parseFloat(invoice.custom_taxes1)) {
data.push({'custom_invoice_label1': formatMoney(invoice.custom_value1, invoice.client.currency_id) })
}
if (NINJA.parseFloat(invoice.custom_value2) && NINJA.parseFloat(invoice.custom_taxes2)) {
data.push({'custom_invoice_label2': formatMoney(invoice.custom_value2, invoice.client.currency_id) })
}
data.push({'tax': invoice.tax_amount > 0 ? formatMoney(invoice.tax_amount, invoice.client.currency_id) : false});
if (NINJA.parseFloat(invoice.custom_value1) && !NINJA.parseFloat(invoice.custom_taxes1)) {
data.push({'custom_invoice_label1': formatMoney(invoice.custom_value1, invoice.client.currency_id) })
}
if (NINJA.parseFloat(invoice.custom_value2) && !NINJA.parseFloat(invoice.custom_taxes2)) {
data.push({'custom_invoice_label2': formatMoney(invoice.custom_value2, invoice.client.currency_id) })
}
var paid = invoice.amount - invoice.balance;
if (invoice.account.hide_paid_to_date === '0' || paid) {
data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)});
}
return displayGrid(doc, invoice, data, 300, y, layout, true, 550, rightAlignTitleX) + 10; return displayGrid(doc, invoice, data, 300, y, layout, true, 550, rightAlignTitleX) + 10;
} }
@ -1485,11 +1503,18 @@ function calculateAmounts(invoice) {
invoice.subtotal_amount = total; invoice.subtotal_amount = total;
if (invoice.discount > 0) { if (invoice.discount > 0) {
var discount = roundToTwo(total * (invoice.discount/100)); var discount = roundToTwo(total * (invoice.discount/100));
total -= discount; total -= discount;
} }
// custom fields with taxes
if (NINJA.parseFloat(invoice.custom_value1) && NINJA.parseFloat(invoice.custom_taxes1)) {
total += roundToTwo(invoice.custom_value1);
}
if (NINJA.parseFloat(invoice.custom_value2) && NINJA.parseFloat(invoice.custom_taxes2)) {
total += roundToTwo(invoice.custom_value2);
}
var tax = 0; var tax = 0;
if (invoice.tax && parseFloat(invoice.tax.rate)) { if (invoice.tax && parseFloat(invoice.tax.rate)) {
tax = parseFloat(invoice.tax.rate); tax = parseFloat(invoice.tax.rate);
@ -1502,9 +1527,17 @@ function calculateAmounts(invoice) {
total = parseFloat(total) + parseFloat(tax); total = parseFloat(total) + parseFloat(tax);
} }
// custom fields w/o with taxes
if (NINJA.parseFloat(invoice.custom_value1) && !NINJA.parseFloat(invoice.custom_taxes1)) {
total += roundToTwo(invoice.custom_value1);
}
if (NINJA.parseFloat(invoice.custom_value2) && !NINJA.parseFloat(invoice.custom_taxes2)) {
total += roundToTwo(invoice.custom_value2);
}
invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.tax_amount = tax;
invoice.discount_amount = discount; invoice.discount_amount = discount;
invoice.tax_amount = tax;
invoice.has_taxes = hasTaxes; invoice.has_taxes = hasTaxes;
return invoice; return invoice;
@ -1530,7 +1563,9 @@ function displayInvoiceHeader(doc, invoice, layout) {
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item); doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item);
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description); doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description);
doc.text(costX, layout.tableTop, invoiceLabels.unit_cost); doc.text(costX, layout.tableTop, invoiceLabels.unit_cost);
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity); if (invoice.account.hide_quantity === '0') {
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity);
}
doc.text(totalX, layout.tableTop, invoiceLabels.line_total); doc.text(totalX, layout.tableTop, invoiceLabels.line_total);
if (invoice.has_taxes) if (invoice.has_taxes)
@ -1545,8 +1580,9 @@ function displayInvoiceItems(doc, invoice, layout) {
var line = 1; var line = 1;
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1; var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
var tableTop = layout.tableTop; var tableTop = layout.tableTop;
var hideQuantity = invoice.account.hide_quantity === '1';
doc.setFontSize(8); doc.setFontSize(8);
for (var i=0; i<invoice.invoice_items.length; i++) { for (var i=0; i<invoice.invoice_items.length; i++) {
@ -1582,7 +1618,7 @@ function displayInvoiceItems(doc, invoice, layout) {
} }
// show at most one blank line // show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !qty && !notes && !productKey) { if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue; continue;
} }
shownItem = true; shownItem = true;
@ -1656,7 +1692,9 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.text(layout.descriptionLeft, y+2, notes); doc.text(layout.descriptionLeft, y+2, notes);
doc.text(costX, y+2, cost); doc.text(costX, y+2, cost);
doc.text(qtyX, y+2, qty); if (!hideQuantity) {
doc.text(qtyX, y+2, qty);
}
doc.text(totalX, y+2, lineTotal); doc.text(totalX, y+2, lineTotal);
if (tax) { if (tax) {
@ -1913,7 +1951,7 @@ function toggleDatePicker(field) {
$('#'+field).datepicker('show'); $('#'+field).datepicker('show');
} }
function roundToTwo(num, toString) { function roundToTwo(num, toString) {
var val = +(Math.round(num + "e+2") + "e-2"); var val = +(Math.round(num + "e+2") + "e-2");
return toString ? val.toFixed(2) : val; return toString ? val.toFixed(2) : val;
} }