3092-module_transactions #740

Merged
joan merged 41 commits from 3092-module_transactions into dev 2021-10-18 07:42:24 +00:00
114 changed files with 44364 additions and 1570 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,11 +5,16 @@ import {url as defaultURL} from './config';
export async function getBrowser() { export async function getBrowser() {
const args = [ const args = [
`--no-sandbox`, '--no-sandbox',
`--window-size=${ 1920 },${ 1080 }` `--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) { if (env.E2E_DEBUG) {
args.push('--auto-open-devtools-for-tabs'); args.push('--auto-open-devtools-for-tabs');
@ -22,6 +27,9 @@ export async function getBrowser() {
defaultViewport: null, defaultViewport: null,
headless: headless, headless: headless,
slowMo: 0, // slow down by ms 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]; let page = (await browser.pages())[0];
@ -38,7 +46,7 @@ export async function getBrowser() {
}); });
}); });
page = extendPage(page); page = extendPage(page);
page.setDefaultTimeout(4000); page.setDefaultTimeout(5000);
await page.goto(defaultURL, {waitUntil: 'load'}); await page.goto(defaultURL, {waitUntil: 'load'});
return {page, close: browser.close.bind(browser)}; return {page, close: browser.close.bind(browser)};
} }

View File

@ -544,7 +544,7 @@ export default {
}, },
ticketSales: { ticketSales: {
setOk: 'vn-ticket-sale vn-tool-bar > vn-button[label="Ok"] > button', 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', saleDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.descriptor.id})"]', 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"]', 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() => { 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.moreMenu);
await page.waitToClick(selectors.itemDescriptor.moreMenuRegularizeButton); await page.waitToClick(selectors.itemDescriptor.moreMenuRegularizeButton);
await page.write(selectors.itemDescriptor.regularizeQuantity, '100'); 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() => { 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.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); 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() => { it('should confirm the new ticket received the line', async() => {
const expectedLines = 1;
const result = await page.countElement(selectors.ticketSales.saleLine); 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() => { 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() => { it('should mark the first sale as reserved', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); 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.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuReserve); await page.waitToClick(selectors.ticketSales.moreMenuReserve);
await page.closePopup(); await page.closePopup();
@ -363,6 +366,7 @@ describe('Ticket Edit sale path', () => {
}); });
it('should unmark the first sale as reserved', async() => { 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.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuUnmarkReseved); await page.waitToClick(selectors.ticketSales.moreMenuUnmarkReseved);
await page.waitForClassPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide'); 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() => { 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.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn); await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn);
await page.waitToClick(selectors.ticketDescriptor.thursdayButton); 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() => { 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.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn); await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn);
await page.waitToClick(selectors.ticketDescriptor.saturdayButton); 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() => { 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.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour); await page.waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour);
await page.pickTime(selectors.ticketDescriptor.changeShippedHour, '08:15'); await page.pickTime(selectors.ticketDescriptor.changeShippedHour, '08:15');
@ -65,6 +66,7 @@ describe('Ticket descriptor path', () => {
describe('Restore ticket', () => { describe('Restore ticket', () => {
it('should restore the ticket using the descriptor menu', async() => { 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.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuRestoreTicket); await page.waitToClick(selectors.ticketDescriptor.moreMenuRestoreTicket);
await page.waitToClick(selectors.ticketDescriptor.acceptDialog); await page.waitToClick(selectors.ticketDescriptor.acceptDialog);
@ -82,6 +84,7 @@ describe('Ticket descriptor path', () => {
}); });
it('should open the add stowaway dialog', async() => { it('should open the add stowaway dialog', async() => {
await page.waitForTimeout(1000); // initialization of functionality takes about 1000ms to work
await page.waitForFunction(() => { await page.waitForFunction(() => {
let element = document.querySelector('vn-ticket-descriptor-menu'); let element = document.querySelector('vn-ticket-descriptor-menu');
return element.$ctrl.canShowStowaway === true; return element.$ctrl.canShowStowaway === true;
@ -114,6 +117,7 @@ describe('Ticket descriptor path', () => {
}); });
it('should delete the stowaway', async() => { 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.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded(); await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteStowawayButton); await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteStowawayButton);
@ -145,6 +149,7 @@ describe('Ticket descriptor path', () => {
}); });
it('should invoice the ticket using the descriptor menu', async() => { 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.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded(); await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuMakeInvoice); await page.waitToClick(selectors.ticketDescriptor.moreMenuMakeInvoice);
@ -176,6 +181,7 @@ describe('Ticket descriptor path', () => {
describe('SMS', () => { describe('SMS', () => {
it('should send the payment SMS using the descriptor menu', async() => { 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.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded(); await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS); 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() => { 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.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded(); await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms); 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() => { 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.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway); await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway);
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket); await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
@ -71,6 +72,7 @@ describe('Ticket create path', () => {
}); });
it('should delete the current ticket', async() => { 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.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket); await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
await page.waitToClick(selectors.ticketDescriptor.acceptDialog); 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() => { 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.moreMenu);
await page.waitToClick(selectors.clientDescriptor.simpleTicketButton); await page.waitToClick(selectors.clientDescriptor.simpleTicketButton);
await page.waitForState('ticket.create'); 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() => { 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.moreMenu);
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuCloneInvoiceIn); await page.waitToClick(selectors.invoiceInDescriptor.moreMenuCloneInvoiceIn);
await page.waitToClick(selectors.invoiceInDescriptor.acceptButton); 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() => { 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.moreMenu);
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuDeleteInvoiceIn); await page.waitToClick(selectors.invoiceInDescriptor.moreMenuDeleteInvoiceIn);
await page.waitToClick(selectors.invoiceInDescriptor.acceptButton); 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() => { 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.moreMenu);
await page.waitToClick(selectors.invoiceOutDescriptor.moreMenuDeleteInvoiceOut); await page.waitToClick(selectors.invoiceOutDescriptor.moreMenuDeleteInvoiceOut);
await page.waitToClick(selectors.invoiceOutDescriptor.acceptDeleteButton); 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() => { 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.createInvoice);
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice); await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm); await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
@ -44,6 +45,7 @@ describe('InvoiceOut manual invoice path', () => {
}); });
it('should now open the manual invoice form', async() => { 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.createInvoice);
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice); await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm); 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() => { 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.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuAddEntry); await page.waitToClick(selectors.travelDescriptor.dotMenuAddEntry);
await page.waitForState('entry.create'); 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() => { 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.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuClone); await page.waitToClick(selectors.travelDescriptor.dotMenuClone);
await page.respondToDialog('accept'); 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() => { 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.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries); await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries);
await page.waitToClick(selectors.travelDescriptor.acceptClonation); 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() => { 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.menu);
await page.waitToClick(selectors.zoneDescriptor.deleteZone); await page.waitToClick(selectors.zoneDescriptor.deleteZone);
await page.respondToDialog('accept'); 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() => { 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.menuButton);
await page.waitToClick(selectors.accountDescriptor.activateAccount); await page.waitToClick(selectors.accountDescriptor.activateAccount);
await page.waitToClick(selectors.accountDescriptor.acceptButton); await page.waitToClick(selectors.accountDescriptor.acceptButton);
@ -138,6 +139,7 @@ describe('Account create and basic data path', () => {
describe('Set password', () => { describe('Set password', () => {
it('should set the password using the descriptor menu', async() => { 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.menuButton);
await page.waitToClick(selectors.accountDescriptor.setPassword); await page.waitToClick(selectors.accountDescriptor.setPassword);
await page.write(selectors.accountDescriptor.newPassword, 'quantum.crypt0graphy'); await page.write(selectors.accountDescriptor.newPassword, 'quantum.crypt0graphy');

View File

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

View File

@ -116,5 +116,6 @@
"This client is not invoiceable": "This client is not invoiceable", "This client is not invoiceable": "This client is not invoiceable",
"INACTIVE_PROVIDER": "Inactive provider", "INACTIVE_PROVIDER": "Inactive provider",
"reference duplicated": "reference duplicated", "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} accessToken: {userId: userId}
}; };
beforeAll(async done => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
done();
}); });
it('should return falsy for a client without the data checked', async() => { it('should return falsy for a client without the data checked', async() => {

View File

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

View File

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

View File

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

View File

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

View File

@ -3,12 +3,10 @@ const LoopBackContext = require('loopback-context');
describe('entry importBuysPreview()', () => { describe('entry importBuysPreview()', () => {
const entryId = 1; const entryId = 1;
beforeAll(async done => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
done();
}); });
it('should return the buys with the calculated packageFk', async() => { 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 { .itemFilter {
vn-table.scrollable { vn-table.scrollable {
height: 500px height: 500px

View File

@ -40,7 +40,7 @@ module.exports = Self => {
if (!tax.taxClassFk) if (!tax.taxClassFk)
throw new UserError('Tax class cannot be blank'); 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}, {id: tax.id},
{taxClassFk: tax.taxClassFk} {taxClassFk: tax.taxClassFk}
), myOptions); ), myOptions);

View File

@ -1,15 +1,15 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('SalesMonitor salesFilter()', () => { 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 ctx = {req: {accessToken: {userId: 9}}, args: {}};
const filter = {order: 'id DESC'}; 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); 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(); const yesterday = new Date();
yesterday.setHours(0, 0, 0, 0); yesterday.setHours(0, 0, 0, 0);
const today = new Date(); const today = new Date();
@ -21,12 +21,12 @@ describe('SalesMonitor salesFilter()', () => {
to: today to: today
}}; }};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(13); 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(); const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
yesterday.setHours(0, 0, 0, 0); yesterday.setHours(0, 0, 0, 0);
@ -39,33 +39,33 @@ describe('SalesMonitor salesFilter()', () => {
to: today to: today
}}; }};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(0); 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 ctx = {req: {accessToken: {userId: 9}}, args: {problems: null}};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(24); 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 ctx = {req: {accessToken: {userId: 9}}, args: {orderFk: 11}};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
const firstRow = result[0]; const firstRow = result[0];
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
expect(firstRow.id).toEqual(11); 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 ctx = {req: {accessToken: {userId: 9}}, args: {pending: true}};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
const length = result.length; const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
@ -74,10 +74,10 @@ describe('SalesMonitor salesFilter()', () => {
expect(anyResult.state).toMatch(/(Libre|Arreglar)/); 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 ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
const firstRow = result[0]; const firstRow = result[0];
const secondRow = result[1]; const secondRow = result[1];
const thirdRow = result[2]; const thirdRow = result[2];
@ -88,18 +88,18 @@ describe('SalesMonitor salesFilter()', () => {
expect(thirdRow.state).toEqual('Entregado'); 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 ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: true}};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(20); 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 ctx = {req: {accessToken: {userId: 18}}, args: {myTeam: false}};
const filter = {}; const filter = {};
const result = await app.models.SalesMonitor.salesFilter(ctx, filter); const result = await models.SalesMonitor.salesFilter(ctx, filter);
expect(result.length).toEqual(4); expect(result.length).toEqual(4);
}); });
@ -113,7 +113,7 @@ describe('SalesMonitor salesFilter()', () => {
const ctx = {req: {accessToken: {userId: 18}}, args: {}}; const ctx = {req: {accessToken: {userId: 18}}, args: {}};
const filter = {order: 'totalProblems DESC'}; 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 firstTicket = result.shift();
const secondTicket = result.shift(); const secondTicket = result.shift();
@ -131,7 +131,7 @@ describe('SalesMonitor salesFilter()', () => {
const ctx = {req: {accessToken: {userId: 18}}, args: {}}; const ctx = {req: {accessToken: {userId: 18}}, args: {}};
const filter = {order: 'totalProblems ASC'}; 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 firstTicket = result.shift();
const secondTicket = result.shift(); const secondTicket = result.shift();

View File

@ -1,6 +1,5 @@
<vn-crud-model auto-load="true" <vn-crud-model
vn-id="model" vn-id="model"
params="::$ctrl.filterParams"
url="SalesMonitors/salesFilter" url="SalesMonitors/salesFilter"
limit="20" limit="20"
order="shippedDate DESC, preparationHour ASC, zoneLanding ASC, id"> order="shippedDate DESC, preparationHour ASC, zoneLanding ASC, id">

View File

@ -6,13 +6,30 @@ export default class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.filterParams = this.fetchParams({ this.filterParams = this.fetchParams();
scopeDays: 1
});
} }
fetchParams($params) { $onInit() {
if (!Object.entries($params).length) 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; $params.scopeDays = 1;
if (typeof $params.scopeDays === 'number') { if (typeof $params.scopeDays === 'number') {

View File

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

View File

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

View File

@ -4,10 +4,8 @@ let UserError = require('vn-loopback/util/user-error');
describe('order new()', () => { describe('order new()', () => {
let orderId; let orderId;
afterAll(async done => { afterAll(async() => {
await app.models.Order.destroyById(orderId); await app.models.Order.destroyById(orderId);
done();
}); });
it('should throw an error if the client isnt active', async() => { 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', () => { describe('Order updateBasicData', () => {
const orderId = 21; const orderId = 21;
afterAll(async done => { afterAll(async() => {
let validparams = {note: null}; let validparams = {note: null};
await app.models.Order.updateBasicData(orderId, validparams); await app.models.Order.updateBasicData(orderId, validparams);
done();
}); });
it('should return an error if the order is confirmed', async() => { it('should return an error if the order is confirmed', async() => {

View File

@ -4,14 +4,12 @@ describe('route guessPriority()', () => {
const targetRouteId = 7; const targetRouteId = 7;
let routeTicketsToRestore; let routeTicketsToRestore;
afterAll(async done => { afterAll(async() => {
let restoreFixtures = []; let restoreFixtures = [];
routeTicketsToRestore.forEach(ticket => { routeTicketsToRestore.forEach(ticket => {
restoreFixtures.push(ticket.updateAttribute('priority', null)); restoreFixtures.push(ticket.updateAttribute('priority', null));
}); });
await Promise.all(restoreFixtures); await Promise.all(restoreFixtures);
done();
}); });
it('should call guessPriority() and then check the tickets in the target route now have their priorities defined', async() => { 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}, accessToken: {userId: deliveryId},
}; };
beforeAll(async done => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
done();
}); });
it('should add the ticket to a route', async() => { it('should add the ticket to a route', async() => {

View File

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

View File

@ -7,13 +7,13 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}, http: {source: 'query'},
}, },
], ],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true, root: true,
}, },
http: { 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( const stmt = new ParameterizedSQL(
`SELECT `SELECT
e.id, e.id,
@ -55,6 +60,6 @@ module.exports = Self => {
`); `);
stmt.merge(Self.buildSuffix(filter, 'e')); 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', accessType: 'READ',
accepts: [{ accepts: [{
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
required: false, required: false,
description: 'Filter defining where and paginated data', description: 'Filter defining where and paginated data',
http: {source: 'query'} http: {source: 'query'}
}], }],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {
@ -22,9 +22,14 @@ module.exports = Self => {
} }
}); });
Self.listPackaging = async filter => { Self.listPackaging = async(filter, options) => {
let conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
let stmt = new ParameterizedSQL( const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(
`SELECT name, itemFk, packagingFk `SELECT name, itemFk, packagingFk
FROM (SELECT i.name, i.id itemFk, p.id packagingFk FROM (SELECT i.name, i.id itemFk, p.id packagingFk
FROM item i FROM item i
@ -33,6 +38,7 @@ module.exports = Self => {
); );
stmt.merge(conn.makeSuffix(filter)); 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()', () => { describe('ticket listPackaging()', () => {
it('should return the packaging', async() => { it('should return the packaging', async() => {
let filter = {where: {packagingFk: 1}}; const tx = await models.Packaging.beginTransaction({});
let response = await app.models.Packaging.listPackaging(filter);
expect(response[0].name).toBeDefined(); try {
expect(response[0].name).toEqual('Container ammo box 1m'); const options = {transaction: tx};
const filter = {where: {packagingFk: 1}};
const response = await models.Packaging.listPackaging(filter, options);
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', accessType: 'READ',
accepts: [{ accepts: [{
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
description: 'Filter defining where and paginated data' description: 'Filter defining where and paginated data'
}], }],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {
@ -20,8 +20,13 @@ module.exports = Self => {
} }
}); });
Self.listSaleTracking = async filter => { Self.listSaleTracking = async(filter, options) => {
let stmt = new ParameterizedSQL(` const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(`
SELECT SELECT
st.id, st.id,
s.ticketFk, s.ticketFk,
@ -41,9 +46,9 @@ module.exports = Self => {
stmt.merge(Self.makeSuffix(filter)); stmt.merge(Self.makeSuffix(filter));
let trackings = await Self.rawStmt(stmt); const trackings = await Self.rawStmt(stmt, myOptions);
let salesFilter = { const salesFilter = {
include: [ include: [
{ {
relation: 'item' relation: 'item'
@ -52,14 +57,14 @@ module.exports = Self => {
where: {ticketFk: filter.where.ticketFk} 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 => { for (const tracking of trackings) {
sales.forEach(sale => { for (const sale of sales) {
if (tracking.itemFk == sale.itemFk) if (tracking.itemFk == sale.itemFk)
tracking.item = sale.item(); tracking.item = sale.item();
}); }
}); }
return trackings; 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()', () => { describe('ticket listSaleTracking()', () => {
it('should call the listSaleTracking method and return the response', async() => { it('should call the listSaleTracking method and return the response', async() => {
let filter = {where: {ticketFk: 1}}; const tx = await models.SaleTracking.beginTransaction({});
let result = await app.models.SaleTracking.listSaleTracking(filter);
expect(result.length).toEqual(4); 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() => { it(`should call the listSaleTracking method and return zero if doesn't have lines`, async() => {
let filter = {where: {ticketFk: 2}}; const tx = await models.SaleTracking.beginTransaction({});
let result = await app.models.SaleTracking.listSaleTracking(filter);
expect(result.length).toEqual(0); 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,54 +26,73 @@ module.exports = Self => {
} }
}); });
Self.deleteSales = async(ctx, sales, ticketId) => { Self.deleteSales = async(ctx, sales, ticketId, options) => {
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const models = Self.app.models; 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);
const ticket = await models.Ticket.findById(ticketId, { if (!myOptions.transaction) {
include: { tx = await Self.beginTransaction({});
relation: 'client', myOptions.transaction = tx;
scope: { }
include: {
relation: 'salesPersonUser', try {
scope: { const canEditSales = await models.Sale.canEdit(ctx, sales, myOptions);
fields: ['id', 'name']
const ticket = await models.Ticket.findById(ticketId, {
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPersonUser',
scope: {
fields: ['id', 'name']
}
} }
} }
} }
}, myOptions);
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
if (!isTicketEditable)
throw new UserError(`The sales of this ticket can't be modified`);
if (!canEditSales)
throw new UserError(`Sale(s) blocked, please contact production`);
const promises = [];
let deletions = '';
for (let sale of sales) {
const deletedSale = models.Sale.destroyById(sale.id, myOptions);
deletions += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
promises.push(deletedSale);
} }
});
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); const salesPerson = ticket.client().salesPersonUser();
if (!isTicketEditable) if (salesPerson) {
throw new UserError(`The sales of this ticket can't be modified`); const origin = ctx.req.headers.origin;
if (!canEditSales) const message = $t('Deleted sales from ticket', {
throw new UserError(`Sale(s) blocked, please contact production`); ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
deletions: deletions
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
}
const promises = []; const deletedSales = await Promise.all(promises);
let deletions = '';
for (let sale of sales) {
const deletedSale = models.Sale.destroyById(sale.id);
deletions += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
promises.push(deletedSale); if (tx) await tx.commit();
return deletedSales;
} catch (e) {
if (tx) await tx.rollback();
throw e;
} }
const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('Deleted sales from ticket', {
ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
deletions: deletions
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
return Promise.all(promises);
}; };
}; };

View File

@ -18,26 +18,32 @@ module.exports = Self => {
} }
}); });
Self.getClaimableFromTicket = async ticketFk => { Self.getClaimableFromTicket = async(ticketFk, options) => {
let query = `SELECT const myOptions = {};
s.id AS saleFk,
t.id AS ticketFk,
t.landed,
s.concept,
s.itemFk,
s.quantity,
s.price,
s.discount,
t.nickname
FROM vn.ticket t
INNER JOIN vn.sale s ON s.ticketFk = t.id
LEFT JOIN vn.claimBeginning cb ON cb.saleFk = s.id
WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, CURDATE()) if (typeof options == 'object')
AND t.id = ? AND cb.id IS NULL Object.assign(myOptions, options);
ORDER BY t.landed DESC, t.id DESC`;
let claimableSales = await Self.rawSql(query, [ticketFk]); const query = `
SELECT
s.id AS saleFk,
t.id AS ticketFk,
t.landed,
s.concept,
s.itemFk,
s.quantity,
s.price,
s.discount,
t.nickname
FROM vn.ticket t
INNER JOIN vn.sale s ON s.ticketFk = t.id
LEFT JOIN vn.claimBeginning cb ON cb.saleFk = s.id
WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, CURDATE())
AND t.id = ? AND cb.id IS NULL
ORDER BY t.landed DESC, t.id DESC`;
const claimableSales = await Self.rawSql(query, [ticketFk], myOptions);
return claimableSales; 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 models = Self.app.models;
const myOptions = {};
let tx;
const sale = await Self.findById(id); if (typeof options == 'object')
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk); Object.assign(myOptions, options);
if (!isEditable) if (!myOptions.transaction) {
throw new UserError(`The sales of this ticket can't be modified`); tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const canEditSale = await models.Sale.canEdit(ctx, [id]); try {
const sale = await Self.findById(id, null, myOptions);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions);
if (!canEditSale) if (!isEditable)
throw new UserError(`Sale(s) blocked, please contact production`); throw new UserError(`The sales of this ticket can't be modified`);
return Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id]); const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`);
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,63 +34,80 @@ module.exports = Self => {
} }
}); });
Self.reserve = async(ctx, ticketId, sales, reserved) => { Self.reserve = async(ctx, ticketId, sales, reserved, options) => {
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const models = Self.app.models; const models = Self.app.models;
const myOptions = {};
let tx;
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); if (typeof options == 'object')
if (!isTicketEditable) Object.assign(myOptions, options);
throw new UserError(`The sales of this ticket can't be modified`);
const canEditSale = await models.Sale.canEdit(ctx, sales); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
if (!canEditSale) myOptions.transaction = tx;
throw new UserError(`Sale(s) blocked, please contact production`);
let changesMade = '';
const promises = [];
for (let sale of sales) {
if (sale.reserved != reserved) {
const oldState = sale.reserved ? 'reserved' : 'regular';
const newState = reserved ? 'reserved' : 'regular';
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
promises.push(reservedSale);
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`;
}
} }
const result = await Promise.all(promises); 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 ticket = await models.Ticket.findById(ticketId, { const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions);
include: {
relation: 'client', if (!canEditSale)
scope: { throw new UserError(`Sale(s) blocked, please contact production`);
include: {
relation: 'salesPersonUser', let changesMade = '';
scope: { const promises = [];
fields: ['id', 'name']
for (let sale of sales) {
if (sale.reserved != reserved) {
const oldState = sale.reserved ? 'reserved' : 'regular';
const newState = reserved ? 'reserved' : 'regular';
const reservedSale = models.Sale.updateAll({id: sale.id}, {reserved: reserved}, myOptions);
promises.push(reservedSale);
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`;
}
}
const result = await Promise.all(promises);
const ticket = await models.Ticket.findById(ticketId, {
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPersonUser',
scope: {
fields: ['id', 'name']
}
} }
} }
} }
}, myOptions);
const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('Changed sale reserved state', {
ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
changes: changesMade
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
} }
});
const salesPerson = ticket.client().salesPersonUser(); if (tx) await tx.commit();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('Changed sale reserved state', { return result;
ticketId: ticketId, } catch (e) {
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, if (tx) await tx.rollback();
changes: changesMade throw e;
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
} }
return result;
}; };
}; };

View File

