diff --git a/AstuteClient2/src/app/invoice-gen/invoice-gen.component.ts b/AstuteClient2/src/app/invoice-gen/invoice-gen.component.ts index 9d141d9..26ad451 100644 --- a/AstuteClient2/src/app/invoice-gen/invoice-gen.component.ts +++ b/AstuteClient2/src/app/invoice-gen/invoice-gen.component.ts @@ -59,7 +59,7 @@ export class InvoiceGenComponent implements OnInit { this.gridY[j] = y; y += 10; } - this.astuteClientService.getInvoiceGen('MDO-01_DRAFT_720').then((data) => { + this.astuteClientService.getInvoiceGen('MDO-01_DRAFT_157').then((data) => { this.name = data.customer.customerName; this.email = data.customer.email; this.address = data.customer.add1 + ' ' + data.customer.add2 + ' ' + diff --git a/AstuteClient2/src/app/invoice/invoice.component.html b/AstuteClient2/src/app/invoice/invoice.component.html index 2bb50f2..c444ad8 100644 --- a/AstuteClient2/src/app/invoice/invoice.component.html +++ b/AstuteClient2/src/app/invoice/invoice.component.html @@ -48,7 +48,7 @@
-
diff --git a/AstuteClient2/src/app/invoice/invoice.component.ts b/AstuteClient2/src/app/invoice/invoice.component.ts index 04482e0..8cdb3cc 100644 --- a/AstuteClient2/src/app/invoice/invoice.component.ts +++ b/AstuteClient2/src/app/invoice/invoice.component.ts @@ -1,7 +1,7 @@ import {Component, OnInit, ViewChild} from '@angular/core'; import {AstuteClientService} from '../services/astute-client-service'; import {formatCurrency} from "@angular/common"; -import {log} from "util"; +import {PrintInvoiceService} from '../services/print-invoice.service'; declare var $: any; @@ -91,7 +91,7 @@ export class InvoiceComponent implements OnInit { } } - constructor(protected astuteClientService: AstuteClientService) { + constructor(protected astuteClientService: AstuteClientService, protected printService: PrintInvoiceService) { } customerDropdownChange(index) { @@ -231,6 +231,10 @@ export class InvoiceComponent implements OnInit { window.open('/invoice-gen'); } + printInvoice() { + this.printService.printPDF (this.chosenInv.invoiceNumber); + } + getSelectedRows() { const selectedNodes = this.agGrid.api.getSelectedNodes(); if (selectedNodes.length) { diff --git a/AstuteClient2/src/app/services/print-invoice.service.spec.ts b/AstuteClient2/src/app/services/print-invoice.service.spec.ts new file mode 100644 index 0000000..e9bb36d --- /dev/null +++ b/AstuteClient2/src/app/services/print-invoice.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { PrintInvoiceService } from './print-invoice.service'; + +describe('PrintInvoiceService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [PrintInvoiceService] + }); + }); + + it('should be created', inject([PrintInvoiceService], (service: PrintInvoiceService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/AstuteClient2/src/app/services/print-invoice.service.ts b/AstuteClient2/src/app/services/print-invoice.service.ts new file mode 100644 index 0000000..acab82e --- /dev/null +++ b/AstuteClient2/src/app/services/print-invoice.service.ts @@ -0,0 +1,333 @@ +import { Injectable } from '@angular/core'; +import {AstuteClientService} from '../services/astute-client-service'; +import {formatCurrency} from '@angular/common'; + +declare var html2pdf: any; +declare var html2canvas: any; +declare var jsPDF: any; +declare var $: any; + +@Injectable({ + providedIn: 'root' +}) +export class PrintInvoiceService { + + gridX = []; // these are the layout grid STARTING + gridY = []; // from the 1 inch border (x: 25; y:23) + + name: string; + email: string; + address: string; + + poNum; + coNum; + inNum; + inDate; + + inDetails; // :[{lineNum:number, desc:string, rate:string, hrs:number, amount:number}]; + + ogCoAmt; + netChanges = 0; + totCoAmt = 0; + prevBill; + inAmt; + balToBeBill; + subTotal; // =inAmt + milage = 0; + otherExp = 0; + outOf = 0; + finTot; // = inAmt; + + notes; + cert; + + constructor(protected astuteClientService: AstuteClientService) { + let x = 28.5; + let y = 25; + for (let i = 0; i < 17; i++) { // + this.gridX[i] = x; + x += 10; + } + for (let j = 0; j < 24; j++) { + this.gridY[j] = y; + y += 10; + } + } + + printPDF(invoice: string) { + this.astuteClientService.getInvoiceGen(invoice).then((data) => { + this.name = data.customer.customerName; + this.email = data.customer.email; + this.address = data.customer.add1 + ' ' + data.customer.add2 + ' ' + + data.customer.city + ', ' + data.customer.state.toUpperCase() + ', ' + + data.customer.zip + '-' + data.customer.ziplast4; + + this.poNum = data.po.ponum; + this.coNum = data.po.contractNum; + this.inNum = data.invoice.invoiceNumber; + this.inDate = data.invoice.invoiceDate; + + this.inDetails = data.invoiceDetail; + + this.ogCoAmt = data.po.contractAmt; + this.netChanges = 0; + this.totCoAmt = data.po.contractAmt; + this.prevBill = data.po.previouslyBilledAmount; + this.balToBeBill = data.balanceToBeBilled; + this.finTot = data.invoice.billAmt; + + this.notes = data.invoice.specialNotes; + this.cert = data.invoice.certification; + + this.testjsPDF(invoice); + }); + } + + private testjsPDF(invoice: string) { + const doc = jsPDF('p', 'mm', 'letter'); + console.log(doc.getFontList()); + // this.printGrid(doc); + this.printHeader(doc); + let len = this.inDetails.length; + if (len <= 6) { + this.printTable(doc, 7, 13, this.inDetails); + } else if (len > 6 && len <= 28) { + this.printTable(doc, 7, 22, this.inDetails.slice(0, 15)); + doc.addPage('letter', 'p'); + this.printTable(doc, 0, 13, this.inDetails.slice(15, 28)); + } else { + this.printTable(doc, 7, 22, this.inDetails.slice(0, 15)); + len -= 15; + let i = 0; + while (len > 13) { + doc.addPage('letter', 'p'); + this.printTable(doc, 0, 22, this.inDetails.slice(15 + (22 * i), 37 + (22 * i))); + i++; + } + doc.addPage('letter', 'p'); + this.printTable(doc, 0, 13, this.inDetails.slice(37 + (22 * (i - 1)))); + } + this.printFooter(doc); + doc.save(invoice + '.pdf'); + } + + private printHeader (doc) { + if (!doc) { + doc = jsPDF('p', 'mm', 'letter'); + } + ///////////////////// Header ////////// + // Logo and INVOICE + doc.setFontSize(35); + doc.text('INVOICE', this.gridX[10] + 5, this.gridY[2] - 5); + doc.addImage('assets/img/AstuteLogo.png', 'png', this.gridX[0], this.gridY[0], 50, 22.04); + + // To, customer + doc.setFontSize(12); + doc.text('To,', this.gridX[0], this.gridY[3]); + const nameSplit = doc.splitTextToSize(this.name, 65); + nameSplit.forEach((text, i) => { + doc.text(text, this.gridX[0] + 5, this.gridY[3] + (5 * (i + 1))); + }); + doc.setTextColor(66, 144, 255); + const emailSplit = doc.splitTextToSize(this.email, 65); + emailSplit.forEach((text, i) => { + doc.text(text, this.gridX[0] + 5, this.gridY[4] + (5 * (i + 1))); + }); + doc.setTextColor(0); + const addressSplit = doc.splitTextToSize(this.address, 65); + addressSplit.forEach((text, i) => { + doc.text(text, this.gridX[0] + 5, this.gridY[5] + (5 * (i + 1))); + }); + + // Invoice Information + doc.text('Purchase Order #:', this.gridX[11], this.gridY[3], {'align': 'right'}); + const poNumSplit = doc.splitTextToSize(' ' + this.poNum, 50); + poNumSplit.forEach((text, i) => { + doc.text(text, this.gridX[11], this.gridY[3] + (5 * i)); + }); + + doc.text('Contract #:', this.gridX[11], this.gridY[4], {'align': 'right'}); + const coNumSplit = doc.splitTextToSize(' ' + this.coNum, 50); + coNumSplit.forEach((text, i) => { + doc.text(text, this.gridX[11], this.gridY[4] + (5 * i)); + }); + + doc.text('Invoice #:', this.gridX[11], this.gridY[5], {'align': 'right'}); + const inNumSplit = doc.splitTextToSize(' ' + this.inNum, 50); + inNumSplit.forEach((text, i) => { + doc.text(text, this.gridX[11], this.gridY[5] + (5 * i)); + }); + + doc.text('Invoice Date:', this.gridX[11], this.gridY[6], {'align': 'right'}); + const inDateSplit = doc.splitTextToSize(' ' + this.inDate, 50); + inDateSplit.forEach((text, i) => { + doc.text(text, this.gridX[11], this.gridY[6] + (5 * i)); + }); + /////////////////////////////////////// + if (!doc) { + doc.autoPrint({variant: 'non-conform'}); + doc.save('grid.pdf'); + } + } + + private printTable (doc, start, end, details) { + if (!doc) { + doc = jsPDF('p', 'mm', 'letter'); + } + ///////////////////// Table /////////// + doc.setLineWidth(.25); + doc.setDrawColor(170); + + // Number Column (header and rows) + doc.setFillColor(225); + doc.rect(this.gridX[0], this.gridY[start], 10, 10, 'FD'); + doc.text('#', this.gridX[0] + 5, this.gridY[start + 1] - 3.5, {'align': 'center'}); + for (let i = start + 1; i <= end; i++) { + doc.rect(this.gridX[0], this.gridY[i], 10, 10); + } + + // Description Column (header and rows) + doc.setFillColor(225); + doc.rect(this.gridX[1], this.gridY[start], 70, 10, 'FD'); + doc.text('Description', this.gridX[4] + 5, this.gridY[start + 1] - 3.5, {'align': 'center'}); + for (let i = start + 1; i <= end; i++) { + doc.rect(this.gridX[1], this.gridY[i], 70, 10); + } + + // Fee Column (header and rows) + doc.setFillColor(225); + doc.rect(this.gridX[8], this.gridY[start], 30, 10, 'FD'); + doc.text('Fee', this.gridX[9] + 5, this.gridY[start + 1] - 3.5, {'align': 'center'}); + for (let i = start + 1; i <= end; i++) { + doc.rect(this.gridX[8], this.gridY[i], 30, 10); + } + + // Quantity Column (header and rows) + doc.setFillColor(225); + doc.rect(this.gridX[11], this.gridY[start], 20, 10, 'FD'); + doc.text('Qty.', this.gridX[12], this.gridY[start + 1] - 3.5, {'align': 'center'}); + for (let i = start + 1; i <= end; i++) { + doc.rect(this.gridX[11], this.gridY[i], 20, 10); + } + + // Amount Column (header and rows) + doc.setFillColor(225); + doc.rect(this.gridX[13], this.gridY[start], 30, 10, 'FD'); + doc.text('Amount', this.gridX[14] + 5, this.gridY[start + 1] - 3.5, {'align': 'center'}); + for (let i = start + 1; i <= end; i++) { + doc.rect(this.gridX[13], this.gridY[i], 30, 10); + } + + // Filling Data + doc.setFontSize(12); + details.forEach((inDet, i) => { + doc.text(inDet.lineItemNum.toString(), this.gridX[0] + 5, this.gridY[start + 2 + i] - 3.5, {'align': 'center'}); + + doc.setFontSize(11); + const descSplit = doc.splitTextToSize(inDet.desc, 66); + descSplit.forEach((text, j) => { + doc.text(text, this.gridX[1] + 2, this.gridY[start + 1 + i] + (4 * (j + 1))); + }); + + doc.setFontSize(12); + const fee = formatCurrency(inDet.fee, 'en-US', '$', 'USD').split('.')[0] + ((inDet.feeTypeId === 2) ? '/hr' : ''); + doc.text(fee, this.gridX[11] - 3.5, this.gridY[start + 2 + i] - 3.5, {'align': 'right'}); + + doc.text(inDet.qty.toString(), this.gridX[13] - 3.5, this.gridY[start + 2 + i] - 3.5, {'align': 'right'}); + + const amt = formatCurrency(inDet.fee * inDet.qty, 'en-US', '$', 'USD'); + doc.text(amt, this.gridX[16] - 3.5, this.gridY[start + 2 + i] - 3.5, {'align': 'right'}); + }); + /////////////////////////////////////// + if (!doc) { + doc.autoPrint({variant: 'non-conform'}); + doc.save('table.pdf'); + } + } + + private printFooter (doc) { + if (!doc) { + doc = jsPDF('p', 'mm', 'letter'); + } + ///////////////////// Footer ////////// + doc.setFontSize(12); + + // Invoice Bill Info Section + doc.setLineWidth(.25); + doc.setDrawColor(170); + doc.rect(this.gridX[0], this.gridY[15], 160, 50); + doc.text('Original Contract Amt.', this.gridX[5] - 3.5, this.gridY[16] - 3.5, {align: 'right'}); + doc.text('Net Changes by CO\'s', this.gridX[5] - 3.5, this.gridY[17] - 3.5, {align: 'right'}); + doc.text('Total Contract Amt.', this.gridX[5] - 3.5, this.gridY[18] - 3.5, {align: 'right'}); + doc.text('Previously Billed', this.gridX[5] - 3.5, this.gridY[19] - 3.5, {align: 'right'}); + doc.text('Balance to be Billed', this.gridX[5] - 3.5, this.gridY[20] - 3.5, {align: 'right'}); + for (let i = 15; i <= 19; i++) { + doc.setFillColor(225); + doc.rect(this.gridX[5], this.gridY[i], 30, 10, 'FD'); + } + doc.setFont('helvetica', 'bold'); + doc.text('Total Due this Invoice', this.gridX[13] - 3.5, this.gridY[16] - 3.5, {align: 'right'}); + doc.setFont('helvetica', 'normal'); + doc.setFillColor(225); + doc.rect(this.gridX[13], this.gridY[15], 30, 10, 'FD'); + + // Fill Data + const ogCoAmt = formatCurrency(this.ogCoAmt, 'en-US', '$', 'USD'); + doc.text(ogCoAmt, this.gridX[8] - 3.5, this.gridY[16] - 3.5, {align: 'right'}); + + const netChanges = formatCurrency(this.netChanges, 'en-US', '$', 'USD'); + doc.text(netChanges, this.gridX[8] - 3.5, this.gridY[17] - 3.5, {align: 'right'}); + + const totCoAmt = formatCurrency(this.totCoAmt, 'en-US', '$', 'USD'); + doc.text(totCoAmt, this.gridX[8] - 3.5, this.gridY[18] - 3.5, {align: 'right'}); + + const prevBill = formatCurrency(this.prevBill, 'en-US', '$', 'USD'); + doc.text(prevBill, this.gridX[8] - 3.5, this.gridY[19] - 3.5, {align: 'right'}); + + const toBeBilled = formatCurrency(this.totCoAmt - this.prevBill - this.finTot, 'en-US', '$', 'USD'); + doc.text(toBeBilled, this.gridX[8] - 3.5, this.gridY[20] - 3.5, {align: 'right'}); + + const finTot = formatCurrency(this.finTot, 'en-US', '$', 'USD'); + doc.setFont('helvetica', 'bold'); + doc.text(finTot, this.gridX[16] - 3.5, this.gridY[16] - 3.5, {align: 'right'}); + doc.setFont('helvetica', 'normal'); + + // Notes and Certification + doc.text('Notes:', this.gridX[0], this.gridY[20] + 5); + const noteSplit = doc.splitTextToSize(this.notes, 145); + noteSplit.forEach((text, i) => { + doc.text(text, this.gridX[1] + 5, this.gridY[20] + (5 * (i + 1))); + }); + const certSplit = doc.splitTextToSize(this.cert, 160); + certSplit.forEach((text, i) => { + doc.text(text, this.gridX[0], this.gridY[22] + (5 * i)); + }); + doc.line(this.gridX[11], this.gridY[23], this.gridX[16], this.gridY[23]); + /////////////////////////////////////// + if (!doc) { + doc.autoPrint({variant: 'non-conform'}); + doc.save('footer.pdf'); + } + } + + private printGrid (doc) { + if (!doc) { + doc = jsPDF('p', 'mm', 'letter'); + } else { + doc.setDrawColor(225); + } + this.gridX.forEach((x) => { + doc.line(x, this.gridY[0], x, this.gridY[this.gridY.length - 1]); + // doc.text(x.toString(), x, this.gridY[0]); + }); + this.gridY.forEach((y) => { + doc.line(this.gridX[0], y, this.gridX[this.gridX.length - 1], y); + // doc.text(y.toString(), this.gridX[0], y); + }); + doc.setDrawColor(0); + if (!doc) { + doc.autoPrint({variant: 'non-conform'}); + doc.save('grid.pdf'); + } + } +}