mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Working on product fields
This commit is contained in:
parent
e8888e38d0
commit
97fd40c8d3
@ -331,6 +331,8 @@ trait PresentsInvoice
|
|||||||
'unit_cost',
|
'unit_cost',
|
||||||
'tax1',
|
'tax1',
|
||||||
'tax2',
|
'tax2',
|
||||||
|
'custom_value1',
|
||||||
|
'custom_value2',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
|
@ -104,7 +104,7 @@ You can disable the feature by adding ``GOOGLE_MAPS_ENABLED=false`` to the .env
|
|||||||
Time Tracking App
|
Time Tracking App
|
||||||
"""""""""""""""""
|
"""""""""""""""""
|
||||||
|
|
||||||
You can create a Windows, MacOS or Linux desktop wrapper for the time tracking app by installing `Nativefier <https://github.com/jiahaog/nativefier>`_ and then running:
|
You can create a Windows, macOS or Linux desktop wrapper for the time tracking app by installing `Nativefier <https://github.com/jiahaog/nativefier>`_ and then running:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -24,7 +24,7 @@ function GetPdfMake(invoice, javascript, callback) {
|
|||||||
if (item.table && item.table.body == '$invoiceLineItems') {
|
if (item.table && item.table.body == '$invoiceLineItems') {
|
||||||
itemsTable = JSON.stringify(item);
|
itemsTable = JSON.stringify(item);
|
||||||
itemsTable = itemsTable.replace('$invoiceLineItems', '$taskLineItems');
|
itemsTable = itemsTable.replace('$invoiceLineItems', '$taskLineItems');
|
||||||
//itemsTable = itemsTable.replace('$invoiceLineItemColumns', '$taskLineItemColumns');
|
itemsTable = itemsTable.replace('$invoiceLineItemColumns', '$taskLineItemColumns');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +223,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
|
|||||||
'invoiceLineItems': invoice.is_statement ? NINJA.statementLines(invoice) : NINJA.invoiceLines(invoice),
|
'invoiceLineItems': invoice.is_statement ? NINJA.statementLines(invoice) : NINJA.invoiceLines(invoice),
|
||||||
'invoiceLineItemColumns': invoice.is_statement ? NINJA.statementColumns(invoice) : NINJA.invoiceColumns(invoice),
|
'invoiceLineItemColumns': invoice.is_statement ? NINJA.statementColumns(invoice) : NINJA.invoiceColumns(invoice),
|
||||||
'taskLineItems': NINJA.invoiceLines(invoice, true),
|
'taskLineItems': NINJA.invoiceLines(invoice, true),
|
||||||
//'taskLineItemColumns': NINJA.invoiceColumns(invoice),
|
'taskLineItemColumns': NINJA.invoiceColumns(invoice, true),
|
||||||
'invoiceDocuments' : NINJA.invoiceDocuments(invoice),
|
'invoiceDocuments' : NINJA.invoiceDocuments(invoice),
|
||||||
'quantityWidth': NINJA.quantityWidth(invoice),
|
'quantityWidth': NINJA.quantityWidth(invoice),
|
||||||
'taxWidth': NINJA.taxWidth(invoice),
|
'taxWidth': NINJA.taxWidth(invoice),
|
||||||
@ -399,33 +399,32 @@ NINJA.statementLines = function(invoice)
|
|||||||
return NINJA.prepareDataTable(grid, 'invoiceItems');
|
return NINJA.prepareDataTable(grid, 'invoiceItems');
|
||||||
}
|
}
|
||||||
|
|
||||||
NINJA.invoiceColumns = function(invoice)
|
NINJA.invoiceColumns = function(invoice, isTasks)
|
||||||
{
|
{
|
||||||
var account = invoice.account;
|
var account = invoice.account;
|
||||||
var columns = [];
|
var columns = [];
|
||||||
|
var fields = NINJA.productFields(invoice, isTasks);
|
||||||
|
var hasDescription = fields.indexOf('product.description') >= 0;
|
||||||
|
|
||||||
if (invoice.has_product_key) {
|
for (var i=0; i<fields.length; i++) {
|
||||||
columns.push("15%");
|
var field = fields[i];
|
||||||
}
|
if (field == 'product.custom_value1') {
|
||||||
|
if (invoice.has_custom_item_value1) {
|
||||||
columns.push("*")
|
columns.push(hasDescription ? '10%' : '*');
|
||||||
|
}
|
||||||
if (invoice.has_custom_item_value1) {
|
} else if (field == 'product.custom_value2') {
|
||||||
columns.push("10%");
|
if (invoice.has_custom_item_value2) {
|
||||||
}
|
columns.push(hasDescription ? '10%' : '*');
|
||||||
if (invoice.has_custom_item_value2) {
|
}
|
||||||
columns.push("10%");
|
} else if (field == 'product.tax') {
|
||||||
}
|
if (invoice.has_taxes) {
|
||||||
|
columns.push(hasDescription ? '15%' : '*');
|
||||||
var count = 3;
|
}
|
||||||
if (account.hide_quantity == '1') {
|
} else if (field == 'product.description') {
|
||||||
count -= 2;
|
columns.push('*');
|
||||||
}
|
} else {
|
||||||
if (account.show_item_taxes == '1') {
|
columns.push(hasDescription ? '15%' : '*');
|
||||||
count++;
|
}
|
||||||
}
|
|
||||||
for (var i=0; i<count; i++) {
|
|
||||||
columns.push("14%");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
@ -456,45 +455,66 @@ NINJA.taxWidth = function(invoice)
|
|||||||
return invoice.account.show_item_taxes == '1' ? '"14%", ' : '';
|
return invoice.account.show_item_taxes == '1' ? '"14%", ' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NINJA.productFields = function(invoice, isTasks) {
|
||||||
|
var account = invoice.account;
|
||||||
|
var fields = JSON.parse(account.invoice_fields);
|
||||||
|
|
||||||
|
if (fields) {
|
||||||
|
if (isTasks && fields.task_fields) {
|
||||||
|
fields = fields.task_fields;
|
||||||
|
} else if (! isTasks && fields.product_fields) {
|
||||||
|
fields = fields.product_fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! fields) {
|
||||||
|
fields = [
|
||||||
|
isTasks ? 'product.service' : 'product.item',
|
||||||
|
'product.description',
|
||||||
|
'product.custom_value1',
|
||||||
|
'product.custom_value2',
|
||||||
|
isTasks ? 'product.rate' : 'product.unit_cost',
|
||||||
|
isTasks ? 'product.hours' : 'product.quantity',
|
||||||
|
'product.tax',
|
||||||
|
'product.line_total',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
NINJA.invoiceLines = function(invoice, isSecondTable) {
|
NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||||
var account = invoice.account;
|
var account = invoice.account;
|
||||||
var total = 0;
|
var total = 0;
|
||||||
var shownItem = false;
|
var shownItem = false;
|
||||||
var hideQuantity = invoice.account.hide_quantity == '1';
|
|
||||||
var showItemTaxes = invoice.account.show_item_taxes == '1';
|
|
||||||
var isTasks = isSecondTable || (invoice.hasTasks && !invoice.hasStandard);
|
var isTasks = isSecondTable || (invoice.hasTasks && !invoice.hasStandard);
|
||||||
|
|
||||||
var grid = [[]];
|
var grid = [[]];
|
||||||
var styles = ['tableHeader'];
|
var styles = ['tableHeader'];
|
||||||
|
|
||||||
if (isSecondTable) {
|
if (isSecondTable) {
|
||||||
styles.push('secondTableHeader');
|
styles.push('secondTableHeader');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invoice.has_product_key) {
|
var fields = NINJA.productFields(invoice, isTasks);
|
||||||
grid[0].push({text: isTasks ? invoiceLabels.service : invoiceLabels.item, style: styles.concat('itemTableHeader')});
|
var hasDescription = fields.indexOf('product.description') >= 0;
|
||||||
|
|
||||||
|
for (var i=0; i<fields.length; i++) {
|
||||||
|
var field = fields[i].split('.')[1]; // split to remove 'product.'
|
||||||
|
|
||||||
|
if (field == 'custom_value1' && ! invoice.has_custom_item_value1) {
|
||||||
|
continue;
|
||||||
|
} else if (field == 'custom_value2' && ! invoice.has_custom_item_value1) {
|
||||||
|
continue;
|
||||||
|
} else if (field == 'tax' && ! invoice.has_item_taxes) {
|
||||||
|
continue;
|
||||||
|
} else if (field == 'product_key' && ! invoice.has_product_key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid[0].push({text: invoiceLabels[field], style: styles.concat(snakeToCamel(field) + 'TableHeader')});
|
||||||
}
|
}
|
||||||
|
|
||||||
grid[0].push({text: invoiceLabels.description, style: styles.concat('descriptionTableHeader')});
|
for (var i=0; i<invoice.invoice_items.length; i++) {
|
||||||
|
|
||||||
if (invoice.has_custom_item_value1) {
|
|
||||||
grid[0].push({text: account.custom_invoice_item_label1, style: styles.concat('custom1TableHeader')});
|
|
||||||
}
|
|
||||||
if (invoice.has_custom_item_value2) {
|
|
||||||
grid[0].push({text: account.custom_invoice_item_label2, style: styles.concat('custom2TableHeader')});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hideQuantity) {
|
|
||||||
grid[0].push({text: isTasks ? invoiceLabels.rate : invoiceLabels.unit_cost, style: styles.concat('costTableHeader')});
|
|
||||||
grid[0].push({text: isTasks ? invoiceLabels.hours : invoiceLabels.quantity, style: styles.concat('qtyTableHeader')});
|
|
||||||
}
|
|
||||||
if (showItemTaxes) {
|
|
||||||
grid[0].push({text: invoiceLabels.tax, style: styles.concat('taxTableHeader')});
|
|
||||||
}
|
|
||||||
|
|
||||||
grid[0].push({text: invoiceLabels.line_total, style: styles.concat('lineTotalTableHeader')});
|
|
||||||
|
|
||||||
for (var i = 0; i < invoice.invoice_items.length; i++) {
|
|
||||||
|
|
||||||
var row = [];
|
var row = [];
|
||||||
var item = invoice.invoice_items[i];
|
var item = invoice.invoice_items[i];
|
||||||
var cost = NINJA.parseFloat(item.cost) ? formatMoneyInvoice(item.cost, invoice, null, getPrecision(item.cost)) : ' ';
|
var cost = NINJA.parseFloat(item.cost) ? formatMoneyInvoice(item.cost, invoice, null, getPrecision(item.cost)) : ' ';
|
||||||
@ -516,13 +536,11 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showItemTaxes) {
|
if (parseFloat(item.tax_rate1) != 0) {
|
||||||
if (parseFloat(item.tax_rate1) != 0) {
|
tax1 = parseFloat(item.tax_rate1);
|
||||||
tax1 = parseFloat(item.tax_rate1);
|
}
|
||||||
}
|
if (parseFloat(item.tax_rate2) != 0) {
|
||||||
if (parseFloat(item.tax_rate2) != 0) {
|
tax2 = parseFloat(item.tax_rate2);
|
||||||
tax2 = parseFloat(item.tax_rate2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// show at most one blank line
|
// show at most one blank line
|
||||||
@ -558,34 +576,48 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
|||||||
}
|
}
|
||||||
rowStyle = (grid.length % 2 == 0) ? 'even' : 'odd';
|
rowStyle = (grid.length % 2 == 0) ? 'even' : 'odd';
|
||||||
|
|
||||||
if (invoice.has_product_key) {
|
for (var j=0; j<fields.length; j++) {
|
||||||
row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
|
var field = fields[j].split('.')[1]; // split to remove 'product.'
|
||||||
}
|
var value = item[field];
|
||||||
row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]});
|
var styles = [snakeToCamel(field), rowStyle];
|
||||||
if (invoice.has_custom_item_value1) {
|
|
||||||
row.push({style:["customValue1", rowStyle], text:custom_value1 || ' '});
|
if (field == 'custom_value1' && ! invoice.has_custom_item_value1) {
|
||||||
}
|
continue;
|
||||||
if (invoice.has_custom_item_value2) {
|
} else if (field == 'custom_value2' && ! invoice.has_custom_item_value1) {
|
||||||
row.push({style:["customValue2", rowStyle], text:custom_value2 || ' '});
|
continue;
|
||||||
}
|
} else if (field == 'tax' && ! invoice.has_item_taxes) {
|
||||||
if (!hideQuantity) {
|
continue;
|
||||||
row.push({style:["cost", rowStyle], text:cost});
|
} else if (field == 'product_key' && ! invoice.has_product_key) {
|
||||||
row.push({style:["quantity", rowStyle], text:formatAmount(qty, invoice.client.currency_id, getPrecision(qty)) || ' '});
|
continue;
|
||||||
}
|
|
||||||
if (showItemTaxes) {
|
|
||||||
var str = ' ';
|
|
||||||
if (item.tax_name1) {
|
|
||||||
str += tax1.toString() + '%';
|
|
||||||
}
|
}
|
||||||
if (item.tax_name2) {
|
|
||||||
|
if (field == 'item' || field == 'service') {
|
||||||
|
value = item.product_key;
|
||||||
|
styles.push('productKey');
|
||||||
|
} else if (field == 'description') {
|
||||||
|
value = item.notes;
|
||||||
|
} else if (field == 'unit_cost') {
|
||||||
|
value = item.cost;
|
||||||
|
styles.push('cost');
|
||||||
|
} else if (field == 'quantity') {
|
||||||
|
value = formatAmount(item.qty, invoice.client.currency_id, getPrecision(item.qty));
|
||||||
|
} else if (field == 'tax') {
|
||||||
|
value = ' ';
|
||||||
if (item.tax_name1) {
|
if (item.tax_name1) {
|
||||||
str += ' ';
|
value += tax1.toString() + '%';
|
||||||
}
|
}
|
||||||
str += tax2.toString() + '%';
|
if (item.tax_name2) {
|
||||||
|
if (item.tax_name1) {
|
||||||
|
value += ' ';
|
||||||
|
}
|
||||||
|
value += tax2.toString() + '%';
|
||||||
|
}
|
||||||
|
} else if (field == 'line_total') {
|
||||||
|
value = lineTotal;
|
||||||
}
|
}
|
||||||
row.push({style:["tax", rowStyle], text:str});
|
|
||||||
|
row.push({text:value || ' ', style:styles});
|
||||||
}
|
}
|
||||||
row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '});
|
|
||||||
|
|
||||||
grid.push(row);
|
grid.push(row);
|
||||||
}
|
}
|
||||||
|
@ -751,6 +751,7 @@ function calculateAmounts(invoice) {
|
|||||||
|
|
||||||
var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100);
|
var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100);
|
||||||
if (taxAmount1 != 0 || taxName1) {
|
if (taxAmount1 != 0 || taxName1) {
|
||||||
|
hasTaxes = true;
|
||||||
var key = taxName1 + taxRate1;
|
var key = taxName1 + taxRate1;
|
||||||
if (taxes.hasOwnProperty(key)) {
|
if (taxes.hasOwnProperty(key)) {
|
||||||
taxes[key].amount += taxAmount1;
|
taxes[key].amount += taxAmount1;
|
||||||
@ -761,6 +762,7 @@ function calculateAmounts(invoice) {
|
|||||||
|
|
||||||
var taxAmount2 = roundToTwo(lineTotal * taxRate2 / 100);
|
var taxAmount2 = roundToTwo(lineTotal * taxRate2 / 100);
|
||||||
if (taxAmount2 != 0 || taxName2) {
|
if (taxAmount2 != 0 || taxName2) {
|
||||||
|
hasTaxes = true;
|
||||||
var key = taxName2 + taxRate2;
|
var key = taxName2 + taxRate2;
|
||||||
if (taxes.hasOwnProperty(key)) {
|
if (taxes.hasOwnProperty(key)) {
|
||||||
taxes[key].amount += taxAmount2;
|
taxes[key].amount += taxAmount2;
|
||||||
@ -768,11 +770,9 @@ function calculateAmounts(invoice) {
|
|||||||
taxes[key] = {name: taxName2, rate:taxRate2, amount:taxAmount2};
|
taxes[key] = {name: taxName2, rate:taxRate2, amount:taxAmount2};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.tax_name1 || item.tax_name2) {
|
|
||||||
hasTaxes = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invoice.has_item_taxes = hasTaxes;
|
||||||
invoice.subtotal_amount = total;
|
invoice.subtotal_amount = total;
|
||||||
|
|
||||||
var discount = 0;
|
var discount = 0;
|
||||||
|
@ -2510,6 +2510,8 @@ $LANG = array(
|
|||||||
'partial_due_date' => 'Partial Due Date',
|
'partial_due_date' => 'Partial Due Date',
|
||||||
'task_fields' => 'Task Fields',
|
'task_fields' => 'Task Fields',
|
||||||
'product_fields_help' => 'Drag and drop fields to change their order',
|
'product_fields_help' => 'Drag and drop fields to change their order',
|
||||||
|
'custom_value1' => 'Custom Value',
|
||||||
|
'custom_value2' => 'Custom Value',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openTimeTracker() {
|
function openTimeTracker() {
|
||||||
var width = 1000;
|
var width = 1060;
|
||||||
var height = 700;
|
var height = 700;
|
||||||
var left = (screen.width/2)-(width/4);
|
var left = (screen.width/2)-(width/4);
|
||||||
var top = (screen.height/2)-(height/1.5);
|
var top = (screen.height/2)-(height/1.5);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user