Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2734-invoice_report
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2021-03-15 13:49:16 +01:00
commit 0ec6d273e6
69 changed files with 50001 additions and 3856 deletions

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
presets: [ presets: [
'@babel/preset-env', '@babel/env',
], ],
}; };

View File

@ -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) {

View File

@ -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,

View File

@ -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`();

View File

@ -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,

View File

@ -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"
}
]
}
]
}
]
}

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -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',

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors'; import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Client Edit billing data path', () => { fdescribe('Client Edit billing data path', () => {
let browser; let browser;
let page; let page;
beforeAll(async() => { beforeAll(async() => {

View File

@ -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(),

View File

@ -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');
}); });
}); });

View File

@ -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);
});
});

View File

@ -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
});
}); });
} }
} }

View File

@ -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', {

View File

@ -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;

View File

@ -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};

3295
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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);
}); });
} }

View File

@ -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)"

View File

@ -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
}); });
} }
}); });

View File

@ -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;
}
};
};

View File

@ -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;
};
};

View File

@ -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',

View File

@ -0,0 +1,80 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('entry import()', () => {
let newEntry;
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({});
const options = {transaction: tx};
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);
// Restores
await tx.rollback();
});
});

View File

@ -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);
});
});

View File

@ -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);
}; };

View File

@ -28,7 +28,7 @@
"type": "boolean" "type": "boolean"
}, },
"notes": { "notes": {
"type": "String" "type": "string"
}, },
"isConfirmed": { "isConfirmed": {
"type": "boolean" "type": "boolean"

View File

@ -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>

View File

@ -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: '<'
}
});

View File

@ -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');
});
});
});
});

View File

@ -0,0 +1,5 @@
vn-entry-buy-import {
.vn-table > tbody td:nth-child(1) {
width: 250px
}
}

View File

@ -1 +0,0 @@

View File

@ -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>

View File

@ -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: {

View File

@ -0,0 +1 @@
Buy: Lineas de entrada

View File

@ -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

View File

@ -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';

View File

@ -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"

View File

@ -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"]
} }
] ]
} }

View File

@ -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"

View File

@ -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>

View File

@ -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></vn-th> <vn-th th-id="m3" number></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>

View File

@ -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;

View File

@ -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, ';');

View File

@ -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;
};
};

View File

@ -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;
};
};

View File

@ -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;
};
};

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});
});

View File

@ -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);
});
});

View File

@ -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);
}); });
}); });

View File

@ -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;
};
};

View File

@ -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',

View File

@ -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);

View File

@ -12,30 +12,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 +44,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": {

View File

@ -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>

View File

@ -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';
} }

View File

@ -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;

View File

@ -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;
}); });
} }

View File

@ -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);
}); });
}); });

View File

@ -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>

View File

@ -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>

46249
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,13 +15,14 @@
"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",
@ -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",

View File

@ -6,6 +6,10 @@ const config = require('../core/config');
module.exports = app => { module.exports = app => {
app.get('/api/closure/all', async function(req, res, next) { app.get('/api/closure/all', async function(req, res, next) {
try { try {
const reqArgs = req.args;
if (!reqArgs.to)
throw new Error('The argument to is required');
res.status(200).json({ res.status(200).json({
message: 'Task executed successfully' message: 'Task executed successfully'
}); });
@ -19,9 +23,12 @@ module.exports = app => {
JOIN ticketState ts ON ts.ticketFk = t.id JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.alertLevel = ts.alertLevel JOIN alertLevel al ON al.alertLevel = ts.alertLevel
WHERE al.code = 'PACKED' WHERE al.code = 'PACKED'
AND DATE(t.shipped) BETWEEN DATE_ADD(CURDATE(), INTERVAL -2 DAY) AND CURDATE() AND DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY)
AND util.dayEnd(:to)
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY e.ticketFk`); GROUP BY e.ticketFk`, {
to: reqArgs.to
});
const ticketIds = tickets.map(ticket => ticket.id); const ticketIds = tickets.map(ticket => ticket.id);
await closeAll(ticketIds, req.args); await closeAll(ticketIds, req.args);
@ -33,10 +40,13 @@ module.exports = app => {
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
JOIN zone z ON z.id = t.zoneFk JOIN zone z ON z.id = t.zoneFk
SET t.routeFk = NULL SET t.routeFk = NULL
WHERE shipped BETWEEN CURDATE() AND util.dayEnd(CURDATE()) WHERE DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY)
AND util.dayEnd(:to)
AND al.code NOT IN('DELIVERED','PACKED') AND al.code NOT IN('DELIVERED','PACKED')
AND t.routeFk AND t.routeFk
AND z.name LIKE '%MADRID%'`); AND z.name LIKE '%MADRID%'`, {
to: reqArgs.to
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@ -100,7 +110,8 @@ module.exports = app => {
WHERE al.code = 'PACKED' WHERE al.code = 'PACKED'
AND t.agencyModeFk IN(:agencyModeId) AND t.agencyModeFk IN(:agencyModeId)
AND t.warehouseFk = :warehouseId AND t.warehouseFk = :warehouseId
AND DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY) AND :to AND DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY)
AND util.dayEnd(:to)
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY e.ticketFk`, { GROUP BY e.ticketFk`, {
agencyModeId: agenciesId, agencyModeId: agenciesId,

2896
print/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"html-pdf": "^2.2.0",
"intl": "^1.2.5", "intl": "^1.2.5",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"juice": "^5.2.0", "juice": "^5.2.0",