@ -1,36 +1,69 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('sale canEdit()', () => { describe('sale canEdit()', () => {
it('should return true if the role is production regardless of the saleTrackings', async() => { it('should return true if the role is production regardless of the saleTrackings', async() => {
const productionUserID = 49; const tx = await models.Sale.beginTransaction({});
let ctx = {req: {accessToken: {userId: productionUserID}}};
const sales = [{id: 3}]; try {
const options = {transaction: tx};
const result = await app.models.Sale.canEdit(ctx, sales); const productionUserID = 49;
const ctx = {req: {accessToken: {userId: productionUserID}}};
expect(result).toEqual(true); const sales = [{id: 3}];
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() => { it('should return true if the role is not production and none of the sales has saleTracking', async() => {
const salesPersonUserID = 18; const tx = await models.Sale.beginTransaction({});
let ctx = {req: {accessToken: {userId: salesPersonUserID}}};
const sales = [{id: 10}]; try {
const options = {transaction: tx};
const result = await app.models.Sale.canEdit(ctx, sales); const salesPersonUserID = 18;
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
expect(result).toEqual(true); const sales = [{id: 10}];
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() => { it('should return false if any of the sales has a saleTracking record', async() => {
const salesPersonUserID = 18; const tx = await models.Sale.beginTransaction({});
let ctx = {req: {accessToken: {userId: salesPersonUserID}}};
const sales = [{id: 3}]; try {
const options = {transaction: tx};
const result = await app.models.Sale.canEdit(ctx, sales); const salesPersonUserID = 18;
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
expect(result).toEqual(false); const sales = [{id: 3}];
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,38 +1,29 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('sale deleteSales()', () => { 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() => { it('should throw an error if the ticket of the given sales is not editable', async() => {
let ctx = { const tx = await models.Sale.beginTransaction({});
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let error; let error;
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
const ticketId = 2;
try { try {
await app.models.Sale.deleteSales(ctx, sales, ticketId); const options = {transaction: tx};
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
const ticketId = 2;
await models.Sale.deleteSales(ctx, sales, ticketId, options);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback();
error = e; error = e;
} }
@ -40,19 +31,33 @@ describe('sale deleteSales()', () => {
}); });
it('should delete the sale', async() => { it('should delete the sale', async() => {
let ctx = { const tx = await models.Sale.beginTransaction({});
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const sales = [{id: newSale.id, instance: 0}]; try {
const ticketId = 16; const options = {transaction: tx};
let res = await app.models.Sale.deleteSales(ctx, sales, ticketId); 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);
expect(res).toEqual([{count: 1}]); const sales = [{id: newSale.id, instance: 0}];
const ticketId = 16;
const deletions = await models.Sale.deleteSales(ctx, sales, ticketId, options);
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()', () => { describe('sale getClaimableFromTicket()', () => {
it('should return the claimable sales of a given ticket', async() => { 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({});
expect(claimableFromTicket[0].concept).toBe('Ranged weapon longbow 2m'); try {
expect(claimableFromTicket.length).toBe(3); 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()', () => { describe('sale recalculatePrice()', () => {
const saleId = 7; const saleId = 7;
it('should update the sale price', async() => { it('should update the sale price', async() => {
const ctx = {req: {accessToken: {userId: 9}}}; const tx = await models.Sale.beginTransaction({});
const response = await app.models.Sale.recalculatePrice(ctx, saleId);
expect(response.affectedRows).toBeDefined(); try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
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() => { it('should throw an error if the ticket is not editable', async() => {
const ctx = {req: {accessToken: {userId: 9}}}; const tx = await models.Sale.beginTransaction({});
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;
});
expect(error).toBeDefined(); let error;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
const immutableSaleId = 1;
await models.Sale.recalculatePrice(ctx, immutableSaleId, 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`));
}); });
}); });

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('sale reserve()', () => { describe('sale reserve()', () => {
const ctx = { const ctx = {
@ -9,51 +9,54 @@ 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() => { it('should throw an error if the ticket can not be modified', async() => {
const tx = await models.Sale.beginTransaction({});
let error; let error;
const ticketId = 2; try {
const sales = [{id: 5}]; const options = {transaction: tx};
const reserved = false;
await app.models.Sale.reserve(ctx, ticketId, sales, reserved) const ticketId = 2;
.catch(response => { const sales = [{id: 5}];
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`)); const reserved = false;
error = response;
});
expect(error).toBeDefined(); await models.Sale.reserve(ctx, ticketId, sales, reserved, 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 update the given sales of a ticket to reserved', async() => { 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({});
expect(originalTicketSales[0].reserved).toEqual(false); try {
expect(originalTicketSales[1].reserved).toEqual(false); const options = {transaction: tx};
const ticketId = 11; originalTicketSales = await models.Ticket.getSales(11, options);
const sales = [{id: 7}, {id: 8}];
const reserved = true;
await app.models.Sale.reserve(ctx, ticketId, sales, reserved); expect(originalTicketSales[0].reserved).toEqual(false);
expect(originalTicketSales[1].reserved).toEqual(false);
const reservedTicketSales = await app.models.Ticket.getSales(ticketId); const ticketId = 11;
const sales = [{id: 7}, {id: 8}];
const reserved = true;
expect(reservedTicketSales[0].reserved).toEqual(true); await models.Sale.reserve(ctx, ticketId, sales, reserved, options);
expect(reservedTicketSales[1].reserved).toEqual(true);
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()', () => { describe('sale updateConcept()', () => {
const ctx = {req: {accessToken: {userId: 9}}}; const ctx = {req: {accessToken: {userId: 9}}};
const saleId = 1; 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() => { it('should throw if ID was undefined', async() => {
let err; const tx = await models.Sale.beginTransaction({});
const newConcept = 'I am he new concept';
let error;
try { 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) { } catch (e) {
err = e; await tx.rollback();
error = e;
} }
expect(err).toBeDefined(); expect(error).toBeDefined();
}); });
it('should update the sale concept', async() => { it('should update the sale concept', async() => {
const newConcept = 'I am the new concept'; const tx = await models.Sale.beginTransaction({});
let response = await app.models.Sale.updateConcept(ctx, saleId, newConcept); try {
const options = {transaction: tx};
expect(response.concept).toEqual(newConcept); const newConcept = 'I am the new concept';
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,104 +1,100 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('sale updatePrice()', () => { describe('sale updatePrice()', () => {
let originalSale; const ctx = {
let originalSalesPersonMana; req: {
let createdSaleComponent; accessToken: {userId: 18},
let saleId = 7; headers: {origin: 'localhost:5000'},
let manaComponentId; __: () => {}
}
beforeAll(async done => { };
let component = await app.models.Component.findOne({where: {code: 'mana'}}); const saleId = 7;
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() => { it('should throw an error if the ticket is not editable', async() => {
const ctx = { const tx = await models.Sale.beginTransaction({});
req: {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let immutableSaleId = 1;
let price = 5;
await app.models.Sale.updatePrice(ctx, immutableSaleId, price) try {
.catch(response => { const options = {transaction: tx};
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`));
error = response;
});
expect(error).toBeDefined(); 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() => { it('should return 0 if the price is an empty string', async() => {
const ctx = { const tx = await models.Sale.beginTransaction({});
req: {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let price = ''; try {
const options = {transaction: tx};
await app.models.Sale.updatePrice(ctx, saleId, price); const price = '';
let updatedSale = await app.models.Sale.findById(saleId);
expect(updatedSale.price).toEqual(0); await models.Sale.updatePrice(ctx, saleId, price, options);
const updatedSale = await models.Sale.findById(saleId, null, options);
// restores expect(updatedSale.price).toEqual(0);
await originalSale.updateAttributes(originalSale);
await app.models.SaleComponent.updateAll({componentFk: manaComponentId, saleFk: saleId}, {value: 0}); await tx.rollback();
await originalSalesPersonMana.updateAttributes(originalSalesPersonMana); } catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should now set price as a number in a string', async() => { it('should now set price as a number in a string', async() => {
const ctx = { const tx = await models.Sale.beginTransaction({});
req: {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let price = '8'; try {
const options = {transaction: tx};
await app.models.Sale.updatePrice(ctx, saleId, price); const price = '8';
let updatedSale = await app.models.Sale.findById(saleId);
expect(updatedSale.price).toEqual(8); await models.Sale.updatePrice(ctx, saleId, price, options);
const updatedSale = await models.Sale.findById(saleId, null, options);
// restores expect(updatedSale.price).toEqual(8);
await originalSale.updateAttributes(originalSale);
await app.models.SaleComponent.updateAll({componentFk: manaComponentId, saleFk: saleId}, {value: 0}); await tx.rollback();
await originalSalesPersonMana.updateAttributes(originalSalesPersonMana); } catch (e) {
await tx.rollback();
throw e;
}
}); });
// #2736 sale updatePrice() returns inconsistent values it('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => {
xit('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({});
let ctx = {req: {accessToken: {userId: 18}}};
let price = 5.4;
await app.models.Sale.updatePrice(ctx, saleId, price); try {
let updatedSale = await app.models.Sale.findById(saleId); const options = {transaction: tx};
createdSaleComponent = await app.models.SaleComponent.findOne({where: {saleFk: saleId, componentFk: manaComponentId}});
expect(updatedSale.price).toBe(price); const price = 5.4;
expect(createdSaleComponent.value).toEqual(-2.04); const originalSalesPersonMana = await models.WorkerMana.findById(18, null, options);
const manaComponent = await models.Component.findOne({where: {code: 'mana'}}, options);
let updatedSalesPersonMana = await app.models.WorkerMana.findById(18); 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(updatedSalesPersonMana.amount).not.toEqual(originalSalesPersonMana.amount); expect(updatedSale.price).toBe(price);
expect(createdSaleComponent.value).toEqual(-2.04);
// restores const updatedSalesPersonMana = await models.WorkerMana.findById(18, null, options);
await originalSale.updateAttributes(originalSale);
await app.models.SaleComponent.updateAll({componentFk: manaComponentId, saleFk: saleId}, {value: 0}); expect(updatedSalesPersonMana.amount).not.toEqual(originalSalesPersonMana.amount);
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()', () => { describe('sale updateQuantity()', () => {
const ctx = { const ctx = {
@ -10,44 +10,61 @@ describe('sale updateQuantity()', () => {
}; };
it('should throw an error if the quantity is not a number', async() => { it('should throw an error if the quantity is not a number', async() => {
const tx = await models.Sale.beginTransaction({});
let error; let error;
try {
const options = {transaction: tx};
await app.models.Sale.updateQuantity(ctx, 1, 'wrong quantity!') await models.Sale.updateQuantity(ctx, 1, 'wrong quantity!', options);
.catch(response => {
expect(response).toEqual(new Error('The value should be a number'));
error = response;
});
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() => { it('should throw an error if the quantity is greater than it should be', async() => {
const tx = await models.Sale.beginTransaction({});
let error; let error;
try {
const options = {transaction: tx};
await app.models.Sale.updateQuantity(ctx, 1, 99) await models.Sale.updateQuantity(ctx, 1, 99, options);
.catch(response => {
expect(response).toEqual(new Error('The new quantity should be smaller than the old one'));
error = response;
});
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() => { 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 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 (!canEditSale) if (!myOptions.transaction) {
throw new UserError(`Sale(s) blocked, please contact production`); tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
return await currentLine.updateAttributes({concept: newConcept}); 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`);
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 $t = ctx.req.__; // $translate
const models = Self.app.models; 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 { try {
const options = {transaction: tx};
const filter = { const filter = {
include: { include: {
relation: 'ticket', 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) if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`); 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) if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`); throw new UserError(`Sale(s) blocked, please contact production`);
const oldPrice = sale.price; const oldPrice = sale.price;
const userId = ctx.req.accessToken.userId; 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 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 componentId = discount.id;
const componentValue = newPrice - sale.price; const componentValue = newPrice - sale.price;
@ -80,23 +87,23 @@ module.exports = Self => {
componentFk: componentId, componentFk: componentId,
saleFk: id saleFk: id
}; };
const saleComponent = await models.SaleComponent.findOne({where}, options); const saleComponent = await models.SaleComponent.findOne({where}, myOptions);
if (saleComponent) { if (saleComponent) {
await models.SaleComponent.updateAll(where, { await models.SaleComponent.updateAll(where, {
value: saleComponent.value + componentValue value: saleComponent.value + componentValue
}, options); }, myOptions);
} else { } else {
await models.SaleComponent.create({ await models.SaleComponent.create({
saleFk: id, saleFk: id,
componentFk: componentId, componentFk: componentId,
value: componentValue value: componentValue
}, options); }, myOptions);
} }
await sale.updateAttributes({price: newPrice}, options); await sale.updateAttributes({price: newPrice}, myOptions);
query = `CALL vn.manaSpellersRequery(?)`; query = `CALL vn.manaSpellersRequery(?)`;
await Self.rawSql(query, [userId], options); await Self.rawSql(query, [userId], myOptions);
const salesPerson = sale.ticket().client().salesPersonUser(); const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
@ -111,14 +118,14 @@ module.exports = Self => {
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` 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; return sale;
} catch (error) { } catch (error) {
await tx.rollback(); if (tx) await tx.rollback();
throw error; throw error;
} }
}; };

View File

@ -26,60 +26,77 @@ module.exports = Self => {
} }
}); });
Self.updateQuantity = async(ctx, id, newQuantity) => { Self.updateQuantity = async(ctx, id, newQuantity, options) => {
const $t = ctx.req.__; // $translate
const models = Self.app.models; 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 (!canEditSale) if (!myOptions.transaction) {
throw new UserError(`Sale(s) blocked, please contact production`); tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
if (isNaN(newQuantity)) try {
throw new UserError(`The value should be a number`); const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
const filter = { if (!canEditSale)
include: { throw new UserError(`Sale(s) blocked, please contact production`);
relation: 'ticket',
scope: { if (isNaN(newQuantity))
include: { throw new UserError(`The value should be a number`);
relation: 'client',
scope: { const filter = {
include: { include: {
relation: 'salesPersonUser', relation: 'ticket',
scope: { scope: {
fields: ['id', 'name'] include: {
relation: 'client',
scope: {
include: {
relation: 'salesPersonUser',
scope: {
fields: ['id', 'name']
}
} }
} }
} }
} }
} }
};
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}, myOptions);
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('Changed sale quantity', {
ticketId: sale.ticket().id,
itemId: sale.itemFk,
concept: sale.concept,
oldQuantity: oldQuantity,
newQuantity: newQuantity,
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
} }
};
const sale = await models.Sale.findById(id, filter); if (tx) await tx.commit();
if (newQuantity > sale.quantity) return result;
throw new UserError('The new quantity should be smaller than the old one'); } catch (error) {
if (tx) await tx.rollback();
const oldQuantity = sale.quantity; throw error;
const result = await sale.updateAttributes({quantity: newQuantity});
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('Changed sale quantity', {
ticketId: sale.ticket().id,
itemId: sale.itemFk,
concept: sale.concept,
oldQuantity: oldQuantity,
newQuantity: newQuantity,
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
} }
return result;
}; };
}; };

View File

@ -1,4 +1,3 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('editableStates', { Self.remoteMethodCtx('editableStates', {
description: 'Gets the editable states according the user role ', description: 'Gets the editable states according the user role ',
@ -8,7 +7,7 @@ module.exports = Self => {
type: 'object' type: 'object'
}, },
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {
@ -17,14 +16,18 @@ module.exports = Self => {
} }
}); });
Self.editableStates = async(ctx, filter) => { Self.editableStates = async(ctx, filter, options) => {
let userId = ctx.req.accessToken.userId; const models = Self.app.models;
let models = Self.app.models; const userId = ctx.req.accessToken.userId;
let statesList = await models.State.find({where: filter.where}); const myOptions = {};
let isProduction = await models.Account.hasRole(userId, 'production'); if (typeof options == 'object')
let isSalesPerson = await models.Account.hasRole(userId, 'salesPerson'); Object.assign(myOptions, options);
let isAdministrative = await models.Account.hasRole(userId, 'administrative');
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) if (isProduction || isAdministrative)
return statesList; 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 accessToken = ctx.req.accessToken;
const models = Self.app.models; const models = Self.app.models;
const userId = accessToken.userId; const userId = accessToken.userId;
const myOptions = {};
let isProduction = await models.Account.hasRole(userId, 'production'); if (typeof options == 'object')
let isSalesPerson = await models.Account.hasRole(userId, 'salesPerson'); Object.assign(myOptions, options);
let isAdministrative = await models.Account.hasRole(userId, 'administrative');
let state = await models.State.findById(stateId);
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; 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()', () => { describe('ticket editableStates()', () => {
const filter = {where: {name: {like: '%%'}}}; const filter = {where: {name: {like: '%%'}}};
it('should return the expected state for the given role', async() => { it('should return the expected state for the given role', async() => {
const productionRole = 49; const tx = await models.State.beginTransaction({});
const ctx = {req: {accessToken: {userId: productionRole}}};
let result = await app.models.State.editableStates(ctx, filter); try {
let deliveredState = result.some(state => state.code == 'DELIVERED'); const options = {transaction: tx};
expect(deliveredState).toBeTruthy(); const productionRole = 49;
const ctx = {req: {accessToken: {userId: productionRole}}};
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() => { it(`should return the expected states by a specific role`, async() => {
const productionRole = 18; const tx = await models.State.beginTransaction({});
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');
expect(deliveredState).toBeFalsy(); try {
expect(pickerDesignedState).toBeTruthy(); const options = {transaction: tx};
const productionRole = 18;
const ctx = {req: {accessToken: {userId: productionRole}}};
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() => { it(`should return again the expected state by a specific role`, async() => {
const employeeRole = 1; const tx = await models.State.beginTransaction({});
const ctx = {req: {accessToken: {userId: employeeRole}}};
let result = await app.models.State.editableStates(ctx, filter);
let pickerDesignedState = result.some(state => state.code == 'PICKER_DESIGNED');
expect(pickerDesignedState).toBeTruthy(); try {
const options = {transaction: tx};
const employeeRole = 1;
const ctx = {req: {accessToken: {userId: employeeRole}}};
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()', () => { describe('state isEditable()', () => {
it('should return false if the state is not editable by a specific role', async() => { it('should return false if the state is not editable by a specific role', async() => {
const salesPersonRole = 18; const tx = await models.State.beginTransaction({});
const onDeliveryState = 13;
let ctx = {req: {accessToken: {userId: salesPersonRole}}};
let result = await app.models.State.isEditable(ctx, onDeliveryState);
expect(result).toBe(false); try {
const options = {transaction: tx};
const salesPersonRole = 18;
const onDeliveryState = 13;
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() => { it('should return true if the state is editable by a specific role', async() => {
const salesPersonRole = 18; const tx = await models.State.beginTransaction({});
const asignedState = 20;
let ctx = {req: {accessToken: {userId: salesPersonRole}}};
let result = await app.models.State.isEditable(ctx, asignedState);
expect(result).toBe(true); try {
const options = {transaction: tx};
const salesPersonRole = 18;
const asignedState = 20;
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() => { it('should return true again if the state is editable by a specific role', async() => {
const employeeRole = 1; const tx = await models.State.beginTransaction({});
const fixingState = 1;
let ctx = {req: {accessToken: {userId: employeeRole}}};
let result = await app.models.State.isEditable(ctx, fixingState);
expect(result).toBe(true); try {
const options = {transaction: tx};
const employeeRole = 1;
const fixingState = 1;
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() => { it('should return false if the state is not editable for the given role', async() => {
const employeeRole = 1; const tx = await models.State.beginTransaction({});
const asignedState = 13;
let ctx = {req: {accessToken: {userId: employeeRole}}};
let result = await app.models.State.isEditable(ctx, asignedState);
expect(result).toBe(false); try {
const options = {transaction: tx};
const employeeRole = 1;
const asignedState = 13;
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() => { it('should return true if the state is editable for the given role', async() => {
const productionRole = 49; const tx = await models.State.beginTransaction({});
const onDeliveryState = 13;
let ctx = {req: {accessToken: {userId: productionRole}}};
let result = await app.models.State.isEditable(ctx, onDeliveryState);
expect(result).toBe(true); try {
const options = {transaction: tx};
const productionRole = 49;
const onDeliveryState = 13;
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() => { it('should return true if the ticket is editable, the role is salesPerson and the ticket state is printed', async() => {
const salesPersonRole = 18; const tx = await models.State.beginTransaction({});
const printedState = 4;
const okState = 3;
const ctx = {req: {accessToken: {userId: salesPersonRole}}};
let canEditCurrent = await app.models.State.isEditable(ctx, printedState); try {
let canAsignNew = await app.models.State.isEditable(ctx, okState); const options = {transaction: tx};
let result = canEditCurrent && canAsignNew;
expect(result).toBe(true); const salesPersonRole = 18;
const printedState = 4;
const okState = 3;
const ctx = {req: {accessToken: {userId: salesPersonRole}}};
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', accessType: 'WRITE',
accepts: { accepts: {
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The document id', description: 'The document id',
http: {source: 'path'} http: {source: 'path'}
}, },
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { 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 models = Self.app.models;
const targetTicketDms = await models.TicketDms.findById(id); const myOptions = {};
const targetDms = await models.Dms.findById(targetTicketDms.dmsFk); let tx;
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}});
await models.Dms.removeFile(ctx, targetTicketDms.dmsFk); if (typeof options == 'object')
await targetTicketDms.destroy(); 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()', () => { describe('TicketDms removeFile()', () => {
const ticketDmsId = 1; const ticketDmsId = 1;
it(`should return an error for a user without enough privileges`, async() => { it(`should return an error for a user without enough privileges`, async() => {
let clientId = 1101; const tx = await models.TicketDms.beginTransaction({});
let ctx = {req: {accessToken: {userId: clientId}}};
let error; let error;
await app.models.TicketDms.removeFile(ctx, ticketDmsId).catch(e => { try {
error = e; const options = {transaction: tx};
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined(); 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;
}
expect(error.message).toEqual(`You don't have enough privileges`);
}); });
}); });

View File

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

View File

@ -4,7 +4,7 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Integer', type: 'number',
required: true, required: true,
description: 'The request ID', description: 'The request ID',
}, { }, {
@ -23,17 +23,37 @@ module.exports = Self => {
} }
}); });
Self.deny = async ctx => { Self.deny = async(ctx, options) => {
let userId = ctx.req.accessToken.userId; const myOptions = {};
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}}); let tx;
let params = { if (typeof options == 'object')
isOk: false, Object.assign(myOptions, options);
attenderFk: worker.id,
response: ctx.args.observation,
};
let request = await Self.app.models.TicketRequest.findById(ctx.args.id); if (!myOptions.transaction) {
return request.updateAttributes(params); 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,
};
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) => { Self.filter = async(ctx, filter, options) => {
let conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
let userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (ctx.args.mine) if (ctx.args.mine)
ctx.args.attenderFk = userId; ctx.args.attenderFk = userId;
@ -111,9 +115,7 @@ module.exports = Self => {
filter = mergeFilters(filter, {where}); filter = mergeFilters(filter, {where});
let stmt; const stmt = new ParameterizedSQL(
stmt = new ParameterizedSQL(
`SELECT `SELECT
tr.id, tr.id,
tr.ticketFk, tr.ticketFk,
@ -149,8 +151,6 @@ module.exports = Self => {
LEFT JOIN account.user ua ON ua.id = wka.userFk`); LEFT JOIN account.user ua ON ua.id = wka.userFk`);
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));
let result = await conn.executeStmt(stmt); return conn.executeStmt(stmt, myOptions);
return result;
}; };
}; };

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()', () => { describe('ticket-request confirm()', () => {
let ctx = { let ctx = {
@ -12,74 +12,89 @@ describe('ticket-request confirm()', () => {
}; };
it(`should throw an error if the item doesn't exist`, async() => { it(`should throw an error if the item doesn't exist`, async() => {
ctx.args = {itemFk: 999}; const tx = await models.TicketRequest.beginTransaction({});
let error; let error;
try { try {
await app.models.TicketRequest.confirm(ctx); const options = {transaction: tx};
} catch (err) {
error = err; 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`); expect(error.message).toEqual(`That item doesn't exists`);
}); });
it('should throw an error if the item is not available', async() => { it('should throw an error if the item is not available', async() => {
const requestId = 5; const tx = await models.TicketRequest.beginTransaction({});
const itemId = 4;
const quantity = 99999;
ctx.args = {
itemFk: itemId,
id: requestId,
quantity: quantity
};
let error; let error;
try { try {
await app.models.TicketRequest.confirm(ctx); const options = {transaction: tx};
} catch (err) {
error = err; const requestId = 5;
const itemId = 4;
const quantity = 99999;
ctx.args = {
itemFk: itemId,
id: requestId,
quantity: quantity
};
await models.TicketRequest.confirm(ctx, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
} }
expect(error.message).toEqual(`This item is not available`); expect(error.message).toEqual(`This item is not available`);
}); });
it(`should throw if there's a sale id`, async() => { it(`should throw if there's a sale id`, async() => {
const requestId = 4; const tx = await models.TicketRequest.beginTransaction({});
const itemId = 1;
const quantity = 10;
ctx.args = {
itemFk: itemId,
id: requestId,
quantity: quantity
};
const request = await app.models.TicketRequest.findById(requestId);
expect(request.saleFk).toBeNull();
await request.updateAttributes({saleFk: 2});
ctx.args = {
itemFk: itemId,
id: requestId,
quantity: quantity
};
let error; let error;
try { try {
await app.models.TicketRequest.confirm(ctx); const options = {transaction: tx};
} catch (err) {
error = err; const requestId = 4;
const itemId = 1;
const quantity = 10;
ctx.args = {
itemFk: itemId,
id: requestId,
quantity: quantity
};
const request = await models.TicketRequest.findById(requestId, null, options);
expect(request.saleFk).toBeNull();
await request.updateAttributes({saleFk: 2}, options);
ctx.args = {
itemFk: itemId,
id: requestId,
quantity: quantity
};
await models.TicketRequest.confirm(ctx, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
} }
expect(error.message).toEqual(`This request already contains a sale`); 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()', () => { describe('ticket-request deny()', () => {
let request; it('should return the dinied ticket request', async() => {
afterAll(async done => { const tx = await models.TicketRequest.beginTransaction({});
let params = {
isOk: null,
attenderFk: request.attenderFk,
response: null,
};
await request.updateAttributes(params); try {
done(); const options = {transaction: tx};
});
it('should return all ticket requests', async() => { const ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}};
let ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}};
request = await app.models.TicketRequest.findById(ctx.args.id); const result = await models.TicketRequest.deny(ctx, options);
let result = await app.models.TicketRequest.deny(ctx); expect(result.id).toEqual(4);
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()', () => { describe('ticket-request filter()', () => {
const userId = 9; const userId = 9;
let ctx = {req: {accessToken: {userId: userId}}}; let ctx = {req: {accessToken: {userId: userId}}};
it('should now return all ticket requests', async() => { it('should now return all ticket requests', async() => {
ctx.args = {}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const options = {transaction: tx};
expect(result.length).toEqual(3); ctx.args = {};
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() => { it('should return the ticket request matching a generic search value which is the ticket ID', async() => {
ctx.args = {search: 11}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(4); ctx.args = {search: 11};
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() => { it('should return the ticket request matching a generic search value which is the client address alias', async() => {
ctx.args = {search: 'NY roofs'}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(4); ctx.args = {search: 'NY roofs'};
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() => { it('should return the ticket request matching the ticket ID', async() => {
ctx.args = {ticketFk: 11}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx);
const requestId = result[0].id;
expect(requestId).toEqual(4); try {
const options = {transaction: tx};
ctx.args = {ticketFk: 11};
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() => { it('should return the ticket request matching the atender ID', async() => {
ctx.args = {attenderFk: 35}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(3); ctx.args = {attenderFk: 35};
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() => { it('should return the ticket request matching the isOk triple-state', async() => {
ctx.args = {isOk: null}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(3); ctx.args = {isOk: null};
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() => { it('should return the ticket request matching the client ID', async() => {
ctx.args = {clientFk: 1102}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(4); ctx.args = {clientFk: 1102};
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() => { it('should return the ticket request matching the warehouse ID', async() => {
ctx.args = {warehouse: 1}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx, {order: 'id'}); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(3); ctx.args = {warehouse: 1};
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() => { it('should return the ticket request matching the salesPerson ID', async() => {
ctx.args = {salesPersonFk: 18}; const tx = await models.TicketRequest.beginTransaction({});
const result = await app.models.TicketRequest.filter(ctx); try {
const requestId = result[0].id; const options = {transaction: tx};
expect(requestId).toEqual(3); ctx.args = {salesPersonFk: 18};
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) => { Self.changeState = async(ctx, params, options) => {
let userId = ctx.req.accessToken.userId; const models = Self.app.models;
let models = Self.app.models; const myOptions = {};
let tx;
if (!params.stateFk && !params.code) if (typeof options == 'object')
throw new UserError('State cannot be blank'); Object.assign(myOptions, options);
if (params.code) { if (!myOptions.transaction) {
let state = await models.State.findOne({where: {code: params.code}, fields: ['id']}); tx = await Self.beginTransaction({});
params.stateFk = state.id; myOptions.transaction = tx;
} }
if (!params.workerFk) { try {
let worker = await models.Worker.findOne({where: {userFk: userId}}); const userId = ctx.req.accessToken.userId;
params.workerFk = worker.id;
if (!params.stateFk && !params.code)
throw new UserError('State cannot be blank');
if (params.code) {
const state = await models.State.findOne({
where: {code: params.code},
fields: ['id']
}, myOptions);
params.stateFk = state.id;
}
if (!params.workerFk) {
const worker = await models.Worker.findOne({
where: {userFk: userId}
}, myOptions);
params.workerFk = worker.id;
}
const ticketState = await models.TicketState.findById(params.ticketFk, {
fields: ['stateFk']
}, myOptions);
let oldStateAllowed;
if (ticketState)
oldStateAllowed = await models.State.isEditable(ctx, ticketState.stateFk, myOptions);
const newStateAllowed = await models.State.isEditable(ctx, params.stateFk, myOptions);
const isAllowed = (!ticketState || oldStateAllowed == true) && newStateAllowed == true;
if (!isAllowed)
throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
const ticketTracking = await models.TicketTracking.create(params, myOptions);
if (tx) await tx.commit();
return ticketTracking;
} catch (e) {
if (tx) await tx.rollback();
throw e;
} }
let ticketState = await models.TicketState.findById(
params.ticketFk,
{fields: ['stateFk']}
);
let oldStateAllowed;
if (ticketState)
oldStateAllowed = await models.State.isEditable(ctx, ticketState.stateFk);
let newStateAllowed = await models.State.isEditable(ctx, params.stateFk);
let isAllowed = (!ticketState || oldStateAllowed == true) && newStateAllowed == true;
if (!isAllowed)
throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
return models.TicketTracking.create(params);
}; };
}; };

View File

@ -6,13 +6,13 @@ module.exports = Self => {
{ {
arg: 'ticketIds', arg: 'ticketIds',
description: 'the array of ticket ids to set as delivered', description: 'the array of ticket ids to set as delivered',
type: ['Number'], type: ['number'],
required: true, required: true,
http: {source: 'body'} http: {source: 'body'}
} }
], ],
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { http: {
@ -21,30 +21,47 @@ module.exports = Self => {
} }
}); });
Self.setDelivered = async(ctx, ticketIds) => { Self.setDelivered = async(ctx, ticketIds, options) => {
let userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
let models = Self.app.models; const models = Self.app.models;
const myOptions = {};
let tx;
let state = await models.State.findOne({ if (typeof options == 'object')
where: { Object.assign(myOptions, options);
code: 'delivered'
},
fields: ['id', 'name', 'alertLevel', 'code']
});
let worker = await models.Worker.findOne({where: {userFk: userId}}); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
let promises = []; myOptions.transaction = tx;
for (let id of ticketIds) {
let promise = models.TicketTracking.changeState(ctx, {
stateFk: state.id,
workerFk: worker.id,
ticketFk: id
});
promises.push(promise);
} }
await Promise.all(promises);
return state; try {
const state = await models.State.findOne({
where: {
code: 'delivered'
},
fields: ['id', 'name', 'alertLevel', 'code']
}, myOptions);
const worker = await models.Worker.findOne({where: {userFk: userId}}, myOptions);
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'); const LoopBackContext = require('loopback-context');
describe('ticket changeState()', () => { describe('ticket changeState()', () => {
const salesPersonId = 18; const salesPersonId = 18;
const employeeId = 1; const employeeId = 1;
const productionId = 49; const productionId = 49;
let activeCtx = { const activeCtx = {
accessToken: {userId: 9}, accessToken: {userId: 9},
}; };
let ctx = {req: activeCtx}; const ctx = {req: activeCtx};
let ticket; 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({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx 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() => { it('should throw if the ticket is not editable and the user isnt production', async() => {
activeCtx.accessToken.userId = salesPersonId; const tx = await models.TicketTracking.beginTransaction({});
let params = {ticketFk: 2, stateFk: 3};
let error;
let errCode;
try { 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) { } 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() => { it('should throw an error if a worker with employee role attemps to a forbidden state', async() => {
activeCtx.accessToken.userId = employeeId; const tx = await models.TicketTracking.beginTransaction({});
let params = {ticketFk: 11, stateFk: 13};
let error;
let errCode;
try { 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) { } 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() => { 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; const tx = await models.TicketTracking.beginTransaction({});
let params = {ticketFk: ticket.id, stateFk: 3};
let ticketTracking = await app.models.TicketTracking.changeState(ctx, params); try {
const options = {transaction: tx};
expect(ticketTracking.__data.ticketFk).toBe(params.ticketFk); const ticket = await models.Ticket.create(sampleTicket, options);
expect(ticketTracking.__data.stateFk).toBe(params.stateFk);
expect(ticketTracking.__data.workerFk).toBe(49);
expect(ticketTracking.__data.id).toBeDefined();
// restores activeCtx.accessToken.userId = productionId;
await app.models.TicketTracking.destroyById(ticketTracking.__data.id); 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();
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() => { 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}}}; const tx = await models.TicketTracking.beginTransaction({});
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);
expect(res.__data.ticketFk).toBe(params.ticketFk); try {
expect(res.__data.stateFk).toBe(params.stateFk); const options = {transaction: tx};
expect(res.__data.workerFk).toBe(params.workerFk);
expect(res.__data.workerFk).toBe(1); const ticket = await models.Ticket.create(sampleTicket, options);
expect(res.__data.id).toBeDefined(); 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'); const LoopBackContext = require('loopback-context');
describe('ticket setDelivered()', () => { describe('ticket setDelivered()', () => {
@ -7,50 +7,40 @@ describe('ticket setDelivered()', () => {
accessToken: {userId: userId}, accessToken: {userId: userId},
}; };
let ticketOne; beforeAll(async() => {
let ticketTwo;
beforeAll(async done => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
});
it('should return the state which has been applied to the given tickets', async() => {
const tx = await models.TicketTracking.beginTransaction({});
try { try {
let originalTicketOne = await app.models.Ticket.findById(8); const options = {transaction: tx};
let originalTicketTwo = await app.models.Ticket.findById(10);
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; originalTicketOne.id = null;
originalTicketTwo.id = null; originalTicketTwo.id = null;
ticketOne = await app.models.Ticket.create(originalTicketOne); const ticketOne = await models.Ticket.create(originalTicketOne, options);
ticketTwo = await app.models.Ticket.create(originalTicketTwo); const ticketTwo = await models.Ticket.create(originalTicketTwo, options);
} catch (error) {
console.error(error); const delivered = await models.State.findOne({where: {code: 'delivered'}, fields: ['id']}, options);
const params = [ticketOne.id, ticketTwo.id];
const state = await models.TicketTracking.setDelivered(ctx, params, options);
expect(state.id).toEqual(delivered.id);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
} }
done();
});
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);
expect(state.id).toEqual(delivered.id);
// restores
await app.models.TicketTracking.destroyById(state.id);
}); });
}); });

View File

@ -10,18 +10,18 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'} http: {source: 'query'}
}, { }, {
arg: 'search', arg: 'search',
type: 'String', type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by client id`, description: `If it's and integer searchs by id, otherwise it searchs by client id`,
http: {source: 'query'} http: {source: 'query'}
} }
], ],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {
@ -30,10 +30,14 @@ module.exports = Self => {
} }
}); });
Self.filter = async(ctx, filter) => { Self.filter = async(ctx, filter, options) => {
let conn = Self.dataSource.connector; 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) { switch (param) {
case 'search': case 'search':
return {or: [ return {or: [
@ -46,10 +50,9 @@ module.exports = Self => {
filter = mergeFilters(ctx.args.filter, {where}); filter = mergeFilters(ctx.args.filter, {where});
let stmts = []; const stmts = [];
let stmt;
stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT t.id AS ticketFk, c.id AS clientFk, c.name AS clientName, tw.weekDay, `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 wh.name AS warehouseName, u.id AS workerFk, u.name AS userName, u.nickName, tw.agencyModeFk
FROM ticketWeekly tw FROM ticketWeekly tw
@ -60,10 +63,10 @@ module.exports = Self => {
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1; const itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql); const result = await conn.executeStmt(sql, myOptions);
return itemsIndex === 0 ? result : result[itemsIndex]; 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()', () => { describe('ticket-weekly filter()', () => {
const authUserId = 9; const authUserId = 9;
it('should all return the tickets matching the filter', async() => { it('should all return the tickets matching the filter', async() => {
const filter = {order: 't.id ASC'}; const tx = await models.TicketWeekly.beginTransaction({});
const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}};
const result = await app.models.TicketWeekly.filter(ctx);
const firstRow = result[0]; try {
const options = {transaction: tx};
expect(firstRow.ticketFk).toEqual(1); const filter = {order: 't.id ASC'};
expect(result.length).toEqual(5);
const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}};
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() => { it('should return the ticket with id one', async() => {
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 2}}; const tx = await models.TicketWeekly.beginTransaction({});
const filter = {};
const result = await app.models.TicketWeekly.filter(ctx, filter);
const firstRow = result[0];
expect(firstRow.ticketFk).toEqual(2); try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 2}};
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() => { it('should return the ticket matching the client name', async() => {
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}}; const tx = await models.TicketWeekly.beginTransaction({});
const filter = {};
const result = await app.models.TicketWeekly.filter(ctx, filter);
const firstRow = result[0];
expect(firstRow.clientName).toEqual('Bruce Wayne'); try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}};
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() => { it('should return the ticket matching the client id', async() => {
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}}; const tx = await models.TicketWeekly.beginTransaction({});
const filter = {};
const result = await app.models.TicketWeekly.filter(ctx, filter);
const firstRow = result[0];
expect(firstRow.clientFk).toEqual(1101); try {
expect(firstRow.clientName).toEqual('Bruce Wayne'); const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}};
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) if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk)
throw new UserError(`You don't have privileges to change the zone`); 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({ const originalTicket = await models.Ticket.findOne({
where: {id: args.id}, where: {id: args.id},

View File

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

View File

@ -11,7 +11,7 @@ module.exports = Self => {
http: {source: 'path'} http: {source: 'path'}
}], }],
returns: { returns: {
type: 'Number', type: 'number',
root: true root: true
}, },
http: { http: {
@ -20,12 +20,32 @@ module.exports = Self => {
} }
}); });
Self.recalculateComponents = async(ctx, id) => { Self.recalculateComponents = async(ctx, id, options) => {
const isEditable = await Self.isEditable(ctx, id); const myOptions = {};
let tx;
if (!isEditable) if (typeof options == 'object')
throw new UserError(`The current ticket can't be modified`); Object.assign(myOptions, options);
return Self.rawSql('CALL vn.ticket_recalcComponents(?, NULL)', [id]); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const isEditable = await Self.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The current ticket can't be modified`);
const recalculation = await Self.rawSql('CALL vn.ticket_recalcComponents(?, NULL)', [id], myOptions);
if (tx) await tx.commit();
return recalculation;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -6,7 +6,7 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
required: true, required: true,
description: 'The ticket id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}

View File

@ -5,23 +5,23 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
required: true, required: true,
description: 'The ticket id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'destination', arg: 'destination',
type: 'String', type: 'string',
required: true, required: true,
}, },
{ {
arg: 'message', arg: 'message',
type: 'String', type: 'string',
required: true, required: true,
}], }],
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { http: {
@ -30,28 +30,46 @@ module.exports = Self => {
} }
}); });
Self.sendSms = async(ctx, id, destination, message) => { Self.sendSms = async(ctx, id, destination, message, options) => {
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
let sms = await Self.app.models.Sms.send(ctx, id, destination, message); try {
let logRecord = { const sms = await Self.app.models.Sms.send(ctx, id, destination, message);
originFk: id, const logRecord = {
userFk: userId, originFk: id,
action: 'insert', userFk: userId,
changedModel: 'sms', action: 'insert',
newInstance: { changedModel: 'sms',
destinationFk: id, newInstance: {
destination: destination, destinationFk: id,
message: message, destination: destination,
statusCode: sms.statusCode, message: message,
status: sms.status statusCode: sms.statusCode,
} status: sms.status
}; }
};
const ticketLog = await Self.app.models.TicketLog.create(logRecord); const ticketLog = await Self.app.models.TicketLog.create(logRecord, myOptions);
sms.logId = ticketLog.id; sms.logId = ticketLog.id;
return sms; if (tx) await tx.commit();
return sms;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -6,7 +6,7 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
required: true, required: true,
description: 'The ticket id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}
@ -21,113 +21,133 @@ module.exports = Self => {
} }
}); });
Self.setDeleted = async(ctx, id) => { Self.setDeleted = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const isEditable = await Self.isEditable(ctx, id);
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const myOptions = {};
let tx;
if (!isEditable) if (typeof options == 'object')
throw new UserError(`The sales of this ticket can't be modified`); Object.assign(myOptions, options);
// Check if has sales with shelving if (!myOptions.transaction) {
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant'); tx = await Self.beginTransaction({});
const sales = await models.Sale.find({ myOptions.transaction = tx;
include: {relation: 'itemShelvingSale'},
where: {ticketFk: id}
});
const hasItemShelvingSales = sales.some(sale => {
return sale.itemShelvingSale();
});
if (hasItemShelvingSales && !isSalesAssistant)
throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
// Check for existing claim
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}});
if (claimOfATicket)
throw new UserError('You must delete the claim id %d first', 'DELETE_CLAIM_FIRST', claimOfATicket.id);
// Check for existing purchase requests
const hasPurchaseRequests = await models.TicketRequest.count({
ticketFk: id,
isOk: true
});
if (hasPurchaseRequests)
throw new UserError('You must delete all the buy requests first');
// removes item shelvings
if (hasItemShelvingSales && isSalesAssistant) {
const promises = [];
for (let sale of sales) {
if (sale.itemShelvingSale()) {
const itemShelvingSale = sale.itemShelvingSale();
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id);
promises.push(destroyedShelving);
}
}
await Promise.all(promises);
} }
// Remove ticket greuges try {
const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}}); const userId = ctx.req.accessToken.userId;
const ownGreuges = ticketGreuges.every(greuge => { const isEditable = await Self.isEditable(ctx, id, myOptions);
return greuge.ticketFk == id;
});
if (ownGreuges) {
for (const greuge of ticketGreuges) {
const instance = await models.Greuge.findById(greuge.id);
await instance.destroy(); if (!isEditable)
} throw new UserError(`The sales of this ticket can't be modified`);
}
const ticket = await models.Ticket.findById(id, { // Check if has sales with shelving
include: [{ const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
relation: 'client', const sales = await models.Sale.find({
scope: { include: {relation: 'itemShelvingSale'},
fields: ['id', 'salesPersonFk'], where: {ticketFk: id}
include: { }, myOptions);
relation: 'salesPersonUser', const hasItemShelvingSales = sales.some(sale => {
scope: { return sale.itemShelvingSale();
fields: ['id', 'name'] });
}
if (hasItemShelvingSales && !isSalesAssistant)
throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
// Check for existing claim
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}}, myOptions);
if (claimOfATicket)
throw new UserError('You must delete the claim id %d first', 'DELETE_CLAIM_FIRST', claimOfATicket.id);
// Check for existing purchase requests
const hasPurchaseRequests = await models.TicketRequest.count({
ticketFk: id,
isOk: true
}, myOptions);
if (hasPurchaseRequests)
throw new UserError('You must delete all the buy requests first');
// removes item shelvings
if (hasItemShelvingSales && isSalesAssistant) {
const promises = [];
for (let sale of sales) {
if (sale.itemShelvingSale()) {
const itemShelvingSale = sale.itemShelvingSale();
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions);
promises.push(destroyedShelving);
} }
} }
}, { await Promise.all(promises);
relation: 'ship' }
}, {
relation: 'stowaway'
}]
});
// Change state to "fixing" if contains an stowaway and remove the link between them // Remove ticket greuges
let otherTicketId; const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}}, myOptions);
if (ticket.stowaway()) const ownGreuges = ticketGreuges.every(greuge => {
otherTicketId = ticket.stowaway().shipFk; return greuge.ticketFk == id;
else if (ticket.ship())
otherTicketId = ticket.ship().id;
if (otherTicketId) {
await models.Ticket.deleteStowaway(ctx, otherTicketId);
await models.TicketTracking.changeState(ctx, {
ticketFk: otherTicketId,
code: 'FIXING'
}); });
} if (ownGreuges) {
for (const greuge of ticketGreuges) {
const instance = await models.Greuge.findById(greuge.id, null, myOptions);
// Send notification to salesPerson await instance.destroy(myOptions);
const salesPersonUser = ticket.client().salesPersonUser(); }
if (salesPersonUser) { }
const origin = ctx.req.headers.origin;
const message = $t(`I have deleted the ticket id`, {
id: id,
url: `${origin}/#!/ticket/${id}/summary`
});
await models.Chat.send(ctx, `@${salesPersonUser.name}`, message);
}
return ticket.updateAttribute('isDeleted', true); const ticket = await models.Ticket.findById(id, {
include: [{
relation: 'client',
scope: {
fields: ['id', 'salesPersonFk'],
include: {
relation: 'salesPersonUser',
scope: {
fields: ['id', 'name']
}
}
}
}, {
relation: 'ship'
}, {
relation: 'stowaway'
}]
}, myOptions);
// Change state to "fixing" if contains an stowaway and remove the link between them
let otherTicketId;
if (ticket.stowaway())
otherTicketId = ticket.stowaway().shipFk;
else if (ticket.ship())
otherTicketId = ticket.ship().id;
if (otherTicketId) {
await models.Ticket.deleteStowaway(ctx, otherTicketId, myOptions);
await models.TicketTracking.changeState(ctx, {
ticketFk: otherTicketId,
code: 'FIXING'
}, myOptions);
}
// Send notification to salesPerson
const salesPersonUser = ticket.client().salesPersonUser();
if (salesPersonUser) {
const origin = ctx.req.headers.origin;
const message = $t(`I have deleted the ticket id`, {
id: id,
url: `${origin}/#!/ticket/${id}/summary`
});
await models.Chat.send(ctx, `@${salesPersonUser.name}`, message);
}
const updatedTicket = await ticket.updateAttribute('isDeleted', true, myOptions);
if (tx) await tx.commit();
return updatedTicket;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -8,12 +8,10 @@ describe('ticket canBeInvoiced()', () => {
accessToken: {userId: userId} accessToken: {userId: userId}
}; };
beforeAll(async done => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
done();
}); });
it('should return falsy for an already invoiced ticket', async() => { it('should return falsy for an already invoiced ticket', async() => {

View File

@ -11,12 +11,10 @@ describe('ticket makeInvoice()', () => {
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
beforeAll(async done => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
done();
}); });
it('should throw an error when invoicing tickets from multiple clients', async() => { it('should throw an error when invoicing tickets from multiple clients', async() => {

View File

@ -1,24 +1,43 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('ticket recalculateComponents()', () => { describe('ticket recalculateComponents()', () => {
const ticketId = 11; const ticketId = 11;
it('should update the ticket components', async() => { it('should update the ticket components', async() => {
const ctx = {req: {accessToken: {userId: 9}}}; const tx = await models.Ticket.beginTransaction({});
const response = await app.models.Ticket.recalculateComponents(ctx, ticketId);
expect(response.affectedRows).toBeDefined(); try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
const response = await models.Ticket.recalculateComponents(ctx, ticketId, 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() => { it('should throw an error if the ticket is not editable', async() => {
const ctx = {req: {accessToken: {userId: 9}}}; const tx = await models.Ticket.beginTransaction({});
const immutableTicketId = 1;
await app.models.Ticket.recalculateComponents(ctx, immutableTicketId)
.catch(response => {
expect(response).toEqual(new Error(`The current ticket can't be modified`));
error = response;
});
expect(error).toBeDefined(); let error;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}};
const immutableTicketId = 1;
await models.Ticket.recalculateComponents(ctx, immutableTicketId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error(`The current ticket can't be modified`));
}); });
}); });

View File

@ -1,29 +1,30 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
const soap = require('soap'); const soap = require('soap');
describe('ticket sendSms()', () => { describe('ticket sendSms()', () => {
let logId;
afterAll(async done => {
await app.models.TicketLog.destroyById(logId);
done();
});
it('should send a message and log it', async() => { it('should send a message and log it', async() => {
spyOn(soap, 'createClientAsync').and.returnValue('a so fake client'); const tx = await models.Ticket.beginTransaction({});
let ctx = {req: {accessToken: {userId: 9}}};
let id = 11;
let destination = 222222222;
let message = 'this is the message created in a test';
let sms = await app.models.Ticket.sendSms(ctx, id, destination, message); try {
const options = {transaction: tx};
logId = sms.logId; spyOn(soap, 'createClientAsync').and.returnValue('a so fake client');
const ctx = {req: {accessToken: {userId: 9}}};
const id = 11;
const destination = 222222222;
const message = 'this is the message created in a test';
let createdLog = await app.models.TicketLog.findById(logId); const sms = await models.Ticket.sendSms(ctx, id, destination, message, options);
let json = JSON.parse(JSON.stringify(createdLog.newInstance));
expect(json.message).toEqual(message); const createdLog = await models.TicketLog.findById(sms.logId, null, options);
const json = JSON.parse(JSON.stringify(createdLog.newInstance));
expect(json.message).toEqual(message);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -1,6 +1,5 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
const models = app.models;
describe('ticket setDeleted()', () => { describe('ticket setDeleted()', () => {
const userId = 1106; const userId = 1106;
@ -10,95 +9,110 @@ describe('ticket setDeleted()', () => {
}; };
it('should throw an error if the given ticket has a claim', async() => { it('should throw an error if the given ticket has a claim', async() => {
const ctx = {req: activeCtx}; const tx = await models.Ticket.beginTransaction({});
const ticketId = 16;
let error;
let error;
try { try {
await app.models.Ticket.setDeleted(ctx, ticketId); const options = {transaction: tx};
const ctx = {req: activeCtx};
const ticketId = 16;
await models.Ticket.setDeleted(ctx, ticketId, options);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback();
error = e; error = e;
} }
expect(error.translateArgs[0]).toEqual(2);
expect(error.message).toEqual('You must delete the claim id %d first'); expect(error.message).toEqual('You must delete the claim id %d first');
}); });
it('should delete the ticket, remove the stowaway link and change the stowaway ticket state to "FIXING" and get rid of the itemshelving', async() => { it('should delete the ticket, remove the stowaway link and change the stowaway ticket state to "FIXING" and get rid of the itemshelving', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ const tx = await models.Ticket.beginTransaction({});
active: activeCtx
});
const ctx = {
req: {
accessToken: {userId: employeeUser},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
let sampleTicket = await models.Ticket.findById(12); try {
let sampleStowaway = await models.Ticket.findById(13); const options = {transaction: tx};
sampleTicket.id = undefined; spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
let shipTicket = await models.Ticket.create(sampleTicket); active: activeCtx
});
const ctx = {
req: {
accessToken: {userId: employeeUser},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
sampleStowaway.id = undefined; const sampleTicket = await models.Ticket.findById(12);
let stowawayTicket = await models.Ticket.create(sampleStowaway); const sampleStowaway = await models.Ticket.findById(13);
await models.Stowaway.rawSql(` sampleTicket.id = undefined;
INSERT INTO vn.stowaway(id, shipFk) const shipTicket = await models.Ticket.create(sampleTicket, options);
VALUES (?, ?)`, [stowawayTicket.id, shipTicket.id]);
const boardingState = await models.State.findOne({ sampleStowaway.id = undefined;
where: { const stowawayTicket = await models.Ticket.create(sampleStowaway, options);
code: 'BOARDING'
}
});
await models.TicketTracking.create({
ticketFk: stowawayTicket.id,
stateFk: boardingState.id,
workerFk: ctx.req.accessToken.userId
});
const okState = await models.State.findOne({ await models.Stowaway.rawSql(`
where: { INSERT INTO vn.stowaway(id, shipFk)
code: 'OK' VALUES (?, ?)`, [stowawayTicket.id, shipTicket.id], options);
}
});
await models.TicketTracking.create({
ticketFk: shipTicket.id,
stateFk: okState.id,
workerFk: ctx.req.accessToken.userId
});
let stowawayTicketState = await models.TicketState.findOne({ const boardingState = await models.State.findOne({
where: { where: {
ticketFk: stowawayTicket.id code: 'BOARDING'
} }
}); }, options);
let stowaway = await models.Stowaway.findById(shipTicket.id); await models.TicketTracking.create({
ticketFk: stowawayTicket.id,
stateFk: boardingState.id,
workerFk: ctx.req.accessToken.userId
}, options);
expect(stowaway).toBeDefined(); const okState = await models.State.findOne({
expect(stowawayTicketState.code).toEqual('BOARDING'); where: {
code: 'OK'
}
}, options);
await models.Ticket.setDeleted(ctx, shipTicket.id); await models.TicketTracking.create({
ticketFk: shipTicket.id,
stateFk: okState.id,
workerFk: ctx.req.accessToken.userId
}, options);
stowawayTicketState = await models.TicketState.findOne({ let stowawayTicketState = await models.TicketState.findOne({
where: { where: {
ticketFk: stowawayTicket.id ticketFk: stowawayTicket.id
} }
}); }, options);
stowaway = await models.Stowaway.findById(shipTicket.id); let stowaway = await models.Stowaway.findById(shipTicket.id, null, options);
expect(stowaway).toBeNull(); expect(stowaway).toBeDefined();
expect(stowawayTicketState.code).toEqual('FIXING'); expect(stowawayTicketState.code).toEqual('BOARDING');
// restores await models.Ticket.setDeleted(ctx, shipTicket.id, options);
await models.Ticket.destroyById(shipTicket.id);
await models.Ticket.destroyById(stowawayTicket.id); stowawayTicketState = await models.TicketState.findOne({
where: {
ticketFk: stowawayTicket.id
}
}, options);
stowaway = await models.Stowaway.findById(shipTicket.id, null, options);
expect(stowaway).toBeNull();
expect(stowawayTicketState.code).toEqual('FIXING');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -1,28 +1,72 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('ticket summary()', () => { describe('ticket summary()', () => {
it('should return a summary object containing data from 1 ticket', async() => { it('should return a summary object containing data from 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); const tx = await models.Ticket.beginTransaction({});
expect(result.id).toEqual(1); try {
expect(result.nickname).toEqual('Bat cave'); const options = {transaction: tx};
const result = await models.Ticket.summary(1, options);
expect(result.id).toEqual(1);
expect(result.nickname).toEqual('Bat cave');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return a summary object containing sales from 1 ticket', async() => { it('should return a summary object containing sales from 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); const tx = await models.Ticket.beginTransaction({});
expect(result.sales.length).toEqual(4); try {
const options = {transaction: tx};
const result = await models.Ticket.summary(1, options);
expect(result.sales.length).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return a summary object containing totalWithoutVat for 1 ticket', async() => { it('should return a summary object containing totalWithoutVat for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); const tx = await models.Ticket.beginTransaction({});
expect(result.totalWithoutVat).toEqual(jasmine.any(Number)); try {
const options = {transaction: tx};
const result = await models.Ticket.summary(1, options);
expect(result.totalWithoutVat).toEqual(jasmine.any(Number));
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return a summary object containing total for 1 ticket', async() => { it('should return a summary object containing total for 1 ticket', async() => {
let result = await app.models.Ticket.summary(1); const tx = await models.Ticket.beginTransaction({});
expect(result.totalWithVat).toEqual(jasmine.any(Number)); try {
const options = {transaction: tx};
const result = await models.Ticket.summary(1, options);
expect(result.totalWithVat).toEqual(jasmine.any(Number));
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'); const LoopBackContext = require('loopback-context');
describe('sale transferSales()', () => { describe('sale transferSales()', () => {
@ -8,160 +8,167 @@ describe('sale transferSales()', () => {
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
let createdTicketsIds = [];
beforeAll(() => { beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
}); });
afterEach(async done => { it('should throw an error as the ticket is not editable', async() => {
if (createdTicketsIds.length) { const tx = await models.Ticket.beginTransaction({});
try {
createdTicketsIds.forEach(async createdTicketId => { let error;
await app.models.Ticket.destroyById(createdTicketId); try {
}); const options = {transaction: tx};
} catch (error) {
console.error(error); const currentTicketId = 1;
} const receiverTicketId = undefined;
const sales = [];
await models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
} }
done(); expect(error.message).toEqual(`The sales of this ticket can't be modified`);
});
it('should throw an error as the ticket is not editable', async() => {
let error;
const currentTicketId = 1;
const receiverTicketId = undefined;
const sales = [];
await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales)
.catch(response => {
expect(response.message).toEqual(`The sales of this ticket can't be modified`);
error = response;
});
expect(error).toBeDefined();
}); });
it('should throw an error if the receiving ticket is not editable', async() => { it('should throw an error if the receiving ticket is not editable', async() => {
const tx = await models.Ticket.beginTransaction({});
let error; let error;
try {
const options = {transaction: tx};
const currentTicketId = 16; const currentTicketId = 16;
const receiverTicketId = 1; const receiverTicketId = 1;
const sales = []; const sales = [];
await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales) await models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales, options);
.catch(response => {
expect(response.message).toEqual(`The sales of the receiver ticket can't be modified`);
error = response;
});
expect(error).toBeDefined(); await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`The sales of the receiver ticket can't be modified`);
}); });
it('should transfer the sales from one ticket to a new one then send them back and delete the created ticket', async() => { it('should transfer the sales from one ticket to a new one then send them back and delete the created ticket', async() => {
const formerTicketId = 11; const tx = await models.Ticket.beginTransaction({});
let createdTicketId = undefined;
let formerTicketSales = await app.models.Ticket.getSales(formerTicketId); try {
const options = {transaction: tx};
expect(formerTicketSales.length).toEqual(2); const formerTicketId = 11;
let createdTicketId = undefined;
let createdTicket = await app.models.Ticket.transferSales( let formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
ctx, formerTicketId, createdTicketId, formerTicketSales);
createdTicketId = createdTicket.id; expect(formerTicketSales.length).toEqual(2);
createdTicketsIds.push(createdTicketId);
formerTicketSales = await app.models.Ticket.getSales(formerTicketId); let createdTicket = await models.Ticket.transferSales(
createdTicketSales = await app.models.Ticket.getSales(createdTicketId); ctx, formerTicketId, createdTicketId, formerTicketSales, options);
expect(formerTicketSales.length).toEqual(0); createdTicketId = createdTicket.id;
expect(createdTicketSales.length).toEqual(2);
await app.models.Ticket.transferSales( formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
ctx, createdTicketId, formerTicketId, createdTicketSales); createdTicketSales = await models.Ticket.getSales(createdTicketId, options);
formerTicketSales = await app.models.Ticket.getSales(formerTicketId); expect(formerTicketSales.length).toEqual(0);
createdTicketSales = await app.models.Ticket.getSales(createdTicketId); expect(createdTicketSales.length).toEqual(2);
createdTicket = await app.models.Ticket.findById(createdTicketId); await models.Ticket.transferSales(
ctx, createdTicketId, formerTicketId, createdTicketSales, options);
expect(createdTicket.isDeleted).toBeTruthy(); formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
expect(formerTicketSales.length).toEqual(2); createdTicketSales = await models.Ticket.getSales(createdTicketId, options);
expect(createdTicketSales.length).toEqual(0);
createdTicket = await models.Ticket.findById(createdTicketId, null, options);
expect(createdTicket.isDeleted).toBeTruthy();
expect(formerTicketSales.length).toEqual(2);
expect(createdTicketSales.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
describe('sale transferPartialSales()', () => { describe('sale transferPartialSales()', () => {
it('should throw an error in the quantity to transfer exceeds the amount from the original sale', async() => { it('should throw an error in the quantity to transfer exceeds the amount from the original sale', async() => {
const tx = await models.Ticket.beginTransaction({});
let error; let error;
let currentTicket = await app.models.Ticket.findById(11); try {
let currentTicketSales = await app.models.Ticket.getSales(currentTicket.id); const options = {transaction: tx};
const currentTicketId = currentTicket.id; const currentTicket = await models.Ticket.findById(11, null, options);
const receiverTicketId = undefined; const currentTicketSales = await models.Ticket.getSales(currentTicket.id, options);
currentTicketSales[0].quantity = 99; const currentTicketId = currentTicket.id;
const receiverTicketId = undefined;
await app.models.Ticket.transferSales( currentTicketSales[0].quantity = 99;
ctx, currentTicketId, receiverTicketId, currentTicketSales)
.catch(response => {
expect(response.message).toEqual(`Invalid quantity`);
error = response;
});
expect(error).toBeDefined(); await models.Ticket.transferSales(
ctx, currentTicketId, receiverTicketId, currentTicketSales, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`Invalid quantity`);
}); });
it('should transfer two sales to a new ticket but one shall be partial', async() => { it('should transfer two sales to a new ticket but one shall be partial', async() => {
const formerTicketId = 11; const tx = await models.Ticket.beginTransaction({});
let createdTicketId = undefined;
let formerTicketSales = await app.models.Ticket.getSales(formerTicketId); try {
const options = {transaction: tx};
const partialSaleId = formerTicketSales[0].id; const formerTicketId = 11;
const completeSaleId = formerTicketSales[1].id; let createdTicketId = undefined;
let partialSaleTotalQuantity = formerTicketSales[0].quantity;
expect(partialSaleTotalQuantity).toEqual(15); let formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
formerTicketSales[0].quantity = 1; const completeSaleId = formerTicketSales[1].id;
let partialSaleTotalQuantity = formerTicketSales[0].quantity;
let createdTicket = await app.models.Ticket.transferSales( expect(partialSaleTotalQuantity).toEqual(15);
ctx, formerTicketId, createdTicketId, formerTicketSales);
createdTicketId = createdTicket.id; formerTicketSales[0].quantity = 1;
createdTicketsIds.push(createdTicket.id);
formerTicketSales = await app.models.Ticket.getSales(formerTicketId); let createdTicket = await models.Ticket.transferSales(
createdTicketSales = await app.models.Ticket.getSales(createdTicketId); ctx, formerTicketId, createdTicketId, formerTicketSales, options);
const [createdPartialSale] = createdTicketSales.filter(sale => { createdTicketId = createdTicket.id;
return sale.id != completeSaleId;
});
expect(formerTicketSales.length).toEqual(1); formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
expect(formerTicketSales[0].quantity).toEqual(partialSaleTotalQuantity - 1); createdTicketSales = await models.Ticket.getSales(createdTicketId, options);
expect(createdTicketSales.length).toEqual(2);
expect(createdPartialSale.quantity).toEqual(1);
let saleToRestore = await app.models.Sale.findById(partialSaleId); const [createdPartialSale] = createdTicketSales.filter(sale => {
await saleToRestore.updateAttribute('quantity', partialSaleTotalQuantity); return sale.id != completeSaleId;
});
let saleToReturnToTicket = await app.models.Sale.findById(completeSaleId); expect(formerTicketSales.length).toEqual(1);
await saleToReturnToTicket.updateAttribute('ticketFk', formerTicketId); expect(formerTicketSales[0].quantity).toEqual(partialSaleTotalQuantity - 1);
expect(createdTicketSales.length).toEqual(2);
expect(createdPartialSale.quantity).toEqual(1);
formerTicketSales = await app.models.Ticket.getSales(formerTicketId); await tx.rollback();
} catch (e) {
const [returningPartialSale] = formerTicketSales.filter(sale => { await tx.rollback();
return sale.id == partialSaleId; throw e;
}); }
expect(returningPartialSale.quantity).toEqual(partialSaleTotalQuantity);
expect(formerTicketSales.length).toEqual(2);
}); });
}); });
}); });

View File

@ -1,156 +1,171 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('sale updateDiscount()', () => { describe('sale updateDiscount()', () => {
const originalSaleId = 8; const originalSaleId = 8;
let componentId;
let originalSale;
let salesPersonMana;
beforeAll(async done => {
try {
originalSale = await app.models.Sale.findById(originalSaleId);
let manaDiscount = await app.models.Component.findOne({where: {code: 'buyerDiscount'}});
componentId = manaDiscount.id;
let ticket = await app.models.Ticket.findById(originalSale.ticketFk);
let client = await app.models.Client.findById(ticket.clientFk);
salesPersonMana = await app.models.WorkerMana.findById(client.salesPersonFk);
} catch (error) {
console.error(error);
}
done();
});
afterAll(async done => {
try {
await originalSale.save();
await app.models.SaleComponent.updateAll({componentFk: componentId, saleFk: originalSaleId}, {value: 0});
await salesPersonMana.save();
} catch (error) {
console.error(error);
}
done();
});
it('should throw an error if no sales were selected', async() => { it('should throw an error if no sales were selected', async() => {
const ctx = { const tx = await models.Ticket.beginTransaction({});
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let error;
const ticketId = 11;
const sales = [];
const newDiscount = 10;
let error;
try { try {
await app.models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount); const options = {transaction: tx};
} catch (err) {
error = err; const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const ticketId = 11;
const sales = [];
const newDiscount = 10;
await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
} }
expect(error.message).toEqual('Please select at least one sale'); expect(error.message).toEqual('Please select at least one sale');
}); });
it('should throw an error if no sales belong to different tickets', async() => { it('should throw an error if no sales belong to different tickets', async() => {
const ctx = { const tx = await models.Ticket.beginTransaction({});
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let error;
const ticketId = 11;
const sales = [1, 14];
const newDiscount = 10;
let error;
try { try {
await app.models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount); const options = {transaction: tx};
} catch (err) {
error = err; const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const ticketId = 11;
const sales = [1, 14];
const newDiscount = 10;
await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
} }
expect(error.message).toEqual('All sales must belong to the same ticket'); expect(error.message).toEqual('All sales must belong to the same ticket');
}); });
it('should throw an error if the ticket is invoiced already', async() => { it('should throw an error if the ticket is invoiced already', async() => {
const ctx = { const tx = await models.Ticket.beginTransaction({});
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
let error;
const ticketId = 1;
const sales = [1];
const newDiscount = 100;
let error;
try { try {
await app.models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount); const options = {transaction: tx};
} catch (err) {
error = err; const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const ticketId = 1;
const sales = [1];
const newDiscount = 100;
await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
} }
expect(error.message).toEqual(`The sales of this ticket can't be modified`); expect(error.message).toEqual(`The sales of this ticket can't be modified`);
}); });
it('should update the discount if the salesPerson has mana', async() => { it('should update the discount if the salesPerson has mana', async() => {
const ctx = { const tx = await models.Ticket.beginTransaction({});
req: {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const ticketId = 11;
const sales = [originalSaleId];
const newDiscount = 100;
let manaDiscount = await app.models.Component.findOne({where: {code: 'mana'}});
componentId = manaDiscount.id;
await app.models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount); try {
const options = {transaction: tx};
let updatedSale = await app.models.Sale.findById(originalSaleId); const ctx = {
let createdSaleComponent = await app.models.SaleComponent.findOne({ req: {
where: { accessToken: {userId: 18},
componentFk: componentId, headers: {origin: 'localhost:5000'},
saleFk: originalSaleId __: () => {}
} }
}); };
const ticketId = 11;
const sales = [originalSaleId];
const newDiscount = 100;
const manaDiscount = await models.Component.findOne({where: {code: 'mana'}}, options);
const componentId = manaDiscount.id;
expect(createdSaleComponent.componentFk).toEqual(componentId); await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, options);
expect(updatedSale.discount).toEqual(100);
const updatedSale = await models.Sale.findById(originalSaleId, null, options);
const createdSaleComponent = await models.SaleComponent.findOne({
where: {
componentFk: componentId,
saleFk: originalSaleId
}
}, options);
expect(createdSaleComponent.componentFk).toEqual(componentId);
expect(updatedSale.discount).toEqual(100);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should update the discount and add company discount component if the worker does not have mana', async() => { it('should update the discount and add company discount component if the worker does not have mana', async() => {
const ctx = { const tx = await models.Ticket.beginTransaction({});
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const ticketId = 11;
const sales = [originalSaleId];
const newDiscount = 100;
await app.models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount); try {
const options = {transaction: tx};
let updatedSale = await app.models.Sale.findById(originalSaleId); const ctx = {
let createdSaleComponent = await app.models.SaleComponent.findOne({ req: {
where: { accessToken: {userId: 9},
componentFk: componentId, headers: {origin: 'localhost:5000'},
saleFk: originalSaleId __: () => {}
} }
}); };
const ticketId = 11;
const sales = [originalSaleId];
const newDiscount = 100;
expect(createdSaleComponent.componentFk).toEqual(componentId); await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, options);
expect(updatedSale.discount).toEqual(100);
const updatedSale = await models.Sale.findById(originalSaleId, null, options);
const manaDiscount = await models.Component.findOne({where: {code: 'buyerDiscount'}}, options);
const componentId = manaDiscount.id;
const createdSaleComponent = await models.SaleComponent.findOne({
where: {
componentFk: componentId,
saleFk: originalSaleId
}
}, options);
expect(createdSaleComponent.componentFk).toEqual(componentId);
expect(updatedSale.discount).toEqual(100);
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'); const LoopBackContext = require('loopback-context');
const userId = 9; const userId = 9;
@ -11,34 +11,44 @@ describe('ticket updateEditableTicket()', () => {
const validTicketId = 12; const validTicketId = 12;
const invalidTicketId = 1; const invalidTicketId = 1;
const data = {addressFk: 1}; const data = {addressFk: 1};
const originalData = {addressFk: 123};
afterAll(async done => {
await app.models.Ticket.updateEditableTicket(ctx, validTicketId, originalData);
done();
});
it('should now throw an error if the ticket is not editable', async() => { it('should now throw an error if the ticket is not editable', async() => {
const tx = await models.Ticket.beginTransaction({});
let error; let error;
try {
const options = {transaction: tx};
await app.models.Ticket.updateEditableTicket(ctx, invalidTicketId, data).catch(e => { await models.Ticket.updateEditableTicket(ctx, invalidTicketId, data, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e; error = e;
}).finally(() => { }
expect(error.message).toEqual('This ticket can not be modified');
});
expect(error).toBeDefined(); expect(error.message).toEqual('This ticket can not be modified');
}); });
it('should edit the ticket address', async() => { it('should edit the ticket address', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ const tx = await models.Ticket.beginTransaction({});
active: activeCtx
});
await app.models.Ticket.updateEditableTicket(ctx, validTicketId, data);
let updatedTicket = await app.models.Ticket.findById(validTicketId); try {
const options = {transaction: tx};
expect(updatedTicket.addressFk).toEqual(1); spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
await models.Ticket.updateEditableTicket(ctx, validTicketId, data, options);
const updatedTicket = await models.Ticket.findById(validTicketId, null, options);
expect(updatedTicket.addressFk).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -1,19 +1,26 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('Ticket uploadFile()', () => { describe('Ticket uploadFile()', () => {
it(`should return an error for a user without enough privileges`, async() => { it(`should return an error for a user without enough privileges`, async() => {
let ticketId = 15; const tx = await models.Ticket.beginTransaction({});
let currentUserId = 1101;
let ticketTypeId = 14;
let ctx = {req: {accessToken: {userId: currentUserId}}, args: {dmsTypeId: ticketTypeId}};
let error; let error;
await app.models.Ticket.uploadFile(ctx, ticketId).catch(e => { try {
error = e; const options = {transaction: tx};
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined(); const ticketId = 15;
const currentUserId = 1101;
const ticketTypeId = 14;
const ctx = {req: {accessToken: {userId: currentUserId}}, args: {dmsTypeId: ticketTypeId}};
await models.Ticket.uploadFile(ctx, ticketId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`You don't have enough privileges`);
}); });
}); });

View File

@ -19,10 +19,17 @@ module.exports = Self => {
} }
}); });
Self.summary = async ticketFk => { Self.summary = async(ticketFk, options) => {
let models = Self.app.models; const models = Self.app.models;
let summaryObj = await getTicketData(Self, ticketFk); const myOptions = {};
summaryObj.sales = await models.Ticket.getSales(ticketFk);
if (typeof options == 'object')
Object.assign(myOptions, options);
const summaryObj = await getTicketData(Self, ticketFk, myOptions);
summaryObj.sales = await models.Ticket.getSales(ticketFk, myOptions);
summaryObj.packagings = await models.TicketPackaging.find({ summaryObj.packagings = await models.TicketPackaging.find({
where: {ticketFk: ticketFk}, where: {ticketFk: ticketFk},
include: [{relation: 'packaging', include: [{relation: 'packaging',
@ -33,24 +40,25 @@ module.exports = Self => {
} }
} }
}] }]
}); }, myOptions);
summaryObj.requests = await getRequests(Self, ticketFk);
summaryObj.requests = await getRequests(Self, ticketFk, myOptions);
summaryObj.services = await models.TicketService.find({ summaryObj.services = await models.TicketService.find({
where: {ticketFk: ticketFk}, where: {ticketFk: ticketFk},
include: [{relation: 'taxClass'}] include: [{relation: 'taxClass'}]
}); }, myOptions);
return summaryObj; return summaryObj;
}; };
async function getTicketData(Self, ticketFk) { async function getTicketData(Self, ticketFk, options) {
let filter = { const filter = {
include: [ include: [
{relation: 'warehouse', scope: {fields: ['name']}}, {relation: 'warehouse', scope: {fields: ['name']}},
{relation: 'agencyMode', scope: {fields: ['name']}}, {relation: 'agencyMode', scope: {fields: ['name']}},
{relation: 'zone', scope: {fields: ['name']}}, {relation: 'zone', scope: {fields: ['name']}},
{ {relation: 'client',
relation: 'client',
scope: { scope: {
fields: ['salesPersonFk', 'name', 'phone', 'mobile'], fields: ['salesPersonFk', 'name', 'phone', 'mobile'],
include: { include: {
@ -99,11 +107,11 @@ module.exports = Self => {
where: {id: ticketFk} where: {id: ticketFk}
}; };
return await Self.findOne(filter); return Self.findOne(filter, options);
} }
async function getRequests(Self, ticketFk) { async function getRequests(Self, ticketFk, options) {
let filter = { const filter = {
where: { where: {
ticketFk: ticketFk ticketFk: ticketFk
}, },
@ -127,6 +135,6 @@ module.exports = Self => {
} }
] ]
}; };
return await Self.app.models.TicketRequest.find(filter); return Self.app.models.TicketRequest.find(filter, options);
} }
}; };

View File

@ -5,25 +5,25 @@ module.exports = Self => {
description: 'Transfer sales to a new or a given ticket', description: 'Transfer sales to a new or a given ticket',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
required: true, required: true,
description: 'Origin ticket id', description: 'Origin ticket id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'ticketId', arg: 'ticketId',
type: 'Number', type: 'number',
description: 'Destination ticket id', description: 'Destination ticket id',
required: false required: false
}, },
{ {
arg: 'sales', arg: 'sales',
type: ['Object'], type: ['object'],
description: 'The sales to transfer', description: 'The sales to transfer',
required: true required: true
}], }],
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { http: {
@ -32,28 +32,35 @@ module.exports = Self => {
} }
}); });
Self.transferSales = async(ctx, id, ticketId, sales) => { Self.transferSales = async(ctx, id, ticketId, sales, options) => {
let userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const myOptions = {};
let tx;
const isEditable = await models.Ticket.isEditable(ctx, id); if (typeof options == 'object')
if (!isEditable) Object.assign(myOptions, options);
throw new UserError(`The sales of this ticket can't be modified`);
if (ticketId) { if (!myOptions.transaction) {
const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId); tx = await Self.beginTransaction({});
if (!isReceiverEditable) myOptions.transaction = tx;
throw new UserError(`The sales of the receiver ticket can't be modified`);
} }
let tx = await Self.beginTransaction({});
try { try {
const options = {transaction: tx}; const isEditable = await models.Ticket.isEditable(ctx, id, myOptions);
const originalTicket = await models.Ticket.findById(id, null, options); if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
if (ticketId) {
const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
if (!isReceiverEditable)
throw new UserError(`The sales of the receiver ticket can't be modified`);
}
const originalTicket = await models.Ticket.findById(id, null, myOptions);
const originalSales = await models.Sale.find({ const originalSales = await models.Sale.find({
where: {ticketFk: id} where: {ticketFk: id}
}, options); }, myOptions);
if (!ticketId) { if (!ticketId) {
const ticket = await models.Ticket.findById(id); const ticket = await models.Ticket.findById(id);
@ -61,7 +68,7 @@ module.exports = Self => {
if (!canCreateTicket) if (!canCreateTicket)
throw new UserError(`You can't create a ticket for a inactive client`); throw new UserError(`You can't create a ticket for a inactive client`);
ticketId = await cloneTicket(originalTicket, options); ticketId = await cloneTicket(originalTicket, myOptions);
} }
const map = new Map(); const map = new Map();
@ -80,10 +87,10 @@ module.exports = Self => {
if (sale.quantity == originalSale.quantity) { if (sale.quantity == originalSale.quantity) {
await models.Sale.updateAll({ await models.Sale.updateAll({
id: sale.id id: sale.id
}, {ticketFk: ticketId}, options); }, {ticketFk: ticketId}, myOptions);
} else if (sale.quantity != originalSale.quantity) { } else if (sale.quantity != originalSale.quantity) {
await transferPartialSale( await transferPartialSale(
ticketId, originalSale, sale, options); ticketId, originalSale, sale, myOptions);
} }
// Log to original ticket // Log to original ticket
@ -105,7 +112,7 @@ module.exports = Self => {
concept: sale.concept, concept: sale.concept,
ticket: ticketId ticket: ticketId
} }
}, options); }, myOptions);
// Log to destination ticket // Log to destination ticket
await models.TicketLog.create({ await models.TicketLog.create({
@ -126,22 +133,22 @@ module.exports = Self => {
concept: sale.concept, concept: sale.concept,
ticket: ticketId ticket: ticketId
} }
}, options); }, myOptions);
} }
const isTicketEmpty = await models.Ticket.isEmpty(id, options); const isTicketEmpty = await models.Ticket.isEmpty(id, myOptions);
if (isTicketEmpty) { if (isTicketEmpty) {
await originalTicket.updateAttributes({ await originalTicket.updateAttributes({
isDeleted: true isDeleted: true
}, options); }, myOptions);
} }
await tx.commit(); if (tx) await tx.commit();
return {id: ticketId}; return {id: ticketId};
} catch (error) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw error; throw e;
} }
}; };
@ -163,7 +170,7 @@ module.exports = Self => {
// Update original sale // Update original sale
const rest = originalSale.quantity - sale.quantity; const rest = originalSale.quantity - sale.quantity;
const originalInstance = await models.Sale.findById(sale.id, options); const originalInstance = await models.Sale.findById(sale.id, null, options);
await originalInstance.updateAttribute('quantity', rest, options); await originalInstance.updateAttribute('quantity', rest, options);
// Clone sale with new quantity // Clone sale with new quantity

View File

@ -35,14 +35,21 @@ module.exports = Self => {
} }
}); });
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => { Self.updateDiscount = async(ctx, id, salesIds, newDiscount, options) => {
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const models = Self.app.models; 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 { try {
const options = {transaction: tx};
const filter = { const filter = {
where: { where: {
id: {inq: salesIds} id: {inq: salesIds}
@ -61,7 +68,7 @@ module.exports = Self => {
} }
}; };
const sales = await models.Sale.find(filter, options); const sales = await models.Sale.find(filter, myOptions);
if (sales.length === 0) if (sales.length === 0)
throw new UserError('Please select at least one sale'); throw new UserError('Please select at least one sale');
@ -71,15 +78,15 @@ module.exports = Self => {
throw new UserError('All sales must belong to the same ticket'); throw new UserError('All sales must belong to the same ticket');
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const isLocked = await models.Ticket.isLocked(id); const isLocked = await models.Ticket.isLocked(id, myOptions);
const roles = await models.Account.getRoles(userId); const roles = await models.Account.getRoles(userId, myOptions);
const hasAllowedRoles = roles.filter(role => const hasAllowedRoles = roles.filter(role =>
role == 'salesPerson' || role == 'claimManager' role == 'salesPerson' || role == 'claimManager'
); );
const state = await Self.app.models.TicketState.findOne({ const state = await Self.app.models.TicketState.findOne({
where: {ticketFk: id} where: {ticketFk: id}
}); }, myOptions);
const alertLevel = state ? state.alertLevel : null; const alertLevel = state ? state.alertLevel : null;
if (isLocked || (!hasAllowedRoles && alertLevel > 0)) if (isLocked || (!hasAllowedRoles && alertLevel > 0))
@ -89,11 +96,11 @@ module.exports = Self => {
where: { where: {
workerFk: userId workerFk: userId
}, },
fields: 'amount'}, options); fields: 'amount'}, myOptions);
const componentCode = usesMana ? 'mana' : 'buyerDiscount'; const componentCode = usesMana ? 'mana' : 'buyerDiscount';
const discountComponent = await models.Component.findOne({ const discountComponent = await models.Component.findOne({
where: {code: componentCode}}, options); where: {code: componentCode}}, myOptions);
const componentId = discountComponent.id; const componentId = discountComponent.id;
const promises = []; const promises = [];
@ -105,9 +112,9 @@ module.exports = Self => {
const newComponent = models.SaleComponent.upsert({ const newComponent = models.SaleComponent.upsert({
saleFk: sale.id, saleFk: sale.id,
value: value, value: value,
componentFk: componentId}, options); componentFk: componentId}, myOptions);
const updatedSale = sale.updateAttribute('discount', newDiscount, options); const updatedSale = sale.updateAttribute('discount', newDiscount, myOptions);
promises.push(newComponent, updatedSale); promises.push(newComponent, updatedSale);
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${oldDiscount}% ➔ *${newDiscount}%*`; changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${oldDiscount}% ➔ *${newDiscount}%*`;
@ -116,7 +123,7 @@ module.exports = Self => {
await Promise.all(promises); await Promise.all(promises);
const query = `call vn.manaSpellersRequery(?)`; const query = `call vn.manaSpellersRequery(?)`;
await Self.rawSql(query, [userId], options); await Self.rawSql(query, [userId], myOptions);
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
include: { include: {
@ -130,7 +137,7 @@ module.exports = Self => {
} }
} }
} }
}, options); }, myOptions);
const salesPerson = ticket.client().salesPersonUser(); const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
@ -141,13 +148,13 @@ module.exports = Self => {
ticketUrl: `${origin}/#!/ticket/${id}/sale`, ticketUrl: `${origin}/#!/ticket/${id}/sale`,
changes: changesMade changes: changesMade
}); });
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();
} catch (error) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw error; throw e;
} }
}; };
}; };

View File

@ -15,7 +15,7 @@ module.exports = Self => {
{ {
arg: 'data', arg: 'data',
description: 'Model instance data', description: 'Model instance data',
type: 'Object', type: 'object',
required: true, required: true,
http: {source: 'body'} http: {source: 'body'}
} }
@ -30,12 +30,30 @@ module.exports = Self => {
} }
}); });
Self.updateEditableTicket = async(ctx, id, data) => { Self.updateEditableTicket = async(ctx, id, data, options) => {
let ticketIsEditable = await Self.app.models.Ticket.isEditable(ctx, id); const myOptions = {};
if (!ticketIsEditable) let tx;
throw new UserError('This ticket can not be modified');
let ticket = await Self.app.models.Ticket.findById(id); if (typeof options == 'object')
await ticket.updateAttributes(data); Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const ticketIsEditable = await Self.app.models.Ticket.isEditable(ctx, id, myOptions);
if (!ticketIsEditable)
throw new UserError('This ticket can not be modified');
const ticket = await Self.app.models.Ticket.findById(id, null, myOptions);
await ticket.updateAttributes(data, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -4,40 +4,40 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The ticket id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}
}, { }, {
arg: 'warehouseId', arg: 'warehouseId',
type: 'Number', type: 'number',
description: 'The warehouse id', description: 'The warehouse id',
required: true required: true
}, { }, {
arg: 'companyId', arg: 'companyId',
type: 'Number', type: 'number',
description: 'The company id', description: 'The company id',
required: true required: true
}, { }, {
arg: 'dmsTypeId', arg: 'dmsTypeId',
type: 'Number', type: 'number',
description: 'The dms type id', description: 'The dms type id',
required: true required: true
}, { }, {
arg: 'reference', arg: 'reference',
type: 'String', type: 'string',
required: true required: true
}, { }, {
arg: 'description', arg: 'description',
type: 'String', type: 'string',
required: true required: true
}, { }, {
arg: 'hasFile', arg: 'hasFile',
type: 'Boolean', type: 'boolean',
description: 'True if has an attached file', description: 'True if has an attached file',
required: true required: true
}], }],
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { http: {
@ -46,31 +46,38 @@ module.exports = Self => {
} }
}); });
Self.uploadFile = async(ctx, id) => { Self.uploadFile = async(ctx, id, options) => {
const models = Self.app.models; 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;
}
const promises = []; const promises = [];
const tx = await Self.beginTransaction({});
try { try {
const options = {transaction: tx}; const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions);
const uploadedFiles = await models.Dms.uploadFile(ctx, options);
uploadedFiles.forEach(dms => { uploadedFiles.forEach(dms => {
const newTicketDms = models.TicketDms.create({ const newTicketDms = models.TicketDms.create({
ticketFk: id, ticketFk: id,
dmsFk: dms.id dmsFk: dms.id
}, options); }, myOptions);
promises.push(newTicketDms); promises.push(newTicketDms);
}); });
const resolvedPromises = await Promise.all(promises); const resolvedPromises = await Promise.all(promises);
await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return resolvedPromises;
} catch (err) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw err; throw e;
} }
}; };
}; };

View File

@ -3,10 +3,8 @@ const app = require('vn-loopback/server/server');
describe('ticket model TicketTracking', () => { describe('ticket model TicketTracking', () => {
let ticketTrackingId; let ticketTrackingId;
afterAll(async done => { afterAll(async() => {
await app.models.TicketPackaging.destroyById(ticketTrackingId); await app.models.TicketPackaging.destroyById(ticketTrackingId);
done();
}); });
it('should save a ticketTraing as the quantity is greater than 0', async() => { it('should save a ticketTraing as the quantity is greater than 0', async() => {

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