Send invoice and delivery-note with CSV file #745
|
@ -3,8 +3,8 @@ import Popover from '../popover';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export default class Menu extends Popover {
|
export default class Menu extends Popover {
|
||||||
show(parent) {
|
show(parent, direction) {
|
||||||
super.show(parent);
|
super.show(parent, direction);
|
||||||
this.windowEl.addEventListener('click', () => this.hide());
|
this.windowEl.addEventListener('click', () => this.hide());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
@import "./effects";
|
@import "./effects";
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
.vn-menu {
|
.vn-menu {
|
||||||
vn-item, .vn-item {
|
vn-item, .vn-item {
|
||||||
@extend %clickable;
|
@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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,15 @@ export default class Popover extends Popup {
|
||||||
* it is shown in a visible relative position to it.
|
* it is shown in a visible relative position to it.
|
||||||
*
|
*
|
||||||
* @param {HTMLElement|Event} parent Overrides the parent property
|
* @param {HTMLElement|Event} parent Overrides the parent property
|
||||||
|
* @param {String} direction - Direction [left]
|
||||||
*/
|
*/
|
||||||
show(parent) {
|
show(parent, direction) {
|
||||||
if (parent instanceof Event)
|
if (parent instanceof Event)
|
||||||
parent = event.target;
|
parent = event.target;
|
||||||
|
|
||||||
if (parent) this.parent = parent;
|
if (parent) this.parent = parent;
|
||||||
|
if (direction) this.direction = direction;
|
||||||
|
|
||||||
super.show();
|
super.show();
|
||||||
this.content = this.popup.querySelector('.content');
|
this.content = this.popup.querySelector('.content');
|
||||||
this.$timeout(() => this.relocate(), 10);
|
this.$timeout(() => this.relocate(), 10);
|
||||||
|
@ -89,21 +92,40 @@ export default class Popover extends Popup {
|
||||||
let width = clamp(popoverRect.width, parentRect.width, maxWith);
|
let width = clamp(popoverRect.width, parentRect.width, maxWith);
|
||||||
let height = popoverRect.height;
|
let height = popoverRect.height;
|
||||||
|
|
||||||
let left = parentRect.left + parentRect.width / 2 - width / 2;
|
let left;
|
||||||
left = clamp(left, margin, maxRight - width);
|
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;
|
let showTop = top + height > maxBottom;
|
||||||
if (showTop) top = parentRect.top - height - arrowOffset;
|
if (showTop) top = parentRect.top - height - arrowOffset;
|
||||||
top = Math.max(top, margin);
|
top = Math.max(top, margin);
|
||||||
|
|
||||||
if (showTop)
|
if (this.direction == 'left')
|
||||||
|
arrowStyle.left = `0`;
|
||||||
|
else if (showTop)
|
||||||
arrowStyle.bottom = `0`;
|
arrowStyle.bottom = `0`;
|
||||||
else
|
else
|
||||||
arrowStyle.top = `0`;
|
arrowStyle.top = `0`;
|
||||||
|
|
||||||
let arrowLeft = (parentRect.left - left) + parentRect.width / 2;
|
let arrowLeft;
|
||||||
arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight);
|
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`;
|
arrowStyle.left = `${arrowLeft}px`;
|
||||||
|
|
||||||
style.top = `${top}px`;
|
style.top = `${top}px`;
|
||||||
|
|
|
@ -18,6 +18,18 @@ class Email {
|
||||||
return this.$http.get(`email/${template}`, {params})
|
return this.$http.get(`email/${template}`, {params})
|
||||||
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
|
.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'];
|
Email.$inject = ['$http', '$translate', 'vnApp'];
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,21 @@ class Report {
|
||||||
const serializedParams = this.$httpParamSerializer(params);
|
const serializedParams = this.$httpParamSerializer(params);
|
||||||
window.open(`api/report/${report}?${serializedParams}`);
|
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'];
|
Report.$inject = ['$httpParamSerializer', 'vnToken'];
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,49 @@
|
||||||
module="invoiceOut"
|
module="invoiceOut"
|
||||||
description="$ctrl.invoiceOut.ref">
|
description="$ctrl.invoiceOut.ref">
|
||||||
<slot-menu>
|
<slot-menu>
|
||||||
<a class="vn-item"
|
<vn-item class="dropdown"
|
||||||
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
|
vn-click-stop="showInvoiceMenu.show($event, 'left')"
|
||||||
target="_blank"
|
|
||||||
name="showInvoicePdf"
|
name="showInvoicePdf"
|
||||||
translate>
|
translate>
|
||||||
Show invoice PDF
|
Show invoice...
|
||||||
</a>
|
|
||||||
<vn-item
|
<vn-menu vn-id="showInvoiceMenu">
|
||||||
ng-click="invoiceConfirmation.show({email: $ctrl.invoiceOut.client.email})"
|
<vn-list>
|
||||||
|
<a class="vn-item"
|
||||||
|
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
|
||||||
|
target="_blank"
|
||||||
|
name="showInvoicePdf"
|
||||||
|
translate>
|
||||||
|
Show as PDF
|
||||||
|
</a>
|
||||||
|
<vn-item
|
||||||
|
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"
|
name="sendInvoice"
|
||||||
translate>
|
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>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="deleteConfirmation.show()"
|
ng-click="deleteConfirmation.show()"
|
||||||
|
@ -104,15 +135,32 @@
|
||||||
message="Generate PDF invoice document">
|
message="Generate PDF invoice document">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
|
|
||||||
<!-- Send invoice confirmation popup -->
|
<!-- Send PDF invoice confirmation popup -->
|
||||||
<vn-dialog class="edit"
|
<vn-dialog
|
||||||
vn-id="invoiceConfirmation"
|
vn-id="sendPdfConfirmation"
|
||||||
on-accept="$ctrl.sendInvoice($data)"
|
on-accept="$ctrl.sendPdfInvoice($data)"
|
||||||
message="Send invoice PDF">
|
message="Send PDF invoice">
|
||||||
<tpl-body>
|
<tpl-body>
|
||||||
<span translate>Are you sure you want to send it?</span>
|
<span translate>Are you sure you want to send it?</span>
|
||||||
<vn-textfield vn-one
|
<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>
|
</vn-textfield>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
<tpl-buttons>
|
<tpl-buttons>
|
||||||
|
|
|
@ -14,29 +14,6 @@ class Controller extends Descriptor {
|
||||||
return this.aclService.hasAny(['invoicing']);
|
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() {
|
get filter() {
|
||||||
if (this.invoiceOut)
|
if (this.invoiceOut)
|
||||||
return JSON.stringify({refFk: this.invoiceOut.ref});
|
return JSON.stringify({refFk: this.invoiceOut.ref});
|
||||||
|
@ -55,7 +32,7 @@ class Controller extends Descriptor {
|
||||||
}, {
|
}, {
|
||||||
relation: 'client',
|
relation: 'client',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id', 'name']
|
fields: ['id', 'name', 'email']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -76,13 +53,51 @@ class Controller extends Descriptor {
|
||||||
// Prevents error when not defined
|
// 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', {
|
return this.vnEmail.send('invoice', {
|
||||||
recipientId: this.invoiceOut.client.id,
|
recipientId: this.invoiceOut.client.id,
|
||||||
recipient: $data.email,
|
recipient: $data.email,
|
||||||
invoiceId: this.id
|
invoiceId: this.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendCsvInvoice($data) {
|
||||||
|
return this.vnEmail.sendCsv('invoice', {
|
||||||
|
recipientId: this.invoiceOut.client.id,
|
||||||
|
recipient: $data.email,
|
||||||
|
invoiceId: this.id
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceOutDescriptor', {
|
ngModule.vnComponent('vnInvoiceOutDescriptor', {
|
||||||
|
|
|
@ -3,30 +3,20 @@ import './index';
|
||||||
describe('vnInvoiceOutDescriptor', () => {
|
describe('vnInvoiceOutDescriptor', () => {
|
||||||
let controller;
|
let controller;
|
||||||
let $httpBackend;
|
let $httpBackend;
|
||||||
const invoiceOut = {id: 1};
|
let $httpParamSerializer;
|
||||||
|
const invoiceOut = {
|
||||||
|
id: 1,
|
||||||
|
client: {id: 1101}
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(ngModule('invoiceOut'));
|
beforeEach(ngModule('invoiceOut'));
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$httpBackend_) => {
|
beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => {
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
|
$httpParamSerializer = _$httpParamSerializer_;
|
||||||
controller = $componentController('vnInvoiceOutDescriptor', {$element: null});
|
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()', () => {
|
describe('loadData()', () => {
|
||||||
it(`should perform a get query to store the invoice in data into the controller`, () => {
|
it(`should perform a get query to store the invoice in data into the controller`, () => {
|
||||||
const id = 1;
|
const id = 1;
|
||||||
|
@ -39,4 +29,81 @@ describe('vnInvoiceOutDescriptor', () => {
|
||||||
expect(controller.invoiceOut).toEqual(response);
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,10 @@ Volume exceded: Volumen excedido
|
||||||
Volume: Volumen
|
Volume: Volumen
|
||||||
Client card: Ficha del cliente
|
Client card: Ficha del cliente
|
||||||
Invoice ticket list: Listado de tickets de la factura
|
Invoice ticket list: Listado de tickets de la factura
|
||||||
Show invoice PDF: Ver factura en PDF
|
Show invoice...: Ver factura...
|
||||||
Send invoice PDF: Enviar factura en PDF
|
Send invoice...: Enviar factura...
|
||||||
|
Send PDF invoice: Enviar factura en PDF
|
||||||
|
Send CSV invoice: Enviar factura en CSV
|
||||||
Delete Invoice: Eliminar factura
|
Delete Invoice: Eliminar factura
|
||||||
Clone Invoice: Clonar factura
|
Clone Invoice: Clonar factura
|
||||||
InvoiceOut deleted: Factura eliminada
|
InvoiceOut deleted: Factura eliminada
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
icon="more_vert"
|
icon="more_vert"
|
||||||
vn-popover="menu">
|
vn-popover="menu">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
|
|
||||||
<vn-menu vn-id="menu">
|
<vn-menu vn-id="menu">
|
||||||
<vn-list>
|
<vn-list>
|
||||||
<vn-item
|
<vn-item
|
||||||
|
@ -12,15 +13,44 @@
|
||||||
translate>
|
translate>
|
||||||
Add turn
|
Add turn
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
<vn-item class="dropdown"
|
||||||
ng-click="$ctrl.showDeliveryNote()"
|
vn-click-stop="showDeliveryNoteMenu.show($event, 'left')"
|
||||||
translate>
|
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="$ctrl.showCsvDeliveryNote()"
|
||||||
|
translate>
|
||||||
|
Show as CSV
|
||||||
|
</vn-item>
|
||||||
|
</vn-list>
|
||||||
|
</vn-menu>
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
<vn-item class="dropdown"
|
||||||
ng-click="confirmDeliveryNote.show()"
|
vn-click-stop="sendDeliveryNoteMenu.show($event, 'left')"
|
||||||
translate>
|
translate>
|
||||||
Send Delivery Note
|
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>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="deleteConfirmation.show()"
|
ng-click="deleteConfirmation.show()"
|
||||||
|
@ -79,7 +109,7 @@
|
||||||
Make invoice
|
Make invoice
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="createInvoicePdfConfirmation.show()"
|
ng-click="createPdfConfirmation.show()"
|
||||||
ng-show="$ctrl.isInvoiced && ($ctrl.hasInvoicing || !$ctrl.ticket.invoiceOut.hasPdf)"
|
ng-show="$ctrl.isInvoiced && ($ctrl.hasInvoicing || !$ctrl.ticket.invoiceOut.hasPdf)"
|
||||||
name="regenerateInvoice"
|
name="regenerateInvoice"
|
||||||
translate>
|
translate>
|
||||||
|
@ -133,13 +163,39 @@
|
||||||
</div>
|
</div>
|
||||||
</vn-popup>
|
</vn-popup>
|
||||||
|
|
||||||
<!-- Send delivery note confirmation popup -->
|
<!-- Send PDF delivery note confirmation popup -->
|
||||||
<vn-confirm
|
<vn-dialog
|
||||||
vn-id="confirmDeliveryNote"
|
vn-id="sendPdfConfirmation"
|
||||||
on-accept="$ctrl.sendDeliveryNote()"
|
on-accept="$ctrl.sendPdfDeliveryNote($data)"
|
||||||
question="Are you sure you want to send it?"
|
message="Send PDF Delivery Note">
|
||||||
message="Send Delivery Note">
|
<tpl-body>
|
||||||
</vn-confirm>
|
<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 -->
|
<!-- Delete ticket confirmation popup -->
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
|
@ -206,8 +262,8 @@
|
||||||
|
|
||||||
<!-- Create invoice PDF confirmation dialog -->
|
<!-- Create invoice PDF confirmation dialog -->
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
vn-id="createInvoicePdfConfirmation"
|
vn-id="createPdfConfirmation"
|
||||||
on-accept="$ctrl.createInvoicePdf()"
|
on-accept="$ctrl.createPdfInvoice()"
|
||||||
question="Are you sure you want to generate/regenerate the PDF invoice?"
|
question="Are you sure you want to generate/regenerate the PDF invoice?"
|
||||||
message="Generate PDF invoice document">
|
message="Generate PDF invoice document">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
|
|
|
@ -115,17 +115,32 @@ class Controller extends Section {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showDeliveryNote() {
|
showPdfDeliveryNote() {
|
||||||
this.vnReport.show('delivery-note', {
|
this.vnReport.show('delivery-note', {
|
||||||
recipientId: this.ticket.client.id,
|
recipientId: this.ticket.client.id,
|
||||||
ticketId: this.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', {
|
return this.vnEmail.send('delivery-note', {
|
||||||
recipientId: this.ticket.client.id,
|
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
|
ticketId: this.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -227,7 +242,7 @@ class Controller extends Section {
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
||||||
}
|
}
|
||||||
|
|
||||||
createInvoicePdf() {
|
createPdfInvoice() {
|
||||||
const invoiceId = this.ticket.invoiceOut.id;
|
const invoiceId = this.ticket.invoiceOut.id;
|
||||||
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
|
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
|
||||||
.then(() => this.reload())
|
.then(() => this.reload())
|
||||||
|
|
|
@ -2,6 +2,7 @@ import './index.js';
|
||||||
|
|
||||||
describe('Ticket Component vnTicketDescriptorMenu', () => {
|
describe('Ticket Component vnTicketDescriptorMenu', () => {
|
||||||
let $httpBackend;
|
let $httpBackend;
|
||||||
|
let $httpParamSerializer;
|
||||||
let controller;
|
let controller;
|
||||||
let $state;
|
let $state;
|
||||||
|
|
||||||
|
@ -25,8 +26,9 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
|
||||||
|
|
||||||
beforeEach(ngModule('ticket'));
|
beforeEach(ngModule('ticket'));
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$httpBackend_, _$state_) => {
|
beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_, _$state_) => {
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
|
$httpParamSerializer = _$httpParamSerializer_;
|
||||||
$state = _$state_;
|
$state = _$state_;
|
||||||
$state.params.id = 16;
|
$state.params.id = 16;
|
||||||
$state.getCurrentPath = () => [null, {state: {name: 'ticket'}}];
|
$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', () => {
|
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 expectedParams = {
|
||||||
const params = {
|
ticketId: ticket.id,
|
||||||
recipientId: ticket.client.id,
|
recipientId: ticket.client.id
|
||||||
ticketId: ticket.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()', () => {
|
it('should make a query and call vnApp.showMessage()', () => {
|
||||||
jest.spyOn(controller.vnEmail, 'send');
|
jest.spyOn(controller.vnEmail, 'send');
|
||||||
|
|
||||||
|
const $data = {email: 'brucebanner@gothamcity.com'};
|
||||||
const params = {
|
const params = {
|
||||||
recipient: ticket.client.email,
|
recipient: $data.email,
|
||||||
recipientId: ticket.client.id,
|
recipientId: ticket.client.id,
|
||||||
ticketId: ticket.id
|
ticketId: ticket.id
|
||||||
};
|
};
|
||||||
controller.sendDeliveryNote();
|
controller.sendPdfDeliveryNote($data);
|
||||||
|
|
||||||
expect(controller.vnEmail.send).toHaveBeenCalledWith('delivery-note', params);
|
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()', () => {
|
describe('makeInvoice()', () => {
|
||||||
it('should make a query and call $state.reload() method', () => {
|
it('should make a query and call $state.reload() method', () => {
|
||||||
jest.spyOn(controller, 'reload').mockReturnThis();
|
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', () => {
|
it('should make a query and show a success snackbar', () => {
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
$httpBackend.whenGET(`Tickets/16`).respond();
|
$httpBackend.whenGET(`Tickets/16`).respond();
|
||||||
$httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond();
|
$httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond();
|
||||||
controller.createInvoicePdf();
|
controller.createPdfInvoice();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
|
|
@ -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
|
|
@ -8,8 +8,6 @@ Add stowaway: Añadir polizón
|
||||||
Delete stowaway: Eliminar 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 delete this stowaway?: ¿Seguro que quieres eliminar este polizón?
|
||||||
Are you sure you want to send it?: ¿Seguro que quieres enviarlo?
|
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
|
Show pallet report: Ver hoja de pallet
|
||||||
Change shipped hour: Cambiar hora de envío
|
Change shipped hour: Cambiar hora de envío
|
||||||
Shipped hour: Hora de envío
|
Shipped hour: Hora de envío
|
||||||
|
|
|
@ -37,20 +37,30 @@ class Email extends Component {
|
||||||
return userTranslations.subject;
|
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 instance = this.build();
|
||||||
const rendered = await this.render();
|
const rendered = await this.render();
|
||||||
const attachments = [];
|
const attachments = [];
|
||||||
const getAttachments = async(componentPath, files) => {
|
const getAttachments = async(componentPath, files) => {
|
||||||
for (file of files) {
|
for (file of files) {
|
||||||
const fileCopy = Object.assign({}, file);
|
const fileCopy = Object.assign({}, file);
|
||||||
|
const fileName = fileCopy.filename;
|
||||||
|
|
||||||
|
if (options.overrideAttachments && !fileName.includes('.png')) continue;
|
||||||
|
|
||||||
if (fileCopy.cid) {
|
if (fileCopy.cid) {
|
||||||
const templatePath = `${componentPath}/${file.path}`;
|
const templatePath = `${componentPath}/${file.path}`;
|
||||||
const fullFilePath = path.resolve(__dirname, templatePath);
|
const fullFilePath = path.resolve(__dirname, templatePath);
|
||||||
|
|
||||||
fileCopy.path = path.resolve(__dirname, fullFilePath);
|
fileCopy.path = path.resolve(__dirname, fullFilePath);
|
||||||
} else {
|
} else {
|
||||||
const reportName = fileCopy.filename.replace('.pdf', '');
|
const reportName = fileName.replace('.pdf', '');
|
||||||
const report = new Report(reportName, this.args);
|
const report = new Report(reportName, this.args);
|
||||||
fileCopy.content = await report.toPdfStream();
|
fileCopy.content = await report.toPdfStream();
|
||||||
}
|
}
|
||||||
|
@ -71,9 +81,14 @@ class Email extends Component {
|
||||||
if (this.attachments)
|
if (this.attachments)
|
||||||
await getAttachments(this.path, 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 localeSubject = await this.getSubject();
|
||||||
const replyTo = this.args.replyTo || this.args.auth.email;
|
const replyTo = this.args.replyTo || this.args.auth.email;
|
||||||
const options = {
|
const mailOptions = {
|
||||||
to: this.args.recipient,
|
to: this.args.recipient,
|
||||||
replyTo: replyTo,
|
replyTo: replyTo,
|
||||||
subject: localeSubject,
|
subject: localeSubject,
|
||||||
|
@ -81,7 +96,7 @@ class Email extends Component {
|
||||||
attachments: attachments
|
attachments: attachments
|
||||||
};
|
};
|
||||||
|
|
||||||
return smtp.send(options);
|
return smtp.send(mailOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,10 @@ module.exports = app => {
|
||||||
const methods = [];
|
const methods = [];
|
||||||
|
|
||||||
// Get all methods
|
// Get all methods
|
||||||
methodsDir.forEach(method => {
|
for (let method of methodsDir) {
|
||||||
methods.push(method.replace('.js', ''));
|
if (method.includes('.js'))
|
||||||
});
|
methods.push(method.replace('.js', ''));
|
||||||
|
}
|
||||||
|
|
||||||
// Auth middleware
|
// Auth middleware
|
||||||
const paths = [];
|
const paths = [];
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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
|
|
@ -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 = ?
|
|
@ -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;
|
||||||
|
};
|
|
@ -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 = ?
|
|
@ -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
|
|
@ -29,6 +29,9 @@ module.exports = {
|
||||||
|
|
||||||
const hash = md5(this.signature.id.toString()).substring(0, 3);
|
const hash = md5(this.signature.id.toString()).substring(0, 3);
|
||||||
const file = `${config.storage.root}/${hash}/${this.signature.id}.png`;
|
const file = `${config.storage.root}/${hash}/${this.signature.id}.png`;
|
||||||
|
|
||||||
|
if (!fs.existsSync(file)) return null;
|
||||||
|
|
||||||
const src = fs.readFileSync(file);
|
const src = fs.readFileSync(file);
|
||||||
const base64 = Buffer.from(src, 'utf8').toString('base64');
|
const base64 = Buffer.from(src, 'utf8').toString('base64');
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
ticketSubtotal(ticket) {
|
ticketSubtotal(ticket) {
|
||||||
let subTotal = 0.00;
|
let subTotal = 0.00;
|
||||||
console.log(ticket.sales);
|
|
||||||
for (let sale of ticket.sales)
|
for (let sale of ticket.sales)
|
||||||
subTotal += this.saleImport(sale);
|
subTotal += this.saleImport(sale);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue