Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3111-uploadPhoto_vertical

This commit is contained in:
Joan Sanchez 2021-10-18 11:10:05 +02:00
commit 0313c8d590
154 changed files with 45356 additions and 1818 deletions

View File

@ -11,7 +11,6 @@ Required applications.
* Node.js = 14.x LTS
* Docker
* Git
* Docker
You will need to install globally the following items.
```

View File

@ -89,6 +89,7 @@ module.exports = Self => {
await uploadNewFile(ctx, dms, myOptions);
if (tx) await tx.commit();
return dms;
} catch (e) {
if (tx) await tx.rollback();

View File

@ -96,6 +96,7 @@ module.exports = Self => {
}
if (tx) await tx.commit();
return addedDms;
} catch (e) {
if (tx) await tx.rollback();

View File

@ -151,6 +151,7 @@ module.exports = Self => {
await fs.unlink(srcFilePath);
await tx.commit();
return newImage;
} catch (e) {
await tx.rollback();

View File

@ -0,0 +1,48 @@
drop procedure `vn`.`ticket_getProblems`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`ticket_getProblems`(IN vIsTodayRelative tinyint(1))
BEGIN
/**
* Calcula los problemas para un conjunto de tickets.
* Agrupados por ticket
*
* @table tmp.sale_getProblems(ticketFk, clientFk, warehouseFk, shipped) Identificadores de los tickets a calcular
* @return tmp.ticket_problems
*/
CALL sale_getProblems(vIsTodayRelative);
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_problems;
CREATE TEMPORARY TABLE tmp.ticket_problems
(PRIMARY KEY (ticketFk))
ENGINE = MEMORY
SELECT
ticketFk,
MAX(p.isFreezed) AS isFreezed,
MAX(p.risk) AS risk,
MAX(p.hasHighRisk) AS hasHighRisk,
MAX(p.hasTicketRequest) AS hasTicketRequest,
MIN(p.isAvailable) AS isAvailable,
MAX(p.itemShortage) AS itemShortage,
MIN(p.isTaxDataChecked) AS isTaxDataChecked,
MAX(p.hasComponentLack) AS hasComponentLack,
0 AS totalProblems
FROM tmp.sale_problems p
GROUP BY ticketFk;
UPDATE tmp.ticket_problems tp
SET tp.totalProblems = (
(tp.isFreezed) +
IF(tp.risk, TRUE, FALSE) +
(tp.hasTicketRequest) +
(tp.isAvailable = 0) +
(tp.isTaxDataChecked = 0) +
(tp.hasComponentLack)
);
DROP TEMPORARY TABLE
tmp.sale_problems;
END;;$$
DELIMITER ;

View File

@ -108,7 +108,7 @@ let actions = {
},
getState: async function() {
return await this.evaluate(() => {
return this.evaluate(() => {
let $state = angular.element(document.body).injector().get('$state');
return $state.current.name;
});
@ -194,7 +194,7 @@ let actions = {
},
getProperty: async function(selector, property) {
return await this.evaluate((selector, property) => {
return this.evaluate((selector, property) => {
return document.querySelector(selector)[property].replace(/\s+/g, ' ').trim();
}, selector, property);
},
@ -202,7 +202,7 @@ let actions = {
getClassName: async function(selector) {
const element = await this.$(selector);
const handle = await element.getProperty('className');
return await handle.jsonValue();
return handle.jsonValue();
},
waitPropertyLength: async function(selector, property, minLength) {
@ -210,7 +210,7 @@ let actions = {
const element = document.querySelector(selector);
return element && element[property] != null && element[property] !== '' && element[property].length >= minLength;
}, {}, selector, property, minLength);
return await this.getProperty(selector, property);
return this.getProperty(selector, property);
},
expectPropertyValue: async function(selector, property, value) {
@ -219,7 +219,7 @@ let actions = {
builtSelector = await this.selectorFormater(selector);
try {
return await this.waitForFunction((selector, property, value) => {
return this.waitForFunction((selector, property, value) => {
const element = document.querySelector(selector);
return element[property] == value;
}, {}, builtSelector, property, value);
@ -239,7 +239,7 @@ let actions = {
return element && element[property] != null && element[property] !== '';
}, {}, builtSelector, property);
return await this.getProperty(builtSelector, property);
return this.getProperty(builtSelector, property);
} catch (error) {
throw new Error(`couldn't get property: ${property} of ${builtSelector}, ${error}`);
}
@ -261,7 +261,7 @@ let actions = {
await this.waitForSelector(selector);
await this.waitForFunction(checkVisibility, {}, selector);
return await this.click(selector);
return this.click(selector);
},
writeOnEditableTD: async function(selector, text) {
@ -274,7 +274,7 @@ let actions = {
focusElement: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
let element = document.querySelector(selector);
element.focus();
}, selector);
@ -282,19 +282,19 @@ let actions = {
isVisible: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(checkVisibility, selector);
return this.evaluate(checkVisibility, selector);
},
waitImgLoad: async function(selector) {
await this.waitForSelector(selector);
return await this.waitForFunction(selector => {
return this.waitForFunction(selector => {
const imageReady = document.querySelector(selector).complete;
return imageReady;
}, {}, selector);
},
countElement: async function(selector) {
return await this.evaluate(selector => {
return this.evaluate(selector => {
return document.querySelectorAll(selector).length;
}, selector);
},
@ -312,7 +312,7 @@ let actions = {
waitForClassNotPresent: async function(selector, className) {
await this.waitForSelector(selector);
return await this.waitForFunction((selector, className) => {
return this.waitForFunction((selector, className) => {
if (!document.querySelector(selector).classList.contains(className))
return true;
}, {}, selector, className);
@ -320,7 +320,7 @@ let actions = {
waitForClassPresent: async function(selector, className) {
await this.waitForSelector(selector);
return await this.waitForFunction((elementSelector, targetClass) => {
return this.waitForFunction((elementSelector, targetClass) => {
if (document.querySelector(elementSelector).classList.contains(targetClass))
return true;
}, {}, selector, className);
@ -387,13 +387,13 @@ let actions = {
const innerText = document.querySelector(selector).innerText;
return innerText != null && innerText != '';
}, {}, selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
return document.querySelector(selector).innerText;
}, selector);
},
waitForEmptyInnerText: async function(selector) {
return await this.waitFunction(selector => {
return this.waitFunction(selector => {
return document.querySelector(selector).innerText == '';
}, selector);
},
@ -521,7 +521,7 @@ let actions = {
checkboxState: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
let checkbox = document.querySelector(selector);
switch (checkbox.$ctrl.field) {
case null:
@ -536,14 +536,14 @@ let actions = {
isDisabled: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
let element = document.querySelector(selector);
return element.$ctrl.disabled;
}, selector);
},
waitForStylePresent: async function(selector, property, value) {
return await this.waitForFunction((selector, property, value) => {
return this.waitForFunction((selector, property, value) => {
const element = document.querySelector(selector);
return element.style[property] == value;
}, {}, selector, property, value);
@ -631,7 +631,7 @@ export function extendPage(page) {
for (let name in actions) {
page[name] = async(...args) => {
try {
return await actions[name].apply(page, args);
return actions[name].apply(page, args);
} catch (err) {
let stringArgs = args
.map(i => typeof i == 'function' ? 'Function' : i)

View File

@ -5,11 +5,16 @@ import {url as defaultURL} from './config';
export async function getBrowser() {
const args = [
`--no-sandbox`,
`--window-size=${ 1920 },${ 1080 }`
'--no-sandbox',
`--window-size=${ 1920 },${ 1080 }`,
'--single-process',
'--no-zygote'
// '--disable-dev-shm-usage'
// '--full-memory-crash-report',
// '--unlimited-storage'
];
let env = process.env;
const env = process.env;
if (env.E2E_DEBUG) {
args.push('--auto-open-devtools-for-tabs');
@ -22,6 +27,9 @@ export async function getBrowser() {
defaultViewport: null,
headless: headless,
slowMo: 0, // slow down by ms
// ignoreDefaultArgs: ['--disable-extensions'],
// executablePath: '/usr/bin/google-chrome-stable',
// executablePath: '/usr/bin/firefox-developer-edition',
});
let page = (await browser.pages())[0];
@ -38,7 +46,7 @@ export async function getBrowser() {
});
});
page = extendPage(page);
page.setDefaultTimeout(4000);
page.setDefaultTimeout(5000);
await page.goto(defaultURL, {waitUntil: 'load'});
return {page, close: browser.close.bind(browser)};
}

View File

@ -544,7 +544,7 @@ export default {
},
ticketSales: {
setOk: 'vn-ticket-sale vn-tool-bar > vn-button[label="Ok"] > button',
saleLine: 'vn-table div > vn-tbody > vn-tr',
saleLine: 'vn-table div > vn-tbody > vn-tr vn-check',
saleDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.descriptor.id})"]',
newItemButton: 'vn-ticket-sale vn-card vn-icon-button[icon="add_circle"]',

View File

@ -107,6 +107,7 @@ describe('Item regularize path', () => {
});
it('should regularize the item once more', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.itemDescriptor.moreMenu);
await page.waitToClick(selectors.itemDescriptor.moreMenuRegularizeButton);
await page.write(selectors.itemDescriptor.regularizeQuantity, '100');

View File

@ -196,6 +196,7 @@ describe('Ticket Edit sale path', () => {
});
it('should select the third sale and create a claim of it', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);
@ -340,9 +341,10 @@ describe('Ticket Edit sale path', () => {
});
it('should confirm the new ticket received the line', async() => {
const expectedLines = 1;
const result = await page.countElement(selectors.ticketSales.saleLine);
expect(result).toEqual(1);
expect(result).toEqual(expectedLines);
});
it('should check the first sale reserved icon isnt visible', async() => {
@ -353,6 +355,7 @@ describe('Ticket Edit sale path', () => {
it('should mark the first sale as reserved', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuReserve);
await page.closePopup();
@ -363,6 +366,7 @@ describe('Ticket Edit sale path', () => {
});
it('should unmark the first sale as reserved', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuUnmarkReseved);
await page.waitForClassPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide');

View File

@ -28,6 +28,7 @@ describe('Ticket descriptor path', () => {
});
it('should add the ticket to thursday turn using the descriptor more menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn);
await page.waitToClick(selectors.ticketDescriptor.thursdayButton);
@ -63,6 +64,7 @@ describe('Ticket descriptor path', () => {
});
it('should add the ticket to saturday turn using the descriptor more menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn);
await page.waitToClick(selectors.ticketDescriptor.saturdayButton);

View File

@ -22,6 +22,7 @@ describe('Ticket descriptor path', () => {
});
it(`should update the shipped hour using the descriptor menu`, async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour);
await page.pickTime(selectors.ticketDescriptor.changeShippedHour, '08:15');
@ -65,6 +66,7 @@ describe('Ticket descriptor path', () => {
describe('Restore ticket', () => {
it('should restore the ticket using the descriptor menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuRestoreTicket);
await page.waitToClick(selectors.ticketDescriptor.acceptDialog);
@ -82,6 +84,7 @@ describe('Ticket descriptor path', () => {
});
it('should open the add stowaway dialog', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitForFunction(() => {
let element = document.querySelector('vn-ticket-descriptor-menu');
return element.$ctrl.canShowStowaway === true;
@ -114,6 +117,7 @@ describe('Ticket descriptor path', () => {
});
it('should delete the stowaway', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteStowawayButton);
@ -145,6 +149,7 @@ describe('Ticket descriptor path', () => {
});
it('should invoice the ticket using the descriptor menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuMakeInvoice);
@ -176,6 +181,7 @@ describe('Ticket descriptor path', () => {
describe('SMS', () => {
it('should send the payment SMS using the descriptor menu', async() => {
await page.waitForTimeout(2000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
@ -188,6 +194,7 @@ describe('Ticket descriptor path', () => {
});
it('should send the import SMS using the descriptor menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms);

View File

@ -62,6 +62,7 @@ describe('Ticket create path', () => {
});
it('should make the previously created ticket the stowaway of the current ticket', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway);
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
@ -71,6 +72,7 @@ describe('Ticket create path', () => {
});
it('should delete the current ticket', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
await page.waitToClick(selectors.ticketDescriptor.acceptDialog);

View File

@ -17,6 +17,7 @@ describe('Ticket create from client path', () => {
});
it('should click the create simple ticket on the descriptor menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.clientDescriptor.moreMenu);
await page.waitToClick(selectors.clientDescriptor.simpleTicketButton);
await page.waitForState('ticket.create');

View File

@ -18,6 +18,7 @@ describe('InvoiceIn descriptor path', () => {
});
it('should clone the invoiceIn using the descriptor more menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.invoiceInDescriptor.moreMenu);
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuCloneInvoiceIn);
await page.waitToClick(selectors.invoiceInDescriptor.acceptButton);
@ -31,6 +32,7 @@ describe('InvoiceIn descriptor path', () => {
});
it('should delete the cloned invoiceIn using the descriptor more menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.invoiceInDescriptor.moreMenu);
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuDeleteInvoiceIn);
await page.waitToClick(selectors.invoiceInDescriptor.acceptButton);

View File

@ -38,6 +38,7 @@ describe('InvoiceOut descriptor path', () => {
});
it('should delete the invoiceOut using the descriptor more menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.invoiceOutDescriptor.moreMenu);
await page.waitToClick(selectors.invoiceOutDescriptor.moreMenuDeleteInvoiceOut);
await page.waitToClick(selectors.invoiceOutDescriptor.acceptDeleteButton);

View File

@ -16,6 +16,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.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
@ -44,6 +45,7 @@ describe('InvoiceOut manual invoice path', () => {
});
it('should now 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.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);

View File

@ -34,6 +34,7 @@ describe('Travel descriptor path', () => {
});
it('should be redirected to the create entry view', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuAddEntry);
await page.waitForState('entry.create');
@ -89,6 +90,7 @@ describe('Travel descriptor path', () => {
});
it('should be redirected to the create travel when using the clone option of the dot menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuClone);
await page.respondToDialog('accept');
@ -114,6 +116,7 @@ describe('Travel descriptor path', () => {
});
it('should atempt to clone the travel and its entries using the descriptor menu but receive an error', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries);
await page.waitToClick(selectors.travelDescriptor.acceptClonation);

View File

@ -17,6 +17,7 @@ describe('Zone descriptor path', () => {
});
it('should eliminate the zone using the descriptor option', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.zoneDescriptor.menu);
await page.waitToClick(selectors.zoneDescriptor.deleteZone);
await page.respondToDialog('accept');

View File

@ -91,6 +91,7 @@ describe('Account create and basic data path', () => {
});
it('should activate the account using the descriptor menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.activateAccount);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
@ -138,6 +139,7 @@ describe('Account create and basic data path', () => {
describe('Set password', () => {
it('should set the password using the descriptor menu', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.setPassword);
await page.write(selectors.accountDescriptor.newPassword, 'quantum.crypt0graphy');

View File

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

View File

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

View File

@ -1,9 +1,10 @@
<div ng-if="$ctrl.model.moreRows">
<vn-button
<div ng-if="$ctrl.model.moreRows" class="vn-py-md">
<div
ng-if="!$ctrl.model.isPaging"
label="Load more results"
ng-click="$ctrl.onLoadClick()">
</vn-button>
<vn-button label="Load more results"></vn-button>
<vn-icon icon="arrow_drop_down"/>
</div>
<vn-spinner
ng-if="$ctrl.model.isPaging"
enable="::true">

View File

@ -1,7 +1,13 @@
@import "variables";
vn-pagination {
display: block;
text-align: center;
color: $color-primary;
vn-button, vn-icon {
display: block
}
& > div > vn-icon-button {
font-size: 2rem;

View File

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

View File

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

View File

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

View File

@ -94,8 +94,11 @@ async function launchBackTest(done) {
await new Promise((resolve, reject) => {
const jasmine = require('gulp-jasmine');
let options = {
const options = {
verbose: false,
includeStackTrace: false,
errorOnFail: false,
timeout: 5000,
config: {}
};

View File

@ -116,5 +116,6 @@
"This client is not invoiceable": "This client is not invoiceable",
"INACTIVE_PROVIDER": "Inactive provider",
"reference duplicated": "reference duplicated",
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option"
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option",
"This item is not available": "This item is not available"
}

View File

@ -8,12 +8,10 @@ describe('client canBeInvoiced()', () => {
accessToken: {userId: userId}
};
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
done();
});
it('should return falsy for a client without the data checked', async() => {

View File

@ -4,13 +4,11 @@ describe('loopback model address', () => {
let createdAddressId;
const clientId = 1101;
afterAll(async done => {
afterAll(async() => {
let client = await app.models.Client.findById(clientId);
await app.models.Address.destroyById(createdAddressId);
await client.updateAttribute('isEqualizated', false);
done();
});
describe('observe()', () => {

View File

@ -41,6 +41,7 @@ module.exports = Self => {
const deleted = await Promise.all(promises);
if (tx) await tx.commit();
return deleted;
} catch (e) {
if (tx) await tx.rollback();

View File

@ -97,6 +97,7 @@ module.exports = Self => {
const sql = ParameterizedSQL.join(stmts, ';');
await conn.executeStmt(sql, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();

View File

@ -10,12 +10,10 @@ describe('entry import()', () => {
accessToken: {userId: buyerId},
};
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
done();
});
it('should import the buy rows', async() => {

View File

@ -3,12 +3,10 @@ const LoopBackContext = require('loopback-context');
describe('entry importBuysPreview()', () => {
const entryId = 1;
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
done();
});
it('should return the buys with the calculated packageFk', async() => {

View File

@ -1,9 +1,3 @@
vn-entry-buy-import {
.vn-table > tbody td:nth-child(1) {
width: 250px
}
}
.itemFilter {
vn-table.scrollable {
height: 500px

View File

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

View File

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

View File

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

View File

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

View File

@ -40,7 +40,7 @@ module.exports = Self => {
if (!tax.taxClassFk)
throw new UserError('Tax class cannot be blank');
promises.push(Self.app.models.ItemTaxCountry.update(
promises.push(Self.app.models.ItemTaxCountry.updateAll(
{id: tax.id},
{taxClassFk: tax.taxClassFk}
), myOptions);

View File

@ -129,54 +129,20 @@ module.exports = Self => {
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'t.id': {inq: value}}
: {'t.nickname': {like: `%${value}%`}};
case 'from':
return {'t.shipped': {gte: value}};
case 'to':
return {'t.shipped': {lte: value}};
case 'nickname':
return {'t.nickname': {like: `%${value}%`}};
case 'refFk':
return {'t.refFk': value};
case 'salesPersonFk':
return {'c.salesPersonFk': value};
case 'provinceFk':
return {'a.provinceFk': value};
case 'stateFk':
return {'ts.stateFk': value};
case 'mine':
case 'myTeam':
if (value)
return {'c.salesPersonFk': {inq: teamMembersId}};
else
return {'c.salesPersonFk': {nin: teamMembersId}};
case 'alertLevel':
return {'ts.alertLevel': value};
case 'pending':
if (value) {
return {and: [
{'st.alertLevel': 0},
{'st.code': {nin: [
'OK',
'BOARDING',
'PRINTED',
'PRINTED_AUTO',
'PICKER_DESIGNED'
]}}
]};
} else {
return {and: [
{'st.alertLevel': {gt: 0}}
]};
}
case 'id':
case 'clientFk':
case 'agencyModeFk':
case 'warehouseFk':
param = `t.${param}`;
return {[param]: value};
}
@ -196,7 +162,6 @@ module.exports = Self => {
t.id,
t.shipped,
CAST(DATE(t.shipped) AS CHAR) AS shippedDate,
HOUR(t.shipped) AS shippedHour,
t.nickname,
t.refFk,
t.routeFk,
@ -217,12 +182,12 @@ module.exports = Self => {
ts.code AS alertLevelCode,
u.name AS userName,
c.salesPersonFk,
c.credit,
z.hour AS zoneLanding,
HOUR(z.hour) AS zoneHour,
MINUTE(z.hour) AS zoneMinute,
z.name AS zoneName,
z.id AS zoneFk,
CAST(z.hour AS CHAR) AS hour,
TIME_FORMAT(t.shipped, '%H:%i') AS preparationHour,
TIME_FORMAT(z.hour, '%H:%i') AS theoreticalhour,
TIME_FORMAT(zed.etc, '%H:%i') AS practicalHour
FROM ticket t
LEFT JOIN invoiceOut io ON t.refFk = io.ref
@ -248,6 +213,47 @@ module.exports = Self => {
stmt.merge(conn.makeWhere(filter.where));
stmts.push(stmt);
// Get client debt balance
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.clientGetDebt');
stmts.push(`
CREATE TEMPORARY TABLE tmp.clientGetDebt
(INDEX (clientFk))
ENGINE = MEMORY
SELECT DISTINCT clientFk FROM tmp.filter`);
stmt = new ParameterizedSQL('CALL clientGetDebt(?)', [args.to]);
stmts.push(stmt);
stmts.push('DROP TEMPORARY TABLE tmp.clientGetDebt');
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.tickets');
stmt = new ParameterizedSQL(`
CREATE TEMPORARY TABLE tmp.tickets
(INDEX (id))
ENGINE = MEMORY
SELECT f.*, r.risk AS debt
FROM tmp.filter f
LEFT JOIN tmp.risk r ON f.clientFk = r.clientFk`);
stmts.push(stmt);
// Sum risk to future
stmts.push(`SET @client:= 0`);
stmts.push('SET @risk := 0');
stmts.push(`
UPDATE tmp.tickets
SET debt = IF(@client <> @client:= clientFk,
-totalWithVat + @risk:= - debt + totalWithVat,
-totalWithVat + @risk:= @risk + totalWithVat
)
ORDER BY clientFk, shipped DESC
`);
// Remove positive risks
stmts.push(`
UPDATE tmp.tickets t
SET debt = 0
WHERE t.debt + t.credit >= 0
`);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems');
stmts.push(`
CREATE TEMPORARY TABLE tmp.sale_getProblems
@ -260,27 +266,39 @@ module.exports = Self => {
AND f.shipped >= CURDATE()`);
stmts.push('CALL ticket_getProblems(FALSE)');
stmts.push(`
INSERT INTO tmp.ticket_problems (ticketFk, risk, totalProblems)
SELECT t.id, t.debt + t.credit AS risk, 1
FROM tmp.tickets t
WHERE (t.debt + t.credit) < 0
ON DUPLICATE KEY UPDATE
risk = t.debt + t.credit, totalProblems = totalProblems + 1
`);
stmt = new ParameterizedSQL(`
SELECT f.*, tp.*
FROM tmp.filter f
LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id`);
SELECT t.*, tp.*,
((tp.risk) + cc.riskTolerance < 0) AS hasHighRisk
FROM tmp.tickets t
LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = t.id
JOIN clientConfig cc`);
const hasProblems = args.problems;
if (hasProblems != undefined && (!args.from && !args.to))
throw new UserError('Choose a date range or days forward');
let problemsFilter;
let finalFilter = {};
let whereProblems;
if (hasProblems === true) {
problemsFilter = {or: [
whereProblems = {or: [
{'tp.isFreezed': true},
{'tp.risk': {gt: 0}},
{'tp.risk': {lt: 0}},
{'tp.hasTicketRequest': true},
{'tp.hasComponentLack': true},
{'tp.isTaxDataChecked': false},
{'tp.isAvailable': false}
]};
} else if (hasProblems === false) {
problemsFilter = {and: [
whereProblems = {and: [
{'tp.isFreezed': false},
{'tp.risk': 0},
{'tp.hasTicketRequest': false},
@ -290,8 +308,53 @@ module.exports = Self => {
]};
}
if (problemsFilter)
stmt.merge(conn.makeWhere(problemsFilter));
if (whereProblems) finalFilter.where = whereProblems;
const myWhere = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'t.id': {inq: value}}
: {'t.nickname': {like: `%${value}%`}};
case 'nickname':
return {'t.nickname': {like: `%${value}%`}};
case 'refFk':
return {'t.refFk': value};
case 'provinceFk':
return {'t.provinceFk': value};
case 'stateFk':
return {'t.stateFk': value};
case 'alertLevel':
return {'t.alertLevel': value};
case 'pending':
if (value) {
return {and: [
{'t.alertLevel': 0},
{'t.alertLevelCode': {nin: [
'OK',
'BOARDING',
'PRINTED',
'PRINTED_AUTO',
'PICKER_DESIGNED'
]}}
]};
} else {
return {and: [
{'t.alertLevel': {gt: 0}}
]};
}
case 'agencyModeFk':
case 'warehouseFk':
param = `t.${param}`;
return {[param]: value};
}
});
finalFilter = mergeFilters(finalFilter, {where: myWhere});
if (finalFilter.where)
stmt.merge(conn.makeWhere(finalFilter.where));
stmt.merge(conn.makeOrderBy(filter.order));
stmt.merge(conn.makeLimit(filter));
@ -300,7 +363,9 @@ module.exports = Self => {
stmts.push(
`DROP TEMPORARY TABLE
tmp.filter,
tmp.ticket_problems`);
tmp.ticket_problems,
tmp.sale_getProblems,
tmp.risk`);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);

View File

@ -1,15 +1,15 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('SalesMonitor salesFilter()', () => {
it('should return the tickets matching the filter', async() => {
it('should now return the tickets matching the filter', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {}};
const filter = {order: 'id DESC'};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(24);
});
it('should return the tickets matching the problems on true', async() => {
it('should now return the tickets matching the problems on true', async() => {
const yesterday = new Date();
yesterday.setHours(0, 0, 0, 0);
const today = new Date();
@ -21,12 +21,12 @@ describe('SalesMonitor salesFilter()', () => {
to: today
}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(9);
expect(result.length).toEqual(13);
});
it('should return the tickets matching the problems on false', async() => {
it('should now return the tickets matching the problems on false', async() => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday.setHours(0, 0, 0, 0);
@ -39,33 +39,33 @@ describe('SalesMonitor salesFilter()', () => {
to: today
}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(0);
});
it('should return the tickets matching the problems on null', async() => {
it('should now return the tickets matching the problems on null', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {problems: null}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(24);
});
it('should return the tickets matching the orderId 11', async() => {
it('should now return the tickets matching the orderId 11', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {orderFk: 11}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
const firstRow = result[0];
expect(result.length).toEqual(1);
expect(firstRow.id).toEqual(11);
});
it('should return the tickets with grouped state "Pending" and not "Ok" nor "BOARDING"', async() => {
it('should now return the tickets with grouped state "Pending" and not "Ok" nor "BOARDING"', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: true}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
@ -74,10 +74,10 @@ describe('SalesMonitor salesFilter()', () => {
expect(anyResult.state).toMatch(/(Libre|Arreglar)/);
});
it('should return the tickets that are not pending', async() => {
it('should now return the tickets that are not pending', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
const firstRow = result[0];
const secondRow = result[1];
const thirdRow = result[2];
@ -88,18 +88,18 @@ describe('SalesMonitor salesFilter()', () => {
expect(thirdRow.state).toEqual('Entregado');
});
it('should return the tickets from the worker team', async() => {
it('should now return the tickets from the worker team', async() => {
const ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: true}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(20);
});
it('should return the tickets that are not from the worker team', async() => {
it('should now return the tickets that are not from the worker team', async() => {
const ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: false}};
const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(4);
});
@ -113,7 +113,7 @@ describe('SalesMonitor salesFilter()', () => {
const ctx = {req: {accessToken: {userId: 18}}, args: {}};
const filter = {order: 'totalProblems DESC'};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
const firstTicket = result.shift();
const secondTicket = result.shift();
@ -131,7 +131,7 @@ describe('SalesMonitor salesFilter()', () => {
const ctx = {req: {accessToken: {userId: 18}}, args: {}};
const filter = {order: 'totalProblems ASC'};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter);
const result = await models.SalesMonitor.salesFilter(ctx, filter);
const firstTicket = result.shift();
const secondTicket = result.shift();

View File

@ -70,7 +70,7 @@
<vn-autocomplete vn-one
data="$ctrl.groupedStates"
label="Grouped States"
value-field="alertLevel"
value-field="id"
show-field="name"
ng-model="filter.alertLevel">
<tpl-item>

View File

@ -14,7 +14,7 @@ class Controller extends SearchPanel {
this.$http.get('AlertLevels').then(res => {
for (let state of res.data) {
groupedStates.push({
alertLevel: state.alertLevel,
id: state.id,
code: state.code,
name: this.$t(state.code)
});

View File

@ -18,7 +18,7 @@ describe('Monitor Component vnMonitorSalesSearchPanel', () => {
jest.spyOn(controller, '$t').mockReturnValue('miCodigo');
const data = [
{
alertLevel: 9999,
id: 9999,
code: 'myCode'
}
];
@ -27,7 +27,7 @@ describe('Monitor Component vnMonitorSalesSearchPanel', () => {
$httpBackend.flush();
expect(controller.groupedStates).toEqual([{
alertLevel: 9999,
id: 9999,
code: 'myCode',
name: 'miCodigo'
}]);

View File

@ -1,9 +1,8 @@
<vn-crud-model auto-load="true"
<vn-crud-model
vn-id="model"
params="::$ctrl.filterParams"
url="SalesMonitors/salesFilter"
limit="20"
order="shippedDate DESC, shippedHour ASC, zoneLanding ASC, id">
order="shippedDate DESC, preparationHour ASC, zoneLanding ASC, id">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
@ -37,8 +36,8 @@
<vn-th field="nickname">Client</vn-th>
<vn-th field="salesPersonFk" class="expendable" shrink>Salesperson</vn-th>
<vn-th field="shipped" shrink-date>Date</vn-th>
<vn-th>Prep.</vn-th>
<vn-th field="hour" shrink>Theoretical</vn-th>
<vn-th field="preparationHour" filter-enabled="false">Prep.</vn-th>
<vn-th field="theoreticalHour">Theoretical</vn-th>
<vn-th field="practicalHour">Practical</vn-th>
<vn-th field="provinceFk" class="expendable">Province</vn-th>
<vn-th field="stateFk">State</vn-th>
@ -51,7 +50,7 @@
<a ng-repeat="ticket in model.data"
class="clickable vn-tr search-result"
ui-sref="ticket.card.summary({id: {{::ticket.id}}})" target="_blank">
<vn-td class="icon-field">
<vn-td expand>
<vn-icon
ng-show="::ticket.isTaxDataChecked === 0"
translate-attr="{title: 'No verified data'}"

View File

@ -6,13 +6,30 @@ export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filterParams = this.fetchParams({
scopeDays: 1
});
this.filterParams = this.fetchParams();
}
fetchParams($params) {
if (!Object.entries($params).length)
$onInit() {
if (!this.$params.q) {
this.$.$applyAsync(
() => this.$.model.applyFilter(null, this.filterParams));
}
}
fetchParams($params = {}) {
const excludedParams = [
'search',
'clientFk',
'orderFk',
'refFk',
'scopeDays'
];
const hasExcludedParams = excludedParams.some(param => {
return $params && $params[param];
});
const hasParams = Object.entries($params).length;
if (!hasParams || !hasExcludedParams)
$params.scopeDays = 1;
if (typeof $params.scopeDays === 'number') {
@ -68,8 +85,10 @@ export default class Controller extends Section {
return {'c.salesPersonFk': value};
case 'provinceFk':
return {'a.provinceFk': value};
case 'hour':
case 'theoreticalHour':
return {'z.hour': value};
case 'practicalHour':
return {'zed.etc': value};
case 'shipped':
return {'t.shipped': {
between: this.dateRange(value)}

View File

@ -3,10 +3,8 @@ const app = require('vn-loopback/server/server');
describe('order addToOrder()', () => {
const orderId = 8;
let rowToDelete;
afterAll(async done => {
afterAll(async() => {
await app.models.OrderRow.removes({rows: [rowToDelete], actualOrderId: orderId});
done();
});
it('should add a row to a given order', async() => {

View File

@ -4,12 +4,10 @@ describe('order removes()', () => {
let row;
let newRow;
beforeAll(async done => {
beforeAll(async() => {
row = await app.models.OrderRow.findOne({where: {id: 12}});
row.id = null;
newRow = await app.models.OrderRow.create(row);
done();
});
it('should throw an error if rows property is empty', async() => {

View File

@ -119,7 +119,7 @@ module.exports = Self => {
case 'sourceApp':
return {'o.source_app': value};
case 'ticketFk':
return {'ort.ticketFk': value};
return {'ot.ticketFk': value};
case 'isConfirmed':
return {'o.confirmed': value ? 1 : 0};
case 'myTeam':
@ -137,7 +137,10 @@ module.exports = Self => {
let stmt;
stmt = new ParameterizedSQL(
`SELECT
`CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
ENGINE = MEMORY
SELECT
o.id,
o.total,
o.date_send landed,
@ -168,20 +171,20 @@ module.exports = Self => {
LEFT JOIN ticket t ON t.id = ot.ticketFk
LEFT JOIN zoneEstimatedDelivery zed ON zed.zoneFk = t.zoneFk`);
if (args && args.ticketFk) {
stmt.merge({
sql: `LEFT JOIN orderTicket ort ON ort.orderFk = o.id`
});
}
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY o.id`);
stmt.merge(conn.makePagination(filter));
stmts.push(stmt);
stmt = new ParameterizedSQL(`SELECT * FROM tmp.filter`);
stmt.merge(`GROUP BY id`);
stmt.merge(conn.makeOrderBy(filter.order));
const ordersIndex = stmts.push(stmt) - 1;
stmts.push(`DROP TEMPORARY TABLE tmp.filter`);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql);
return result;
return result[ordersIndex];
};
};

View File

@ -4,10 +4,8 @@ let UserError = require('vn-loopback/util/user-error');
describe('order new()', () => {
let orderId;
afterAll(async done => {
afterAll(async() => {
await app.models.Order.destroyById(orderId);
done();
});
it('should throw an error if the client isnt active', async() => {

View File

@ -2,12 +2,10 @@ const app = require('vn-loopback/server/server');
describe('Order updateBasicData', () => {
const orderId = 21;
afterAll(async done => {
afterAll(async() => {
let validparams = {note: null};
await app.models.Order.updateBasicData(orderId, validparams);
done();
});
it('should return an error if the order is confirmed', async() => {

View File

@ -17,7 +17,7 @@
<vn-th field="landed" shrink-date>Landed</vn-th>
<vn-th field="created" center>Hour</vn-th>
<vn-th field="agencyName" center>Agency</vn-th>
<vn-th center>Total</vn-th>
<vn-th field="total" center>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>

View File

@ -4,14 +4,12 @@ describe('route guessPriority()', () => {
const targetRouteId = 7;
let routeTicketsToRestore;
afterAll(async done => {
afterAll(async() => {
let restoreFixtures = [];
routeTicketsToRestore.forEach(ticket => {
restoreFixtures.push(ticket.updateAttribute('priority', null));
});
await Promise.all(restoreFixtures);
done();
});
it('should call guessPriority() and then check the tickets in the target route now have their priorities defined', async() => {

View File

@ -8,12 +8,10 @@ describe('route insertTicket()', () => {
accessToken: {userId: deliveryId},
};
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
done();
});
it('should add the ticket to a route', async() => {

View File

@ -4,18 +4,14 @@ describe('loopback model Supplier', () => {
let supplierOne;
let supplierTwo;
beforeAll(async done => {
beforeAll(async() => {
supplierOne = await app.models.Supplier.findById(1);
supplierTwo = await app.models.Supplier.findById(442);
done();
});
afterAll(async done => {
afterAll(async() => {
await supplierOne.updateAttribute('payMethodFk', supplierOne.payMethodFk);
await supplierTwo.updateAttribute('payMethodFk', supplierTwo.payMethodFk);
done();
});
describe('payMethodFk', () => {

View File

@ -7,13 +7,13 @@ module.exports = Self => {
accepts: [
{
arg: 'filter',
type: 'Object',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'},
},
],
returns: {
type: ['Object'],
type: ['object'],
root: true,
},
http: {
@ -22,7 +22,12 @@ module.exports = Self => {
},
});
Self.filter = async filter => {
Self.filter = async(filter, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(
`SELECT
e.id,
@ -55,6 +60,6 @@ module.exports = Self => {
`);
stmt.merge(Self.buildSuffix(filter, 'e'));
return await Self.rawStmt(stmt);
return Self.rawStmt(stmt, myOptions);
};
};

View File

@ -0,0 +1,21 @@
const models = require('vn-loopback/server/server').models;
describe('expedition filter()', () => {
it('should return the expeditions matching the filter', async() => {
const tx = await models.Expedition.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {packagingFk: 1}};
const response = await models.Expedition.filter(filter, options);
expect(response.length).toEqual(10);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -7,13 +7,13 @@ module.exports = Self => {
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'Object',
type: 'object',
required: false,
description: 'Filter defining where and paginated data',
http: {source: 'query'}
}],
returns: {
type: ['Object'],
type: ['object'],
root: true
},
http: {
@ -22,9 +22,14 @@ module.exports = Self => {
}
});
Self.listPackaging = async filter => {
let conn = Self.dataSource.connector;
let stmt = new ParameterizedSQL(
Self.listPackaging = async(filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(
`SELECT name, itemFk, packagingFk
FROM (SELECT i.name, i.id itemFk, p.id packagingFk
FROM item i
@ -33,6 +38,7 @@ module.exports = Self => {
);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
return conn.executeStmt(stmt, myOptions);
};
};

View File

@ -1,12 +1,22 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket listPackaging()', () => {
it('should return the packaging', async() => {
let filter = {where: {packagingFk: 1}};
let response = await app.models.Packaging.listPackaging(filter);
const tx = await models.Packaging.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {packagingFk: 1}};
const response = await models.Packaging.listPackaging(filter, options);
expect(response[0].name).toBeDefined();
expect(response[0].name).toEqual('Container ammo box 1m');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -7,11 +7,11 @@ module.exports = Self => {
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'Object',
type: 'object',
description: 'Filter defining where and paginated data'
}],
returns: {
type: ['Object'],
type: ['object'],
root: true
},
http: {
@ -20,8 +20,13 @@ module.exports = Self => {
}
});
Self.listSaleTracking = async filter => {
let stmt = new ParameterizedSQL(`
Self.listSaleTracking = async(filter, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(`
SELECT
st.id,
s.ticketFk,
@ -41,9 +46,9 @@ module.exports = Self => {
stmt.merge(Self.makeSuffix(filter));
let trackings = await Self.rawStmt(stmt);
const trackings = await Self.rawStmt(stmt, myOptions);
let salesFilter = {
const salesFilter = {
include: [
{
relation: 'item'
@ -52,14 +57,14 @@ module.exports = Self => {
where: {ticketFk: filter.where.ticketFk}
};
let sales = await Self.app.models.Sale.find(salesFilter);
const sales = await Self.app.models.Sale.find(salesFilter, myOptions);
trackings.forEach(tracking => {
sales.forEach(sale => {
for (const tracking of trackings) {
for (const sale of sales) {
if (tracking.itemFk == sale.itemFk)
tracking.item = sale.item();
});
});
}
}
return trackings;
};

View File

@ -1,17 +1,39 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket listSaleTracking()', () => {
it('should call the listSaleTracking method and return the response', async() => {
let filter = {where: {ticketFk: 1}};
let result = await app.models.SaleTracking.listSaleTracking(filter);
const tx = await models.SaleTracking.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {ticketFk: 1}};
const result = await models.SaleTracking.listSaleTracking(filter, options);
expect(result.length).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should call the listSaleTracking method and return zero if doesn't have lines`, async() => {
let filter = {where: {ticketFk: 2}};
let result = await app.models.SaleTracking.listSaleTracking(filter);
const tx = await models.SaleTracking.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {ticketFk: 2}};
const result = await models.SaleTracking.listSaleTracking(filter, options);
expect(result.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -26,11 +26,22 @@ module.exports = Self => {
}
});
Self.deleteSales = async(ctx, sales, ticketId) => {
Self.deleteSales = async(ctx, sales, ticketId, options) => {
const $t = ctx.req.__; // $translate
const models = Self.app.models;
const myOptions = {};
let tx;
const canEditSales = await models.Sale.canEdit(ctx, sales);
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const canEditSales = await models.Sale.canEdit(ctx, sales, myOptions);
const ticket = await models.Ticket.findById(ticketId, {
include: {
@ -44,9 +55,9 @@ module.exports = Self => {
}
}
}
});
}, myOptions);
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
if (!isTicketEditable)
throw new UserError(`The sales of this ticket can't be modified`);
@ -56,7 +67,7 @@ module.exports = Self => {
const promises = [];
let deletions = '';
for (let sale of sales) {
const deletedSale = models.Sale.destroyById(sale.id);
const deletedSale = models.Sale.destroyById(sale.id, myOptions);
deletions += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
promises.push(deletedSale);
@ -71,9 +82,17 @@ module.exports = Self => {
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
deletions: deletions
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
}
return Promise.all(promises);
const deletedSales = await Promise.all(promises);
if (tx) await tx.commit();
return deletedSales;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -18,8 +18,14 @@ module.exports = Self => {
}
});
Self.getClaimableFromTicket = async ticketFk => {
let query = `SELECT
Self.getClaimableFromTicket = async(ticketFk, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const query = `
SELECT
s.id AS saleFk,
t.id AS ticketFk,
t.landed,
@ -37,7 +43,7 @@ module.exports = Self => {
AND t.id = ? AND cb.id IS NULL
ORDER BY t.landed DESC, t.id DESC`;
let claimableSales = await Self.rawSql(query, [ticketFk]);
const claimableSales = await Self.rawSql(query, [ticketFk], myOptions);
return claimableSales;
};

View File

@ -20,20 +20,39 @@ module.exports = Self => {
}
});
Self.recalculatePrice = async(ctx, id) => {
Self.recalculatePrice = async(ctx, id, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
const sale = await Self.findById(id);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const sale = await Self.findById(id, null, myOptions);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const canEditSale = await models.Sale.canEdit(ctx, [id]);
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`);
return Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id]);
const recalculation = await Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id], myOptions);
if (tx) await tx.commit();
return recalculation;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -34,15 +34,26 @@ module.exports = Self => {
}
});
Self.reserve = async(ctx, ticketId, sales, reserved) => {
Self.reserve = async(ctx, ticketId, sales, reserved, options) => {
const $t = ctx.req.__; // $translate
const models = Self.app.models;
const myOptions = {};
let tx;
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
if (!isTicketEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const canEditSale = await models.Sale.canEdit(ctx, sales);
const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions);
if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`);
@ -55,7 +66,7 @@ module.exports = Self => {
const oldState = sale.reserved ? 'reserved' : 'regular';
const newState = reserved ? 'reserved' : 'regular';
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
const reservedSale = models.Sale.updateAll({id: sale.id}, {reserved: reserved}, myOptions);
promises.push(reservedSale);
@ -77,7 +88,7 @@ module.exports = Self => {
}
}
}
});
}, myOptions);
const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) {
@ -88,9 +99,15 @@ module.exports = Self => {
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
changes: changesMade
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
}
if (tx) await tx.commit();
return result;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,36 +1,69 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale canEdit()', () => {
it('should return true if the role is production regardless of the saleTrackings', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const productionUserID = 49;
let ctx = {req: {accessToken: {userId: productionUserID}}};
const ctx = {req: {accessToken: {userId: productionUserID}}};
const sales = [{id: 3}];
const result = await app.models.Sale.canEdit(ctx, sales);
const result = await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return true if the role is not production and none of the sales has saleTracking', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const salesPersonUserID = 18;
let ctx = {req: {accessToken: {userId: salesPersonUserID}}};
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
const sales = [{id: 10}];
const result = await app.models.Sale.canEdit(ctx, sales);
const result = await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return false if any of the sales has a saleTracking record', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const salesPersonUserID = 18;
let ctx = {req: {accessToken: {userId: salesPersonUserID}}};
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
const sales = [{id: 3}];
const result = await app.models.Sale.canEdit(ctx, sales);
const result = await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(false);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,23 +1,14 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale deleteSales()', () => {
let sale;
let newSale;
beforeAll(async done => {
try {
sale = await app.models.Sale.findOne({where: {id: 9}});
sale.id = null;
newSale = await app.models.Sale.create(sale);
} catch (error) {
console.error(error);
}
done();
});
it('should throw an error if the ticket of the given sales is not editable', async() => {
let ctx = {
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
@ -25,14 +16,14 @@ describe('sale deleteSales()', () => {
}
};
let error;
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
const ticketId = 2;
try {
await app.models.Sale.deleteSales(ctx, sales, ticketId);
await models.Sale.deleteSales(ctx, sales, ticketId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
@ -40,19 +31,33 @@ describe('sale deleteSales()', () => {
});
it('should delete the sale', async() => {
let ctx = {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const sale = await models.Sale.findOne({where: {id: 9}}, options);
sale.id = null;
const newSale = await models.Sale.create(sale, options);
const sales = [{id: newSale.id, instance: 0}];
const ticketId = 16;
let res = await app.models.Sale.deleteSales(ctx, sales, ticketId);
const deletions = await models.Sale.deleteSales(ctx, sales, ticketId, options);
expect(res).toEqual([{count: 1}]);
expect(deletions).toEqual([{count: 1}]);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,10 +1,21 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale getClaimableFromTicket()', () => {
it('should return the claimable sales of a given ticket', async() => {
let claimableFromTicket = await app.models.Sale.getClaimableFromTicket(16);
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const claimableFromTicket = await models.Sale.getClaimableFromTicket(16, options);
expect(claimableFromTicket[0].concept).toBe('Ranged weapon longbow 2m');
expect(claimableFromTicket.length).toBe(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,24 +1,43 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale recalculatePrice()', () => {
const saleId = 7;
it('should update the sale price', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
const response = await app.models.Sale.recalculatePrice(ctx, saleId);
const response = await models.Sale.recalculatePrice(ctx, saleId, options);
expect(response.affectedRows).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error if the ticket is not editable', async() => {
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
const immutableSaleId = 1;
await app.models.Sale.recalculatePrice(ctx, immutableSaleId)
.catch(response => {
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`));
error = response;
});
await models.Sale.recalculatePrice(ctx, immutableSaleId, options);
expect(error).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error(`The sales of this ticket can't be modified`));
});
});

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale reserve()', () => {
const ctx = {
@ -9,38 +9,35 @@ describe('sale reserve()', () => {
}
};
afterAll(async done => {
let ctx = {req: {accessToken: {userId: 9}}};
let params = {
sales: [
{id: 7},
{id: 8}],
ticketFk: 11,
reserved: false
};
await app.models.Sale.reserve(ctx, params);
done();
});
it('should throw an error if the ticket can not be modified', async() => {
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ticketId = 2;
const sales = [{id: 5}];
const reserved = false;
await app.models.Sale.reserve(ctx, ticketId, sales, reserved)
.catch(response => {
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`));
error = response;
});
await models.Sale.reserve(ctx, ticketId, sales, reserved, options);
expect(error).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error(`The sales of this ticket can't be modified`));
});
it('should update the given sales of a ticket to reserved', async() => {
originalTicketSales = await app.models.Ticket.getSales(11);
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
originalTicketSales = await models.Ticket.getSales(11, options);
expect(originalTicketSales[0].reserved).toEqual(false);
expect(originalTicketSales[1].reserved).toEqual(false);
@ -49,11 +46,17 @@ describe('sale reserve()', () => {
const sales = [{id: 7}, {id: 8}];
const reserved = true;
await app.models.Sale.reserve(ctx, ticketId, sales, reserved);
await models.Sale.reserve(ctx, ticketId, sales, reserved, options);
const reservedTicketSales = await app.models.Ticket.getSales(ticketId);
const reservedTicketSales = await models.Ticket.getSales(ticketId, options);
expect(reservedTicketSales[0].reserved).toEqual(true);
expect(reservedTicketSales[1].reserved).toEqual(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,40 +1,45 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale updateConcept()', () => {
const ctx = {req: {accessToken: {userId: 9}}};
const saleId = 1;
let originalSale;
beforeAll(async done => {
originalSale = await app.models.Sale.findById(saleId);
done();
});
afterAll(async done => {
await originalSale.save();
done();
});
it('should throw if ID was undefined', async() => {
let err;
const newConcept = 'I am he new concept';
const tx = await models.Sale.beginTransaction({});
let error;
try {
await app.models.Sale.updateConcept(ctx, undefined, newConcept);
const options = {transaction: tx};
const newConcept = 'not going to heppen';
await models.Sale.updateConcept(ctx, undefined, newConcept, options);
await tx.rollback();
} catch (e) {
err = e;
await tx.rollback();
error = e;
}
expect(err).toBeDefined();
expect(error).toBeDefined();
});
it('should update the sale concept', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const newConcept = 'I am the new concept';
let response = await app.models.Sale.updateConcept(ctx, saleId, newConcept);
let response = await models.Sale.updateConcept(ctx, saleId, newConcept, options);
expect(response.concept).toEqual(newConcept);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,22 +1,6 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale updatePrice()', () => {
let originalSale;
let originalSalesPersonMana;
let createdSaleComponent;
let saleId = 7;
let manaComponentId;
beforeAll(async done => {
let component = await app.models.Component.findOne({where: {code: 'mana'}});
manaComponentId = component.id;
originalSale = await app.models.Sale.findById(saleId);
originalSalesPersonMana = await app.models.WorkerMana.findById(18);
done();
});
it('should throw an error if the ticket is not editable', async() => {
const ctx = {
req: {
accessToken: {userId: 18},
@ -24,81 +8,93 @@ describe('sale updatePrice()', () => {
__: () => {}
}
};
let immutableSaleId = 1;
let price = 5;
const saleId = 7;
await app.models.Sale.updatePrice(ctx, immutableSaleId, price)
.catch(response => {
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`));
error = response;
});
it('should throw an error if the ticket is not editable', async() => {
const tx = await models.Sale.beginTransaction({});
expect(error).toBeDefined();
try {
const options = {transaction: tx};
const immutableSaleId = 1;
const price = 5;
await models.Sale.updatePrice(ctx, immutableSaleId, price, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error(`The sales of this ticket can't be modified`));
});
it('should return 0 if the price is an empty string', async() => {
const ctx = {
req: {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
let price = '';
try {
const options = {transaction: tx};
await app.models.Sale.updatePrice(ctx, saleId, price);
let updatedSale = await app.models.Sale.findById(saleId);
const price = '';
await models.Sale.updatePrice(ctx, saleId, price, options);
const updatedSale = await models.Sale.findById(saleId, null, options);
expect(updatedSale.price).toEqual(0);
// restores
await originalSale.updateAttributes(originalSale);
await app.models.SaleComponent.updateAll({componentFk: manaComponentId, saleFk: saleId}, {value: 0});
await originalSalesPersonMana.updateAttributes(originalSalesPersonMana);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should now set price as a number in a string', async() => {
const ctx = {
req: {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
let price = '8';
try {
const options = {transaction: tx};
await app.models.Sale.updatePrice(ctx, saleId, price);
let updatedSale = await app.models.Sale.findById(saleId);
const price = '8';
await models.Sale.updatePrice(ctx, saleId, price, options);
const updatedSale = await models.Sale.findById(saleId, null, options);
expect(updatedSale.price).toEqual(8);
// restores
await originalSale.updateAttributes(originalSale);
await app.models.SaleComponent.updateAll({componentFk: manaComponentId, saleFk: saleId}, {value: 0});
await originalSalesPersonMana.updateAttributes(originalSalesPersonMana);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
// #2736 sale updatePrice() returns inconsistent values
xit('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let price = 5.4;
it('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => {
const tx = await models.Sale.beginTransaction({});
await app.models.Sale.updatePrice(ctx, saleId, price);
let updatedSale = await app.models.Sale.findById(saleId);
createdSaleComponent = await app.models.SaleComponent.findOne({where: {saleFk: saleId, componentFk: manaComponentId}});
try {
const options = {transaction: tx};
const price = 5.4;
const originalSalesPersonMana = await models.WorkerMana.findById(18, null, options);
const manaComponent = await models.Component.findOne({where: {code: 'mana'}}, options);
await models.Sale.updatePrice(ctx, saleId, price, options);
const updatedSale = await models.Sale.findById(saleId, null, options);
createdSaleComponent = await models.SaleComponent.findOne({where: {saleFk: saleId, componentFk: manaComponent.id}}, options);
expect(updatedSale.price).toBe(price);
expect(createdSaleComponent.value).toEqual(-2.04);
let updatedSalesPersonMana = await app.models.WorkerMana.findById(18);
const updatedSalesPersonMana = await models.WorkerMana.findById(18, null, options);
expect(updatedSalesPersonMana.amount).not.toEqual(originalSalesPersonMana.amount);
// restores
await originalSale.updateAttributes(originalSale);
await app.models.SaleComponent.updateAll({componentFk: manaComponentId, saleFk: saleId}, {value: 0});
await originalSalesPersonMana.updateAttributes(originalSalesPersonMana);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('sale updateQuantity()', () => {
const ctx = {
@ -10,44 +10,61 @@ describe('sale updateQuantity()', () => {
};
it('should throw an error if the quantity is not a number', async() => {
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await app.models.Sale.updateQuantity(ctx, 1, 'wrong quantity!')
.catch(response => {
expect(response).toEqual(new Error('The value should be a number'));
error = response;
});
await models.Sale.updateQuantity(ctx, 1, 'wrong quantity!', options);
expect(error).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The value should be a number'));
});
it('should throw an error if the quantity is greater than it should be', async() => {
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await app.models.Sale.updateQuantity(ctx, 1, 99)
.catch(response => {
expect(response).toEqual(new Error('The new quantity should be smaller than the old one'));
error = response;
});
await models.Sale.updateQuantity(ctx, 1, 99, options);
expect(error).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The new quantity should be smaller than the old one'));
});
it('should update the quantity of a given sale current line', async() => {
let originalLineData = await app.models.Sale.findOne({where: {id: 1}, fields: ['quantity']});
const tx = await models.Sale.beginTransaction({});
expect(originalLineData.quantity).toEqual(5);
try {
const options = {transaction: tx};
await app.models.Sale.updateQuantity(ctx, 1, 4);
const originalLine = await models.Sale.findOne({where: {id: 1}, fields: ['quantity']}, options);
let modifiedLineData = await app.models.Sale.findOne({where: {id: 1}, fields: ['quantity']});
expect(originalLine.quantity).toEqual(5);
expect(modifiedLineData.quantity).toEqual(4);
await models.Sale.updateQuantity(ctx, 1, 4, options);
await app.models.Sale.update({id: 1}, {quantity: 5});
const modifiedLine = await models.Sale.findOne({where: {id: 1}, fields: ['quantity']}, options);
let resetLineDataValues = await app.models.Sale.findOne({where: {id: 1}, fields: ['quantity']});
expect(modifiedLine.quantity).toEqual(4);
expect(resetLineDataValues.quantity).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -24,15 +24,35 @@ module.exports = Self => {
}
});
Self.updateConcept = async(ctx, id, newConcept) => {
Self.updateConcept = async(ctx, id, newConcept, options) => {
const models = Self.app.models;
const currentLine = await models.Sale.findById(id);
const myOptions = {};
let tx;
const canEditSale = await models.Sale.canEdit(ctx, [id]);
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const currentLine = await models.Sale.findById(id, null, myOptions);
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`);
return await currentLine.updateAttributes({concept: newConcept});
const line = await currentLine.updateAttributes({concept: newConcept}, myOptions);
if (tx) await tx.commit();
return line;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -28,14 +28,21 @@ module.exports = Self => {
}
});
Self.updatePrice = async(ctx, id, newPrice) => {
Self.updatePrice = async(ctx, id, newPrice, options) => {
const $t = ctx.req.__; // $translate
const models = Self.app.models;
const tx = await Self.beginTransaction({});
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const options = {transaction: tx};
const filter = {
include: {
relation: 'ticket',
@ -57,22 +64,22 @@ module.exports = Self => {
}
};
const sale = await models.Sale.findById(id, filter, options);
const sale = await models.Sale.findById(id, filter, myOptions);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const canEditSale = await models.Sale.canEdit(ctx, [id]);
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`);
const oldPrice = sale.price;
const userId = ctx.req.accessToken.userId;
const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options);
const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, myOptions);
const componentCode = usesMana ? 'mana' : 'buyerDiscount';
const discount = await models.Component.findOne({where: {code: componentCode}}, options);
const discount = await models.Component.findOne({where: {code: componentCode}}, myOptions);
const componentId = discount.id;
const componentValue = newPrice - sale.price;
@ -80,23 +87,23 @@ module.exports = Self => {
componentFk: componentId,
saleFk: id
};
const saleComponent = await models.SaleComponent.findOne({where}, options);
const saleComponent = await models.SaleComponent.findOne({where}, myOptions);
if (saleComponent) {
await models.SaleComponent.updateAll(where, {
value: saleComponent.value + componentValue
}, options);
}, myOptions);
} else {
await models.SaleComponent.create({
saleFk: id,
componentFk: componentId,
value: componentValue
}, options);
}, myOptions);
}
await sale.updateAttributes({price: newPrice}, options);
await sale.updateAttributes({price: newPrice}, myOptions);
query = `CALL vn.manaSpellersRequery(?)`;
await Self.rawSql(query, [userId], options);
await Self.rawSql(query, [userId], myOptions);
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {
@ -111,14 +118,14 @@ module.exports = Self => {
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
}
await tx.commit();
if (tx) await tx.commit();
return sale;
} catch (error) {
await tx.rollback();
if (tx) await tx.rollback();
throw error;
}
};

View File

@ -26,11 +26,22 @@ module.exports = Self => {
}
});
Self.updateQuantity = async(ctx, id, newQuantity) => {
const $t = ctx.req.__; // $translate
Self.updateQuantity = async(ctx, id, newQuantity, options) => {
const models = Self.app.models;
const $t = ctx.req.__; // $translate
const myOptions = {};
let tx;
const canEditSale = await models.Sale.canEdit(ctx, [id]);
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`);
@ -57,13 +68,13 @@ module.exports = Self => {
}
};
const sale = await models.Sale.findById(id, filter);
const sale = await models.Sale.findById(id, filter, myOptions);
if (newQuantity > sale.quantity)
throw new UserError('The new quantity should be smaller than the old one');
const oldQuantity = sale.quantity;
const result = await sale.updateAttributes({quantity: newQuantity});
const result = await sale.updateAttributes({quantity: newQuantity}, myOptions);
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {
@ -77,9 +88,15 @@ module.exports = Self => {
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
}
if (tx) await tx.commit();
return result;
} catch (error) {
if (tx) await tx.rollback();
throw error;
}
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => {
Self.remoteMethodCtx('editableStates', {
description: 'Gets the editable states according the user role ',
@ -8,7 +7,7 @@ module.exports = Self => {
type: 'object'
},
returns: {
type: ['Object'],
type: ['object'],
root: true
},
http: {
@ -17,14 +16,18 @@ module.exports = Self => {
}
});
Self.editableStates = async(ctx, filter) => {
let userId = ctx.req.accessToken.userId;
let models = Self.app.models;
let statesList = await models.State.find({where: filter.where});
Self.editableStates = async(ctx, filter, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
let isProduction = await models.Account.hasRole(userId, 'production');
let isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
let isAdministrative = await models.Account.hasRole(userId, 'administrative');
if (typeof options == 'object')
Object.assign(myOptions, options);
let statesList = await models.State.find({where: filter.where}, myOptions);
const isProduction = await models.Account.hasRole(userId, 'production', myOptions);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson', myOptions);
const isAdministrative = await models.Account.hasRole(userId, 'administrative', myOptions);
if (isProduction || isAdministrative)
return statesList;

View File

@ -18,19 +18,23 @@ module.exports = Self => {
}
});
Self.isEditable = async(ctx, stateId) => {
Self.isEditable = async(ctx, stateId, options) => {
const accessToken = ctx.req.accessToken;
const models = Self.app.models;
const userId = accessToken.userId;
const myOptions = {};
let isProduction = await models.Account.hasRole(userId, 'production');
let isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
let isAdministrative = await models.Account.hasRole(userId, 'administrative');
let state = await models.State.findById(stateId);
if (typeof options == 'object')
Object.assign(myOptions, options);
let salesPersonAllowed = (isSalesPerson && (state.code == 'PICKER_DESIGNED' || state.code == 'PRINTED'));
const isProduction = await models.Account.hasRole(userId, 'production', myOptions);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson', myOptions);
const isAdministrative = await models.Account.hasRole(userId, 'administrative', myOptions);
const state = await models.State.findById(stateId, null, myOptions);
let isAllowed = isProduction || isAdministrative || salesPersonAllowed || state.alertLevel == 0;
const salesPersonAllowed = (isSalesPerson && (state.code == 'PICKER_DESIGNED' || state.code == 'PRINTED'));
const isAllowed = isProduction || isAdministrative || salesPersonAllowed || state.alertLevel == 0;
return isAllowed;
};
};

View File

@ -1,34 +1,73 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket editableStates()', () => {
const filter = {where: {name: {like: '%%'}}};
it('should return the expected state for the given role', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const productionRole = 49;
const ctx = {req: {accessToken: {userId: productionRole}}};
let result = await app.models.State.editableStates(ctx, filter);
let deliveredState = result.some(state => state.code == 'DELIVERED');
const editableStates = await models.State.editableStates(ctx, filter, options);
const deliveredState = editableStates.some(state => state.code == 'DELIVERED');
expect(deliveredState).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should return the expected states by a specific role`, async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const productionRole = 18;
const ctx = {req: {accessToken: {userId: productionRole}}};
let result = await app.models.State.editableStates(ctx, filter);
let deliveredState = result.some(state => state.code == 'DELIVERED');
let pickerDesignedState = result.some(state => state.code == 'PICKER_DESIGNED');
const editableStates = await models.State.editableStates(ctx, filter, options);
const deliveredState = editableStates.some(state => state.code == 'DELIVERED');
const pickerDesignedState = editableStates.some(state => state.code == 'PICKER_DESIGNED');
expect(deliveredState).toBeFalsy();
expect(pickerDesignedState).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should return again the expected state by a specific role`, async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const employeeRole = 1;
const ctx = {req: {accessToken: {userId: employeeRole}}};
let result = await app.models.State.editableStates(ctx, filter);
let pickerDesignedState = result.some(state => state.code == 'PICKER_DESIGNED');
const editableStates = await models.State.editableStates(ctx, filter, options);
const pickerDesignedState = editableStates.some(state => state.code == 'PICKER_DESIGNED');
expect(pickerDesignedState).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,61 +1,127 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('state isEditable()', () => {
it('should return false if the state is not editable by a specific role', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const salesPersonRole = 18;
const onDeliveryState = 13;
let ctx = {req: {accessToken: {userId: salesPersonRole}}};
let result = await app.models.State.isEditable(ctx, onDeliveryState);
const ctx = {req: {accessToken: {userId: salesPersonRole}}};
const result = await models.State.isEditable(ctx, onDeliveryState, options);
expect(result).toBe(false);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return true if the state is editable by a specific role', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const salesPersonRole = 18;
const asignedState = 20;
let ctx = {req: {accessToken: {userId: salesPersonRole}}};
let result = await app.models.State.isEditable(ctx, asignedState);
const ctx = {req: {accessToken: {userId: salesPersonRole}}};
const result = await models.State.isEditable(ctx, asignedState, options);
expect(result).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return true again if the state is editable by a specific role', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const employeeRole = 1;
const fixingState = 1;
let ctx = {req: {accessToken: {userId: employeeRole}}};
let result = await app.models.State.isEditable(ctx, fixingState);
const ctx = {req: {accessToken: {userId: employeeRole}}};
const result = await models.State.isEditable(ctx, fixingState, options);
expect(result).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return false if the state is not editable for the given role', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const employeeRole = 1;
const asignedState = 13;
let ctx = {req: {accessToken: {userId: employeeRole}}};
let result = await app.models.State.isEditable(ctx, asignedState);
const ctx = {req: {accessToken: {userId: employeeRole}}};
const result = await models.State.isEditable(ctx, asignedState, options);
expect(result).toBe(false);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return true if the state is editable for the given role', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const productionRole = 49;
const onDeliveryState = 13;
let ctx = {req: {accessToken: {userId: productionRole}}};
let result = await app.models.State.isEditable(ctx, onDeliveryState);
const ctx = {req: {accessToken: {userId: productionRole}}};
const result = await models.State.isEditable(ctx, onDeliveryState, options);
expect(result).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return true if the ticket is editable, the role is salesPerson and the ticket state is printed', async() => {
const tx = await models.State.beginTransaction({});
try {
const options = {transaction: tx};
const salesPersonRole = 18;
const printedState = 4;
const okState = 3;
const ctx = {req: {accessToken: {userId: salesPersonRole}}};
let canEditCurrent = await app.models.State.isEditable(ctx, printedState);
let canAsignNew = await app.models.State.isEditable(ctx, okState);
let result = canEditCurrent && canAsignNew;
const canEditCurrent = await models.State.isEditable(ctx, printedState, options);
const canAsignNew = await models.State.isEditable(ctx, okState, options);
const result = canEditCurrent && canAsignNew;
expect(result).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -4,12 +4,12 @@ module.exports = Self => {
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'Number',
type: 'number',
description: 'The document id',
http: {source: 'path'}
},
returns: {
type: 'Object',
type: 'object',
root: true
},
http: {
@ -18,16 +18,36 @@ module.exports = Self => {
}
});
Self.removeFile = async(ctx, id) => {
Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models;
const targetTicketDms = await models.TicketDms.findById(id);
const targetDms = await models.Dms.findById(targetTicketDms.dmsFk);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}});
const myOptions = {};
let tx;
await models.Dms.removeFile(ctx, targetTicketDms.dmsFk);
await targetTicketDms.destroy();
if (typeof options == 'object')
Object.assign(myOptions, options);
return targetDms.updateAttribute('dmsTypeFk', trashDmsType.id);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const targetTicketDms = await models.TicketDms.findById(id, null, myOptions);
const targetDms = await models.Dms.findById(targetTicketDms.dmsFk, null, myOptions);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions);
await models.Dms.removeFile(ctx, targetTicketDms.dmsFk, myOptions);
await targetTicketDms.destroy(myOptions);
await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions);
if (tx) await tx.commit();
return targetDms;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,18 +1,25 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('TicketDms removeFile()', () => {
const ticketDmsId = 1;
it(`should return an error for a user without enough privileges`, async() => {
let clientId = 1101;
let ctx = {req: {accessToken: {userId: clientId}}};
const tx = await models.TicketDms.beginTransaction({});
let error;
await app.models.TicketDms.removeFile(ctx, ticketDmsId).catch(e => {
try {
const options = {transaction: tx};
const clientId = 1101;
const ctx = {req: {accessToken: {userId: clientId}}};
await models.TicketDms.removeFile(ctx, ticketDmsId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}).finally(() => {
}
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
});

View File

@ -6,22 +6,22 @@ module.exports = Self => {
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Integer',
type: 'number',
required: true,
description: 'The request ID',
}, {
arg: 'itemFk',
type: 'Integer',
type: 'number',
required: true,
description: 'The requested item ID',
}, {
arg: 'quantity',
type: 'Integer',
type: 'number',
required: true,
description: 'The requested item quantity',
}],
returns: {
type: 'Object',
type: 'object',
root: true
},
http: {
@ -30,25 +30,37 @@ module.exports = Self => {
}
});
Self.confirm = async ctx => {
Self.confirm = async(ctx, options) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const tx = await Self.beginTransaction({});
const $t = ctx.req.__; // $translate
let sale;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
let options = {transaction: tx};
let item = await models.Item.findById(ctx.args.itemFk, null, options);
const item = await models.Item.findById(ctx.args.itemFk, null, myOptions);
if (!item)
throw new UserError(`That item doesn't exists`);
let request = await models.TicketRequest.findById(ctx.args.id, {
const request = await models.TicketRequest.findById(ctx.args.id, {
include: {relation: 'ticket'}
}, options);
}, myOptions);
const itemStock = await models.Item.getVisibleAvailable(
ctx.args.itemFk,
request.ticket().warehouseFk,
request.ticket().shipped,
myOptions
);
const itemStock = await models.Item.getVisibleAvailable(ctx.args.itemFk, request.ticket().warehouseFk, request.ticket().shipped);
const isAvailable = itemStock.available > 0;
if (!isAvailable)
@ -57,23 +69,24 @@ module.exports = Self => {
if (request.saleFk)
throw new UserError(`This request already contains a sale`);
sale = await models.Sale.create({
const sale = await models.Sale.create({
ticketFk: request.ticketFk,
itemFk: ctx.args.itemFk,
quantity: ctx.args.quantity,
concept: item.name
}, options);
}, myOptions);
await request.updateAttributes({
saleFk: sale.id,
itemFk: sale.itemFk,
isOk: true
}, options);
}, myOptions);
query = `CALL vn.sale_calculateComponent(?, NULL)`;
await Self.rawSql(query, [sale.id], options);
const query = `CALL vn.sale_calculateComponent(?, NULL)`;
await Self.rawSql(query, [sale.id], myOptions);
const origin = ctx.req.headers.origin;
const requesterId = request.requesterFk;
const message = $t('Bought units from buy request', {
quantity: sale.quantity,
concept: sale.concept,
@ -82,10 +95,9 @@ module.exports = Self => {
url: `${origin}/#!/ticket/${sale.ticketFk}/summary`,
urlItem: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, requesterId, message);
await models.Chat.sendCheckingPresence(ctx, requesterId, message, myOptions);
// log
let logRecord = {
const logRecord = {
originFk: sale.ticketFk,
userFk: userId,
action: 'update',
@ -99,14 +111,14 @@ module.exports = Self => {
}
};
await Self.app.models.TicketLog.create(logRecord);
await Self.app.models.TicketLog.create(logRecord, myOptions);
await tx.commit();
if (tx) await tx.commit();
return sale;
} catch (error) {
await tx.rollback();
throw error;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -4,7 +4,7 @@ module.exports = Self => {
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Integer',
type: 'number',
required: true,
description: 'The request ID',
}, {
@ -23,17 +23,37 @@ module.exports = Self => {
}
});
Self.deny = async ctx => {
let userId = ctx.req.accessToken.userId;
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}});
Self.deny = async(ctx, options) => {
const myOptions = {};
let tx;
let params = {
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const userId = ctx.req.accessToken.userId;
const worker = await Self.app.models.Worker.findOne({where: {userFk: userId}}, myOptions);
const params = {
isOk: false,
attenderFk: worker.id,
response: ctx.args.observation,
};
let request = await Self.app.models.TicketRequest.findById(ctx.args.id);
return request.updateAttributes(params);
const request = await Self.app.models.TicketRequest.findById(ctx.args.id, null, myOptions);
await request.updateAttributes(params, myOptions);
if (tx) await tx.commit();
return request;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -68,9 +68,13 @@ module.exports = Self => {
}
});
Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let userId = ctx.req.accessToken.userId;
Self.filter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (ctx.args.mine)
ctx.args.attenderFk = userId;
@ -111,9 +115,7 @@ module.exports = Self => {
filter = mergeFilters(filter, {where});
let stmt;
stmt = new ParameterizedSQL(
const stmt = new ParameterizedSQL(
`SELECT
tr.id,
tr.ticketFk,
@ -149,8 +151,6 @@ module.exports = Self => {
LEFT JOIN account.user ua ON ua.id = wka.userFk`);
stmt.merge(conn.makeSuffix(filter));
let result = await conn.executeStmt(stmt);
return result;
return conn.executeStmt(stmt, myOptions);
};
};

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket-request confirm()', () => {
let ctx = {
@ -12,19 +12,31 @@ describe('ticket-request confirm()', () => {
};
it(`should throw an error if the item doesn't exist`, async() => {
ctx.args = {itemFk: 999};
const tx = await models.TicketRequest.beginTransaction({});
let error;
try {
await app.models.TicketRequest.confirm(ctx);
} catch (err) {
error = err;
const options = {transaction: tx};
ctx.args = {itemFk: 999};
await models.TicketRequest.confirm(ctx, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`That item doesn't exists`);
});
it('should throw an error if the item is not available', async() => {
const tx = await models.TicketRequest.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const requestId = 5;
const itemId = 4;
const quantity = 99999;
@ -35,18 +47,24 @@ describe('ticket-request confirm()', () => {
quantity: quantity
};
let error;
await models.TicketRequest.confirm(ctx, options);
try {
await app.models.TicketRequest.confirm(ctx);
} catch (err) {
error = err;
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`This item is not available`);
});
it(`should throw if there's a sale id`, async() => {
const tx = await models.TicketRequest.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const requestId = 4;
const itemId = 1;
const quantity = 10;
@ -57,11 +75,11 @@ describe('ticket-request confirm()', () => {
quantity: quantity
};
const request = await app.models.TicketRequest.findById(requestId);
const request = await models.TicketRequest.findById(requestId, null, options);
expect(request.saleFk).toBeNull();
await request.updateAttributes({saleFk: 2});
await request.updateAttributes({saleFk: 2}, options);
ctx.args = {
itemFk: itemId,
@ -69,17 +87,14 @@ describe('ticket-request confirm()', () => {
quantity: quantity
};
let error;
await models.TicketRequest.confirm(ctx, options);
try {
await app.models.TicketRequest.confirm(ctx);
} catch (err) {
error = err;
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`This request already contains a sale`);
// restores
await request.updateAttributes({saleFk: null});
});
});

View File

@ -1,25 +1,22 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket-request deny()', () => {
let request;
afterAll(async done => {
let params = {
isOk: null,
attenderFk: request.attenderFk,
response: null,
};
it('should return the dinied ticket request', async() => {
const tx = await models.TicketRequest.beginTransaction({});
await request.updateAttributes(params);
done();
});
try {
const options = {transaction: tx};
it('should return all ticket requests', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}};
const ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}};
request = await app.models.TicketRequest.findById(ctx.args.id);
let result = await app.models.TicketRequest.deny(ctx);
const result = await models.TicketRequest.deny(ctx, options);
expect(result.id).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,85 +1,184 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket-request filter()', () => {
const userId = 9;
let ctx = {req: {accessToken: {userId: userId}}};
it('should now return all ticket requests', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
expect(result.length).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching a generic search value which is the ticket ID', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {search: 11};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching a generic search value which is the client address alias', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {search: 'NY roofs'};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching the ticket ID', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {ticketFk: 11};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching the atender ID', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {attenderFk: 35};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching the isOk triple-state', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {isOk: null};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching the client ID', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {clientFk: 1102};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching the warehouse ID', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {warehouse: 1};
const result = await app.models.TicketRequest.filter(ctx, {order: 'id'});
const result = await models.TicketRequest.filter(ctx, {order: 'id'}, options);
const requestId = result[0].id;
expect(requestId).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket request matching the salesPerson ID', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
ctx.args = {salesPersonFk: 18};
const result = await app.models.TicketRequest.filter(ctx);
const result = await models.TicketRequest.filter(ctx, options);
const requestId = result[0].id;
expect(requestId).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -23,38 +23,64 @@ module.exports = Self => {
}
});
Self.changeState = async(ctx, params) => {
let userId = ctx.req.accessToken.userId;
let models = Self.app.models;
Self.changeState = async(ctx, params, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const userId = ctx.req.accessToken.userId;
if (!params.stateFk && !params.code)
throw new UserError('State cannot be blank');
if (params.code) {
let state = await models.State.findOne({where: {code: params.code}, fields: ['id']});
const state = await models.State.findOne({
where: {code: params.code},
fields: ['id']
}, myOptions);
params.stateFk = state.id;
}
if (!params.workerFk) {
let worker = await models.Worker.findOne({where: {userFk: userId}});
const worker = await models.Worker.findOne({
where: {userFk: userId}
}, myOptions);
params.workerFk = worker.id;
}
let ticketState = await models.TicketState.findById(
params.ticketFk,
{fields: ['stateFk']}
);
const ticketState = await models.TicketState.findById(params.ticketFk, {
fields: ['stateFk']
}, myOptions);
let oldStateAllowed;
if (ticketState)
oldStateAllowed = await models.State.isEditable(ctx, ticketState.stateFk);
let newStateAllowed = await models.State.isEditable(ctx, params.stateFk);
oldStateAllowed = await models.State.isEditable(ctx, ticketState.stateFk, myOptions);
const newStateAllowed = await models.State.isEditable(ctx, params.stateFk, myOptions);
let isAllowed = (!ticketState || oldStateAllowed == true) && newStateAllowed == true;
const isAllowed = (!ticketState || oldStateAllowed == true) && newStateAllowed == true;
if (!isAllowed)
throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
return models.TicketTracking.create(params);
const ticketTracking = await models.TicketTracking.create(params, myOptions);
if (tx) await tx.commit();
return ticketTracking;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -6,13 +6,13 @@ module.exports = Self => {
{
arg: 'ticketIds',
description: 'the array of ticket ids to set as delivered',
type: ['Number'],
type: ['number'],
required: true,
http: {source: 'body'}
}
],
returns: {
type: 'Object',
type: 'object',
root: true
},
http: {
@ -21,30 +21,47 @@ module.exports = Self => {
}
});
Self.setDelivered = async(ctx, ticketIds) => {
let userId = ctx.req.accessToken.userId;
let models = Self.app.models;
Self.setDelivered = async(ctx, ticketIds, options) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const myOptions = {};
let tx;
let state = await models.State.findOne({
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const state = await models.State.findOne({
where: {
code: 'delivered'
},
fields: ['id', 'name', 'alertLevel', 'code']
});
}, myOptions);
let worker = await models.Worker.findOne({where: {userFk: userId}});
const worker = await models.Worker.findOne({where: {userFk: userId}}, myOptions);
let promises = [];
for (let id of ticketIds) {
let promise = models.TicketTracking.changeState(ctx, {
const promises = [];
for (const id of ticketIds) {
const promise = models.TicketTracking.changeState(ctx, {
stateFk: state.id,
workerFk: worker.id,
ticketFk: id
});
}, myOptions);
promises.push(promise);
}
await Promise.all(promises);
if (tx) await tx.commit();
return state;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,110 +1,133 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket changeState()', () => {
const salesPersonId = 18;
const employeeId = 1;
const productionId = 49;
let activeCtx = {
const activeCtx = {
accessToken: {userId: 9},
};
let ctx = {req: activeCtx};
let ticket;
const ctx = {req: activeCtx};
const now = new Date();
const sampleTicket = {
shipped: now,
landed: now,
nickname: 'Many Places',
packages: 0,
updated: now,
priority: 1,
zoneFk: 3,
zonePrice: 5,
zoneBonus: 1,
totalWithVat: 120,
totalWithoutVat: 100,
clientFk: 1106,
warehouseFk: 1,
addressFk: 126,
routeFk: 6,
companyFk: 442,
agencyModeFk: 7
};
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
done();
});
beforeEach(async done => {
try {
let originalTicket = await app.models.Ticket.findOne({where: {id: 16}});
originalTicket.id = null;
ticket = await app.models.Ticket.create(originalTicket);
} catch (error) {
console.error(error);
}
done();
});
afterEach(async done => {
try {
await app.models.Ticket.destroyById(ticket.id);
} catch (error) {
console.error(error);
}
done();
});
afterAll(async done => {
try {
await app.models.Ticket.destroyById(ticket.id);
} catch (error) {
console.error(error);
}
done();
});
it('should throw if the ticket is not editable and the user isnt production', async() => {
activeCtx.accessToken.userId = salesPersonId;
let params = {ticketFk: 2, stateFk: 3};
const tx = await models.TicketTracking.beginTransaction({});
let error;
let errCode;
try {
await app.models.TicketTracking.changeState(ctx, params);
const options = {transaction: tx};
activeCtx.accessToken.userId = salesPersonId;
const params = {ticketFk: 2, stateFk: 3};
await models.TicketTracking.changeState(ctx, params, options);
await tx.rollback();
} catch (e) {
errCode = e.code;
await tx.rollback();
error = e;
}
expect(errCode).toBe('ACCESS_DENIED');
expect(error.code).toBe('ACCESS_DENIED');
});
it('should throw an error if a worker with employee role attemps to a forbidden state', async() => {
activeCtx.accessToken.userId = employeeId;
let params = {ticketFk: 11, stateFk: 13};
const tx = await models.TicketTracking.beginTransaction({});
let error;
let errCode;
try {
await app.models.TicketTracking.changeState(ctx, params);
const options = {transaction: tx};
activeCtx.accessToken.userId = employeeId;
const params = {ticketFk: 11, stateFk: 13};
await models.TicketTracking.changeState(ctx, params, options);
await tx.rollback();
} catch (e) {
errCode = e.code;
await tx.rollback();
error = e;
}
expect(errCode).toBe('ACCESS_DENIED');
expect(error.code).toBe('ACCESS_DENIED');
});
it('should be able to create a ticket tracking line for a not editable ticket if the user has the production role', async() => {
activeCtx.accessToken.userId = productionId;
let params = {ticketFk: ticket.id, stateFk: 3};
const tx = await models.TicketTracking.beginTransaction({});
let ticketTracking = await app.models.TicketTracking.changeState(ctx, params);
try {
const options = {transaction: tx};
const ticket = await models.Ticket.create(sampleTicket, options);
activeCtx.accessToken.userId = productionId;
const params = {ticketFk: ticket.id, stateFk: 3};
const ticketTracking = await models.TicketTracking.changeState(ctx, params, options);
expect(ticketTracking.__data.ticketFk).toBe(params.ticketFk);
expect(ticketTracking.__data.stateFk).toBe(params.stateFk);
expect(ticketTracking.__data.workerFk).toBe(49);
expect(ticketTracking.__data.id).toBeDefined();
// restores
await app.models.TicketTracking.destroyById(ticketTracking.__data.id);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should update the ticket tracking line when the user is salesperson, uses the state assigned and a valid worker id', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let assignedState = await app.models.State.findOne({where: {code: 'PICKER_DESIGNED'}});
let params = {ticketFk: ticket.id, stateFk: assignedState.id, workerFk: 1};
let res = await app.models.TicketTracking.changeState(ctx, params);
const tx = await models.TicketTracking.beginTransaction({});
try {
const options = {transaction: tx};
const ticket = await models.Ticket.create(sampleTicket, options);
const ctx = {req: {accessToken: {userId: 18}}};
const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options);
const params = {ticketFk: ticket.id, stateFk: assignedState.id, workerFk: 1};
const res = await models.TicketTracking.changeState(ctx, params, options);
expect(res.__data.ticketFk).toBe(params.ticketFk);
expect(res.__data.stateFk).toBe(params.stateFk);
expect(res.__data.workerFk).toBe(params.workerFk);
expect(res.__data.workerFk).toBe(1);
expect(res.__data.id).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket setDelivered()', () => {
@ -7,50 +7,40 @@ describe('ticket setDelivered()', () => {
accessToken: {userId: userId},
};
let ticketOne;
let ticketTwo;
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should return the state which has been applied to the given tickets', async() => {
const tx = await models.TicketTracking.beginTransaction({});
try {
let originalTicketOne = await app.models.Ticket.findById(8);
let originalTicketTwo = await app.models.Ticket.findById(10);
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 49}}};
const originalTicketOne = await models.Ticket.findById(8, null, options);
const originalTicketTwo = await models.Ticket.findById(10, null, options);
originalTicketOne.id = null;
originalTicketTwo.id = null;
ticketOne = await app.models.Ticket.create(originalTicketOne);
ticketTwo = await app.models.Ticket.create(originalTicketTwo);
} catch (error) {
console.error(error);
}
const ticketOne = await models.Ticket.create(originalTicketOne, options);
const ticketTwo = await models.Ticket.create(originalTicketTwo, options);
done();
});
const delivered = await models.State.findOne({where: {code: 'delivered'}, fields: ['id']}, options);
afterAll(async done => {
try {
await app.models.Ticket.destroyById(ticketOne.id);
await app.models.Ticket.destroyById(ticketTwo.id);
} catch (error) {
console.error(error);
}
done();
});
it('should return the state which has been applied to the given tickets', async() => {
let ctx = {req: {accessToken: {userId: 49}}};
let delivered = await app.models.State.findOne({where: {code: 'delivered'}, fields: ['id']});
let params = [ticketOne.id, ticketTwo.id];
let state = await app.models.TicketTracking.setDelivered(ctx, params);
const params = [ticketOne.id, ticketTwo.id];
const state = await models.TicketTracking.setDelivered(ctx, params, options);
expect(state.id).toEqual(delivered.id);
// restores
await app.models.TicketTracking.destroyById(state.id);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -10,18 +10,18 @@ module.exports = Self => {
accepts: [
{
arg: 'filter',
type: 'Object',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}, {
arg: 'search',
type: 'String',
type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by client id`,
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
type: ['object'],
root: true
},
http: {
@ -30,10 +30,14 @@ module.exports = Self => {
}
});
Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
Self.filter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
let where = buildFilter(ctx.args, (param, value) => {
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return {or: [
@ -46,10 +50,9 @@ module.exports = Self => {
filter = mergeFilters(ctx.args.filter, {where});
let stmts = [];
let stmt;
const stmts = [];
stmt = new ParameterizedSQL(
const stmt = new ParameterizedSQL(
`SELECT t.id AS ticketFk, c.id AS clientFk, c.name AS clientName, tw.weekDay,
wh.name AS warehouseName, u.id AS workerFk, u.name AS userName, u.nickName, tw.agencyModeFk
FROM ticketWeekly tw
@ -60,10 +63,10 @@ module.exports = Self => {
);
stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1;
const itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return itemsIndex === 0 ? result : result[itemsIndex];
};
};

View File

@ -1,43 +1,89 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('ticket-weekly filter()', () => {
const authUserId = 9;
it('should all return the tickets matching the filter', async() => {
const tx = await models.TicketWeekly.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {order: 't.id ASC'};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}};
const result = await app.models.TicketWeekly.filter(ctx);
const result = await models.TicketWeekly.filter(ctx, null, options);
const firstRow = result[0];
expect(firstRow.ticketFk).toEqual(1);
expect(result.length).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket with id one', async() => {
const tx = await models.TicketWeekly.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 2}};
const filter = {};
const result = await app.models.TicketWeekly.filter(ctx, filter);
const result = await models.TicketWeekly.filter(ctx, null, options);
const firstRow = result[0];
expect(firstRow.ticketFk).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket matching the client name', async() => {
const tx = await models.TicketWeekly.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}};
const filter = {};
const result = await app.models.TicketWeekly.filter(ctx, filter);
const result = await models.TicketWeekly.filter(ctx, null, options);
const firstRow = result[0];
expect(firstRow.clientName).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the ticket matching the client id', async() => {
const tx = await models.TicketWeekly.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}};
const filter = {};
const result = await app.models.TicketWeekly.filter(ctx, filter);
const result = await models.TicketWeekly.filter(ctx, null, options);
const firstRow = result[0];
expect(firstRow.clientFk).toEqual(1101);
expect(firstRow.clientName).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -117,9 +117,6 @@ module.exports = Self => {
if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk)
throw new UserError(`You don't have privileges to change the zone`);
}
const observationTypeDelivery = await models.ObservationType.findOne({
where: {code: 'delivery'}
}, myOptions);
const originalTicket = await models.Ticket.findOne({
where: {id: args.id},

View File

@ -12,12 +12,12 @@ module.exports = Self => {
},
{
arg: 'shipped',
type: 'Date',
type: 'date',
description: `The shipment date filter`
},
{
arg: 'landed',
type: 'Date',
type: 'date',
description: `The landing date filter`
},
{
@ -142,7 +142,7 @@ module.exports = Self => {
if (tx) await tx.commit();
return await ticket;
return ticket;
} catch (e) {
if (tx) await tx.rollback();
throw e;

View File

@ -66,6 +66,7 @@ module.exports = Self => {
myOptions.transaction = tx;
}
try {
const isEditable = await Self.isEditable(ctx, args.id, myOptions);
if (!isEditable)
@ -109,10 +110,6 @@ module.exports = Self => {
for (difComponent of difComponents)
map.set(difComponent.saleFk, difComponent);
function round(value) {
return Math.round(value * 100) / 100;
}
for (sale of salesObj.items) {
const difComponent = map.get(sale.id);
@ -130,6 +127,16 @@ module.exports = Self => {
salesObj.totalUnitPrice = round(salesObj.totalUnitPrice);
}
if (tx) await tx.commit();
return salesObj;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
function round(value) {
return Math.round(value * 100) / 100;
}
};

Some files were not shown because too many files have changed in this diff Show More