2790-dialog_shortcuts #570
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: [
|
||||||
'@babel/preset-env',
|
'@babel/env',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
const request = require('request-promise-native');
|
const got = require('got');
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('send', {
|
Self.remoteMethodCtx('send', {
|
||||||
description: 'Send a RocketChat message',
|
description: 'Send a RocketChat message',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'to',
|
arg: 'to',
|
||||||
type: 'String',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'User (@) or channel (#) to send the message'
|
description: 'User (@) or channel (#) to send the message'
|
||||||
}, {
|
}, {
|
||||||
arg: 'message',
|
arg: 'message',
|
||||||
type: 'String',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The message'
|
description: 'The message'
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'Object',
|
type: 'object',
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -30,8 +30,15 @@ module.exports = Self => {
|
||||||
const sender = await models.Account.findById(accessToken.userId);
|
const sender = await models.Account.findById(accessToken.userId);
|
||||||
const recipient = to.replace('@', '');
|
const recipient = to.replace('@', '');
|
||||||
|
|
||||||
if (sender.name != recipient)
|
if (sender.name != recipient) {
|
||||||
return sendMessage(sender, to, message);
|
let {body} = await sendMessage(sender, to, message);
|
||||||
|
if (body)
|
||||||
|
body = JSON.parse(body);
|
||||||
|
else
|
||||||
|
body = false;
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -65,12 +72,15 @@ module.exports = Self => {
|
||||||
if (!this.auth || this.auth && !this.auth.authToken) {
|
if (!this.auth || this.auth && !this.auth.authToken) {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const uri = `${config.api}/login`;
|
const uri = `${config.api}/login`;
|
||||||
const res = await send(uri, {
|
let {body} = await send(uri, {
|
||||||
user: config.user,
|
user: config.user,
|
||||||
password: config.password
|
password: config.password
|
||||||
});
|
});
|
||||||
|
|
||||||
this.auth = res.data;
|
if (body) {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
this.auth = body.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.auth;
|
return this.auth;
|
||||||
|
@ -93,29 +103,29 @@ module.exports = Self => {
|
||||||
/**
|
/**
|
||||||
* Send unauthenticated request
|
* Send unauthenticated request
|
||||||
* @param {*} uri - Request uri
|
* @param {*} uri - Request uri
|
||||||
* @param {*} body - Request params
|
* @param {*} params - Request params
|
||||||
* @param {*} options - Request options
|
* @param {*} options - Request options
|
||||||
*
|
*
|
||||||
* @return {Object} Request response
|
* @return {Object} Request response
|
||||||
*/
|
*/
|
||||||
async function send(uri, body, options) {
|
async function send(uri, params, options = {}) {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
return resolve({statusCode: 200, message: 'Fake notification sent'});
|
return resolve({
|
||||||
|
body: JSON.stringify(
|
||||||
|
{statusCode: 200, message: 'Fake notification sent'}
|
||||||
|
)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
method: 'POST',
|
body: params
|
||||||
uri: uri,
|
|
||||||
body: body,
|
|
||||||
headers: {'content-type': 'application/json'},
|
|
||||||
json: true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options) Object.assign(defaultOptions, options);
|
if (options) Object.assign(defaultOptions, options);
|
||||||
|
|
||||||
return request(defaultOptions);
|
return got.post(uri, defaultOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,7 +138,7 @@ module.exports = Self => {
|
||||||
async function sendAuth(uri, body) {
|
async function sendAuth(uri, body) {
|
||||||
const login = await getAuthToken();
|
const login = await getAuthToken();
|
||||||
const options = {
|
const options = {
|
||||||
headers: {'content-type': 'application/json'}
|
headers: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (login) {
|
if (login) {
|
||||||
|
|
|
@ -7,7 +7,8 @@ module.exports = Self => {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The worker id of the destinatary'
|
description: 'The worker id of the destinatary'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'message',
|
arg: 'message',
|
||||||
type: 'String',
|
type: 'String',
|
||||||
required: true,
|
required: true,
|
||||||
|
|
|
@ -48,7 +48,7 @@ module.exports = Self => {
|
||||||
throw new UserError(`You don't have enough privileges`);
|
throw new UserError(`You don't have enough privileges`);
|
||||||
|
|
||||||
if (process.env.NODE_ENV == 'test')
|
if (process.env.NODE_ENV == 'test')
|
||||||
throw new UserError(`You can't upload images on the test environment`);
|
throw new UserError(`Action not allowed on the test environment`);
|
||||||
|
|
||||||
// Upload file to temporary path
|
// Upload file to temporary path
|
||||||
const tempContainer = await TempContainer.container(args.collection);
|
const tempContainer = await TempContainer.container(args.collection);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
VALUES
|
VALUES
|
||||||
('Genus', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss'),
|
('Genus', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss'),
|
||||||
('Specie', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss');
|
('Specie', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss'),
|
||||||
|
('InvoiceOut', 'createPdf', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');
|
||||||
|
|
|
@ -2178,6 +2178,15 @@ INSERT INTO `hedera`.`imageCollectionSize`(`id`, `collectionFk`,`width`, `height
|
||||||
VALUES
|
VALUES
|
||||||
(1, 4, 160, 160);
|
(1, 4, 160, 160);
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`rateConfig`(`rate0`, `rate1`, `rate2`, `rate3`)
|
||||||
|
VALUES
|
||||||
|
(36, 31, 25, 21);
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`rate`(`dated`, `warehouseFk`, `rate0`, `rate1`, `rate2`, `rate3`)
|
||||||
|
VALUES
|
||||||
|
(DATE_ADD(CURDATE(), INTERVAL -1 YEAR), 1, 10, 15, 20, 25),
|
||||||
|
(CURDATE(), 1, 12, 17, 22, 27);
|
||||||
|
|
||||||
INSERT INTO `vn`.`awb` (id, code, package, weight, created, amount, transitoryFk, taxFk)
|
INSERT INTO `vn`.`awb` (id, code, package, weight, created, amount, transitoryFk, taxFk)
|
||||||
VALUES
|
VALUES
|
||||||
(1, '07546501420', 67, 671, CURDATE(), 1761, 1, 1),
|
(1, '07546501420', 67, 671, CURDATE(), 1761, 1, 1),
|
||||||
|
@ -2243,3 +2252,8 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`)
|
||||||
(8, 8, 8),
|
(8, 8, 8),
|
||||||
(9, 9, 9),
|
(9, 9, 9),
|
||||||
(10, 10, 10);
|
(10, 10, 10);
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
|
||||||
|
SELECT `id` FROM `vn`.`ticket`;
|
||||||
|
|
||||||
|
CALL `vn`.`ticket_doRecalc`();
|
||||||
|
|
|
@ -67138,7 +67138,7 @@ BEGIN
|
||||||
isTaxDataChecked = FALSE;
|
isTaxDataChecked = FALSE;
|
||||||
|
|
||||||
|
|
||||||
SELECT * FROM tmp.ticketProblems;
|
-- SELECT * FROM tmp.ticketProblems;
|
||||||
|
|
||||||
DROP TEMPORARY TABLE
|
DROP TEMPORARY TABLE
|
||||||
tmp.clientGetDebt,
|
tmp.clientGetDebt,
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
{
|
||||||
|
"invoices": [
|
||||||
|
{
|
||||||
|
"tx_company": "TESSAROSES S.A.",
|
||||||
|
"id_invoice": "20062926",
|
||||||
|
"id_purchaseorder": "20106319",
|
||||||
|
"tx_customer_ref": "",
|
||||||
|
"id_customer": "56116",
|
||||||
|
"id_customer_floricode": "",
|
||||||
|
"nm_bill": "VERDNATURA LEVANTE SL",
|
||||||
|
"nm_ship": "VERDNATURA LEVANTE SL",
|
||||||
|
"nm_cargo": "OYAMBARILLO",
|
||||||
|
"dt_purchaseorder": "06/19/2020",
|
||||||
|
"dt_fly": "06/20/2020",
|
||||||
|
"dt_invoice": "06/19/2020",
|
||||||
|
"nm_incoterm": "FOB UIO",
|
||||||
|
"tx_awb": "729-6340 2846",
|
||||||
|
"tx_hawb": "LA0061832844",
|
||||||
|
"tx_oe": "05520204000335992",
|
||||||
|
"nu_totalstemsPO": "850",
|
||||||
|
"mny_flower": "272.5000",
|
||||||
|
"mny_freight": "0.0000",
|
||||||
|
"mny_total": "272.5000",
|
||||||
|
"nu_boxes": "4",
|
||||||
|
"nu_fulls": "1.75",
|
||||||
|
"dt_posted": "2020-06-19T13:31:41",
|
||||||
|
"boxes": [
|
||||||
|
{
|
||||||
|
"id_box": "200573095",
|
||||||
|
"nm_box": "HB",
|
||||||
|
"tp_box": "HB",
|
||||||
|
"tx_label": "",
|
||||||
|
"nu_length": "96",
|
||||||
|
"nu_width": "32",
|
||||||
|
"nu_height": "30.5",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id_floricode": "27887",
|
||||||
|
"id_migros_variety": "",
|
||||||
|
"nm_product": "FREEDOM 60CM 25ST",
|
||||||
|
"nm_species": "ROSES",
|
||||||
|
"nm_variety": "FREEDOM",
|
||||||
|
"nu_length": "60",
|
||||||
|
"nu_stems_bunch": "25",
|
||||||
|
"nu_bunches": "10",
|
||||||
|
"mny_rate_stem": "0.3500",
|
||||||
|
"mny_freight_unit": "0.0000",
|
||||||
|
"barcodes": "202727621,202725344,202725345,202725571,202725730,202725731,202725732,202725925,202726131,202726685"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id_box": "200573106",
|
||||||
|
"nm_box": "HB",
|
||||||
|
"tp_box": "HB",
|
||||||
|
"tx_label": "",
|
||||||
|
"nu_length": "96",
|
||||||
|
"nu_width": "32",
|
||||||
|
"nu_height": "30.5",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id_floricode": "27887",
|
||||||
|
"id_migros_variety": "",
|
||||||
|
"nm_product": "FREEDOM 70CM 25ST",
|
||||||
|
"nm_species": "ROSES",
|
||||||
|
"nm_variety": "FREEDOM",
|
||||||
|
"nu_length": "70",
|
||||||
|
"nu_stems_bunch": "25",
|
||||||
|
"nu_bunches": "8",
|
||||||
|
"mny_rate_stem": "0.4000",
|
||||||
|
"mny_freight_unit": "0.0000",
|
||||||
|
"barcodes": "202727077,202727078,202727079,202727080,202727650,202727654,202727656,202727657"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id_box": "200573117",
|
||||||
|
"nm_box": "HB",
|
||||||
|
"tp_box": "HB",
|
||||||
|
"tx_label": "",
|
||||||
|
"nu_length": "96",
|
||||||
|
"nu_width": "32",
|
||||||
|
"nu_height": "30.5",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id_floricode": "28409",
|
||||||
|
"id_migros_variety": "",
|
||||||
|
"nm_product": "TIBET 40CM 25ST",
|
||||||
|
"nm_species": "ROSES",
|
||||||
|
"nm_variety": "TIBET",
|
||||||
|
"nu_length": "40",
|
||||||
|
"nu_stems_bunch": "25",
|
||||||
|
"nu_bunches": "12",
|
||||||
|
"mny_rate_stem": "0.2500",
|
||||||
|
"mny_freight_unit": "0.0000",
|
||||||
|
"barcodes": "202723350,202723351,202723352,202723353,202723354,202723355,202723356,202723357,202726690,202726745,202726813,202726814"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id_box": "200573506",
|
||||||
|
"nm_box": "QB 2",
|
||||||
|
"tp_box": "QB",
|
||||||
|
"tx_label": "",
|
||||||
|
"nu_length": "80",
|
||||||
|
"nu_width": "30",
|
||||||
|
"nu_height": "17.5",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id_floricode": "27887",
|
||||||
|
"id_migros_variety": "",
|
||||||
|
"nm_product": "FREEDOM 50CM 25ST",
|
||||||
|
"nm_species": "ROSES",
|
||||||
|
"nm_variety": "FREEDOM",
|
||||||
|
"nu_length": "50",
|
||||||
|
"nu_stems_bunch": "25",
|
||||||
|
"nu_bunches": "4",
|
||||||
|
"mny_rate_stem": "0.3000",
|
||||||
|
"mny_freight_unit": "0.0000",
|
||||||
|
"barcodes": "202727837,202727839,202727842,202726682"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 13 KiB |
|
@ -1015,6 +1015,17 @@ export default {
|
||||||
travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a',
|
travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a',
|
||||||
entriesQuicklink: 'vn-entry-descriptor vn-quick-link[icon="icon-entry"] > a'
|
entriesQuicklink: 'vn-entry-descriptor vn-quick-link[icon="icon-entry"] > a'
|
||||||
},
|
},
|
||||||
|
entryBuys: {
|
||||||
|
importButton: 'vn-entry-buy-index vn-icon[icon="publish"]',
|
||||||
|
ref: 'vn-entry-buy-import vn-textfield[ng-model="$ctrl.import.ref"]',
|
||||||
|
observation: 'vn-entry-buy-import vn-textarea[ng-model="$ctrl.import.observation"]',
|
||||||
|
file: 'vn-entry-buy-import vn-input-file[ng-model="$ctrl.import.file"]',
|
||||||
|
firstImportedItem: 'vn-entry-buy-import tbody:nth-child(2) vn-autocomplete[ng-model="buy.itemFk"]',
|
||||||
|
secondImportedItem: 'vn-entry-buy-import tbody:nth-child(3) vn-autocomplete[ng-model="buy.itemFk"]',
|
||||||
|
thirdImportedItem: 'vn-entry-buy-import tbody:nth-child(4) vn-autocomplete[ng-model="buy.itemFk"]',
|
||||||
|
fourthImportedItem: 'vn-entry-buy-import tbody:nth-child(5) vn-autocomplete[ng-model="buy.itemFk"]',
|
||||||
|
importBuysButton: 'vn-entry-buy-import button[type="submit"]'
|
||||||
|
},
|
||||||
entryLatestBuys: {
|
entryLatestBuys: {
|
||||||
firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)',
|
firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)',
|
||||||
allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check',
|
allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check',
|
||||||
|
|
|
@ -162,7 +162,7 @@ describe('Ticket descriptor path', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should regenerate the invoice using the descriptor menu`, async() => {
|
it(`should regenerate the invoice using the descriptor menu`, async() => {
|
||||||
const expectedMessage = 'Invoice sent for a regeneration, will be available in a few minutes';
|
const expectedMessage = 'The invoice PDF document has been regenerated';
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
||||||
await page.waitForContentLoaded();
|
await page.waitForContentLoaded();
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe('Travel thermograph path', () => {
|
||||||
|
|
||||||
it('should select the file to upload', async() => {
|
it('should select the file to upload', async() => {
|
||||||
let currentDir = process.cwd();
|
let currentDir = process.cwd();
|
||||||
let filePath = `${currentDir}/storage/dms/ecc/3.jpeg`;
|
let filePath = `${currentDir}/e2e/assets/thermograph.jpeg`;
|
||||||
|
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
|
|
|
@ -41,6 +41,6 @@ describe('Entry lastest buys path', () => {
|
||||||
|
|
||||||
it('should navigate to the entry.buy section by clicking one of the buys', async() => {
|
it('should navigate to the entry.buy section by clicking one of the buys', async() => {
|
||||||
await page.waitToClick(selectors.entryLatestBuys.firstBuy);
|
await page.waitToClick(selectors.entryLatestBuys.firstBuy);
|
||||||
await page.waitForState('entry.card.buy');
|
await page.waitForState('entry.card.buy.index');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import selectors from '../../helpers/selectors.js';
|
||||||
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('Entry import buys path', () => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
await page.loginAndModule('buyer', 'entry');
|
||||||
|
await page.accessToSearchResult('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should count the summary buys and find there only one at this point', async() => {
|
||||||
|
const buysCount = await page.countElement(selectors.entrySummary.anyBuyLine);
|
||||||
|
|
||||||
|
expect(buysCount).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to the buy section and then click the import button opening the import form', async() => {
|
||||||
|
await page.accessToSection('entry.card.buy.index');
|
||||||
|
await page.waitToClick(selectors.entryBuys.importButton);
|
||||||
|
await page.waitForState('entry.card.buy.import');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fill the form, import the designated JSON file and select items for each import and confirm import', async() => {
|
||||||
|
await page.write(selectors.entryBuys.ref, 'a reference');
|
||||||
|
await page.write(selectors.entryBuys.observation, 'an observation');
|
||||||
|
|
||||||
|
let currentDir = process.cwd();
|
||||||
|
let filePath = `${currentDir}/e2e/assets/07_import_buys.json`;
|
||||||
|
|
||||||
|
const [fileChooser] = await Promise.all([
|
||||||
|
page.waitForFileChooser(),
|
||||||
|
page.waitToClick(selectors.entryBuys.file)
|
||||||
|
]);
|
||||||
|
await fileChooser.accept([filePath]);
|
||||||
|
|
||||||
|
await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged Reinforced weapon pistol 9mm');
|
||||||
|
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Melee Reinforced weapon heavy shield 1x0.5m');
|
||||||
|
await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Container medical box 1m');
|
||||||
|
await page.autocompleteSearch(selectors.entryBuys.fourthImportedItem, 'Container ammo box 1m');
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.entryBuys.importBuysButton);
|
||||||
|
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
const state = await page.getState();
|
||||||
|
|
||||||
|
expect(message.text).toContain('Data saved!');
|
||||||
|
expect(state).toBe('entry.card.buy.index');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to the entry summary and count the buys to find 4 buys have been added', async() => {
|
||||||
|
await page.waitToClick('vn-icon[icon="preview"]');
|
||||||
|
await page.waitForNumberOfElements(selectors.entrySummary.anyBuyLine, 5);
|
||||||
|
});
|
||||||
|
});
|
|
@ -20,7 +20,8 @@ export default class Field extends FormInput {
|
||||||
super.$onInit();
|
super.$onInit();
|
||||||
|
|
||||||
if (this.info) this.classList.add('has-icons');
|
if (this.info) this.classList.add('has-icons');
|
||||||
this.input.addEventListener('change', () => this.onChange());
|
this.input.addEventListener('change', event =>
|
||||||
|
this.onChange(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
set field(value) {
|
set field(value) {
|
||||||
|
@ -82,6 +83,9 @@ export default class Field extends FormInput {
|
||||||
this._required = value;
|
this._required = value;
|
||||||
let required = this.element.querySelector('.required');
|
let required = this.element.querySelector('.required');
|
||||||
display(required, this._required);
|
display(required, this._required);
|
||||||
|
|
||||||
|
this.$.$applyAsync(() =>
|
||||||
|
this.input.setAttribute('required', value));
|
||||||
}
|
}
|
||||||
|
|
||||||
get required() {
|
get required() {
|
||||||
|
@ -186,10 +190,13 @@ export default class Field extends FormInput {
|
||||||
this.refreshHint();
|
this.refreshHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
onChange($event) {
|
||||||
// Changes doesn't reflect until appling async
|
// Changes doesn't reflect until appling async
|
||||||
this.$.$applyAsync(() => {
|
this.$.$applyAsync(() => {
|
||||||
this.emit('change', {value: this.field});
|
this.emit('change', {
|
||||||
|
value: this.field,
|
||||||
|
$event: $event
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,12 +71,23 @@ export default class InputFile extends Field {
|
||||||
this.input.click();
|
this.input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
onChange($event) {
|
||||||
this.emit('change', {
|
this.emit('change', {
|
||||||
value: this.field,
|
value: this.field,
|
||||||
$files: this.files
|
$files: this.files,
|
||||||
|
$event: $event
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get accept() {
|
||||||
|
return this._accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
set accept(value) {
|
||||||
|
this._accept = value;
|
||||||
|
this.$.$applyAsync(() =>
|
||||||
|
this.input.setAttribute('accept', value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnInputFile', {
|
ngModule.vnComponent('vnInputFile', {
|
||||||
|
|
|
@ -81,6 +81,9 @@ vn-table {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
&[shrink-date] {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
&[expand] {
|
&[expand] {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import '@babel/polyfill';
|
import 'core-js/stable';
|
||||||
|
import 'regenerator-runtime/runtime';
|
||||||
import * as ng from 'angular';
|
import * as ng from 'angular';
|
||||||
export {ng};
|
export {ng};
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,6 @@
|
||||||
"url": "https://gitea.verdnatura.es/verdnatura/salix"
|
"url": "https://gitea.verdnatura.es/verdnatura/salix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/polyfill": "^7.2.5",
|
|
||||||
"@uirouter/angularjs": "^1.0.20",
|
"@uirouter/angularjs": "^1.0.20",
|
||||||
"angular": "^1.7.5",
|
"angular": "^1.7.5",
|
||||||
"angular-animate": "^1.7.8",
|
"angular-animate": "^1.7.8",
|
||||||
|
@ -17,7 +16,6 @@
|
||||||
"angular-translate-loader-partial": "^2.18.1",
|
"angular-translate-loader-partial": "^2.18.1",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"mg-crud": "^1.1.2",
|
"mg-crud": "^1.1.2",
|
||||||
"npm": "^6.11.3",
|
|
||||||
"oclazyload": "^0.6.3",
|
"oclazyload": "^0.6.3",
|
||||||
"require-yaml": "0.0.1",
|
"require-yaml": "0.0.1",
|
||||||
"validator": "^6.3.0"
|
"validator": "^6.3.0"
|
||||||
|
|
22
gulpfile.js
22
gulpfile.js
|
@ -3,7 +3,7 @@ const gulp = require('gulp');
|
||||||
const PluginError = require('plugin-error');
|
const PluginError = require('plugin-error');
|
||||||
const argv = require('minimist')(process.argv.slice(2));
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
const log = require('fancy-log');
|
const log = require('fancy-log');
|
||||||
const request = require('request');
|
const got = require('got');
|
||||||
const e2eConfig = require('./e2e/helpers/config.js');
|
const e2eConfig = require('./e2e/helpers/config.js');
|
||||||
const Docker = require('./db/docker.js');
|
const Docker = require('./db/docker.js');
|
||||||
|
|
||||||
|
@ -143,8 +143,9 @@ backTest.description = `Watches for changes in modules to execute backTest task`
|
||||||
|
|
||||||
// End to end tests
|
// End to end tests
|
||||||
function e2eSingleRun() {
|
function e2eSingleRun() {
|
||||||
require('@babel/register')({presets: ['@babel/preset-env']});
|
require('@babel/register')({presets: ['@babel/env']});
|
||||||
require('@babel/polyfill');
|
require('core-js/stable');
|
||||||
|
require('regenerator-runtime/runtime');
|
||||||
|
|
||||||
const jasmine = require('gulp-jasmine');
|
const jasmine = require('gulp-jasmine');
|
||||||
const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||||
|
@ -224,17 +225,20 @@ async function backendStatus() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let timer;
|
let timer;
|
||||||
let attempts = 1;
|
let attempts = 1;
|
||||||
timer = setInterval(() => {
|
timer = setInterval(async() => {
|
||||||
|
try {
|
||||||
const url = `${e2eConfig.url}/api/Applications/status`;
|
const url = `${e2eConfig.url}/api/Applications/status`;
|
||||||
request.get(url, (err, res) => {
|
const {body} = await got.get(url);
|
||||||
if (err || attempts > 100) // 250ms * 100 => 25s timeout
|
|
||||||
throw new Error('Could not connect to backend');
|
if (body == 'true') {
|
||||||
else if (res && res.body == 'true') {
|
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
resolve(attempts);
|
resolve(attempts);
|
||||||
} else
|
} else
|
||||||
attempts++;
|
attempts++;
|
||||||
});
|
} catch (error) {
|
||||||
|
if (error || attempts > 100) // 250ms * 100 => 25s timeout
|
||||||
|
throw new Error('Could not connect to backend');
|
||||||
|
}
|
||||||
}, milliseconds);
|
}, milliseconds);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,8 +134,9 @@ module.exports = function(Self) {
|
||||||
if (value instanceof Object)
|
if (value instanceof Object)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (value === undefined || value === null) continue;
|
if (value === undefined) continue;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
for (let relationName in relations) {
|
for (let relationName in relations) {
|
||||||
const relation = relations[relationName];
|
const relation = relations[relationName];
|
||||||
if (relation.keyFrom == key && key != 'id') {
|
if (relation.keyFrom == key && key != 'id') {
|
||||||
|
@ -177,6 +178,7 @@ module.exports = function(Self) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
|
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
|
||||||
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
||||||
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
|
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
|
||||||
"MESSAGE_BOUGHT_UNITS": "Bought {{quantity}} units of {{concept}} ({{itemId}}) for the ticket id [{{ticketId}}]({{{url}}})",
|
"Bought units from buy request": "Bought {{quantity}} units of {{concept}} [{{itemId}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
|
||||||
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
|
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
|
||||||
"MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
|
"MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||||
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
|
|
@ -121,7 +121,7 @@
|
||||||
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
|
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
|
||||||
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
|
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
|
||||||
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
|
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
|
||||||
"MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} ({{itemId}}) para el ticket id [{{ticketId}}]({{{url}}})",
|
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de {{concept}} [{{itemId}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
|
||||||
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
|
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
|
||||||
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||||
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
"Amount cannot be zero": "El importe no puede ser cero",
|
"Amount cannot be zero": "El importe no puede ser cero",
|
||||||
"Company has to be official": "Empresa inválida",
|
"Company has to be official": "Empresa inválida",
|
||||||
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
|
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
|
||||||
"You can't upload images on the test environment": "No puedes subir imágenes en el entorno de pruebas",
|
"Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
|
||||||
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
|
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
|
||||||
"Sorts whole route": "Reordena ruta entera",
|
"Sorts whole route": "Reordena ruta entera",
|
||||||
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día <strong>{{shipped}}</strong>, con una cantidad de <strong>{{quantity}}</strong> y un precio de <strong>{{price}} €</strong>",
|
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día <strong>{{shipped}}</strong>, con una cantidad de <strong>{{quantity}}</strong> y un precio de <strong>{{price}} €</strong>",
|
||||||
|
|
|
@ -68,5 +68,16 @@
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/jpg"
|
"image/jpg"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"invoiceStorage": {
|
||||||
|
"name": "invoiceStorage",
|
||||||
|
"connector": "loopback-component-storage",
|
||||||
|
"provider": "filesystem",
|
||||||
|
"root": "./storage/pdfs/invoice",
|
||||||
|
"maxFileSize": "52428800",
|
||||||
|
"allowedContentTypes": [
|
||||||
|
"application/octet-stream",
|
||||||
|
"application/pdf"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* @param {Object} instance - The model or context instance
|
* @param {Object} instance - The model or context instance
|
||||||
* @param {Object} changes - Object containing changes
|
* @param {Object} changes - Object containing changes
|
||||||
*/
|
*/
|
||||||
exports.translateValues = async(instance, changes) => {
|
exports.translateValues = async(instance, changes, options = {}) => {
|
||||||
const models = instance.app.models;
|
const models = instance.app.models;
|
||||||
function getRelation(instance, property) {
|
function getRelation(instance, property) {
|
||||||
const relations = instance.definition.settings.relations;
|
const relations = instance.definition.settings.relations;
|
||||||
|
@ -38,12 +38,20 @@ exports.translateValues = async(instance, changes) => {
|
||||||
|
|
||||||
const properties = Object.assign({}, changes);
|
const properties = Object.assign({}, changes);
|
||||||
for (let property in properties) {
|
for (let property in properties) {
|
||||||
|
const firstChar = property.substring(0, 1);
|
||||||
|
const isPrivate = firstChar == '$';
|
||||||
|
if (isPrivate) {
|
||||||
|
delete properties[property];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const relation = getRelation(instance, property);
|
const relation = getRelation(instance, property);
|
||||||
const value = properties[property];
|
const value = properties[property];
|
||||||
let finalValue = value;
|
const hasValue = value != null && value != undefined;
|
||||||
|
|
||||||
if (relation) {
|
let finalValue = value;
|
||||||
let fieldsToShow = ['alias', 'name', 'code', 'description'];
|
if (relation && hasValue) {
|
||||||
|
let fieldsToShow = ['nickname', 'name', 'code', 'description'];
|
||||||
const modelName = relation.model;
|
const modelName = relation.model;
|
||||||
const model = models[modelName];
|
const model = models[modelName];
|
||||||
const log = model.definition.settings.log;
|
const log = model.definition.settings.log;
|
||||||
|
@ -53,7 +61,7 @@ exports.translateValues = async(instance, changes) => {
|
||||||
|
|
||||||
const row = await model.findById(value, {
|
const row = await model.findById(value, {
|
||||||
fields: fieldsToShow
|
fields: fieldsToShow
|
||||||
});
|
}, options);
|
||||||
const newValue = getValue(row);
|
const newValue = getValue(row);
|
||||||
if (newValue) finalValue = newValue;
|
if (newValue) finalValue = newValue;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +84,12 @@ exports.translateValues = async(instance, changes) => {
|
||||||
exports.getChanges = (original, changes) => {
|
exports.getChanges = (original, changes) => {
|
||||||
const oldChanges = {};
|
const oldChanges = {};
|
||||||
const newChanges = {};
|
const newChanges = {};
|
||||||
|
|
||||||
for (let property in changes) {
|
for (let property in changes) {
|
||||||
|
const firstChar = property.substring(0, 1);
|
||||||
|
const isPrivate = firstChar == '$';
|
||||||
|
if (isPrivate) return;
|
||||||
|
|
||||||
if (changes[property] != original[property]) {
|
if (changes[property] != original[property]) {
|
||||||
newChanges[property] = changes[property];
|
newChanges[property] = changes[property];
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,10 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.importToNewRefundTicket = async(ctx, id) => {
|
Self.importToNewRefundTicket = async(ctx, id, options) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const token = ctx.req.accessToken;
|
const token = ctx.req.accessToken;
|
||||||
const userId = token.userId;
|
const userId = token.userId;
|
||||||
const tx = await Self.beginTransaction({});
|
|
||||||
const filter = {
|
const filter = {
|
||||||
where: {id: id},
|
where: {id: id},
|
||||||
include: [
|
include: [
|
||||||
|
@ -63,29 +62,39 @@ module.exports = Self => {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let tx;
|
||||||
|
let myOptions = {};
|
||||||
|
|
||||||
|
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 worker = await models.Worker.findOne({
|
const worker = await models.Worker.findOne({
|
||||||
where: {userFk: userId}
|
where: {userFk: userId}
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const obsevationType = await models.ObservationType.findOne({
|
const obsevationType = await models.ObservationType.findOne({
|
||||||
where: {description: 'comercial'}
|
where: {description: 'comercial'}
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const agencyMode = await models.AgencyMode.findOne({
|
const agencyMode = await models.AgencyMode.findOne({
|
||||||
where: {code: 'refund'}
|
where: {code: 'refund'}
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const state = await models.State.findOne({
|
const state = await models.State.findOne({
|
||||||
where: {code: 'DELIVERED'}
|
where: {code: 'DELIVERED'}
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const zone = await models.Zone.findOne({
|
const zone = await models.Zone.findOne({
|
||||||
where: {agencyModeFk: agencyMode.id}
|
where: {agencyModeFk: agencyMode.id}
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const claim = await models.Claim.findOne(filter, options);
|
const claim = await models.Claim.findOne(filter, myOptions);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
||||||
const newRefundTicket = await models.Ticket.create({
|
const newRefundTicket = await models.Ticket.create({
|
||||||
|
@ -98,33 +107,33 @@ module.exports = Self => {
|
||||||
addressFk: claim.ticket().addressFk,
|
addressFk: claim.ticket().addressFk,
|
||||||
agencyModeFk: agencyMode.id,
|
agencyModeFk: agencyMode.id,
|
||||||
zoneFk: zone.id
|
zoneFk: zone.id
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
await saveObservation({
|
await saveObservation({
|
||||||
description: `Reclama ticket: ${claim.ticketFk}`,
|
description: `Reclama ticket: ${claim.ticketFk}`,
|
||||||
ticketFk: newRefundTicket.id,
|
ticketFk: newRefundTicket.id,
|
||||||
observationTypeFk: obsevationType.id
|
observationTypeFk: obsevationType.id
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
await models.TicketTracking.create({
|
await models.TicketTracking.create({
|
||||||
ticketFk: newRefundTicket.id,
|
ticketFk: newRefundTicket.id,
|
||||||
stateFk: state.id,
|
stateFk: state.id,
|
||||||
workerFk: worker.id
|
workerFk: worker.id
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const salesToRefund = await models.ClaimBeginning.find(salesFilter, options);
|
const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions);
|
||||||
const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, options);
|
const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, myOptions);
|
||||||
await insertIntoClaimEnd(createdSales, id, worker.id, options);
|
await insertIntoClaimEnd(createdSales, id, worker.id, myOptions);
|
||||||
|
|
||||||
await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [
|
await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [
|
||||||
newRefundTicket.id, claim.ticketFk
|
newRefundTicket.id, claim.ticketFk
|
||||||
], options);
|
], myOptions);
|
||||||
|
|
||||||
await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return newRefundTicket;
|
return newRefundTicket;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await tx.rollback();
|
if (tx) await tx.rollback();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,42 +1,43 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
const models = app.models;
|
||||||
|
|
||||||
describe('claimBeginning', () => {
|
describe('claimBeginning', () => {
|
||||||
const claimManagerId = 72;
|
const claimManagerId = 72;
|
||||||
let ticket;
|
|
||||||
let refundTicketSales;
|
|
||||||
let salesInsertedInClaimEnd;
|
|
||||||
|
|
||||||
const activeCtx = {
|
const activeCtx = {
|
||||||
accessToken: {userId: claimManagerId},
|
accessToken: {userId: claimManagerId},
|
||||||
};
|
};
|
||||||
const ctx = {req: activeCtx};
|
const ctx = {req: activeCtx};
|
||||||
|
|
||||||
afterAll(async done => {
|
|
||||||
try {
|
|
||||||
await app.models.Ticket.destroyById(ticket.id);
|
|
||||||
await app.models.Ticket.rawSql(`DELETE FROM vn.orderTicket WHERE ticketFk ='${ticket.id}';`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('importToNewRefundTicket()', () => {
|
describe('importToNewRefundTicket()', () => {
|
||||||
it('should create a new ticket with negative sales and insert the negative sales into claimEnd', async() => {
|
it('should create a new ticket with negative sales and insert the negative sales into claimEnd', async() => {
|
||||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
active: activeCtx
|
active: activeCtx
|
||||||
});
|
});
|
||||||
let claimId = 1;
|
let claimId = 1;
|
||||||
ticket = await app.models.ClaimBeginning.importToNewRefundTicket(ctx, claimId);
|
|
||||||
|
|
||||||
refundTicketSales = await app.models.Sale.find({where: {ticketFk: ticket.id}});
|
const tx = await models.Entry.beginTransaction({});
|
||||||
salesInsertedInClaimEnd = await app.models.ClaimEnd.find({where: {claimFk: claimId}});
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options);
|
||||||
|
|
||||||
|
const refundTicketSales = await models.Sale.find({
|
||||||
|
where: {ticketFk: ticket.id}
|
||||||
|
}, options);
|
||||||
|
const salesInsertedInClaimEnd = await models.ClaimEnd.find({
|
||||||
|
where: {claimFk: claimId}
|
||||||
|
}, options);
|
||||||
|
|
||||||
expect(refundTicketSales.length).toEqual(1);
|
expect(refundTicketSales.length).toEqual(1);
|
||||||
expect(refundTicketSales[0].quantity).toEqual(-5);
|
expect(refundTicketSales[0].quantity).toEqual(-5);
|
||||||
expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id);
|
expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
<vn-th field="id" number>Id</vn-th>
|
<vn-th field="id" number>Id</vn-th>
|
||||||
<vn-th field="clientFk">Client</vn-th>
|
<vn-th field="clientFk">Client</vn-th>
|
||||||
<vn-th field="created" center expand>Created</vn-th>
|
<vn-th field="created" center shrink-date>Created</vn-th>
|
||||||
<vn-th field="workerFk">Worker</vn-th>
|
<vn-th field="workerFk">Worker</vn-th>
|
||||||
<vn-th field="claimStateFk">State</vn-th>
|
<vn-th field="claimStateFk">State</vn-th>
|
||||||
<vn-th></vn-th>
|
<vn-th></vn-th>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
{{::claim.name}}
|
{{::claim.name}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td center expand>{{::claim.created | date:'dd/MM/yyyy'}}</vn-td>
|
<vn-td center shrink-date>{{::claim.created | date:'dd/MM/yyyy'}}</vn-td>
|
||||||
<vn-td expand>
|
<vn-td expand>
|
||||||
<span
|
<span
|
||||||
vn-click-stop="workerDescriptor.show($event, claim.workerFk)"
|
vn-click-stop="workerDescriptor.show($event, claim.workerFk)"
|
||||||
|
|
|
@ -199,7 +199,7 @@
|
||||||
<span
|
<span
|
||||||
ng-click="ticketDescriptor.show($event, action.sale.ticket.id)"
|
ng-click="ticketDescriptor.show($event, action.sale.ticket.id)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::action.sale.ticket.id | zeroFill:6}}
|
{{::action.sale.ticket.id}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>{{::action.claimBeggining.description}}</vn-td>
|
<vn-td expand>{{::action.claimBeggining.description}}</vn-td>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const request = require('request-promise-native');
|
const got = require('got');
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
const getFinalState = require('vn-loopback/util/hook').getFinalState;
|
const getFinalState = require('vn-loopback/util/hook').getFinalState;
|
||||||
const isMultiple = require('vn-loopback/util/hook').isMultiple;
|
const isMultiple = require('vn-loopback/util/hook').isMultiple;
|
||||||
|
@ -299,8 +299,8 @@ module.exports = Self => {
|
||||||
recipientId: instance.id,
|
recipientId: instance.id,
|
||||||
recipient: instance.email
|
recipient: instance.email
|
||||||
};
|
};
|
||||||
await request.get(`${origin}/api/email/payment-update`, {
|
await got.get(`${origin}/api/email/payment-update`, {
|
||||||
qs: params
|
query: params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
|
||||||
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('importBuys', {
|
||||||
|
description: 'Imports the buys from a list',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The entry id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'options',
|
||||||
|
type: 'object',
|
||||||
|
description: 'Callback options',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'ref',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The buyed boxes ids',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'observation',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The observation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'buys',
|
||||||
|
type: ['Object'],
|
||||||
|
description: 'The buys',
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: ['Object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/importBuys`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.importBuys = async(ctx, id, options = {}) => {
|
||||||
|
const conn = Self.dataSource.connector;
|
||||||
|
const args = ctx.args;
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
let tx;
|
||||||
|
if (!options.transaction) {
|
||||||
|
tx = await Self.beginTransaction({});
|
||||||
|
options.transaction = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entry = await models.Entry.findById(id, null, options);
|
||||||
|
await entry.updateAttributes({
|
||||||
|
observation: args.observation,
|
||||||
|
ref: args.ref
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
const buys = [];
|
||||||
|
for (let buy of args.buys) {
|
||||||
|
buys.push({
|
||||||
|
entryFk: entry.id,
|
||||||
|
itemFk: buy.itemFk,
|
||||||
|
stickers: 1,
|
||||||
|
quantity: 1,
|
||||||
|
packing: buy.packing,
|
||||||
|
grouping: buy.grouping,
|
||||||
|
buyingValue: buy.buyingValue,
|
||||||
|
packageFk: buy.packageFk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdBuys = await models.Buy.create(buys, options);
|
||||||
|
const buyIds = createdBuys.map(buy => buy.id);
|
||||||
|
|
||||||
|
let stmts = [];
|
||||||
|
let stmt;
|
||||||
|
|
||||||
|
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc');
|
||||||
|
stmt = new ParameterizedSQL(
|
||||||
|
`CREATE TEMPORARY TABLE tmp.buyRecalc
|
||||||
|
(INDEX (id))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT ? AS id`, [buyIds]);
|
||||||
|
|
||||||
|
stmts.push(stmt);
|
||||||
|
stmts.push('CALL buy_recalcPrices()');
|
||||||
|
|
||||||
|
const sql = ParameterizedSQL.join(stmts, ';');
|
||||||
|
await conn.executeStmt(sql, options);
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
} catch (e) {
|
||||||
|
if (tx) await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('importBuysPreview', {
|
||||||
|
description: 'Calculates the preview buys for an entry import',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The entry id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'buys',
|
||||||
|
type: ['Object'],
|
||||||
|
description: 'The buys',
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: ['Object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/importBuysPreview`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.importBuysPreview = async(id, buys) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
for (let buy of buys) {
|
||||||
|
const packaging = await models.Packaging.findOne({
|
||||||
|
fields: ['id'],
|
||||||
|
where: {volume: {gte: buy.volume}},
|
||||||
|
order: 'volume ASC'
|
||||||
|
});
|
||||||
|
buy.packageFk = packaging.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buys;
|
||||||
|
};
|
||||||
|
};
|
|
@ -37,11 +37,11 @@ module.exports = Self => {
|
||||||
}, {
|
}, {
|
||||||
arg: 'active',
|
arg: 'active',
|
||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
description: 'Whether the the item is or not active',
|
description: 'Whether the item is or not active',
|
||||||
}, {
|
}, {
|
||||||
arg: 'visible',
|
arg: 'visible',
|
||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
description: 'Whether the the item is or not visible',
|
description: 'Whether the item is or not visible',
|
||||||
}, {
|
}, {
|
||||||
arg: 'typeFk',
|
arg: 'typeFk',
|
||||||
type: 'Integer',
|
type: 'Integer',
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('entry import()', () => {
|
||||||
|
const buyerId = 35;
|
||||||
|
const companyId = 442;
|
||||||
|
const travelId = 1;
|
||||||
|
const supplierId = 1;
|
||||||
|
const activeCtx = {
|
||||||
|
accessToken: {userId: buyerId},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async done => {
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: activeCtx
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import the buy rows', async() => {
|
||||||
|
const expectedRef = '1, 2';
|
||||||
|
const expectedObservation = '123456';
|
||||||
|
const ctx = {
|
||||||
|
req: activeCtx,
|
||||||
|
args: {
|
||||||
|
observation: expectedObservation,
|
||||||
|
ref: expectedRef,
|
||||||
|
buys: [
|
||||||
|
{
|
||||||
|
itemFk: 1,
|
||||||
|
buyingValue: 5.77,
|
||||||
|
description: 'Bow',
|
||||||
|
grouping: 1,
|
||||||
|
packing: 1,
|
||||||
|
size: 1,
|
||||||
|
volume: 1200,
|
||||||
|
packageFk: '94'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemFk: 4,
|
||||||
|
buyingValue: 2.16,
|
||||||
|
description: 'Arrow',
|
||||||
|
grouping: 1,
|
||||||
|
packing: 1,
|
||||||
|
size: 25,
|
||||||
|
volume: 1125,
|
||||||
|
packageFk: '94'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tx = await app.models.Entry.beginTransaction({});
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const newEntry = await app.models.Entry.create({
|
||||||
|
dated: new Date(),
|
||||||
|
supplierFk: supplierId,
|
||||||
|
travelFk: travelId,
|
||||||
|
companyFk: companyId,
|
||||||
|
observation: 'The entry',
|
||||||
|
ref: 'Entry ref'
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
await app.models.Entry.importBuys(ctx, newEntry.id, options);
|
||||||
|
|
||||||
|
const updatedEntry = await app.models.Entry.findById(newEntry.id, null, options);
|
||||||
|
const entryBuys = await app.models.Buy.find({
|
||||||
|
where: {entryFk: newEntry.id}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
expect(updatedEntry.observation).toEqual(expectedObservation);
|
||||||
|
expect(updatedEntry.ref).toEqual(expectedRef);
|
||||||
|
expect(entryBuys.length).toEqual(2);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,41 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('entry importBuysPreview()', () => {
|
||||||
|
const entryId = 1;
|
||||||
|
beforeAll(async done => {
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: activeCtx
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the buys with the calculated packageFk', async() => {
|
||||||
|
const expectedPackageFk = '3';
|
||||||
|
const buys = [
|
||||||
|
{
|
||||||
|
itemFk: 1,
|
||||||
|
buyingValue: 5.77,
|
||||||
|
description: 'Bow',
|
||||||
|
grouping: 1,
|
||||||
|
size: 1,
|
||||||
|
volume: 1200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemFk: 4,
|
||||||
|
buyingValue: 2.16,
|
||||||
|
description: 'Arrow',
|
||||||
|
grouping: 1,
|
||||||
|
size: 25,
|
||||||
|
volume: 1125
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await app.models.Entry.importBuysPreview(entryId, buys);
|
||||||
|
const randomIndex = Math.floor(Math.random() * result.length);
|
||||||
|
const buy = result[randomIndex];
|
||||||
|
|
||||||
|
expect(buy.packageFk).toEqual(expectedPackageFk);
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,4 +2,6 @@ module.exports = Self => {
|
||||||
require('../methods/entry/filter')(Self);
|
require('../methods/entry/filter')(Self);
|
||||||
require('../methods/entry/getEntry')(Self);
|
require('../methods/entry/getEntry')(Self);
|
||||||
require('../methods/entry/getBuys')(Self);
|
require('../methods/entry/getBuys')(Self);
|
||||||
|
require('../methods/entry/importBuys')(Self);
|
||||||
|
require('../methods/entry/importBuysPreview')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"type": "String"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"isConfirmed": {
|
"isConfirmed": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
<mg-ajax path="dms/upload" options="vnPost"></mg-ajax>
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
data="$ctrl.dms">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
ng-submit="$ctrl.onSubmit()"
|
||||||
|
class="vn-ma-md">
|
||||||
|
<div class="vn-w-lg">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield vn-focus
|
||||||
|
vn-one
|
||||||
|
label="Reference"
|
||||||
|
ng-model="$ctrl.import.ref">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textarea
|
||||||
|
vn-one
|
||||||
|
label="Observation"
|
||||||
|
ng-model="$ctrl.import.observation">
|
||||||
|
</vn-textarea>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-input-file
|
||||||
|
vn-one
|
||||||
|
label="File"
|
||||||
|
ng-model="$ctrl.import.file"
|
||||||
|
on-change="$ctrl.onFileChange($event)"
|
||||||
|
accept="application/json"
|
||||||
|
required="true">
|
||||||
|
<append>
|
||||||
|
<vn-icon vn-none
|
||||||
|
color-marginal
|
||||||
|
title="{{'JSON files only' | translate}}"
|
||||||
|
icon="info">
|
||||||
|
</vn-icon>
|
||||||
|
</append>
|
||||||
|
</vn-input-file>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal ng-show="$ctrl.import.buys.length > 0">
|
||||||
|
<table class="vn-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th translate>Item</th>
|
||||||
|
<th translate expand>Description</th>
|
||||||
|
<th translate center>Size</th>
|
||||||
|
<th translate center>Packing</th>
|
||||||
|
<th translate center>Grouping</th>
|
||||||
|
<th translate center>Buying value</th>
|
||||||
|
<th translate center>Box</th>
|
||||||
|
<th translate center>Volume</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody ng-repeat="buy in $ctrl.import.buys">
|
||||||
|
<tr>
|
||||||
|
<td title="{{::buy.itemFk}}">
|
||||||
|
<vn-autocomplete
|
||||||
|
class="dense"
|
||||||
|
vn-focus
|
||||||
|
url="Items"
|
||||||
|
ng-model="buy.itemFk"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
search-function="$ctrl.itemSearchFunc($search)"
|
||||||
|
order="id DESC"
|
||||||
|
tabindex="1">
|
||||||
|
<tpl-item>
|
||||||
|
{{::id}} - {{::name}}
|
||||||
|
</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</td>
|
||||||
|
<td title="{{::buy.description}}" expand>{{::buy.description | dashIfEmpty}}</td>
|
||||||
|
<td center title="{{::buy.size}}">{{::buy.size | dashIfEmpty}}</td>
|
||||||
|
<td center>
|
||||||
|
<vn-chip>
|
||||||
|
<span>{{::buy.packing | dashIfEmpty}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
</td>
|
||||||
|
<td center>
|
||||||
|
<vn-chip>
|
||||||
|
<span>{{::buy.grouping | dashIfEmpty}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
</vn-td>
|
||||||
|
<td>{{::buy.buyingValue | currency: 'EUR':2}}</td>
|
||||||
|
<td center title="{{::buy.packageFk | dashIfEmpty}}">
|
||||||
|
<vn-autocomplete
|
||||||
|
vn-one
|
||||||
|
url="Packagings"
|
||||||
|
show-field="id"
|
||||||
|
value-field="id"
|
||||||
|
where="{isBox: true}"
|
||||||
|
ng-model="buy.packageFk">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</td>
|
||||||
|
<td center title="{{::buy.volume}}">{{::buy.volume | number}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-submit
|
||||||
|
label="Import buys">
|
||||||
|
</vn-submit>
|
||||||
|
<vn-button
|
||||||
|
class="cancel"
|
||||||
|
label="Cancel"
|
||||||
|
ui-sref="entry.card.buy.index">
|
||||||
|
</vn-button>
|
||||||
|
</vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,99 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
class Controller extends Section {
|
||||||
|
constructor($element, $) {
|
||||||
|
super($element, $);
|
||||||
|
this.import = {
|
||||||
|
file: '',
|
||||||
|
invoice: null,
|
||||||
|
buys: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileChange($event) {
|
||||||
|
const input = $event.target;
|
||||||
|
const file = input.files[0];
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = event =>
|
||||||
|
this.fillData(event.target.result);
|
||||||
|
reader.readAsText(file, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
fillData(raw) {
|
||||||
|
const data = JSON.parse(raw);
|
||||||
|
const [invoice] = data.invoices;
|
||||||
|
|
||||||
|
this.$.$applyAsync(() => {
|
||||||
|
this.import.observation = invoice.tx_awb;
|
||||||
|
|
||||||
|
const boxes = invoice.boxes;
|
||||||
|
const buys = [];
|
||||||
|
for (let box of boxes) {
|
||||||
|
const boxVolume = box.nu_length * box.nu_width * box.nu_height;
|
||||||
|
for (let product of box.products) {
|
||||||
|
const packing = product.nu_stems_bunch * product.nu_bunches;
|
||||||
|
buys.push({
|
||||||
|
description: product.nm_product,
|
||||||
|
size: product.nu_length,
|
||||||
|
packing: packing,
|
||||||
|
grouping: product.nu_stems_bunch,
|
||||||
|
buyingValue: parseFloat(product.mny_rate_stem),
|
||||||
|
volume: boxVolume
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const boxesId = boxes.map(box => box.id_box);
|
||||||
|
this.import.ref = boxesId.join(', ');
|
||||||
|
|
||||||
|
this.fetchBuys(buys);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchBuys(buys) {
|
||||||
|
const params = {buys};
|
||||||
|
const query = `Entries/${this.entry.id}/importBuysPreview`;
|
||||||
|
this.$http.get(query, {params}).then(res => {
|
||||||
|
this.import.buys = res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
try {
|
||||||
|
const params = this.import;
|
||||||
|
const hasAnyEmptyRow = params.buys.some(buy => {
|
||||||
|
return buy.itemFk == null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasAnyEmptyRow)
|
||||||
|
throw new Error(`Some of the imported buys doesn't have an item`);
|
||||||
|
|
||||||
|
const query = `Entries/${this.entry.id}/importBuys`;
|
||||||
|
return this.$http.post(query, params)
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
|
||||||
|
.then(() => this.$state.go('entry.card.buy.index'));
|
||||||
|
} catch (e) {
|
||||||
|
this.vnApp.showError(this.$t(e.message));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemSearchFunc($search) {
|
||||||
|
return /^\d+$/.test($search)
|
||||||
|
? {id: $search}
|
||||||
|
: {name: {like: '%' + $search + '%'}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnEntryBuyImport', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
worker: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,145 @@
|
||||||
|
import './index.js';
|
||||||
|
|
||||||
|
describe('Entry', () => {
|
||||||
|
describe('Component vnEntryBuyImport', () => {
|
||||||
|
let controller;
|
||||||
|
let $httpParamSerializer;
|
||||||
|
let $httpBackend;
|
||||||
|
|
||||||
|
beforeEach(ngModule('entry'));
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
$httpParamSerializer = _$httpParamSerializer_;
|
||||||
|
let $element = $compile('<vn-entry-buy-import-buys></vn-entry-latest-buys')($rootScope);
|
||||||
|
controller = $componentController('vnEntryBuyImport', {$element});
|
||||||
|
controller.entry = {
|
||||||
|
id: 1
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('fillData()', () => {
|
||||||
|
it(`should call to the fillData() method`, () => {
|
||||||
|
controller.fetchBuys = jest.fn();
|
||||||
|
|
||||||
|
const rawData = `{
|
||||||
|
"invoices": [
|
||||||
|
{
|
||||||
|
"tx_awb": "123456",
|
||||||
|
"boxes": [
|
||||||
|
{
|
||||||
|
"id_box": 1,
|
||||||
|
"nu_length": 1,
|
||||||
|
"nu_width": 15,
|
||||||
|
"nu_height": 80,
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"nm_product": "Bow",
|
||||||
|
"nu_length": 1,
|
||||||
|
"nu_stems_bunch": 1,
|
||||||
|
"nu_bunches": 1,
|
||||||
|
"mny_rate_stem": 5.77
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id_box": 2,
|
||||||
|
"nu_length": 25,
|
||||||
|
"nu_width": 1,
|
||||||
|
"nu_height": 45,
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"nm_product": "Arrow",
|
||||||
|
"nu_length": 25,
|
||||||
|
"nu_stems_bunch": 1,
|
||||||
|
"nu_bunches": 1,
|
||||||
|
"mny_rate_stem": 2.16
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}`;
|
||||||
|
const expectedBuys = [
|
||||||
|
{'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
|
||||||
|
{'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
|
||||||
|
];
|
||||||
|
controller.fillData(rawData);
|
||||||
|
controller.$.$apply();
|
||||||
|
|
||||||
|
const importData = controller.import;
|
||||||
|
|
||||||
|
expect(importData.observation).toEqual('123456');
|
||||||
|
expect(importData.ref).toEqual('1, 2');
|
||||||
|
|
||||||
|
expect(controller.fetchBuys).toHaveBeenCalledWith(expectedBuys);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchBuys()', () => {
|
||||||
|
it(`should perform a query to fetch the buys data`, () => {
|
||||||
|
const buys = [
|
||||||
|
{'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
|
||||||
|
{'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
|
||||||
|
];
|
||||||
|
|
||||||
|
const serializedParams = $httpParamSerializer({buys});
|
||||||
|
const query = `Entries/1/importBuysPreview?${serializedParams}`;
|
||||||
|
$httpBackend.expectGET(query).respond(200, buys);
|
||||||
|
controller.fetchBuys(buys);
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
const importData = controller.import;
|
||||||
|
|
||||||
|
expect(importData.buys.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onSubmit()', () => {
|
||||||
|
it(`should throw an error when some of the rows doesn't have an item`, () => {
|
||||||
|
jest.spyOn(controller.vnApp, 'showError');
|
||||||
|
|
||||||
|
controller.import = {
|
||||||
|
observation: '123456',
|
||||||
|
ref: '1, 2',
|
||||||
|
buys: [
|
||||||
|
{'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
|
||||||
|
{'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.onSubmit();
|
||||||
|
|
||||||
|
expect(controller.vnApp.showError).toHaveBeenCalledWith(`Some of the imported buys doesn't have an item`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should perform a query to update columns`, () => {
|
||||||
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
controller.$state.go = jest.fn();
|
||||||
|
|
||||||
|
controller.import = {
|
||||||
|
observation: '123456',
|
||||||
|
ref: '1, 2',
|
||||||
|
buys: [
|
||||||
|
{'itemFk': 10, 'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
|
||||||
|
{'itemFk': 11, 'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const params = controller.import;
|
||||||
|
|
||||||
|
const query = `Entries/1/importBuys`;
|
||||||
|
$httpBackend.expectPOST(query, params).respond(200, params.buys);
|
||||||
|
controller.onSubmit();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
const importData = controller.import;
|
||||||
|
|
||||||
|
expect(importData.buys.length).toEqual(2);
|
||||||
|
|
||||||
|
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||||
|
expect(controller.$state.go).toHaveBeenCalledWith('entry.card.buy.index');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
vn-entry-buy-import {
|
||||||
|
.vn-table > tbody td:nth-child(1) {
|
||||||
|
width: 250px
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<div fixed-bottom-right>
|
||||||
|
<vn-vertical style="align-items: center;">
|
||||||
|
<a ui-sref="entry.card.buy.import"
|
||||||
|
vn-bind="+"
|
||||||
|
vn-acl="buyer"
|
||||||
|
vn-acl-action="remove">
|
||||||
|
<vn-button class="round md vn-mb-sm"
|
||||||
|
icon="publish"
|
||||||
|
vn-tooltip="Import buys"
|
||||||
|
tooltip-position="left">
|
||||||
|
</vn-button>
|
||||||
|
</a>
|
||||||
|
</vn-vertical>
|
||||||
|
</div>
|
|
@ -1,7 +1,7 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../../module';
|
||||||
import Section from 'salix/components/section';
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
ngModule.vnComponent('vnEntryBuy', {
|
ngModule.vnComponent('vnEntryBuyIndex', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Section,
|
controller: Section,
|
||||||
bindings: {
|
bindings: {
|
|
@ -0,0 +1 @@
|
||||||
|
Buy: Lineas de entrada
|
|
@ -1 +1,5 @@
|
||||||
Buy: Lineas de entrada
|
reference: Referencia
|
||||||
|
Observation: Observación
|
||||||
|
Box: Embalaje
|
||||||
|
Import buys: Importar compras
|
||||||
|
Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo
|
|
@ -13,4 +13,6 @@ import './card';
|
||||||
import './note';
|
import './note';
|
||||||
import './summary';
|
import './summary';
|
||||||
import './log';
|
import './log';
|
||||||
import './buy';
|
import './buy/index';
|
||||||
|
import './buy/import';
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<vn-tbody>
|
<vn-tbody>
|
||||||
<a ng-repeat="buy in $ctrl.buys"
|
<a ng-repeat="buy in $ctrl.buys"
|
||||||
class="clickable vn-tr search-result"
|
class="clickable vn-tr search-result"
|
||||||
ui-sref="entry.card.buy({id: {{::buy.entryFk}}})">
|
ui-sref="entry.card.buy.index({id: {{::buy.entryFk}}})">
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-check
|
<vn-check
|
||||||
ng-model="buy.checked"
|
ng-model="buy.checked"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
],
|
],
|
||||||
"card": [
|
"card": [
|
||||||
{"state": "entry.card.basicData", "icon": "settings"},
|
{"state": "entry.card.basicData", "icon": "settings"},
|
||||||
{"state": "entry.card.buy", "icon": "icon-lines"},
|
{"state": "entry.card.buy.index", "icon": "icon-lines"},
|
||||||
{"state": "entry.card.observation", "icon": "insert_drive_file"},
|
{"state": "entry.card.observation", "icon": "insert_drive_file"},
|
||||||
{"state": "entry.card.log", "icon": "history"}
|
{"state": "entry.card.log", "icon": "history"}
|
||||||
]
|
]
|
||||||
|
@ -56,7 +56,8 @@
|
||||||
"description": "Summary",
|
"description": "Summary",
|
||||||
"params": {
|
"params": {
|
||||||
"entry": "$ctrl.entry"
|
"entry": "$ctrl.entry"
|
||||||
}
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
}, {
|
}, {
|
||||||
"url": "/basic-data",
|
"url": "/basic-data",
|
||||||
"state": "entry.card.basicData",
|
"state": "entry.card.basicData",
|
||||||
|
@ -64,7 +65,8 @@
|
||||||
"description": "Basic data",
|
"description": "Basic data",
|
||||||
"params": {
|
"params": {
|
||||||
"entry": "$ctrl.entry"
|
"entry": "$ctrl.entry"
|
||||||
}
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
},{
|
},{
|
||||||
"url": "/observation",
|
"url": "/observation",
|
||||||
"state": "entry.card.observation",
|
"state": "entry.card.observation",
|
||||||
|
@ -72,20 +74,40 @@
|
||||||
"description": "Notes",
|
"description": "Notes",
|
||||||
"params": {
|
"params": {
|
||||||
"entry": "$ctrl.entry"
|
"entry": "$ctrl.entry"
|
||||||
}
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
},{
|
},{
|
||||||
"url" : "/log",
|
"url" : "/log",
|
||||||
"state": "entry.card.log",
|
"state": "entry.card.log",
|
||||||
"component": "vn-entry-log",
|
"component": "vn-entry-log",
|
||||||
"description": "Log"
|
"description": "Log",
|
||||||
}, {
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
"url": "/buy",
|
"url": "/buy",
|
||||||
"state": "entry.card.buy",
|
"state": "entry.card.buy",
|
||||||
"component": "vn-entry-buy",
|
"abstract": true,
|
||||||
|
"component": "ui-view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url" : "/index",
|
||||||
|
"state": "entry.card.buy.index",
|
||||||
|
"component": "vn-entry-buy-index",
|
||||||
"description": "Buy",
|
"description": "Buy",
|
||||||
"params": {
|
"params": {
|
||||||
"entry": "$ctrl.entry"
|
"entry": "$ctrl.entry"
|
||||||
}
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url" : "/import",
|
||||||
|
"state": "entry.card.buy.import",
|
||||||
|
"component": "vn-entry-buy-import",
|
||||||
|
"description": "Import buys",
|
||||||
|
"params": {
|
||||||
|
"entry": "$ctrl.entry"
|
||||||
|
},
|
||||||
|
"acl": ["buyer"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -21,10 +21,20 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.book = async ref => {
|
Self.book = async ref => {
|
||||||
let ticketAddress = await Self.app.models.Ticket.findOne({where: {invoiceOut: ref}});
|
const models = Self.app.models;
|
||||||
let invoiceCompany = await Self.app.models.InvoiceOut.findOne({where: {ref: ref}});
|
const ticketAddress = await models.Ticket.findOne({
|
||||||
let [taxArea] = await Self.rawSql(`Select vn.addressTaxArea(?, ?) AS code`, [ticketAddress.address, invoiceCompany.company]);
|
where: {invoiceOut: ref}
|
||||||
|
});
|
||||||
|
const invoiceCompany = await models.InvoiceOut.findOne({
|
||||||
|
where: {ref: ref}
|
||||||
|
});
|
||||||
|
let query = 'SELECT vn.addressTaxArea(?, ?) AS code';
|
||||||
|
const [taxArea] = await Self.rawSql(query, [
|
||||||
|
ticketAddress.address,
|
||||||
|
invoiceCompany.company
|
||||||
|
]);
|
||||||
|
|
||||||
return Self.rawSql(`CALL vn.invoiceOutAgain(?, ?)`, [ref, taxArea.code]);
|
query = 'CALL vn.invoiceOutAgain(?, ?)';
|
||||||
|
return Self.rawSql(query, [ref, taxArea.code]);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const got = require('got');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('createPdf', {
|
||||||
|
description: 'Creates an invoice PDF',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The invoice id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/createPdf`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.createPdf = async function(ctx, id, options) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const headers = ctx.req.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
const authorization = headers.authorization;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV == 'test')
|
||||||
|
throw new UserError(`Action not allowed on the test environment`);
|
||||||
|
|
||||||
|
let tx;
|
||||||
|
let newOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(newOptions, options);
|
||||||
|
|
||||||
|
if (!newOptions.transaction) {
|
||||||
|
tx = await Self.beginTransaction({});
|
||||||
|
newOptions.transaction = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileSrc;
|
||||||
|
try {
|
||||||
|
const invoiceOut = await Self.findById(id, null, newOptions);
|
||||||
|
await invoiceOut.updateAttributes({
|
||||||
|
hasPdf: true
|
||||||
|
}, newOptions);
|
||||||
|
|
||||||
|
const response = got.stream(`${origin}/api/report/invoice`, {
|
||||||
|
query: {
|
||||||
|
authorization: authorization,
|
||||||
|
invoiceId: id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const invoiceYear = invoiceOut.created.getFullYear().toString();
|
||||||
|
const container = await models.InvoiceContainer.container(invoiceYear);
|
||||||
|
const rootPath = container.client.root;
|
||||||
|
const fileName = `${invoiceOut.ref}.pdf`;
|
||||||
|
fileSrc = path.join(rootPath, invoiceYear, fileName);
|
||||||
|
|
||||||
|
const writeStream = fs.createWriteStream(fileSrc);
|
||||||
|
writeStream.on('open', () => {
|
||||||
|
response.pipe(writeStream);
|
||||||
|
});
|
||||||
|
|
||||||
|
writeStream.on('finish', async function() {
|
||||||
|
writeStream.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
return invoiceOut;
|
||||||
|
} catch (e) {
|
||||||
|
if (tx) await tx.rollback();
|
||||||
|
if (fs.existsSync(fileSrc))
|
||||||
|
await fs.unlink(fileSrc);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,51 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethodCtx('regenerate', {
|
|
||||||
description: 'Sends an invoice to a regeneration queue',
|
|
||||||
accessType: 'WRITE',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
description: 'The invoiceOut id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: '/:id/regenerate',
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.regenerate = async(ctx, id) => {
|
|
||||||
const userId = ctx.req.accessToken.userId;
|
|
||||||
const models = Self.app.models;
|
|
||||||
const invoiceReportFk = 30; // Should be deprecated
|
|
||||||
const worker = await models.Worker.findOne({where: {userFk: userId}});
|
|
||||||
const tx = await Self.beginTransaction({});
|
|
||||||
|
|
||||||
try {
|
|
||||||
let options = {transaction: tx};
|
|
||||||
|
|
||||||
// Remove all invoice references from tickets
|
|
||||||
const invoiceOut = await models.InvoiceOut.findById(id, null, options);
|
|
||||||
await invoiceOut.updateAttributes({
|
|
||||||
hasPdf: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send to print queue
|
|
||||||
await Self.rawSql(`
|
|
||||||
INSERT INTO vn.printServerQueue (reportFk, param1, workerFk)
|
|
||||||
VALUES (?, ?, ?)`, [invoiceReportFk, id, worker.id], options);
|
|
||||||
|
|
||||||
await tx.commit();
|
|
||||||
|
|
||||||
return invoiceOut;
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
const got = require('got');
|
||||||
|
|
||||||
|
describe('InvoiceOut createPdf()', () => {
|
||||||
|
const userId = 1;
|
||||||
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
|
||||||
|
accessToken: {userId: userId},
|
||||||
|
headers: {origin: 'http://localhost:5000'},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should create a new PDF file and set true the hasPdf property', async() => {
|
||||||
|
const invoiceId = 1;
|
||||||
|
const response = {
|
||||||
|
pipe: () => {},
|
||||||
|
on: () => {},
|
||||||
|
};
|
||||||
|
spyOn(got, 'stream').and.returnValue(response);
|
||||||
|
|
||||||
|
let result = await app.models.InvoiceOut.createPdf(ctx, invoiceId);
|
||||||
|
|
||||||
|
expect(result.hasPdf).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,36 +0,0 @@
|
||||||
const app = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('invoiceOut regenerate()', () => {
|
|
||||||
const invoiceReportFk = 30;
|
|
||||||
const invoiceOutId = 1;
|
|
||||||
|
|
||||||
it('should check that the invoice has a PDF and is not in print generation queue', async() => {
|
|
||||||
const invoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
|
|
||||||
const [queue] = await app.models.InvoiceOut.rawSql(`
|
|
||||||
SELECT COUNT(*) AS total
|
|
||||||
FROM vn.printServerQueue
|
|
||||||
WHERE reportFk = ?`, [invoiceReportFk]);
|
|
||||||
|
|
||||||
expect(invoiceOut.hasPdf).toBeTruthy();
|
|
||||||
expect(queue.total).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should mark the invoice as doesn't have PDF and add it to a print queue`, async() => {
|
|
||||||
const ctx = {req: {accessToken: {userId: 5}}};
|
|
||||||
const invoiceOut = await app.models.InvoiceOut.regenerate(ctx, invoiceOutId);
|
|
||||||
const [queue] = await app.models.InvoiceOut.rawSql(`
|
|
||||||
SELECT COUNT(*) AS total
|
|
||||||
FROM vn.printServerQueue
|
|
||||||
WHERE reportFk = ?`, [invoiceReportFk]);
|
|
||||||
|
|
||||||
expect(invoiceOut.hasPdf).toBeFalsy();
|
|
||||||
expect(queue.total).toEqual(1);
|
|
||||||
|
|
||||||
// restores
|
|
||||||
const invoiceOutToRestore = await app.models.InvoiceOut.findById(invoiceOutId);
|
|
||||||
await invoiceOutToRestore.updateAttributes({hasPdf: true});
|
|
||||||
await app.models.InvoiceOut.rawSql(`
|
|
||||||
DELETE FROM vn.printServerQueue
|
|
||||||
WHERE reportFk = ?`, [invoiceReportFk]);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"InvoiceOut": {
|
"InvoiceOut": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"InvoiceContainer": {
|
||||||
|
"dataSource": "invoiceStorage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "InvoiceContainer",
|
||||||
|
"base": "Container",
|
||||||
|
"acls": [{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ module.exports = Self => {
|
||||||
require('../methods/invoiceOut/filter')(Self);
|
require('../methods/invoiceOut/filter')(Self);
|
||||||
require('../methods/invoiceOut/summary')(Self);
|
require('../methods/invoiceOut/summary')(Self);
|
||||||
require('../methods/invoiceOut/download')(Self);
|
require('../methods/invoiceOut/download')(Self);
|
||||||
require('../methods/invoiceOut/regenerate')(Self);
|
|
||||||
require('../methods/invoiceOut/delete')(Self);
|
require('../methods/invoiceOut/delete')(Self);
|
||||||
require('../methods/invoiceOut/book')(Self);
|
require('../methods/invoiceOut/book')(Self);
|
||||||
|
require('../methods/invoiceOut/createPdf')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,14 @@
|
||||||
translate>
|
translate>
|
||||||
Book invoice
|
Book invoice
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
<vn-item
|
||||||
|
ng-click="createInvoicePdfConfirmation.show()"
|
||||||
|
vn-acl="invoicing"
|
||||||
|
vn-acl-action="remove"
|
||||||
|
name="regenerateInvoice"
|
||||||
|
translate>
|
||||||
|
Regenerate invoice PDF
|
||||||
|
</vn-item>
|
||||||
</slot-menu>
|
</slot-menu>
|
||||||
<slot-body>
|
<slot-body>
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
|
@ -82,3 +90,11 @@
|
||||||
<vn-client-descriptor-popover
|
<vn-client-descriptor-popover
|
||||||
vn-id="clientDescriptor">
|
vn-id="clientDescriptor">
|
||||||
</vn-client-descriptor-popover>
|
</vn-client-descriptor-popover>
|
||||||
|
|
||||||
|
<!-- Create invoice PDF confirmation dialog -->
|
||||||
|
<vn-confirm
|
||||||
|
vn-id="createInvoicePdfConfirmation"
|
||||||
|
on-accept="$ctrl.createInvoicePdf()"
|
||||||
|
question="Are you sure you want to regenerate the invoice PDF document?"
|
||||||
|
message="You are going to regenerate the invoice PDF document">
|
||||||
|
</vn-confirm>
|
|
@ -22,6 +22,16 @@ class Controller extends Descriptor {
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
|
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createInvoicePdf() {
|
||||||
|
const invoiceId = this.invoiceOut.id;
|
||||||
|
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
|
||||||
|
.then(() => {
|
||||||
|
const snackbarMessage = this.$t(
|
||||||
|
`The invoice PDF document has been regenerated`);
|
||||||
|
this.vnApp.showSuccess(snackbarMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get filter() {
|
get filter() {
|
||||||
if (this.invoiceOut)
|
if (this.invoiceOut)
|
||||||
return JSON.stringify({refFk: this.invoiceOut.ref});
|
return JSON.stringify({refFk: this.invoiceOut.ref});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import './index';
|
||||||
describe('vnInvoiceOutDescriptor', () => {
|
describe('vnInvoiceOutDescriptor', () => {
|
||||||
let controller;
|
let controller;
|
||||||
let $httpBackend;
|
let $httpBackend;
|
||||||
|
const invoiceOut = {id: 1};
|
||||||
|
|
||||||
beforeEach(ngModule('invoiceOut'));
|
beforeEach(ngModule('invoiceOut'));
|
||||||
|
|
||||||
|
@ -11,6 +12,20 @@ describe('vnInvoiceOutDescriptor', () => {
|
||||||
controller = $componentController('vnInvoiceOutDescriptor', {$element: null});
|
controller = $componentController('vnInvoiceOutDescriptor', {$element: null});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
describe('createInvoicePdf()', () => {
|
||||||
|
it('should make a query and show a success snackbar', () => {
|
||||||
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
|
controller.invoiceOut = invoiceOut;
|
||||||
|
|
||||||
|
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
|
||||||
|
controller.createInvoicePdf();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('loadData()', () => {
|
describe('loadData()', () => {
|
||||||
it(`should perform a get query to store the invoice in data into the controller`, () => {
|
it(`should perform a get query to store the invoice in data into the controller`, () => {
|
||||||
const id = 1;
|
const id = 1;
|
||||||
|
|
|
@ -9,3 +9,5 @@ Are you sure you want to delete this invoice?: Estas seguro de eliminar esta fac
|
||||||
Book invoice: Asentar factura
|
Book invoice: Asentar factura
|
||||||
InvoiceOut booked: Factura asentada
|
InvoiceOut booked: Factura asentada
|
||||||
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
|
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
|
||||||
|
Regenerate invoice PDF: Regenerar PDF factura
|
||||||
|
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
|
|
@ -24,7 +24,7 @@
|
||||||
class="clickable vn-tr search-result"
|
class="clickable vn-tr search-result"
|
||||||
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
||||||
<vn-td>{{::invoiceOut.ref | dashIfEmpty}}</vn-td>
|
<vn-td>{{::invoiceOut.ref | dashIfEmpty}}</vn-td>
|
||||||
<vn-td expand>{{::invoiceOut.issued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td shrink>{{::invoiceOut.issued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td number>{{::invoiceOut.amount | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
|
<vn-td number>{{::invoiceOut.amount | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>
|
<vn-td>
|
||||||
<span
|
<span
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>{{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td expand>{{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>{{::invoiceOut.companyCode | dashIfEmpty}}</vn-td>
|
<vn-td>{{::invoiceOut.companyCode | dashIfEmpty}}</vn-td>
|
||||||
<vn-td expand>{{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td shrink>{{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>
|
<vn-td>
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
ng-show="invoiceOut.hasPdf"
|
ng-show="invoiceOut.hasPdf"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<vn-th field="clientFk">Client</vn-th>
|
<vn-th field="clientFk">Client</vn-th>
|
||||||
<vn-th field="isConfirmed" center>Confirmed</vn-th>
|
<vn-th field="isConfirmed" center>Confirmed</vn-th>
|
||||||
<vn-th field="created" center expand>Created</vn-th>
|
<vn-th field="created" center expand>Created</vn-th>
|
||||||
<vn-th field="landed" default-order="DESC" center expand>Landed</vn-th>
|
<vn-th field="landed" default-order="DESC" shrink-date>Landed</vn-th>
|
||||||
<vn-th field="created" center>Hour</vn-th>
|
<vn-th field="created" center>Hour</vn-th>
|
||||||
<vn-th field="agencyName" center>Agency</vn-th>
|
<vn-th field="agencyName" center>Agency</vn-th>
|
||||||
<vn-th center>Total</vn-th>
|
<vn-th center>Total</vn-th>
|
||||||
|
@ -46,8 +46,8 @@
|
||||||
disabled="true">
|
disabled="true">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td center expand>{{::order.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
|
<vn-td center>{{::order.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||||
<vn-td center expand>
|
<vn-td shrink-date>
|
||||||
<span class="chip {{$ctrl.compareDate(order.landed)}}">
|
<span class="chip {{$ctrl.compareDate(order.landed)}}">
|
||||||
{{::order.landed | date:'dd/MM/yyyy'}}
|
{{::order.landed | date:'dd/MM/yyyy'}}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<vn-th th-id="worker">Worker</vn-th>
|
<vn-th th-id="worker">Worker</vn-th>
|
||||||
<vn-th th-id="agency">Agency</vn-th>
|
<vn-th th-id="agency">Agency</vn-th>
|
||||||
<vn-th th-id="vehicle">Vehicle</vn-th>
|
<vn-th th-id="vehicle">Vehicle</vn-th>
|
||||||
<vn-th th-id="created" expand>Date</vn-th>
|
<vn-th th-id="created" shrink-date>Date</vn-th>
|
||||||
<vn-th th-id="m3" number>m³</vn-th>
|
<vn-th th-id="m3" number>m³</vn-th>
|
||||||
<vn-th th-id="description">Description</vn-th>
|
<vn-th th-id="description">Description</vn-th>
|
||||||
<vn-th shrink></vn-th>
|
<vn-th shrink></vn-th>
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td>{{::route.agencyName | dashIfEmpty}}</vn-td>
|
<vn-td>{{::route.agencyName | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>{{::route.vehiclePlateNumber | dashIfEmpty}}</vn-td>
|
<vn-td>{{::route.vehiclePlateNumber | dashIfEmpty}}</vn-td>
|
||||||
<vn-td expand>{{::route.created | dashIfEmpty | date:'dd/MM/yyyy'}}</vn-td>
|
<vn-td shrink-date>{{::route.created | dashIfEmpty | date:'dd/MM/yyyy'}}</vn-td>
|
||||||
<vn-td number>{{::route.m3 | dashIfEmpty}}</vn-td>
|
<vn-td number>{{::route.m3 | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>{{::route.description | dashIfEmpty}}</vn-td>
|
<vn-td>{{::route.description | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>
|
<vn-td>
|
||||||
|
|
|
@ -74,12 +74,13 @@ module.exports = Self => {
|
||||||
|
|
||||||
const origin = ctx.req.headers.origin;
|
const origin = ctx.req.headers.origin;
|
||||||
const requesterId = request.requesterFk;
|
const requesterId = request.requesterFk;
|
||||||
const message = $t('MESSAGE_BOUGHT_UNITS', {
|
const message = $t('Bought units from buy request', {
|
||||||
quantity: sale.quantity,
|
quantity: sale.quantity,
|
||||||
concept: sale.concept,
|
concept: sale.concept,
|
||||||
itemId: sale.itemFk,
|
itemId: sale.itemFk,
|
||||||
ticketId: sale.ticketFk,
|
ticketId: sale.ticketFk,
|
||||||
url: `${origin}/#!/ticket/${sale.ticketFk}/summary`
|
url: `${origin}/#!/ticket/${sale.ticketFk}/summary`,
|
||||||
|
urlItem: `${origin}/#!/item/${sale.itemFk}/summary`
|
||||||
});
|
});
|
||||||
await models.Chat.sendCheckingPresence(ctx, requesterId, message);
|
await models.Chat.sendCheckingPresence(ctx, requesterId, message);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
Self.remoteMethodCtx('canBeInvoiced', {
|
Self.remoteMethodCtx('canBeInvoiced', {
|
||||||
description: 'Change property isEqualizated in all client addresses',
|
description: 'Whether the ticket can or not be invoiced',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
type: 'string',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Client id',
|
description: 'The ticket id',
|
||||||
http: {source: 'path'}
|
http: {source: 'path'}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -23,8 +23,9 @@ module.exports = function(Self) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.canBeInvoiced = async id => {
|
Self.canBeInvoiced = async id => {
|
||||||
let ticket = await Self.app.models.Ticket.findById(id, {fields: ['id', 'refFk', 'shipped']});
|
let ticket = await Self.app.models.Ticket.findById(id, {
|
||||||
let ticketTotal = await Self.app.models.Ticket.getTotal(id);
|
fields: ['id', 'refFk', 'shipped', 'totalWithVat']
|
||||||
|
});
|
||||||
|
|
||||||
let query = `SELECT vn.hasSomeNegativeBase(?) AS hasSomeNegativeBase`;
|
let query = `SELECT vn.hasSomeNegativeBase(?) AS hasSomeNegativeBase`;
|
||||||
let [result] = await Self.rawSql(query, [id]);
|
let [result] = await Self.rawSql(query, [id]);
|
||||||
|
@ -33,7 +34,7 @@ module.exports = function(Self) {
|
||||||
let today = new Date();
|
let today = new Date();
|
||||||
let shipped = new Date(ticket.shipped);
|
let shipped = new Date(ticket.shipped);
|
||||||
|
|
||||||
if (ticket.refFk || ticketTotal == 0 || shipped.getTime() > today.getTime() || hasSomeNegativeBase)
|
if (ticket.refFk || ticket.totalWithVat == 0 || shipped.getTime() > today.getTime() || hasSomeNegativeBase)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -203,6 +203,8 @@ module.exports = Self => {
|
||||||
t.routeFk,
|
t.routeFk,
|
||||||
t.warehouseFk,
|
t.warehouseFk,
|
||||||
t.clientFk,
|
t.clientFk,
|
||||||
|
t.totalWithoutVat,
|
||||||
|
t.totalWithVat,
|
||||||
io.id AS invoiceOutId,
|
io.id AS invoiceOutId,
|
||||||
a.provinceFk,
|
a.provinceFk,
|
||||||
p.name AS province,
|
p.name AS province,
|
||||||
|
@ -246,6 +248,7 @@ module.exports = Self => {
|
||||||
stmts.push(stmt);
|
stmts.push(stmt);
|
||||||
|
|
||||||
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticketGetProblems');
|
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticketGetProblems');
|
||||||
|
|
||||||
stmts.push(`
|
stmts.push(`
|
||||||
CREATE TEMPORARY TABLE tmp.ticketGetProblems
|
CREATE TEMPORARY TABLE tmp.ticketGetProblems
|
||||||
(INDEX (ticketFk))
|
(INDEX (ticketFk))
|
||||||
|
@ -255,23 +258,15 @@ module.exports = Self => {
|
||||||
LEFT JOIN alertLevel al ON al.alertLevel = f.alertLevel
|
LEFT JOIN alertLevel al ON al.alertLevel = f.alertLevel
|
||||||
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
|
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
|
||||||
AND f.shipped >= CURDATE()`);
|
AND f.shipped >= CURDATE()`);
|
||||||
stmts.push('CALL ticketGetProblems()');
|
|
||||||
|
|
||||||
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket');
|
stmts.push('CALL ticketGetProblems()');
|
||||||
stmts.push(`
|
|
||||||
CREATE TEMPORARY TABLE tmp.ticket
|
|
||||||
(INDEX (ticketFk)) ENGINE = MEMORY
|
|
||||||
SELECT id ticketFk FROM tmp.filter`);
|
|
||||||
stmts.push('CALL ticketGetTotal()');
|
|
||||||
|
|
||||||
stmt = new ParameterizedSQL(`
|
stmt = new ParameterizedSQL(`
|
||||||
SELECT
|
SELECT
|
||||||
f.*,
|
f.*,
|
||||||
tt.total,
|
|
||||||
tp.*
|
tp.*
|
||||||
FROM tmp.filter f
|
FROM tmp.filter f
|
||||||
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id
|
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id`);
|
||||||
LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`);
|
|
||||||
|
|
||||||
if (args.problems != undefined && (!args.from && !args.to))
|
if (args.problems != undefined && (!args.from && !args.to))
|
||||||
throw new UserError('Choose a date range or days forward');
|
throw new UserError('Choose a date range or days forward');
|
||||||
|
@ -308,13 +303,12 @@ module.exports = Self => {
|
||||||
|
|
||||||
stmt.merge(conn.makeOrderBy(filter.order));
|
stmt.merge(conn.makeOrderBy(filter.order));
|
||||||
stmt.merge(conn.makeLimit(filter));
|
stmt.merge(conn.makeLimit(filter));
|
||||||
let ticketsIndex = stmts.push(stmt);
|
let ticketsIndex = stmts.push(stmt) - 1;
|
||||||
|
|
||||||
stmts.push(
|
stmts.push(
|
||||||
`DROP TEMPORARY TABLE
|
`DROP TEMPORARY TABLE
|
||||||
tmp.filter,
|
tmp.filter,
|
||||||
tmp.ticket,
|
tmp.ticket,
|
||||||
tmp.ticketTotal,
|
|
||||||
tmp.ticketGetProblems`);
|
tmp.ticketGetProblems`);
|
||||||
|
|
||||||
let sql = ParameterizedSQL.join(stmts, ';');
|
let sql = ParameterizedSQL.join(stmts, ';');
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('getTaxes', {
|
|
||||||
description: 'Returns ticket taxes',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
description: 'ticket id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'number',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/:id/getTaxes`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.getTaxes = async ticketFk => {
|
|
||||||
let query = `CALL vn.ticketGetTaxAdd(?)`;
|
|
||||||
let [taxes] = await Self.rawSql(query, [ticketFk]);
|
|
||||||
|
|
||||||
return taxes;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('getTotal', {
|
|
||||||
description: 'Returns the total of a ticket',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
description: 'ticket id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'number',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/:id/getTotal`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.getTotal = async ticketFk => {
|
|
||||||
let query = `SELECT vn.ticketGetTotal(?) AS amount`;
|
|
||||||
let [total] = await Self.rawSql(query, [ticketFk]);
|
|
||||||
|
|
||||||
return total.amount ? total.amount : 0.00;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('getVAT', {
|
|
||||||
description: 'Returns ticket total VAT',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
description: 'ticket id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'number',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/:id/getVAT`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.getVAT = async ticketFk => {
|
|
||||||
let totalTax = 0.00;
|
|
||||||
let taxes = await Self.app.models.Ticket.getTaxes(ticketFk);
|
|
||||||
|
|
||||||
taxes.forEach(tax => {
|
|
||||||
totalTax += tax.tax;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Math.round(totalTax * 100) / 100;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -67,11 +67,7 @@ module.exports = function(Self) {
|
||||||
|
|
||||||
if (serial != 'R' && invoiceId) {
|
if (serial != 'R' && invoiceId) {
|
||||||
await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], options);
|
await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], options);
|
||||||
await models.PrintServerQueue.create({
|
await models.InvoiceOut.createPdf(ctx, invoiceId, options);
|
||||||
reportFk: 3, // Tarea #2734 (Nueva): crear informe facturas
|
|
||||||
param1: invoiceId,
|
|
||||||
workerFk: userId
|
|
||||||
}, options);
|
|
||||||
}
|
}
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
const app = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('ticket getTaxes()', () => {
|
|
||||||
it('should return the tax of a given ticket', async() => {
|
|
||||||
let result = await app.models.Ticket.getTaxes(1);
|
|
||||||
|
|
||||||
expect(result[0].tax).toEqual(7.1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,15 +0,0 @@
|
||||||
const app = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('ticket getTotal()', () => {
|
|
||||||
it('should return the total of a ticket', async() => {
|
|
||||||
let result = await app.models.Ticket.getTotal(1);
|
|
||||||
|
|
||||||
expect(result).toEqual(891.87);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should return zero if the ticket doesn't have lines`, async() => {
|
|
||||||
let result = await app.models.Ticket.getTotal(21);
|
|
||||||
|
|
||||||
expect(result).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
const app = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('ticket getVAT()', () => {
|
|
||||||
it('should return the ticket VAT', async() => {
|
|
||||||
await app.models.Ticket.getVAT(1)
|
|
||||||
.then(response => {
|
|
||||||
expect(response).toEqual(84.64);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -5,6 +5,7 @@ describe('ticket makeInvoice()', () => {
|
||||||
const userId = 19;
|
const userId = 19;
|
||||||
const activeCtx = {
|
const activeCtx = {
|
||||||
accessToken: {userId: userId},
|
accessToken: {userId: userId},
|
||||||
|
headers: {origin: 'http://localhost:5000'},
|
||||||
};
|
};
|
||||||
const ctx = {req: activeCtx};
|
const ctx = {req: activeCtx};
|
||||||
|
|
||||||
|
@ -43,6 +44,9 @@ describe('ticket makeInvoice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should invoice a ticket, then try again to fail', async() => {
|
it('should invoice a ticket, then try again to fail', async() => {
|
||||||
|
const invoiceOutModel = app.models.InvoiceOut;
|
||||||
|
spyOn(invoiceOutModel, 'createPdf');
|
||||||
|
|
||||||
invoice = await app.models.Ticket.makeInvoice(ctx, ticketId);
|
invoice = await app.models.Ticket.makeInvoice(ctx, ticketId);
|
||||||
|
|
||||||
expect(invoice.invoiceFk).toBeDefined();
|
expect(invoice.invoiceFk).toBeDefined();
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
const app = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('ticket subtotal()', () => {
|
|
||||||
it('should return the subtotal of a ticket', async() => {
|
|
||||||
let result = await app.models.Ticket.subtotal(1);
|
|
||||||
|
|
||||||
expect(result).toEqual(809.23);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should return 0 if the ticket doesn't have lines`, async() => {
|
|
||||||
let result = await app.models.Ticket.subtotal(21);
|
|
||||||
|
|
||||||
expect(result).toEqual(0.00);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -14,23 +14,15 @@ describe('ticket summary()', () => {
|
||||||
expect(result.sales.length).toEqual(4);
|
expect(result.sales.length).toEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a summary object containing subtotal for 1 ticket', async() => {
|
it('should return a summary object containing totalWithoutVat for 1 ticket', async() => {
|
||||||
let result = await app.models.Ticket.summary(1);
|
let result = await app.models.Ticket.summary(1);
|
||||||
|
|
||||||
expect(Math.round(result.subtotal * 100) / 100).toEqual(809.23);
|
expect(result.totalWithoutVat).toEqual(807.23);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a summary object containing VAT for 1 ticket', async() => {
|
|
||||||
let result = await app.models.Ticket.summary(1);
|
|
||||||
|
|
||||||
expect(Math.round(result.vat * 100) / 100).toEqual(84.64);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
let result = await app.models.Ticket.summary(1);
|
||||||
let total = result.subtotal + result.vat;
|
|
||||||
let expectedTotal = Math.round(total * 100) / 100;
|
|
||||||
|
|
||||||
expect(result.total).toEqual(expectedTotal);
|
expect(result.totalWithVat).toEqual(891.87);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('subtotal', {
|
|
||||||
description: 'Returns the total of a ticket',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
description: 'ticket id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}],
|
|
||||||
returns: {
|
|
||||||
type: 'number',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/:id/subtotal`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.subtotal = async ticketFk => {
|
|
||||||
const sale = Self.app.models.Sale;
|
|
||||||
const ticketSales = await sale.find({where: {ticketFk}});
|
|
||||||
const ticketService = Self.app.models.TicketService;
|
|
||||||
const ticketServices = await ticketService.find({where: {ticketFk}});
|
|
||||||
|
|
||||||
let subtotal = 0.00;
|
|
||||||
ticketSales.forEach(sale => {
|
|
||||||
subtotal += sale.price * sale.quantity * ((100 - sale.discount) / 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
ticketServices.forEach(service => {
|
|
||||||
subtotal += service.price * service.quantity;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Math.round(subtotal * 100) / 100;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -23,9 +23,6 @@ module.exports = Self => {
|
||||||
let models = Self.app.models;
|
let models = Self.app.models;
|
||||||
let summaryObj = await getTicketData(Self, ticketFk);
|
let summaryObj = await getTicketData(Self, ticketFk);
|
||||||
summaryObj.sales = await getSales(models.Sale, ticketFk);
|
summaryObj.sales = await getSales(models.Sale, ticketFk);
|
||||||
summaryObj.subtotal = await models.Ticket.subtotal(ticketFk);
|
|
||||||
summaryObj.vat = await models.Ticket.getVAT(ticketFk);
|
|
||||||
summaryObj.total = summaryObj.subtotal + summaryObj.vat;
|
|
||||||
summaryObj.packagings = await models.TicketPackaging.find({
|
summaryObj.packagings = await models.TicketPackaging.find({
|
||||||
where: {ticketFk: ticketFk},
|
where: {ticketFk: ticketFk},
|
||||||
include: [{relation: 'packaging',
|
include: [{relation: 'packaging',
|
||||||
|
|
|
@ -6,16 +6,12 @@ module.exports = Self => {
|
||||||
require('../methods/ticket/getVolume')(Self);
|
require('../methods/ticket/getVolume')(Self);
|
||||||
require('../methods/ticket/getTotalVolume')(Self);
|
require('../methods/ticket/getTotalVolume')(Self);
|
||||||
require('../methods/ticket/summary')(Self);
|
require('../methods/ticket/summary')(Self);
|
||||||
require('../methods/ticket/getTotal')(Self);
|
|
||||||
require('../methods/ticket/getTaxes')(Self);
|
|
||||||
require('../methods/ticket/subtotal')(Self);
|
|
||||||
require('../methods/ticket/priceDifference')(Self);
|
require('../methods/ticket/priceDifference')(Self);
|
||||||
require('../methods/ticket/componentUpdate')(Self);
|
require('../methods/ticket/componentUpdate')(Self);
|
||||||
require('../methods/ticket/new')(Self);
|
require('../methods/ticket/new')(Self);
|
||||||
require('../methods/ticket/isEditable')(Self);
|
require('../methods/ticket/isEditable')(Self);
|
||||||
require('../methods/ticket/setDeleted')(Self);
|
require('../methods/ticket/setDeleted')(Self);
|
||||||
require('../methods/ticket/restore')(Self);
|
require('../methods/ticket/restore')(Self);
|
||||||
require('../methods/ticket/getVAT')(Self);
|
|
||||||
require('../methods/ticket/getSales')(Self);
|
require('../methods/ticket/getSales')(Self);
|
||||||
require('../methods/ticket/getSalesPersonMana')(Self);
|
require('../methods/ticket/getSalesPersonMana')(Self);
|
||||||
require('../methods/ticket/filter')(Self);
|
require('../methods/ticket/filter')(Self);
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
"name": "Ticket",
|
"name": "Ticket",
|
||||||
"base": "Loggable",
|
"base": "Loggable",
|
||||||
"log": {
|
"log": {
|
||||||
"model":"TicketLog"
|
"model":"TicketLog",
|
||||||
|
"showField": "id"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
|
@ -12,30 +13,30 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"id": true,
|
"id": true,
|
||||||
"type": "Number",
|
"type": "number",
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
},
|
},
|
||||||
"shipped": {
|
"shipped": {
|
||||||
"type": "Date",
|
"type": "date",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"landed": {
|
"landed": {
|
||||||
"type": "Date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
"nickname": {
|
"nickname": {
|
||||||
"type": "String"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"type": "String"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"solution": {
|
"solution": {
|
||||||
"type": "String"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"type": "Number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"updated": {
|
"updated": {
|
||||||
"type": "Date",
|
"type": "date",
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"columnName": "created"
|
"columnName": "created"
|
||||||
}
|
}
|
||||||
|
@ -44,16 +45,22 @@
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"priority": {
|
"priority": {
|
||||||
"type": "Number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"zoneFk": {
|
"zoneFk": {
|
||||||
"type": "Number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"zonePrice": {
|
"zonePrice": {
|
||||||
"type": "Number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"zoneBonus": {
|
"zoneBonus": {
|
||||||
"type": "Number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"totalWithVat": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"totalWithoutVat": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
|
@ -80,13 +80,13 @@
|
||||||
Make invoice
|
Make invoice
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="regenerateInvoiceConfirmation.show()"
|
ng-click="createInvoicePdfConfirmation.show()"
|
||||||
ng-show="$ctrl.isInvoiced"
|
ng-show="$ctrl.isInvoiced"
|
||||||
vn-acl="invoicing"
|
vn-acl="invoicing"
|
||||||
vn-acl-action="remove"
|
vn-acl-action="remove"
|
||||||
name="regenerateInvoice"
|
name="regenerateInvoice"
|
||||||
translate>
|
translate>
|
||||||
Regenerate invoice
|
Regenerate invoice PDF
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="recalculateComponentsConfirmation.show()"
|
ng-click="recalculateComponentsConfirmation.show()"
|
||||||
|
@ -207,12 +207,12 @@
|
||||||
message="Are you sure you want to invoice this ticket?">
|
message="Are you sure you want to invoice this ticket?">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
|
|
||||||
<!-- Regenerate invoice confirmation dialog -->
|
<!-- Create invoice PDF confirmation dialog -->
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
vn-id="regenerateInvoiceConfirmation"
|
vn-id="createInvoicePdfConfirmation"
|
||||||
on-accept="$ctrl.regenerateInvoice()"
|
on-accept="$ctrl.createInvoicePdf()"
|
||||||
question="You are going to regenerate the invoice"
|
question="Are you sure you want to regenerate the invoice PDF document?"
|
||||||
message="Are you sure you want to regenerate the invoice?">
|
message="You are going to regenerate the invoice PDF document">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
|
|
||||||
<!-- Recalculate components confirmation dialog -->
|
<!-- Recalculate components confirmation dialog -->
|
||||||
|
|
|
@ -219,12 +219,12 @@ class Controller extends Section {
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
||||||
}
|
}
|
||||||
|
|
||||||
regenerateInvoice() {
|
createInvoicePdf() {
|
||||||
const invoiceId = this.ticket.invoiceOut.id;
|
const invoiceId = this.ticket.invoiceOut.id;
|
||||||
return this.$http.post(`InvoiceOuts/${invoiceId}/regenerate`)
|
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const snackbarMessage = this.$t(
|
const snackbarMessage = this.$t(
|
||||||
`Invoice sent for a regeneration, will be available in a few minutes`);
|
`The invoice PDF document has been regenerated`);
|
||||||
this.vnApp.showSuccess(snackbarMessage);
|
this.vnApp.showSuccess(snackbarMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,12 +148,12 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('regenerateInvoice()', () => {
|
describe('createInvoicePdf()', () => {
|
||||||
it('should make a query and show a success snackbar', () => {
|
it('should make a query and show a success snackbar', () => {
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
$httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/regenerate`).respond();
|
$httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond();
|
||||||
controller.regenerateInvoice();
|
controller.createInvoicePdf();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
|
|
@ -17,12 +17,12 @@ Make a payment: "Verdnatura le comunica:\rSu pedido está pendiente de pago.\rPo
|
||||||
Minimum is needed: "Verdnatura le recuerda:\rEs necesario un importe mínimo de 50€ (Sin IVA) en su pedido {{ticketId}} del día {{created | date: 'dd/MM/yyyy'}} para recibirlo sin portes adicionales."
|
Minimum is needed: "Verdnatura le recuerda:\rEs necesario un importe mínimo de 50€ (Sin IVA) en su pedido {{ticketId}} del día {{created | date: 'dd/MM/yyyy'}} para recibirlo sin portes adicionales."
|
||||||
Ticket invoiced: Ticket facturado
|
Ticket invoiced: Ticket facturado
|
||||||
Make invoice: Crear factura
|
Make invoice: Crear factura
|
||||||
Regenerate invoice: Regenerar factura
|
Regenerate invoice PDF: Regenerar PDF factura
|
||||||
|
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
|
||||||
You are going to invoice this ticket: Vas a facturar este ticket
|
You are going to invoice this ticket: Vas a facturar este ticket
|
||||||
Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket?
|
Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket?
|
||||||
You are going to regenerate the invoice: Vas a regenerar la factura
|
You are going to regenerate the invoice PDF document: Vas a regenerar el documento PDF de la factura
|
||||||
Are you sure you want to regenerate the invoice?: ¿Seguro que quieres regenerar la factura?
|
Are you sure you want to regenerate the invoice PDF document?: ¿Seguro que quieres regenerar el documento PDF de la factura?
|
||||||
Invoice sent for a regeneration, will be available in a few minutes: La factura ha sido enviada para ser regenerada, estará disponible en unos minutos
|
|
||||||
Shipped hour updated: Hora de envio modificada
|
Shipped hour updated: Hora de envio modificada
|
||||||
Deleted ticket: Ticket eliminado
|
Deleted ticket: Ticket eliminado
|
||||||
Recalculate components: Recalcular componentes
|
Recalculate components: Recalcular componentes
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<vn-th class="icon-field"></vn-th>
|
<vn-th class="icon-field"></vn-th>
|
||||||
<vn-th field="id">Id</vn-th>
|
<vn-th field="id">Id</vn-th>
|
||||||
<vn-th field="salesPersonFk" class="expendable">Salesperson</vn-th>
|
<vn-th field="salesPersonFk" class="expendable">Salesperson</vn-th>
|
||||||
<vn-th field="shipped">Date</vn-th>
|
<vn-th field="shipped" shrink-date>Date</vn-th>
|
||||||
<vn-th>Hour</vn-th>
|
<vn-th>Hour</vn-th>
|
||||||
<vn-th field="hour" shrink>Closure</vn-th>
|
<vn-th field="hour" shrink>Closure</vn-th>
|
||||||
<vn-th field="nickname">Alias</vn-th>
|
<vn-th field="nickname">Alias</vn-th>
|
||||||
|
@ -80,12 +80,12 @@
|
||||||
{{::ticket.userName | dashIfEmpty}}
|
{{::ticket.userName | dashIfEmpty}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>
|
<vn-td shrink-date>
|
||||||
<span class="chip {{$ctrl.compareDate(ticket.shipped)}}">
|
<span class="chip {{$ctrl.compareDate(ticket.shipped)}}">
|
||||||
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
|
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td>{{::ticket.shipped | date: 'HH:mm'}}</vn-td>
|
<vn-td shrink>{{::ticket.shipped | date: 'HH:mm'}}</vn-td>
|
||||||
<vn-td shrink>{{::ticket.zoneLanding | date: 'HH:mm'}}</vn-td>
|
<vn-td shrink>{{::ticket.zoneLanding | date: 'HH:mm'}}</vn-td>
|
||||||
<vn-td>
|
<vn-td>
|
||||||
<span
|
<span
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
<vn-td>{{::ticket.warehouse}}</vn-td>
|
<vn-td>{{::ticket.warehouse}}</vn-td>
|
||||||
<vn-td number>
|
<vn-td number>
|
||||||
<span class="chip {{$ctrl.totalPriceColor(ticket)}}">
|
<span class="chip {{$ctrl.totalPriceColor(ticket)}}">
|
||||||
{{::ticket.total | currency: 'EUR': 2}}
|
{{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td actions>
|
<vn-td actions>
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default class Controller extends Section {
|
||||||
throw new UserError('You cannot make a payment on account from multiple clients');
|
throw new UserError('You cannot make a payment on account from multiple clients');
|
||||||
|
|
||||||
for (let ticket of checkedTickets) {
|
for (let ticket of checkedTickets) {
|
||||||
this.$.balanceCreateDialog.amountPaid += ticket.total;
|
this.$.balanceCreateDialog.amountPaid += ticket.totalWithVat;
|
||||||
this.$.balanceCreateDialog.clientFk = ticket.clientFk;
|
this.$.balanceCreateDialog.clientFk = ticket.clientFk;
|
||||||
description.push(`${ticket.id}`);
|
description.push(`${ticket.id}`);
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ export default class Controller extends Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
totalPriceColor(ticket) {
|
totalPriceColor(ticket) {
|
||||||
const total = parseInt(ticket.total);
|
const total = parseInt(ticket.totalWithVat);
|
||||||
if (total > 0 && total < 50)
|
if (total > 0 && total < 50)
|
||||||
return 'warning';
|
return 'warning';
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,18 @@ describe('Component vnTicketIndex', () => {
|
||||||
let tickets = [{
|
let tickets = [{
|
||||||
id: 1,
|
id: 1,
|
||||||
clientFk: 1,
|
clientFk: 1,
|
||||||
total: 10.5,
|
checked: false,
|
||||||
checked: false
|
totalWithVat: 10.5
|
||||||
}, {
|
}, {
|
||||||
id: 2,
|
id: 2,
|
||||||
clientFk: 1,
|
clientFk: 1,
|
||||||
total: 20.5,
|
checked: true,
|
||||||
checked: true
|
totalWithVat: 20.5
|
||||||
}, {
|
}, {
|
||||||
id: 3,
|
id: 3,
|
||||||
clientFk: 1,
|
clientFk: 1,
|
||||||
total: 30,
|
checked: true,
|
||||||
checked: true
|
totalWithVat: 30
|
||||||
}];
|
}];
|
||||||
|
|
||||||
beforeEach(ngModule('ticket'));
|
beforeEach(ngModule('ticket'));
|
||||||
|
@ -76,7 +76,6 @@ describe('Component vnTicketIndex', () => {
|
||||||
jest.spyOn(controller.$.balanceCreateDialog, 'show').mockReturnThis();
|
jest.spyOn(controller.$.balanceCreateDialog, 'show').mockReturnThis();
|
||||||
|
|
||||||
controller.$.model = {data: tickets};
|
controller.$.model = {data: tickets};
|
||||||
controller.$.balanceCreateDialog.amountPaid = 0;
|
|
||||||
controller.openBalanceDialog();
|
controller.openBalanceDialog();
|
||||||
|
|
||||||
let description = controller.$.balanceCreateDialog.description;
|
let description = controller.$.balanceCreateDialog.description;
|
||||||
|
|
|
@ -39,8 +39,8 @@ class Controller extends Section {
|
||||||
|
|
||||||
getSubTotal() {
|
getSubTotal() {
|
||||||
if (!this.$params.id || !this.sales) return;
|
if (!this.$params.id || !this.sales) return;
|
||||||
this.$http.get(`Tickets/${this.$params.id}/subtotal`).then(res => {
|
this.$http.get(`Tickets/${this.$params.id}`).then(res => {
|
||||||
this.subtotal = res.data || 0.0;
|
this.subtotal = res.data.totalWithoutVat || 0.0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +62,8 @@ class Controller extends Section {
|
||||||
getVat() {
|
getVat() {
|
||||||
this.VAT = 0.0;
|
this.VAT = 0.0;
|
||||||
if (!this.$params.id || !this.sales) return;
|
if (!this.$params.id || !this.sales) return;
|
||||||
this.$http.get(`Tickets/${this.$params.id}/getVAT`).then(res => {
|
this.$http.get(`Tickets/${this.$params.id}`).then(res => {
|
||||||
this.VAT = res.data || 0.0;
|
this.VAT = res.data.totalWithVat - res.data.totalWithoutVat || 0.0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,13 +84,13 @@ describe('Ticket', () => {
|
||||||
|
|
||||||
describe('getSubTotal()', () => {
|
describe('getSubTotal()', () => {
|
||||||
it('should make an HTTP GET query and then set the subtotal property', () => {
|
it('should make an HTTP GET query and then set the subtotal property', () => {
|
||||||
const expectedAmount = 128;
|
const expectedResponse = {totalWithoutVat: 128};
|
||||||
|
|
||||||
$httpBackend.expect('GET', 'Tickets/1/subtotal').respond(200, expectedAmount);
|
$httpBackend.expect('GET', 'Tickets/1').respond(200, expectedResponse);
|
||||||
controller.getSubTotal();
|
controller.getSubTotal();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.subtotal).toEqual(expectedAmount);
|
expect(controller.subtotal).toEqual(expectedResponse.totalWithoutVat);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,13 +125,15 @@ describe('Ticket', () => {
|
||||||
describe('getVat()', () => {
|
describe('getVat()', () => {
|
||||||
it('should make an HTTP GET query and return the ticket VAT', () => {
|
it('should make an HTTP GET query and return the ticket VAT', () => {
|
||||||
controller.edit = {};
|
controller.edit = {};
|
||||||
const expectedAmount = 67;
|
const expectedResponse = {totalWithVat: 1000, totalWithoutVat: 999};
|
||||||
|
|
||||||
$httpBackend.expect('GET', 'Tickets/1/getVAT').respond(200, expectedAmount);
|
const expectedVAT = expectedResponse.totalWithVat - expectedResponse.totalWithoutVat;
|
||||||
|
|
||||||
|
$httpBackend.expect('GET', 'Tickets/1').respond(200, expectedResponse);
|
||||||
controller.getVat();
|
controller.getVat();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.VAT).toEqual(expectedAmount);
|
expect(controller.VAT).toEqual(expectedVAT);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ You have to allow pop-ups in your web browser to use this functionality:
|
||||||
Debes permitir los pop-pups en tu navegador para que esta herramienta funcione correctamente
|
Debes permitir los pop-pups en tu navegador para que esta herramienta funcione correctamente
|
||||||
Disc: Dto
|
Disc: Dto
|
||||||
Available: Disponible
|
Available: Disponible
|
||||||
What is the day of receipt of the ticket?: ¿Cual es del día de recepción del pedido?
|
What is the day of receipt of the ticket?: ¿Cual es el día de preparación del pedido?
|
||||||
Add claim: Crear reclamación
|
Add claim: Crear reclamación
|
||||||
Claim: Reclamación
|
Claim: Reclamación
|
||||||
Transfer lines: Transferir líneas
|
Transfer lines: Transferir líneas
|
||||||
|
|
|
@ -99,9 +99,9 @@
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-one class="taxes">
|
<vn-one class="taxes">
|
||||||
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.summary.subtotal | currency: 'EUR':2}}</p>
|
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.summary.totalWithoutVat | currency: 'EUR':2}}</p>
|
||||||
<p><vn-label translate>VAT</vn-label> {{$ctrl.summary.vat | currency: 'EUR':2}}</p>
|
<p><vn-label translate>VAT</vn-label> {{$ctrl.summary.totalWithVat - $ctrl.summary.totalWithoutVat | currency: 'EUR':2}}</p>
|
||||||
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.total | currency: 'EUR':2}}</strong></p>
|
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.totalWithVat | currency: 'EUR':2}}</strong></p>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-auto name="sales">
|
<vn-auto name="sales">
|
||||||
<h4>
|
<h4>
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
<vn-th field="ref">Reference</vn-th>
|
<vn-th field="ref">Reference</vn-th>
|
||||||
<vn-th field="agencyFk">Agency</vn-th>
|
<vn-th field="agencyFk">Agency</vn-th>
|
||||||
<vn-th field="warehouseOutFk">Warehouse Out</vn-th>
|
<vn-th field="warehouseOutFk">Warehouse Out</vn-th>
|
||||||
<vn-th field="shipped" center expand>Shipped</vn-th>
|
<vn-th field="shipped" center shrink-date>Shipped</vn-th>
|
||||||
<vn-th field="isDelivered" center>Delivered</vn-th>
|
<vn-th field="isDelivered" center>Delivered</vn-th>
|
||||||
<vn-th field="warehouseInFk">Warehouse In</vn-th>
|
<vn-th field="warehouseInFk">Warehouse In</vn-th>
|
||||||
<vn-th field="landed" center expand>Landed</vn-th>
|
<vn-th field="landed" center shrink-date>Landed</vn-th>
|
||||||
<vn-th field="isReceived" center>Received</vn-th>
|
<vn-th field="isReceived" center>Received</vn-th>
|
||||||
<vn-th shrink></vn-th>
|
<vn-th shrink></vn-th>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
|
@ -29,14 +29,14 @@
|
||||||
<vn-td>{{::travel.ref}}</vn-td>
|
<vn-td>{{::travel.ref}}</vn-td>
|
||||||
<vn-td>{{::travel.agencyModeName}}</vn-td>
|
<vn-td>{{::travel.agencyModeName}}</vn-td>
|
||||||
<vn-td>{{::travel.warehouseOutName}}</vn-td>
|
<vn-td>{{::travel.warehouseOutName}}</vn-td>
|
||||||
<vn-td center expand>
|
<vn-td center shrink-date>
|
||||||
<span class="chip {{$ctrl.compareDate(travel.shipped)}}">
|
<span class="chip {{$ctrl.compareDate(travel.shipped)}}">
|
||||||
{{::travel.shipped | date:'dd/MM/yyyy'}}
|
{{::travel.shipped | date:'dd/MM/yyyy'}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td center><vn-check ng-model="travel.isDelivered" disabled="true"></vn-check></vn-td>
|
<vn-td center><vn-check ng-model="travel.isDelivered" disabled="true"></vn-check></vn-td>
|
||||||
<vn-td expand>{{::travel.warehouseInName}}</vn-td>
|
<vn-td expand>{{::travel.warehouseInName}}</vn-td>
|
||||||
<vn-td center expand>
|
<vn-td center shrink-date>
|
||||||
<span class="chip {{$ctrl.compareDate(travel.landed)}}">
|
<span class="chip {{$ctrl.compareDate(travel.landed)}}">
|
||||||
{{::travel.landed | date:'dd/MM/yyyy'}}
|
{{::travel.landed | date:'dd/MM/yyyy'}}
|
||||||
</span>
|
</span>
|
||||||
|
|
11
package.json
11
package.json
|
@ -15,15 +15,16 @@
|
||||||
"bmp-js": "^0.1.0",
|
"bmp-js": "^0.1.0",
|
||||||
"compression": "^1.7.3",
|
"compression": "^1.7.3",
|
||||||
"fs-extra": "^5.0.0",
|
"fs-extra": "^5.0.0",
|
||||||
|
"got": "^6.7.1",
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.21.2",
|
||||||
"i18n": "^0.8.4",
|
"i18n": "^0.8.4",
|
||||||
"image-type": "^4.1.0",
|
"image-type": "^4.1.0",
|
||||||
"imap": "^0.8.19",
|
"imap": "^0.8.19",
|
||||||
"ldapjs": "^2.2.0",
|
"ldapjs": "^2.2.0",
|
||||||
"loopback": "^3.26.0",
|
"loopback": "^3.26.0",
|
||||||
"loopback-boot": "^2.27.1",
|
"loopback-boot": "3.3.1",
|
||||||
"loopback-component-explorer": "^6.5.0",
|
"loopback-component-explorer": "^6.5.0",
|
||||||
"loopback-component-storage": "^3.6.1",
|
"loopback-component-storage": "3.6.1",
|
||||||
"loopback-connector-mysql": "^5.4.3",
|
"loopback-connector-mysql": "^5.4.3",
|
||||||
"loopback-connector-remote": "^3.4.1",
|
"loopback-connector-remote": "^3.4.1",
|
||||||
"loopback-context": "^3.4.0",
|
"loopback-context": "^3.4.0",
|
||||||
|
@ -34,8 +35,6 @@
|
||||||
"object.pick": "^1.3.0",
|
"object.pick": "^1.3.0",
|
||||||
"puppeteer": "^7.1.0",
|
"puppeteer": "^7.1.0",
|
||||||
"read-chunk": "^3.2.0",
|
"read-chunk": "^3.2.0",
|
||||||
"request": "^2.88.0",
|
|
||||||
"request-promise-native": "^1.0.8",
|
|
||||||
"require-yaml": "0.0.1",
|
"require-yaml": "0.0.1",
|
||||||
"sharp": "^0.27.1",
|
"sharp": "^0.27.1",
|
||||||
"smbhash": "0.0.1",
|
"smbhash": "0.0.1",
|
||||||
|
@ -48,13 +47,12 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.7.7",
|
"@babel/core": "^7.7.7",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
|
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
|
||||||
"@babel/polyfill": "^7.7.0",
|
|
||||||
"@babel/preset-env": "^7.11.0",
|
"@babel/preset-env": "^7.11.0",
|
||||||
"@babel/register": "^7.7.7",
|
"@babel/register": "^7.7.7",
|
||||||
"angular-mocks": "^1.7.9",
|
"angular-mocks": "^1.7.9",
|
||||||
"babel-jest": "^26.0.1",
|
"babel-jest": "^26.0.1",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"core-js": "^3.9.1",
|
||||||
"css-loader": "^2.1.0",
|
"css-loader": "^2.1.0",
|
||||||
"del": "^2.2.2",
|
"del": "^2.2.2",
|
||||||
"eslint": "^7.11.0",
|
"eslint": "^7.11.0",
|
||||||
|
@ -90,6 +88,7 @@
|
||||||
"nodemon": "^1.19.4",
|
"nodemon": "^1.19.4",
|
||||||
"plugin-error": "^1.0.1",
|
"plugin-error": "^1.0.1",
|
||||||
"raw-loader": "^1.0.0",
|
"raw-loader": "^1.0.0",
|
||||||
|
"regenerator-runtime": "^0.13.7",
|
||||||
"sass-loader": "^7.3.1",
|
"sass-loader": "^7.3.1",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"webpack": "^4.41.5",
|
"webpack": "^4.41.5",
|
||||||
|
|
|
@ -46,3 +46,7 @@
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
break-inside: avoid
|
break-inside: avoid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-break-after {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
|
@ -9,6 +9,6 @@ body {
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
font-size: 3em;
|
font-size: 2.6rem;
|
||||||
margin-top: 0
|
margin-top: 0
|
||||||
}
|
}
|
|
@ -83,6 +83,11 @@ class Component {
|
||||||
component.template = juice.inlineContent(this.template, this.stylesheet, {
|
component.template = juice.inlineContent(this.template, this.stylesheet, {
|
||||||
inlinePseudoElements: true
|
inlinePseudoElements: true
|
||||||
});
|
});
|
||||||
|
const tplPath = this.path;
|
||||||
|
if (!component.computed) component.computed = {};
|
||||||
|
component.computed.path = function() {
|
||||||
|
return tplPath;
|
||||||
|
};
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +98,7 @@ class Component {
|
||||||
|
|
||||||
const component = this.build();
|
const component = this.build();
|
||||||
const i18n = new VueI18n(config.i18n);
|
const i18n = new VueI18n(config.i18n);
|
||||||
const props = {tplPath: this.path, ...this.args};
|
const props = {...this.args};
|
||||||
this._component = new Vue({
|
this._component = new Vue({
|
||||||
i18n: i18n,
|
i18n: i18n,
|
||||||
render: h => h(component, {
|
render: h => h(component, {
|
||||||
|
|
|
@ -36,13 +36,14 @@ module.exports = {
|
||||||
* Makes a query from a SQL file
|
* Makes a query from a SQL file
|
||||||
* @param {String} queryName - The SQL file name
|
* @param {String} queryName - The SQL file name
|
||||||
* @param {Object} params - Parameterized values
|
* @param {Object} params - Parameterized values
|
||||||
|
* @param {Object} connection - Optional pool connection
|
||||||
*
|
*
|
||||||
* @return {Object} - Result promise
|
* @return {Object} - Result promise
|
||||||
*/
|
*/
|
||||||
rawSqlFromDef(queryName, params) {
|
rawSqlFromDef(queryName, params, connection) {
|
||||||
const query = fs.readFileSync(`${queryName}.sql`, 'utf8');
|
const query = fs.readFileSync(`${queryName}.sql`, 'utf8');
|
||||||
|
|
||||||
return this.rawSql(query, params);
|
return this.rawSql(query, params, connection);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,12 +19,13 @@ const dbHelper = {
|
||||||
* Makes a query from a SQL file
|
* Makes a query from a SQL file
|
||||||
* @param {String} queryName - The SQL file name
|
* @param {String} queryName - The SQL file name
|
||||||
* @param {Object} params - Parameterized values
|
* @param {Object} params - Parameterized values
|
||||||
|
* @param {Object} connection - Optional pool connection
|
||||||
*
|
*
|
||||||
* @return {Object} - Result promise
|
* @return {Object} - Result promise
|
||||||
*/
|
*/
|
||||||
rawSqlFromDef(queryName, params) {
|
rawSqlFromDef(queryName, params, connection) {
|
||||||
const absolutePath = path.join(__dirname, '../', this.tplPath, 'sql', queryName);
|
const absolutePath = path.join(__dirname, '../', this.path, 'sql', queryName);
|
||||||
return db.rawSqlFromDef(absolutePath, params);
|
return db.rawSqlFromDef(absolutePath, params, connection);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,7 +67,7 @@ const dbHelper = {
|
||||||
*/
|
*/
|
||||||
findValueFromDef(queryName, params) {
|
findValueFromDef(queryName, params) {
|
||||||
return this.findOneFromDef(queryName, params).then(row => {
|
return this.findOneFromDef(queryName, params).then(row => {
|
||||||
return Object.values(row)[0];
|
if (row) return Object.values(row)[0];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ const dbHelper = {
|
||||||
* @return {Object} - SQL
|
* @return {Object} - SQL
|
||||||
*/
|
*/
|
||||||
getSqlFromDef(queryName) {
|
getSqlFromDef(queryName) {
|
||||||
const absolutePath = path.join(__dirname, '../', this.tplPath, 'sql', queryName);
|
const absolutePath = path.join(__dirname, '../', this.path, 'sql', queryName);
|
||||||
return db.getSqlFromDef(absolutePath);
|
return db.getSqlFromDef(absolutePath);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue