1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00
This commit is contained in:
Hillel Coren 2015-04-17 14:57:34 +03:00
commit dea421259c
23 changed files with 1446 additions and 587 deletions

3
.bowerrc Normal file
View File

@ -0,0 +1,3 @@
{
"directory": "./public/vendor"
}

View File

@ -57,9 +57,14 @@ module.exports = function(grunt) {
'public/vendor/spectrum/spectrum.js',
'public/vendor/jspdf/dist/jspdf.min.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
'public/vendor/pdfmake/build/pdfmake.min.js',
'public/vendor/pdfmake/build/vfs_fonts.js',
//'public/js/vfs_fonts.js',
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js',
'public/js/script.js',
'public/js/pdf.pdfmake.js',
],
dest: 'public/js/built.js',
nonull: true
@ -84,7 +89,7 @@ module.exports = function(grunt) {
'public/vendor/datatables/media/css/jquery.dataTables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/css/datepicker.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker.css',
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',

View File

@ -198,7 +198,7 @@ class AccountController extends BaseController
$invoice->invoice_items = [$invoiceItem];
$data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get();
$data['invoiceDesigns'] = InvoiceDesign::availableDesigns();
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);

View File

@ -346,7 +346,7 @@ class InvoiceController extends BaseController
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
'frequencies' => array(
1 => 'Weekly',
2 => 'Two weeks',
@ -582,7 +582,7 @@ class InvoiceController extends BaseController
'invoice' => $invoice,
'versionsJson' => json_encode($versionsJson),
'versionsSelect' => $versionsSelect,
'invoiceDesigns' => InvoiceDesign::where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
];
return View::make('invoices.history', $data);

View File

@ -155,7 +155,7 @@ class QuoteController extends BaseController
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'invoiceDesigns' => InvoiceDesign::where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'invoiceDesigns' => InvoiceDesign::availableDesigns(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
];
}

View File

@ -5,4 +5,20 @@ use Eloquent;
class InvoiceDesign extends Eloquent
{
public $timestamps = false;
public function scopeAvailableDesigns($query) {
$designs = $query->where('id', '<=', \Auth::user()->maxInvoiceDesignId())->orderBy('id')->get();
foreach($designs as $design) {
if($design->filename) {
$fileName = public_path($design->filename);
if(file_exists($fileName)) {
$design->javascript = file_get_contents($fileName);
}
}
}
return $designs;
}
}

27
bower.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "hillelcoren/invoice-ninja",
"version": "0.9.0",
"dependencies": {
"jquery": "~1.11",
"bootstrap": "~3.*",
"jquery-ui": "~1.*",
"datatables": "1.10.5",
"datatables-bootstrap3": "*",
"knockout.js": "~3.*",
"knockout-mapping": "*",
"knockout-sortable": "*",
"font-awesome": "~4.*",
"underscore": "~1.*",
"jspdf": "*",
"bootstrap-datepicker": "~1.*",
"typeahead.js": "~0.9.3",
"accounting": "~0.*",
"spectrum": "~1.3.4",
"d3": "~3.4.11",
"handsontable": "*",
"pdfmake": "*"
},
"resolutions": {
"jquery": "~1.11"
}
}

View File

@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class InvoicesFile extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoice_designs', function($table)
{
$table->text('filename')->nullable();
});
DB::table('invoice_designs')->where('id', 1)->update([
'filename'=>'js/templates/clean.js'
]);
DB::table('invoice_designs')->where('id', 2)->update([
'filename'=>'js/templates/bold.js'
]);
DB::table('invoice_designs')->where('id', 3)->update([
'filename'=>'js/templates/modern.js'
]);
DB::table('invoice_designs')->where('id', 4)->update([
'filename'=>'js/templates/plain.js'
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoice_designs', function($table)
{
$table->dropColumn('filename');
});
}
}

View File

@ -3,11 +3,13 @@
"devDependencies": {
"gulp": "^3.8.8",
"laravel-elixir": "*",
"grunt": "~0.4.0",
"grunt": "~0.4.4",
"grunt-contrib-jshint": "~0.6.3",
"grunt-contrib-nodeunit": "~0.2.0",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-concat": "~0.4.0"
"grunt-contrib-concat": "~0.4.0"
},
"dependencies": {
"grunt-dump-dir": "^0.1.2"
}
}

75
public/css/built.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

154
public/js/pdf.pdfmake.js Normal file
View File

@ -0,0 +1,154 @@
function GetPdfMake(invoice, javascript, callback) {
var account = invoice.account;
eval(javascript);
doc = pdfMake.createPdf(dd);
doc.save = function(fileName) {
this.download(fileName);
};
return doc;
}
function notesAndTerms(invoice)
{
var text = [];
if (invoice.public_notes) {
text.push({text:invoice.public_notes, style:'notes'});
}
if (invoice.terms) {
text.push({text:invoiceLabels.terms, style:'termsLabel'});
text.push({text:invoice.terms, style:'terms'});
}
return text;
}
function invoiceLines(invoice) {
var grid =
[[{text: invoiceLabels.item, style: 'tableHeader'},
{text: invoiceLabels.description, style: 'tableHeader'},
{text: invoiceLabels.unit_cost, style: 'tableHeader'},
{text: invoiceLabels.quantity, style: 'tableHeader'},
{text: invoice.has_taxes?invoiceLabels.tax:'', style: 'tableHeader'},
{text: invoiceLabels.line_total, style: 'tableHeader'}]];
var total = 0;
var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
var hideQuantity = invoice.account.hide_quantity == '1';
for (var i = 0; i < invoice.invoice_items.length; i++) {
var row = [];
var item = invoice.invoice_items[i];
var cost = formatMoney(item.cost, currencyId, true);
var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : '';
var notes = item.notes;
var productKey = item.product_key;
var tax = "";
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate);
}
// show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue;
}
shownItem = true;
// process date variables
if (invoice.is_recurring) {
notes = processVariables(notes);
productKey = processVariables(productKey);
}
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (tax) {
lineTotal += lineTotal * tax / 100;
}
if (lineTotal) {
total += lineTotal;
}
lineTotal = formatMoney(lineTotal, currencyId);
rowStyle = i%2===0?'odd':'even';
row[0] = {style:["productKey", rowStyle], text:productKey};
row[1] = {style:["notes", rowStyle], text:notes};
row[2] = {style:["cost", rowStyle], text:cost};
row[3] = {style:["quantity", rowStyle], text:qty};
row[4] = {style:["tax", rowStyle], text:""+tax};
row[5] = {style:["lineTotal", rowStyle], text:lineTotal};
grid.push(row);
}
return grid;
}
function subtotals(invoice)
{
if (!invoice) {
return;
}
var data = [
[invoiceLabels.subtotal, formatMoney(invoice.subtotal_amount, invoice.client.currency_id)],
];
if(invoice.discount_amount != 0) {
data.push([invoiceLabels.discount, formatMoney(invoice.discount_amount, invoice.client.currency_id)]);
}
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 == '1') {
data.push([invoiceLabels.custom_invoice_label1, formatMoney(invoice.custom_value1, invoice.client.currency_id)]);
}
if (NINJA.parseFloat(invoice.custom_value2) && invoice.custom_taxes2 == '1') {
data.push([invoiceLabels.custom_invoice_label2, formatMoney(invoice.custom_value2, invoice.client.currency_id)]);
}
if(invoice.tax && invoice.tax.name || invoice.tax_name) {
data.push([invoiceLabels.tax, formatMoney(invoice.tax_amount, invoice.client.currency_id)]);
}
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {
data.push([invoiceLabels.custom_invoice_label1, formatMoney(invoice.custom_value1, invoice.client.currency_id)]);
}
if (NINJA.parseFloat(invoice.custom_value2) && invoice.custom_taxes2 != '1') {
data.push([invoiceLabels.custom_invoice_label2, formatMoney(invoice.custom_value2, invoice.client.currency_id)]);
}
var paid = invoice.amount - invoice.balance;
if (invoice.account.hide_paid_to_date != '1' || paid) {
data.push([invoiceLabels.paid_to_date, formatMoney(paid, invoice.client.currency_id)]);
}
data.push([{text:invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due, style:'balanceDueLabel'},
{text:formatMoney(invoice.balance_amount, invoice.client.currency_id), style:'balanceDueValue'}]);
return data;
}
function accountDetails(account) {
var data = [];
if(account.name) data.push({text:account.name, style:'accountDetails'});
if(account.id_number) data.push({text:account.id_number, style:'accountDetails'});
if(account.vat_number) data.push({text:account.vat_number, style:'accountDetails'});
if(account.work_email) data.push({text:account.work_email, style:'accountDetails'});
if(account.work_phone) data.push({text:account.work_phone, style:'accountDetails'});
return data;
}
function accountAddress(account) {
var data = [];
if(account.address1) data.push({text:account.address1, style:'accountDetails'});
if(account.address2) data.push({text:account.address2, style:'accountDetails'});
if(account.city) data.push({text:account.city, style:'accountDetails'});
if(account.state) data.push({text:account.state, style:'accountDetails'});
if(account.postal_code) data.push({text:account.postal_code, style:'accountDetails'});
return data;
}
function primaryColor( defaultColor) {
return NINJA.primaryColor?NINJA.primaryColor:defaultColor;
}
function secondaryColor( defaultColor) {
return NINJA.primaryColor?NINJA.secondaryColor:defaultColor;
}

View File

@ -8,16 +8,25 @@ var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
var invoiceOld;
function generatePDF(invoice, javascript, force) {
function generatePDF(invoice, javascript, force, cb) {
invoice = calculateAmounts(invoice);
var a = copyInvoice(invoice);
var b = copyInvoice(invoiceOld);
if (!force && _.isEqual(a, b)) {
return;
}
pdfmakeMarker = "//pdfmake";
invoiceOld = invoice;
report_id = invoice.invoice_design_id;
doc = GetPdf(invoice, javascript);
if(javascript.slice(0, pdfmakeMarker.length) === pdfmakeMarker) {
doc = GetPdfMake(invoice, javascript, cb);
//doc.getDataUrl(cb);
} else {
doc = GetPdf(invoice, javascript);
doc.getDataUrl = function(cb) {
cb( this.output("datauristring"));
};
}
return doc;
}
@ -814,7 +823,7 @@ function concatStrings() {
concatStr += ' ';
}
}
return data.length ? concatStr : false;
return data.length ? concatStr : "";
}
function displayGrid(doc, invoice, data, x, y, layout, options) {

View File

@ -0,0 +1,182 @@
//pdfmake
var dd = {
content: [
{
columns: [
[
invoice.image?
{
image: invoice.image,
fit: [150, 80]
}:""
],
{
stack: accountDetails(account)
},
{
stack: accountAddress(account)
}
]
},
{
text:(invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice).toUpperCase(),
margin: [8, 16, 8, 16],
style: 'primaryColor'
},
{
style: 'tableExample',
table: {
headerRows: 1,
widths: ['auto', 'auto', '*'],
body: [
[invoice.is_quote ? invoiceLabels.quote_number:invoiceLabels.invoice_number, {style: 'bold', text: invoice.invoice_number}, ""],
[invoice.is_quote ? invoiceLabels.quote_date:invoiceLabels.invoice_date, invoice.invoice_date, ""],
[invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due, formatMoney(invoice.balance_amount, invoice.client.currency_id), ""],
]
},
layout: {
hLineWidth: function (i, node) {
return (i === 0 || i === node.table.body.length) ? 1 : 0;
},
vLineWidth: function (i, node) {
return 0;//(i === 0 || i === node.table.widths.length) ? 2 : 1;
},
hLineColor: function (i, node) {
return '#D8D8D8';//(i === 0 || i === node.table.body.length) ? 'black' : 'gray';
},
/*vLineColor: function (i, node) {
return (i === 0 || i === node.table.widths.length) ? 'black' : 'gray';
},*/
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
}
},
'\n',
{
table: {
headerRows: 1,
widths: ['auto', '*', 'auto', 'auto', 'auto', 'auto'],
body:invoiceLines(invoice),
},
layout: {
hLineWidth: function (i, node) {
return i === 0 ? 0 : 1;
},
vLineWidth: function (i, node) {
return 0;
},
hLineColor: function (i, node) {
return '#D8D8D8';
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 8; },
paddingBottom: function(i, node) { return 8; }
},
},
'\n',
{
columns: [
notesAndTerms(invoice),
{
style: 'subtotals',
table: {
widths: ['*', '*'],
body: subtotals(invoice),
},
layout: {
hLineWidth: function (i, node) {
return 0;
},
vLineWidth: function (i, node) {
return 0;
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
},
}
]
},
],
footer: function(){
f = [{ text:invoice.invoice_footer?invoice.invoice_footer:"", margin: [72, 0]}]
if (!invoice.is_pro && logoImages.imageLogo1) {
f.push({
image: logoImages.imageLogo1,
width: 150,
margin: [72,0]
});
}
return f;
},
defaultStyle: {
//font: 'sans'
fontSize: 9,
margin: [8, 4, 8, 4]
},
styles: {
primaryColor:{
color: primaryColor('#299CC2')
},
accountDetails: {
margin: [4, 2, 4, 2],
color: '#AAA9A9'
},
bold: {
bold: true
},
even: {
},
odd: {
fillColor:'#F4F4F4'
},
productKey: {
color:primaryColor('#299CC2')
},
cost: {
alignment: 'right'
},
quantity: {
alignment: 'right'
},
tax: {
alignment: 'right'
},
lineTotal: {
alignment: 'right'
},
right: {
alignment: 'right'
},
subtotals: {
alignment: 'right'
},
tableHeader: {
bold: true
},
balanceDueLabel: {
fontSize: 11
},
balanceDueValue: {
fontSize: 11,
color:primaryColor('#299CC2')
},
notes: {
},
terms: {
},
termsLabel: {
bold: true,
fontSize: 10,
margin: [0, 10, 0, 4]
}
},
pageMargins: [72, 40, 40, 80]
};

1
public/js/vfs_fonts.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -36,6 +36,7 @@ Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kant
* [Troels Liebe Bentsen](https://github.com/tlbdk)
* [Jeramy Simpson](https://github.com/JeramyMywork) - [MyWork](https://www.mywork.com.au)
* [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas)
### Documentation

View File

@ -26,7 +26,7 @@
}
}
function getPDFString() {
function getPDFString(cb) {
invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!};
invoice.account.hide_quantity = $('#hide_quantity').is(":checked");
invoice.account.hide_paid_to_date = $('#hide_paid_to_date').is(":checked");
@ -35,11 +35,8 @@
NINJA.primaryColor = $('#primary_color').val();
NINJA.secondaryColor = $('#secondary_color').val();
var doc = generatePDF(invoice, getDesignJavascript(), true);
if (!doc) {
return;
}
return doc.output('datauristring');
doc = generatePDF(invoice, getDesignJavascript(), true);
doc.getDataUrl(cb);
}
$(function() {

View File

@ -700,13 +700,12 @@
return invoice;
}
function getPDFString() {
function getPDFString(cb) {
var invoice = createInvoiceModel();
var design = getDesignJavascript();
if (!design) return;
var doc = generatePDF(invoice, design);
if (!doc) return;
return doc.output('datauristring');
doc = generatePDF(invoice, design, false);
doc.getDataUrl(cb);
}
function getDesignJavascript() {
@ -744,10 +743,13 @@
var invoice = createInvoiceModel();
var design = getDesignJavascript();
if (!design) return;
var doc = generatePDF(invoice, design, true);
$('form.form-horizontal.warn-on-exit').append('<input type="hidden" name="pdfupload" value="'+doc.output('datauristring')+'">');
submitAction('');
doc = generatePDF(invoice, design, true);
doc.getDataUrl( function(pdfString){
$('form.form-horizontal.warn-on-exit').append('<input type="hidden" name="pdfupload" value="'+pdfString+'">');
submitAction('');
});
}
}

View File

@ -13,7 +13,7 @@
var currentInvoice = {!! $invoice !!};
var versionsJson = {!! $versionsJson !!};
function getPDFString() {
function getPDFString(cb) {
var version = $('#version').val();
var invoice;
@ -32,11 +32,8 @@
invoiceDesign = invoiceDesigns[0];
}
var doc = generatePDF(invoice, invoiceDesign.javascript, true);
if (!doc) {
return;
}
return doc.output('datauristring');
doc = generatePDF(invoice, invoiceDesign.javascript, true);
doc.getDataUrl(cb);
}
$(function() {

View File

@ -81,18 +81,19 @@
var needsRefresh = false;
function refreshPDF() {
getPDFString(refreshPDFCB);
}
function refreshPDFCB(string) {
if (!string) return;
PDFJS.workerSrc = '{{ asset('js/pdf_viewer.worker.js') }}';
if ({{ Auth::check() && Auth::user()->force_pdfjs ? 'false' : 'true' }} && (isFirefox || (isChrome && !isChromium))) {
var string = getPDFString();
if (!string) return;
if ({{ Auth::check() && Auth::user()->force_pdfjs ? 'false' : 'true' }} && (isFirefox || (isChrome && !isChromium))) {
$('#theFrame').attr('src', string).show();
} else {
if (isRefreshing) {
needsRefresh = true;
return;
}
var string = getPDFString();
if (!string) return;
isRefreshing = true;
var pdfAsArray = convertDataURIToBinary(string);
PDFJS.getDocument(pdfAsArray).then(function getPdfHelloWorld(pdf) {

View File

@ -47,10 +47,9 @@
invoice.is_quote = {{ $invoice->is_quote ? 'true' : 'false' }};
invoice.contact = {!! $contact->toJson() !!};
function getPDFString() {
var doc = generatePDF(invoice, invoice.invoice_design.javascript);
if (!doc) return;
return doc.output('datauristring');
function getPDFString(cb) {
doc = generatePDF(invoice, invoice.invoice_design.javascript);
doc.getDataUrl(cb);
}
$(function() {
@ -59,7 +58,7 @@
function onDownloadClick() {
var doc = generatePDF(invoice, invoice.invoice_design.javascript, true);
var fileName = invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice;
var fileName = invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice;
doc.save(fileName + '-' + invoice.invoice_number + '.pdf');
}