Send invoice and delivery-note with CSV file #745

Merged
carlosjr merged 8 commits from 3094-invoice_csv into dev 2021-10-18 09:09:46 +00:00
25 changed files with 721 additions and 111 deletions

View File

@ -3,8 +3,8 @@ import Popover from '../popover';
import './style.scss';
export default class Menu extends Popover {
show(parent) {
super.show(parent);
show(parent, direction) {
super.show(parent, direction);
this.windowEl.addEventListener('click', () => this.hide());
}
}

View File

@ -1,7 +1,18 @@
@import "./effects";
@import "variables";
.vn-menu {
vn-item, .vn-item {
@extend %clickable;
}
vn-item.dropdown:after,
.vn-item.dropdown:after {
font-family: 'Material Icons';
content: 'keyboard_arrow_right';
position: absolute;
color: $color-spacer;
font-size: 1.5em;
right: 0
}
}

View File

@ -23,12 +23,15 @@ export default class Popover extends Popup {
* it is shown in a visible relative position to it.
*
* @param {HTMLElement|Event} parent Overrides the parent property
* @param {String} direction - Direction [left]
*/
show(parent) {
show(parent, direction) {
if (parent instanceof Event)
parent = event.target;
if (parent) this.parent = parent;
if (direction) this.direction = direction;
super.show();
this.content = this.popup.querySelector('.content');
this.$timeout(() => this.relocate(), 10);
@ -89,21 +92,40 @@ export default class Popover extends Popup {
let width = clamp(popoverRect.width, parentRect.width, maxWith);
let height = popoverRect.height;
let left = parentRect.left + parentRect.width / 2 - width / 2;
let left;
if (this.direction == 'left') {
left = parentRect.left + parentRect.width;
left = clamp(left, margin, maxRight - width);
} else {
left = parentRect.left + parentRect.width / 2 - width / 2;
left = clamp(left, margin, maxRight - width);
}
let top = parentRect.top + parentRect.height + arrowOffset;
let top;
if (this.direction == 'left')
top = parentRect.top;
else
top = parentRect.top + parentRect.height + arrowOffset;
let showTop = top + height > maxBottom;
if (showTop) top = parentRect.top - height - arrowOffset;
top = Math.max(top, margin);
if (showTop)
if (this.direction == 'left')
arrowStyle.left = `0`;
else if (showTop)
arrowStyle.bottom = `0`;
else
arrowStyle.top = `0`;
let arrowLeft = (parentRect.left - left) + parentRect.width / 2;
let arrowLeft;
if (this.direction == 'left') {
arrowLeft = 0;
let arrowTop = arrowOffset;
arrowStyle.top = `${arrowTop}px`;
} else {
arrowLeft = (parentRect.left - left) + parentRect.width / 2;
arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight);
}
arrowStyle.left = `${arrowLeft}px`;
style.top = `${top}px`;

View File

@ -18,6 +18,18 @@ class Email {
return this.$http.get(`email/${template}`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
/**
* Sends an email displaying a notification when it's sent.
*
* @param {String} template The email report name
* @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent
*/
sendCsv(template, params) {
return this.$http.get(`csv/${template}/send`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
}
Email.$inject = ['$http', '$translate', 'vnApp'];

View File

@ -20,6 +20,21 @@ class Report {
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/report/${report}?${serializedParams}`);
}
/**
* Shows a report in another window, automatically adds the authorization
* token to params.
*
* @param {String} report The report name
* @param {Object} params The report parameters
*/
showCsv(report, params) {
params = Object.assign({
authorization: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/csv/${report}/download?${serializedParams}`);
}
}
Report.$inject = ['$httpParamSerializer', 'vnToken'];

View File

@ -2,18 +2,49 @@
module="invoiceOut"
description="$ctrl.invoiceOut.ref">
<slot-menu>
<vn-item class="dropdown"
vn-click-stop="showInvoiceMenu.show($event, 'left')"
name="showInvoicePdf"
translate>
Show invoice...
<vn-menu vn-id="showInvoiceMenu">
<vn-list>
<a class="vn-item"
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
target="_blank"
name="showInvoicePdf"
translate>
Show invoice PDF
Show as PDF
</a>
<vn-item
ng-click="invoiceConfirmation.show({email: $ctrl.invoiceOut.client.email})"
ng-click="$ctrl.showCsvInvoice()"
translate>
Show as CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item class="dropdown"
vn-click-stop="sendInvoiceMenu.show($event, 'left')"
name="sendInvoice"
translate>
Send invoice PDF
Send invoice...
<vn-menu vn-id="sendInvoiceMenu">
<vn-list>
<vn-item
ng-click="sendPdfConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send PDF
</vn-item>
<vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item
ng-click="deleteConfirmation.show()"
@ -104,15 +135,32 @@
message="Generate PDF invoice document">
</vn-confirm>
<!-- Send invoice confirmation popup -->
<vn-dialog class="edit"
vn-id="invoiceConfirmation"
on-accept="$ctrl.sendInvoice($data)"
message="Send invoice PDF">
<!-- Send PDF invoice confirmation popup -->
<vn-dialog
vn-id="sendPdfConfirmation"
on-accept="$ctrl.sendPdfInvoice($data)"
message="Send PDF invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="invoiceConfirmation.data.email">
ng-model="sendPdfConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Send CSV invoice confirmation popup -->
<vn-dialog
vn-id="sendCsvConfirmation"
on-accept="$ctrl.sendCsvInvoice($data)"
message="Send CSV invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendCsvConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>

View File

@ -14,29 +14,6 @@ class Controller extends Descriptor {
return this.aclService.hasAny(['invoicing']);
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
createInvoicePdf() {
const invoiceId = this.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
get filter() {
if (this.invoiceOut)
return JSON.stringify({refFk: this.invoiceOut.ref});
@ -55,7 +32,7 @@ class Controller extends Descriptor {
}, {
relation: 'client',
scope: {
fields: ['id', 'name']
fields: ['id', 'name', 'email']
}
}
]
@ -76,13 +53,51 @@ class Controller extends Descriptor {
// Prevents error when not defined
}
sendInvoice($data) {
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
createPdfInvoice() {
const invoiceId = this.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
showCsvInvoice() {
this.vnReport.showCsv('invoice', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id,
});
}
sendPdfInvoice($data) {
return this.vnEmail.send('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
sendCsvInvoice($data) {
return this.vnEmail.sendCsv('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
}
ngModule.vnComponent('vnInvoiceOutDescriptor', {

View File

@ -3,30 +3,20 @@ import './index';
describe('vnInvoiceOutDescriptor', () => {
let controller;
let $httpBackend;
const invoiceOut = {id: 1};
let $httpParamSerializer;
const invoiceOut = {
id: 1,
client: {id: 1101}
};
beforeEach(ngModule('invoiceOut'));
beforeEach(inject(($componentController, _$httpBackend_) => {
beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnInvoiceOutDescriptor', {$element: null});
}));
describe('createInvoicePdf()', () => {
it('should make a query and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createInvoicePdf();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('loadData()', () => {
it(`should perform a get query to store the invoice in data into the controller`, () => {
const id = 1;
@ -39,4 +29,81 @@ describe('vnInvoiceOutDescriptor', () => {
expect(controller.invoiceOut).toEqual(response);
});
});
describe('createPdfInvoice()', () => {
it('should make a query to the createPdf() endpoint and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createPdfInvoice();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('showCsvInvoice()', () => {
it('should make a query to the csv invoice download endpoint and show a message snackbar', () => {
jest.spyOn(window, 'open').mockReturnThis();
controller.invoiceOut = invoiceOut;
const expectedParams = {
invoiceId: invoiceOut.id,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`;
controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendPdfInvoice()', () => {
it('should make a query to the email invoice endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`email/invoice?${serializedParams}`).respond();
controller.sendPdfInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('sendCsvInvoice()', () => {
it('should make a query to the csv invoice send endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond();
controller.sendCsvInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
});

View File

@ -2,8 +2,10 @@ Volume exceded: Volumen excedido
Volume: Volumen
Client card: Ficha del cliente
Invoice ticket list: Listado de tickets de la factura
Show invoice PDF: Ver factura en PDF
Send invoice PDF: Enviar factura en PDF
Show invoice...: Ver factura...
Send invoice...: Enviar factura...
Send PDF invoice: Enviar factura en PDF
Send CSV invoice: Enviar factura en CSV
Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura
InvoiceOut deleted: Factura eliminada

View File

@ -2,6 +2,7 @@
icon="more_vert"
vn-popover="menu">
</vn-icon-button>
<vn-menu vn-id="menu">
<vn-list>
<vn-item
@ -12,15 +13,44 @@
translate>
Add turn
</vn-item>
<vn-item
ng-click="$ctrl.showDeliveryNote()"
<vn-item class="dropdown"
vn-click-stop="showDeliveryNoteMenu.show($event, 'left')"
translate>
Show Delivery Note
Show Delivery Note...
<vn-menu vn-id="showDeliveryNoteMenu">
<vn-list>
<vn-item
ng-click="$ctrl.showPdfDeliveryNote()"
translate>
Show as PDF
</vn-item>
<vn-item
ng-click="confirmDeliveryNote.show()"
ng-click="$ctrl.showCsvDeliveryNote()"
translate>
Send Delivery Note
Show as CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item class="dropdown"
vn-click-stop="sendDeliveryNoteMenu.show($event, 'left')"
translate>
Send Delivery Note...
<vn-menu vn-id="sendDeliveryNoteMenu">
<vn-list>
<vn-item
ng-click="sendPdfConfirmation.show({email: $ctrl.ticket.client.email})"
translate>
Send PDF
</vn-item>
<vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.ticket.client.email})"
translate>
Send CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item
ng-click="deleteConfirmation.show()"
@ -79,7 +109,7 @@
Make invoice
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
ng-click="createPdfConfirmation.show()"
ng-show="$ctrl.isInvoiced && ($ctrl.hasInvoicing || !$ctrl.ticket.invoiceOut.hasPdf)"
name="regenerateInvoice"
translate>
@ -133,13 +163,39 @@
</div>
</vn-popup>
<!-- Send delivery note confirmation popup -->
<vn-confirm
vn-id="confirmDeliveryNote"
on-accept="$ctrl.sendDeliveryNote()"
question="Are you sure you want to send it?"
message="Send Delivery Note">
</vn-confirm>
<!-- Send PDF delivery note confirmation popup -->
<vn-dialog
vn-id="sendPdfConfirmation"
on-accept="$ctrl.sendPdfDeliveryNote($data)"
message="Send PDF Delivery Note">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendPdfConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Send CSV delivery note confirmation popup -->
<vn-dialog
vn-id="sendCsvConfirmation"
on-accept="$ctrl.sendCsvDeliveryNote($data)"
message="Send CSV Delivery Note">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendCsvConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Delete ticket confirmation popup -->
<vn-confirm
@ -206,8 +262,8 @@
<!-- Create invoice PDF confirmation dialog -->
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createInvoicePdf()"
vn-id="createPdfConfirmation"
on-accept="$ctrl.createPdfInvoice()"
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>

View File

@ -115,17 +115,32 @@ class Controller extends Section {
});
}
showDeliveryNote() {
showPdfDeliveryNote() {
this.vnReport.show('delivery-note', {
recipientId: this.ticket.client.id,
ticketId: this.id,
});
}
sendDeliveryNote() {
showCsvDeliveryNote() {
this.vnReport.showCsv('delivery-note', {
recipientId: this.ticket.client.id,
ticketId: this.id,
});
}
sendPdfDeliveryNote($data) {
return this.vnEmail.send('delivery-note', {
recipientId: this.ticket.client.id,
recipient: this.ticket.client.email,
recipient: $data.email,
ticketId: this.id
});
}
sendCsvDeliveryNote($data) {
return this.vnEmail.sendCsv('delivery-note', {
recipientId: this.ticket.client.id,
recipient: $data.email,
ticketId: this.id
});
}
@ -227,7 +242,7 @@ class Controller extends Section {
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
}
createInvoicePdf() {
createPdfInvoice() {
const invoiceId = this.ticket.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())

View File

@ -2,6 +2,7 @@ import './index.js';
describe('Ticket Component vnTicketDescriptorMenu', () => {
let $httpBackend;
let $httpParamSerializer;
let controller;
let $state;
@ -25,8 +26,9 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
beforeEach(ngModule('ticket'));
beforeEach(inject(($componentController, _$httpBackend_, _$state_) => {
beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_, _$state_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$state = _$state_;
$state.params.id = 16;
$state.getCurrentPath = () => [null, {state: {name: 'ticket'}}];
@ -104,36 +106,74 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
});
});
describe('showDeliveryNote()', () => {
describe('showPdfDeliveryNote()', () => {
it('should open a new window showing a delivery note PDF document', () => {
jest.spyOn(controller.vnReport, 'show');
jest.spyOn(window, 'open').mockReturnThis();
window.open = jasmine.createSpy('open');
const params = {
recipientId: ticket.client.id,
ticketId: ticket.id
const expectedParams = {
ticketId: ticket.id,
recipientId: ticket.client.id
};
controller.showDeliveryNote();
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/report/delivery-note?${serializedParams}`;
controller.showPdfDeliveryNote();
expect(controller.vnReport.show).toHaveBeenCalledWith('delivery-note', params);
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendDeliveryNote()', () => {
describe('sendPdfDeliveryNote()', () => {
it('should make a query and call vnApp.showMessage()', () => {
jest.spyOn(controller.vnEmail, 'send');
const $data = {email: 'brucebanner@gothamcity.com'};
const params = {
recipient: ticket.client.email,
recipient: $data.email,
recipientId: ticket.client.id,
ticketId: ticket.id
};
controller.sendDeliveryNote();
controller.sendPdfDeliveryNote($data);
expect(controller.vnEmail.send).toHaveBeenCalledWith('delivery-note', params);
});
});
describe('showCsvDeliveryNote()', () => {
it('should make a query to the csv delivery-note download endpoint and show a message snackbar', () => {
jest.spyOn(window, 'open').mockReturnThis();
const expectedParams = {
ticketId: ticket.id,
recipientId: ticket.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/delivery-note/download?${serializedParams}`;
controller.showCsvDeliveryNote();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendCsvDeliveryNote()', () => {
it('should make a query to the csv delivery-note send endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
ticketId: ticket.id,
recipient: $data.email,
recipientId: ticket.client.id,
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/delivery-note/send?${serializedParams}`).respond();
controller.sendCsvDeliveryNote($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('makeInvoice()', () => {
it('should make a query and call $state.reload() method', () => {
jest.spyOn(controller, 'reload').mockReturnThis();
@ -149,13 +189,13 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
});
});
describe('createInvoicePdf()', () => {
describe('createPdfInvoice()', () => {
it('should make a query and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.whenGET(`Tickets/16`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond();
controller.createInvoicePdf();
controller.createPdfInvoice();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();

View File

@ -0,0 +1,8 @@
Show Delivery Note...: Ver albarán...
Send Delivery Note...: Enviar albarán...
Show as PDF: Ver como PDF
Show as CSV: Ver como CSV
Send PDF: Enviar PDF
Send CSV: Enviar CSV
Send CSV Delivery Note: Enviar albarán en CSV
Send PDF Delivery Note: Enviar albarán en PDF

View File

@ -8,8 +8,6 @@ Add stowaway: Añadir polizón
Delete stowaway: Eliminar polizón
Are you sure you want to delete this stowaway?: ¿Seguro que quieres eliminar este polizón?
Are you sure you want to send it?: ¿Seguro que quieres enviarlo?
Show Delivery Note: Ver albarán
Send Delivery Note: Enviar albarán
Show pallet report: Ver hoja de pallet
Change shipped hour: Cambiar hora de envío
Shipped hour: Hora de envío

View File

@ -37,20 +37,30 @@ class Email extends Component {
return userTranslations.subject;
}
async send() {
/**
* @param {Object} [options] - Additional options
* @param {Boolean} [options.overrideAttachments] - Overrides default PDF attachments
* @param {Array} [options.attachments] - Array containing attachment objects
* @return {Promise} SMTP Promise
*/
async send(options = {}) {
const instance = this.build();
const rendered = await this.render();
const attachments = [];
const getAttachments = async(componentPath, files) => {
for (file of files) {
const fileCopy = Object.assign({}, file);
const fileName = fileCopy.filename;
if (options.overrideAttachments && !fileName.includes('.png')) continue;
if (fileCopy.cid) {
const templatePath = `${componentPath}/${file.path}`;
const fullFilePath = path.resolve(__dirname, templatePath);
fileCopy.path = path.resolve(__dirname, fullFilePath);
} else {
const reportName = fileCopy.filename.replace('.pdf', '');
const reportName = fileName.replace('.pdf', '');
const report = new Report(reportName, this.args);
fileCopy.content = await report.toPdfStream();
}
@ -71,9 +81,14 @@ class Email extends Component {
if (this.attachments)
await getAttachments(this.path, this.attachments);
if (options.attachments) {
for (let attachment of options.attachments)
attachments.push(attachment);
}
const localeSubject = await this.getSubject();
const replyTo = this.args.replyTo || this.args.auth.email;
const options = {
const mailOptions = {
to: this.args.recipient,
replyTo: replyTo,
subject: localeSubject,
@ -81,7 +96,7 @@ class Email extends Component {
attachments: attachments
};
return smtp.send(options);
return smtp.send(mailOptions);
}
}

View File

@ -8,9 +8,10 @@ module.exports = app => {
const methods = [];
// Get all methods
methodsDir.forEach(method => {
for (let method of methodsDir) {
if (method.includes('.js'))
methods.push(method.replace('.js', ''));
});
}
// Auth middleware
const paths = [];

31
print/methods/csv.js Normal file
View File

@ -0,0 +1,31 @@
module.exports = app => {
app.use('/api/csv/delivery-note', require('./csv/delivery-note')(app));
app.use('/api/csv/invoice', require('./csv/invoice')(app));
app.toCSV = function toCSV(rows) {
const [columns] = rows;
let content = Object.keys(columns).join('\t');
for (let row of rows) {
const values = Object.values(row);
const finalValues = values.map(value => {
if (value instanceof Date) return formatDate(value);
if (value === null) return '';
return value;
});
content += '\n';
content += finalValues.join('\t');
}
return content;
};
function formatDate(date) {
return new Intl.DateTimeFormat('es', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).format(date);
}
};

View File

@ -0,0 +1,82 @@
const express = require('express');
const router = new express.Router();
const path = require('path');
const db = require('../../../core/database');
const sqlPath = path.join(__dirname, 'sql');
module.exports = app => {
router.get('/preview', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const content = app.toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
res.setHeader('Content-type', 'application/json; charset=utf-8');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
router.get('/download', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const content = app.toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
res.setHeader('Content-type', 'text/csv');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
const Email = require('../../../core/email');
router.get('/send', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const ticket = await db.findOneFromDef(`${sqlPath}/ticket`, [ticketId]);
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const args = Object.assign({
ticketId: (String(ticket.id)),
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
const content = app.toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
const email = new Email('delivery-note', args);
await email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
res.status(200).json({message: 'ok'});
} catch (error) {
next(error);
}
});
return router;
};

View File

@ -0,0 +1,35 @@
SELECT io.ref Invoice,
io.issued InvoiceDate,
s.ticketFk Ticket,
s.itemFk Item,
s.concept Description,
i.size,
i.subName Producer,
s.quantity Quantity,
s.price Price,
s.discount Discount,
s.created Created,
tc.code Taxcode,
tc.description TaxDescription,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7,
i.tag8,
i.value8,
i.tag9,
i.value9,
i.tag10,
i.value10
FROM vn.sale s
JOIN vn.ticket t ON t.id = s.ticketFk
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.supplier s2 ON s2.id = t.companyFk
JOIN vn.itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = s2.countryFk
JOIN vn.taxClass tc ON tc.id = itc.taxClassFk
LEFT JOIN vn.invoiceOut io ON io.id = t.refFk
WHERE s.ticketFk = ?
ORDER BY s.ticketFk, s.created

View File

@ -0,0 +1,9 @@
SELECT
t.id,
t.clientFk,
c.email recipient,
eu.email salesPersonEmail
FROM ticket t
JOIN client c ON c.id = t.clientFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE t.id = ?

View File

@ -0,0 +1,82 @@
const express = require('express');
const router = new express.Router();
const path = require('path');
const db = require('../../../core/database');
const sqlPath = path.join(__dirname, 'sql');
module.exports = app => {
router.get('/preview', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const content = app.toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
res.setHeader('Content-type', 'application/json; charset=utf-8');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
router.get('/download', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const content = app.toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
res.setHeader('Content-type', 'text/csv');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
const Email = require('../../../core/email');
router.get('/send', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [invoiceId]);
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const args = Object.assign({
invoiceId: (String(invoice.id)),
recipientId: invoice.clientFk,
recipient: invoice.recipient,
replyTo: invoice.salesPersonEmail
}, reqArgs);
const content = app.toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
const email = new Email('invoice', args);
await email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
res.status(200).json({message: 'ok'});
} catch (error) {
next(error);
}
});
return router;
};

View File

@ -0,0 +1,9 @@
SELECT
io.id,
io.clientFk,
c.email recipient,
eu.email salesPersonEmail
FROM invoiceOut io
JOIN client c ON c.id = io.clientFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE io.id = ?

View File

@ -0,0 +1,35 @@
SELECT io.ref Invoice,
io.issued InvoiceDate,
s.ticketFk Ticket,
s.itemFk Item,
s.concept Description,
i.size,
i.subName Producer,
s.quantity Quantity,
s.price Price,
s.discount Discount,
s.created Created,
tc.code Taxcode,
tc.description TaxDescription,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7,
i.tag8,
i.value8,
i.tag9,
i.value9,
i.tag10,
i.value10
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
JOIN item i ON i.id = s.itemFk
JOIN supplier s2 ON s2.id = t.companyFk
JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = s2.countryFk
JOIN taxClass tc ON tc.id = itc.taxClassFk
JOIN invoiceOut io ON io.ref = t.refFk
WHERE io.id = ?
ORDER BY s.ticketFk, s.created

View File

@ -29,6 +29,9 @@ module.exports = {
const hash = md5(this.signature.id.toString()).substring(0, 3);
const file = `${config.storage.root}/${hash}/${this.signature.id}.png`;
if (!fs.existsSync(file)) return null;
const src = fs.readFileSync(file);
const base64 = Buffer.from(src, 'utf8').toString('base64');

View File

@ -95,7 +95,6 @@ module.exports = {
},
ticketSubtotal(ticket) {
let subTotal = 0.00;
console.log(ticket.sales);
for (let sale of ticket.sales)
subTotal += this.saleImport(sale);