Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3108-item_isBargain_isOnOffer_deprecation
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
b41a0bd849
|
@ -0,0 +1,144 @@
|
|||
DROP PROCEDURE IF EXISTS vn.item_getBalance;
|
||||
|
||||
DELIMITER $$
|
||||
$$
|
||||
CREATE
|
||||
definer = root@`%` procedure vn.item_getBalance(IN vItemId int, IN vWarehouse int)
|
||||
BEGIN
|
||||
DECLARE vDateInventory DATETIME;
|
||||
DECLARE vCurdate DATE DEFAULT CURDATE();
|
||||
DECLARE vDayEnd DATETIME DEFAULT util.dayEnd(vCurdate);
|
||||
|
||||
SELECT inventoried INTO vDateInventory FROM config;
|
||||
SET @a = 0;
|
||||
SET @currentLineFk = 0;
|
||||
SET @shipped = '';
|
||||
|
||||
SELECT DATE(@shipped:= shipped) shipped,
|
||||
alertLevel,
|
||||
stateName,
|
||||
origin,
|
||||
reference,
|
||||
clientFk,
|
||||
name,
|
||||
`in` AS invalue,
|
||||
`out`,
|
||||
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance,
|
||||
@currentLineFk := IF (@shipped < CURDATE()
|
||||
OR (@shipped = CURDATE() AND (isPicked OR alertLevel >= 2)),
|
||||
lineFk,@currentLineFk) lastPreparedLineFk,
|
||||
isTicket,
|
||||
lineFk,
|
||||
isPicked,
|
||||
clientType,
|
||||
claimFk
|
||||
FROM
|
||||
( SELECT tr.landed AS shipped,
|
||||
b.quantity AS `in`,
|
||||
NULL AS `out`,
|
||||
al.id AS alertLevel,
|
||||
st.name AS stateName,
|
||||
s.name AS name,
|
||||
e.ref AS reference,
|
||||
e.id AS origin,
|
||||
s.id AS clientFk,
|
||||
IF(al.id = 3, TRUE, FALSE) isPicked,
|
||||
FALSE AS isTicket,
|
||||
b.id lineFk,
|
||||
NULL `order`,
|
||||
NULL AS clientType,
|
||||
NULL AS claimFk
|
||||
FROM buy b
|
||||
JOIN entry e ON e.id = b.entryFk
|
||||
JOIN travel tr ON tr.id = e.travelFk
|
||||
JOIN supplier s ON s.id = e.supplierFk
|
||||
JOIN alertLevel al ON al.id =
|
||||
CASE
|
||||
WHEN tr.shipped < CURDATE() THEN 3
|
||||
WHEN tr.shipped = CURDATE() AND tr.isReceived = TRUE THEN 3
|
||||
ELSE 0
|
||||
END
|
||||
JOIN state st ON st.code = al.code
|
||||
WHERE tr.landed >= vDateInventory
|
||||
AND vWarehouse = tr.warehouseInFk
|
||||
AND b.itemFk = vItemId
|
||||
AND e.isInventory = FALSE
|
||||
AND e.isRaid = FALSE
|
||||
UNION ALL
|
||||
|
||||
SELECT tr.shipped,
|
||||
NULL,
|
||||
b.quantity,
|
||||
al.id,
|
||||
st.name,
|
||||
s.name,
|
||||
e.ref,
|
||||
e.id,
|
||||
s.id,
|
||||
IF(al.id = 3, TRUE, FALSE),
|
||||
FALSE,
|
||||
b.id,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
FROM buy b
|
||||
JOIN entry e ON e.id = b.entryFk
|
||||
JOIN travel tr ON tr.id = e.travelFk
|
||||
JOIN warehouse w ON w.id = tr.warehouseOutFk
|
||||
JOIN supplier s ON s.id = e.supplierFk
|
||||
JOIN alertLevel al ON al.id =
|
||||
CASE
|
||||
WHEN tr.shipped < CURDATE() THEN 3
|
||||
WHEN tr.shipped = CURDATE() AND tr.isReceived = TRUE THEN 3
|
||||
ELSE 0
|
||||
END
|
||||
JOIN state st ON st.code = al.code
|
||||
WHERE tr.shipped >= vDateInventory
|
||||
AND vWarehouse =tr.warehouseOutFk
|
||||
AND s.id <> 4
|
||||
AND b.itemFk = vItemId
|
||||
AND e.isInventory = FALSE
|
||||
AND w.isFeedStock = FALSE
|
||||
AND e.isRaid = FALSE
|
||||
UNION ALL
|
||||
|
||||
SELECT DATE(t.shipped),
|
||||
NULL,
|
||||
s.quantity,
|
||||
al.id,
|
||||
st.name,
|
||||
t.nickname,
|
||||
t.refFk,
|
||||
t.id,
|
||||
t.clientFk,
|
||||
stk.id,
|
||||
TRUE,
|
||||
s.id,
|
||||
st.`order`,
|
||||
ct.code,
|
||||
cl.id
|
||||
FROM sale s
|
||||
JOIN ticket t ON t.id = s.ticketFk
|
||||
LEFT JOIN ticketState ts ON ts.ticket = t.id
|
||||
LEFT JOIN state st ON st.code = ts.code
|
||||
JOIN client c ON c.id = t.clientFk
|
||||
JOIN clientType ct ON ct.id = c.clientTypeFk
|
||||
JOIN alertLevel al ON al.id =
|
||||
CASE
|
||||
WHEN t.shipped < curdate() THEN 3
|
||||
WHEN t.shipped > util.dayEnd(curdate()) THEN 0
|
||||
ELSE IFNULL(ts.alertLevel, 0)
|
||||
END
|
||||
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
|
||||
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
|
||||
LEFT JOIN claim cl ON cl.ticketFk = t.id
|
||||
WHERE t.shipped >= vDateInventory
|
||||
AND s.itemFk = vItemId
|
||||
AND vWarehouse =t.warehouseFk
|
||||
ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC
|
||||
) AS itemDiary;
|
||||
|
||||
END;
|
||||
$$
|
||||
DELIMITER ;
|
||||
|
|
@ -47,6 +47,13 @@ export async function getBrowser() {
|
|||
});
|
||||
page = extendPage(page);
|
||||
page.setDefaultTimeout(5000);
|
||||
await page.addStyleTag({
|
||||
content: `* {
|
||||
transition: none!important;
|
||||
animation: none!important;
|
||||
}`
|
||||
});
|
||||
|
||||
await page.goto(defaultURL, {waitUntil: 'load'});
|
||||
return {page, close: browser.close.bind(browser)};
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ describe('InvoiceOut manual invoice path', () => {
|
|||
it('should open the manual invoice form', async() => {
|
||||
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
|
||||
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
||||
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
|
||||
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
|
||||
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
||||
});
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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'];
|
||||
|
||||
|
|
|
@ -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'];
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
icon="menu"
|
||||
class="show-menu"
|
||||
ng-if="$ctrl.leftMenu"
|
||||
ng-click="$ctrl.leftMenu.show()">
|
||||
ng-click="$ctrl.leftMenu.toggle()">
|
||||
</vn-icon-button>
|
||||
<div class="side start">
|
||||
<a class="logo" ui-sref="home" title="{{'Home' | translate}}">
|
||||
|
|
|
@ -48,6 +48,10 @@ vn-layout {
|
|||
.show-menu {
|
||||
display: none;
|
||||
}
|
||||
& > .show-menu {
|
||||
margin-right: 5px;
|
||||
display: block
|
||||
}
|
||||
.vn-button {
|
||||
color: inherit;
|
||||
font-size: 1.05rem;
|
||||
|
@ -71,6 +75,10 @@ vn-layout {
|
|||
& > .main-view {
|
||||
padding-left: $menu-width;
|
||||
}
|
||||
|
||||
&.shown > .main-view {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
&.right-menu {
|
||||
& > vn-topbar > .end {
|
||||
|
@ -85,6 +93,8 @@ vn-layout {
|
|||
}
|
||||
& > .main-view {
|
||||
padding-top: $topbar-height;
|
||||
|
||||
transition: padding-left 200ms ease-out;
|
||||
}
|
||||
ui-view {
|
||||
& > * {
|
||||
|
@ -134,7 +144,8 @@ vn-layout {
|
|||
& > vn-topbar {
|
||||
left: 0;
|
||||
}
|
||||
& > .main-view {
|
||||
& > .main-view,
|
||||
&.shown > .main-view {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,12 +52,18 @@ export default class SideMenu extends Component {
|
|||
this.hide();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.shown) this.hide();
|
||||
else this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
if (this.shown) return;
|
||||
this.shown = true;
|
||||
this.handler = e => this.onEscape(e);
|
||||
this.$window.addEventListener('keydown', this.handler);
|
||||
this.stateHandler = this.$transitions.onStart({}, t => this.onTransition(t));
|
||||
this.layout.element.classList.add('shown');
|
||||
}
|
||||
|
||||
hide() {
|
||||
|
@ -65,6 +71,7 @@ export default class SideMenu extends Component {
|
|||
this.$window.removeEventListener('keydown', this.handler);
|
||||
this.stateHandler();
|
||||
this.shown = false;
|
||||
this.layout.element.classList.remove('shown');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,16 +14,20 @@ vn-side-menu > .menu {
|
|||
box-shadow: 0 1px 3px $color-shadow;
|
||||
overflow: auto;
|
||||
top: $topbar-height;
|
||||
transition: transform 200ms ease-out;
|
||||
|
||||
&.left {
|
||||
left: 0;
|
||||
left: 0
|
||||
}
|
||||
&.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.shown {
|
||||
transform: translateZ(0) translateX(-$menu-width);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-width) {
|
||||
transition: transform 200ms ease-out;
|
||||
z-index: 15;
|
||||
top: 0;
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
label="Type"
|
||||
label="Orientation"
|
||||
ng-model="$ctrl.viewportType"
|
||||
data="$ctrl.viewportTypes"
|
||||
selection="$ctrl.viewportSelection"
|
||||
|
|
|
@ -36,6 +36,18 @@ export default class UploadPhoto extends Component {
|
|||
width: 1350,
|
||||
height: 900
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'vertical',
|
||||
description: this.$t('Vertical'),
|
||||
viewport: {
|
||||
width: 306.66,
|
||||
height: 533.33
|
||||
},
|
||||
output: {
|
||||
width: 460,
|
||||
height: 800
|
||||
}
|
||||
}
|
||||
];
|
||||
this.viewportType = 'normal';
|
||||
|
@ -103,8 +115,17 @@ export default class UploadPhoto extends Component {
|
|||
const reader = new FileReader();
|
||||
reader.onload = e => this.editor.bind({url: e.target.result});
|
||||
reader.readAsDataURL(value);
|
||||
} else if (this.uploadMethod == 'URL')
|
||||
this.editor.bind({url: value});
|
||||
} else if (this.uploadMethod == 'URL') {
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'Anonymous';
|
||||
img.src = value;
|
||||
img.onload = () => this.editor.bind({url: value});
|
||||
img.onerror = () => {
|
||||
this.vnApp.showError(
|
||||
this.$t(`This photo provider doesn't allow remote downloads`)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ Select an image: Selecciona una imagen
|
|||
File name: Nombre del fichero
|
||||
Rotate left: Girar a la izquierda
|
||||
Rotate right: Girar a la derecha
|
||||
Panoramic: Panorámico
|
||||
Panoramic: Panorámica
|
||||
Orientation: Orientación
|
||||
Select from computer: Seleccionar desde ordenador
|
||||
Import from external URL: Importar desde URL externa
|
||||
This photo provider doesn't allow remote downloads: Este proveedor de fotos no permite descargas remotas
|
|
@ -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>
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th shrink></vn-th>
|
||||
<vn-th expand>Date</vn-th>
|
||||
<vn-th number order="DESC" shrink>Id</vn-th>
|
||||
<vn-th>State</vn-th>
|
||||
|
@ -48,6 +49,14 @@
|
|||
vn-repeat-last
|
||||
on-last="$ctrl.scrollToLine(sale.lastPreparedLineFk)"
|
||||
ng-attr-id="vnItemDiary-{{::sale.lineFk}}">
|
||||
<vn-td shrink>
|
||||
<a ui-sref="claim.card.basicData({id: sale.claimFk})">
|
||||
<vn-icon icon="icon-claims"
|
||||
ng-show="sale.claimFk"
|
||||
vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claimFk}}">
|
||||
</vn-icon>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td expand>
|
||||
<span class="chip"
|
||||
ng-class="::{warning: $ctrl.today == sale.shipped}">
|
||||
|
|
|
@ -7,17 +7,26 @@
|
|||
order="landed DESC, buyFk DESC"
|
||||
limit="20">
|
||||
</vn-crud-model>
|
||||
<vn-card class="vn-mb-md vn-w-xl vn-pa-lg">
|
||||
<vn-date-picker
|
||||
vn-none
|
||||
|
||||
<vn-card class="vn-w-md vn-pa-md">
|
||||
<vn-horizontal>
|
||||
<vn-date-picker class="vn-pa-xs"
|
||||
vn-one
|
||||
label="Since"
|
||||
ng-model="$ctrl.date">
|
||||
ng-model="$ctrl.dateFrom">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker class="vn-pa-xs"
|
||||
vn-one
|
||||
label="To"
|
||||
ng-model="$ctrl.dateTo">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
|
||||
|
||||
<vn-data-viewer
|
||||
model="model"
|
||||
class="vn-mb-xl vn-w-xl">
|
||||
class="vn-mb-xl vn-w-xl vn-pa-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-vertical>
|
||||
<vn-table model="model">
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
|
||||
const from = new Date();
|
||||
from.setDate(from.getDate() - 75);
|
||||
from.setDate(from.getDate());
|
||||
from.setHours(0, 0, 0, 0);
|
||||
|
||||
const to = new Date();
|
||||
to.setDate(to.getDate() + 60);
|
||||
to.setDate(to.getDate() + 10);
|
||||
to.setHours(23, 59, 59, 59);
|
||||
|
||||
this.filter = {
|
||||
|
@ -22,19 +21,19 @@ class Controller extends Section {
|
|||
}
|
||||
}
|
||||
};
|
||||
this._date = from;
|
||||
this._dateFrom = from;
|
||||
this._dateTo = to;
|
||||
}
|
||||
|
||||
set date(value) {
|
||||
this._date = value;
|
||||
set dateFrom(value) {
|
||||
this._dateFrom = value;
|
||||
|
||||
if (!value) return;
|
||||
|
||||
const from = new Date(value);
|
||||
from.setHours(0, 0, 0, 0);
|
||||
|
||||
const to = new Date();
|
||||
to.setDate(to.getDate() + 60);
|
||||
const to = new Date(this._dateTo);
|
||||
to.setHours(23, 59, 59, 59);
|
||||
|
||||
this.filter.where.shipped = {
|
||||
|
@ -43,8 +42,29 @@ class Controller extends Section {
|
|||
this.$.model.refresh();
|
||||
}
|
||||
|
||||
get date() {
|
||||
return this._date;
|
||||
set dateTo(value) {
|
||||
this._dateTo = value;
|
||||
|
||||
if (!value) return;
|
||||
|
||||
const from = new Date(this._dateFrom);
|
||||
from.setHours(0, 0, 0, 0);
|
||||
|
||||
const to = new Date(value);
|
||||
to.setHours(23, 59, 59, 59);
|
||||
|
||||
this.filter.where.shipped = {
|
||||
between: [from, to]
|
||||
};
|
||||
this.$.model.refresh();
|
||||
}
|
||||
|
||||
get dateFrom() {
|
||||
return this._dateFrom;
|
||||
}
|
||||
|
||||
get dateTo() {
|
||||
return this._dateTo;
|
||||
}
|
||||
|
||||
exprBuilder(param, value) {
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
@import "variables";
|
||||
|
||||
vn-item-last-entries {
|
||||
.round {
|
||||
background-color: $color-spacer;
|
||||
border-radius: 25px;
|
||||
float: right;
|
||||
width: 25px;
|
||||
color: $color-font-dark;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
vn-horizontal {
|
||||
justify-content: center;
|
||||
}
|
||||
vn-date-picker {
|
||||
flex: none !important;
|
||||
}
|
||||
@media screen and (max-width: 1440px) {
|
||||
.expendable {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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 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');
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue