1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00

Added custom invoice item fields

This commit is contained in:
Hillel Coren 2016-02-28 13:59:52 +02:00
parent 1c47f4e03f
commit 2153a8e227
10 changed files with 161 additions and 9 deletions

View File

@ -684,6 +684,8 @@ class AccountController extends BaseController
$account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false; $account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false;
$account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1')); $account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1'));
$account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2')); $account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2'));
$account->custom_invoice_item_label1 = trim(Input::get('custom_invoice_item_label1'));
$account->custom_invoice_item_label2 = trim(Input::get('custom_invoice_item_label2'));
$account->invoice_number_counter = Input::get('invoice_number_counter'); $account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix'); $account->quote_number_prefix = Input::get('quote_number_prefix');

View File

@ -455,12 +455,16 @@ class Invoice extends EntityModel implements BalanceAffecting
'show_item_taxes', 'show_item_taxes',
'custom_invoice_text_label1', 'custom_invoice_text_label1',
'custom_invoice_text_label2', 'custom_invoice_text_label2',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
]); ]);
foreach ($this->invoice_items as $invoiceItem) { foreach ($this->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([ $invoiceItem->setVisible([
'product_key', 'product_key',
'notes', 'notes',
'custom_value1',
'custom_value2',
'cost', 'cost',
'qty', 'qty',
'tax_name', 'tax_name',

View File

@ -398,7 +398,7 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $item) { foreach ($data['invoice_items'] as $item) {
$item = (array) $item; $item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) { if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) {
continue; continue;
} }
@ -439,6 +439,13 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->qty = Utils::parseFloat($item['qty']); $invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0; $invoiceItem->tax_rate = 0;
if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1'];
}
if (isset($item['custom_value2'])) {
$invoiceItem->custom_value2 = $item['custom_value2'];
}
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) { if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']); $invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']); $invoiceItem['tax_name'] = trim($item['tax_name']);

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCustomInvoiceFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table) {
$table->string('custom_invoice_item_label1')->nullable();
$table->string('custom_invoice_item_label2')->nullable();
$table->string('recurring_invoice_number_prefix')->default('R');
$table->boolean('enable_client_portal')->default(true);
$table->text('invoice_fields')->nullable();
$table->text('devices')->nullable();
});
Schema::table('invoice_items', function($table) {
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table) {
$table->dropColumn('custom_invoice_item_label1');
$table->dropColumn('custom_invoice_item_label2');
$table->dropColumn('recurring_invoice_number_prefix');
$table->dropColumn('enable_client_portal');
$table->dropColumn('invoice_fields');
$table->dropColumn('devices');
});
Schema::table('accounts', function($table) {
$table->dropColumn('custom_value1');
$table->dropColumn('custom_value2');
});
}
}

View File

@ -31216,6 +31216,13 @@ NINJA.invoiceColumns = function(invoice)
columns.push("*") columns.push("*")
if (account.custom_invoice_item_label1) {
columns.push("10%");
}
if (account.custom_invoice_item_label2) {
columns.push("10%");
}
var count = 3; var count = 3;
if (account.hide_quantity == '1') { if (account.hide_quantity == '1') {
count--; count--;
@ -31226,6 +31233,7 @@ NINJA.invoiceColumns = function(invoice)
for (var i=0; i<count; i++) { for (var i=0; i<count; i++) {
columns.push("14%"); columns.push("14%");
} }
return columns; return columns;
} }
@ -31249,6 +31257,7 @@ NINJA.taxWidth = function(invoice)
} }
NINJA.invoiceLines = function(invoice) { NINJA.invoiceLines = function(invoice) {
var account = invoice.account;
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var hideQuantity = invoice.account.hide_quantity == '1'; var hideQuantity = invoice.account.hide_quantity == '1';
@ -31261,6 +31270,14 @@ NINJA.invoiceLines = function(invoice) {
} }
grid[0].push({text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']}); grid[0].push({text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']});
if (account.custom_invoice_item_label1) {
grid[0].push({text: account.custom_invoice_item_label1, style: ['tableHeader', 'custom1TableHeader']});
}
if (account.custom_invoice_item_label2) {
grid[0].push({text: account.custom_invoice_item_label2, style: ['tableHeader', 'custom2TableHeader']});
}
grid[0].push({text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']}); grid[0].push({text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']});
if (!hideQuantity) { if (!hideQuantity) {
@ -31312,6 +31329,12 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
} }
row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]}); row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]});
if (account.custom_invoice_item_label1) {
row.push({style:["customValue1", rowStyle], text:item.custom_value1 || ' '});
}
if (account.custom_invoice_item_label2) {
row.push({style:["customValue2", rowStyle], text:item.custom_value2 || ' '});
}
row.push({style:["cost", rowStyle], text:cost}); row.push({style:["cost", rowStyle], text:cost});
if (!hideQuantity) { if (!hideQuantity) {
row.push({style:["quantity", rowStyle], text:qty || ' '}); row.push({style:["quantity", rowStyle], text:qty || ' '});

View File

@ -267,6 +267,13 @@ NINJA.invoiceColumns = function(invoice)
columns.push("*") columns.push("*")
if (account.custom_invoice_item_label1) {
columns.push("10%");
}
if (account.custom_invoice_item_label2) {
columns.push("10%");
}
var count = 3; var count = 3;
if (account.hide_quantity == '1') { if (account.hide_quantity == '1') {
count--; count--;
@ -277,6 +284,7 @@ NINJA.invoiceColumns = function(invoice)
for (var i=0; i<count; i++) { for (var i=0; i<count; i++) {
columns.push("14%"); columns.push("14%");
} }
return columns; return columns;
} }
@ -300,6 +308,7 @@ NINJA.taxWidth = function(invoice)
} }
NINJA.invoiceLines = function(invoice) { NINJA.invoiceLines = function(invoice) {
var account = invoice.account;
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var hideQuantity = invoice.account.hide_quantity == '1'; var hideQuantity = invoice.account.hide_quantity == '1';
@ -312,6 +321,14 @@ NINJA.invoiceLines = function(invoice) {
} }
grid[0].push({text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']}); grid[0].push({text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']});
if (account.custom_invoice_item_label1) {
grid[0].push({text: account.custom_invoice_item_label1, style: ['tableHeader', 'custom1TableHeader']});
}
if (account.custom_invoice_item_label2) {
grid[0].push({text: account.custom_invoice_item_label2, style: ['tableHeader', 'custom2TableHeader']});
}
grid[0].push({text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']}); grid[0].push({text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']});
if (!hideQuantity) { if (!hideQuantity) {
@ -363,6 +380,12 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
} }
row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]}); row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]});
if (account.custom_invoice_item_label1) {
row.push({style:["customValue1", rowStyle], text:item.custom_value1 || ' '});
}
if (account.custom_invoice_item_label2) {
row.push({style:["customValue2", rowStyle], text:item.custom_value2 || ' '});
}
row.push({style:["cost", rowStyle], text:cost}); row.push({style:["cost", rowStyle], text:cost});
if (!hideQuantity) { if (!hideQuantity) {
row.push({style:["quantity", rowStyle], text:qty || ' '}); row.push({style:["quantity", rowStyle], text:qty || ' '});

View File

@ -845,10 +845,10 @@ $LANG = array(
'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.',
'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', 'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.',
'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.', 'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.',
'custom_client_fields_helps' => 'Add a text input to the client create/edit page and display the label and value on the PDF.', 'custom_client_fields_helps' => 'Add a field when creating/editing a client and display the label and value on the PDF.',
'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', 'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.',
'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.', 'custom_invoice_fields_helps' => 'Add a field when creating an invoice and display the label and value on the PDF.',
'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', 'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.',
'token_expired' => 'Validation token was expired. Please try again.', 'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link', 'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.', 'button_confirmation_message' => 'Click to confirm your email address.',
@ -1045,6 +1045,8 @@ $LANG = array(
'new_product' => 'New Product', 'new_product' => 'New Product',
'new_tax_rate' => 'New Tax Rate', 'new_tax_rate' => 'New Tax Rate',
'invoiced_amount' => 'Invoiced Amount', 'invoiced_amount' => 'Invoiced Amount',
'invoice_item_fields' => 'Invoice Item Fields',
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.',
); );

View File

@ -108,10 +108,21 @@
<div role="tabpanel"> <div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none"> <ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#clientFields" aria-controls="clientFields" role="tab" data-toggle="tab">{{ trans('texts.client_fields') }}</a></li> <li role="presentation" class="active">
<li role="presentation"><a href="#companyFields" aria-controls="companyFields" role="tab" data-toggle="tab">{{ trans('texts.company_fields') }}</a></li> <a href="#clientFields" aria-controls="clientFields" role="tab" data-toggle="tab">{{ trans('texts.client_fields') }}</a>
<li role="presentation"><a href="#invoiceFields" aria-controls="invoiceFields" role="tab" data-toggle="tab">{{ trans('texts.invoice_fields') }}</a></li> </li>
<li role="presentation"><a href="#invoiceCharges" aria-controls="invoiceCharges" role="tab" data-toggle="tab">{{ trans('texts.invoice_charges') }}</a></li> <li role="presentation">
<a href="#companyFields" aria-controls="companyFields" role="tab" data-toggle="tab">{{ trans('texts.company_fields') }}</a>
</li>
<li role="presentation">
<a href="#invoiceFields" aria-controls="invoiceFields" role="tab" data-toggle="tab">{{ trans('texts.invoice_fields') }}</a>
</li>
<li role="presentation">
<a href="#invoiceItemFields" aria-controls="invoiceItemFields" role="tab" data-toggle="tab">{{ trans('texts.invoice_item_fields') }}</a>
</li>
<li role="presentation">
<a href="#invoiceCharges" aria-controls="invoiceCharges" role="tab" data-toggle="tab">{{ trans('texts.invoice_charges') }}</a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -153,6 +164,17 @@
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="invoiceItemFields">
<div class="panel-body">
{!! Former::text('custom_invoice_item_label1')
->label(trans('texts.field_label')) !!}
{!! Former::text('custom_invoice_item_label2')
->label(trans('texts.field_label'))
->help(trans('texts.custom_invoice_item_fields_help')) !!}
</div>
</div>
<div role="tabpanel" class="tab-pane" id="invoiceCharges"> <div role="tabpanel" class="tab-pane" id="invoiceCharges">
<div class="panel-body"> <div class="panel-body">

View File

@ -189,6 +189,12 @@
<th style="min-width:32px;" class="hide-border"></th> <th style="min-width:32px;" class="hide-border"></th>
<th style="min-width:160px">{{ $invoiceLabels['item'] }}</th> <th style="min-width:160px">{{ $invoiceLabels['item'] }}</th>
<th style="width:100%">{{ $invoiceLabels['description'] }}</th> <th style="width:100%">{{ $invoiceLabels['description'] }}</th>
@if ($account->custom_invoice_item_label1)
<th style="min-width:120px">{{ $account->custom_invoice_item_label1 }}</th>
@endif
@if ($account->custom_invoice_item_label2)
<th style="min-width:120px">{{ $account->custom_invoice_item_label2 }}</th>
@endif
<th style="min-width:120px" data-bind="text: costLabel">{{ $invoiceLabels['unit_cost'] }}</th> <th style="min-width:120px" data-bind="text: costLabel">{{ $invoiceLabels['unit_cost'] }}</th>
<th style="{{ $account->hide_quantity ? 'display:none' : 'min-width:120px' }}" data-bind="text: qtyLabel">{{ $invoiceLabels['quantity'] }}</th> <th style="{{ $account->hide_quantity ? 'display:none' : 'min-width:120px' }}" data-bind="text: qtyLabel">{{ $invoiceLabels['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>
@ -215,6 +221,16 @@
<input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/> <input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/>
<input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/> <input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/>
</td> </td>
@if ($account->custom_invoice_item_label1)
<td>
<input data-bind="value: custom_value1, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][custom_value1]'}" class="form-control invoice-item"/>
</td>
@endif
@if ($account->custom_invoice_item_label2)
<td>
<input data-bind="value: custom_value2, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][custom_value2]'}" class="form-control invoice-item"/>
</td>
@endif
<td> <td>
<input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}" <input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}"
style="text-align: right" class="form-control invoice-item"/> style="text-align: right" class="form-control invoice-item"/>
@ -243,7 +259,7 @@
<tfoot> <tfoot>
<tr> <tr>
<td class="hide-border"/> <td class="hide-border"/>
<td class="hide-border" colspan="2" rowspan="6" style="vertical-align:top"> <td class="hide-border" colspan="{{ 2 + ($account->custom_invoice_item_label1 ? 1 : 0) + ($account->custom_invoice_item_label2 ? 1 : 0) }}" rowspan="6" style="vertical-align:top">
<br/> <br/>
<div role="tabpanel"> <div role="tabpanel">

View File

@ -714,6 +714,8 @@ function ItemModel(data) {
self.notes = ko.observable(''); self.notes = ko.observable('');
self.cost = ko.observable(0); self.cost = ko.observable(0);
self.qty = ko.observable(0); self.qty = ko.observable(0);
self.custom_value1 = ko.observable('');
self.custom_value2 = ko.observable('');
self.tax_name = ko.observable(''); self.tax_name = ko.observable('');
self.tax_rate = ko.observable(0); self.tax_rate = ko.observable(0);
self.task_public_id = ko.observable(''); self.task_public_id = ko.observable('');