Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2275-claim_detail
This commit is contained in:
commit
9d80ec11af
|
@ -68,7 +68,7 @@ pipeline {
|
||||||
stage('Backend') {
|
stage('Backend') {
|
||||||
steps {
|
steps {
|
||||||
nodejs('node-lts') {
|
nodejs('node-lts') {
|
||||||
sh 'gulp backTestDockerOnce --junit --random'
|
sh 'gulp backTestOnce --ci'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
README.md
19
README.md
|
@ -8,18 +8,12 @@ Salix is also the scientific name of a beautifull tree! :)
|
||||||
|
|
||||||
Required applications.
|
Required applications.
|
||||||
|
|
||||||
* Visual Studio Code
|
|
||||||
* Node.js = 12.17.0 LTS
|
* Node.js = 12.17.0 LTS
|
||||||
* Docker
|
* Docker
|
||||||
|
|
||||||
In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command.
|
|
||||||
```
|
|
||||||
ext install dbaeumer.vscode-eslint
|
|
||||||
```
|
|
||||||
|
|
||||||
You will need to install globally the following items.
|
You will need to install globally the following items.
|
||||||
```
|
```
|
||||||
# sudo npm install -g jest gulp-cli nodemon
|
# sudo npm install -g jest gulp-cli
|
||||||
```
|
```
|
||||||
## Linux Only Prerequisites
|
## Linux Only Prerequisites
|
||||||
|
|
||||||
|
@ -65,6 +59,15 @@ For end-to-end tests run from project's root.
|
||||||
$ gulp e2e
|
$ gulp e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Recommended tools
|
||||||
|
|
||||||
|
* Visual Studio Code
|
||||||
|
|
||||||
|
In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command.
|
||||||
|
```
|
||||||
|
ext install dbaeumer.vscode-eslint
|
||||||
|
```
|
||||||
|
|
||||||
## Built With
|
## Built With
|
||||||
|
|
||||||
* [angularjs](https://angularjs.org/)
|
* [angularjs](https://angularjs.org/)
|
||||||
|
@ -75,4 +78,4 @@ $ gulp e2e
|
||||||
* [gulp.js](https://gulpjs.com/)
|
* [gulp.js](https://gulpjs.com/)
|
||||||
* [jest](https://jestjs.io/)
|
* [jest](https://jestjs.io/)
|
||||||
* [Jasmine](https://jasmine.github.io/)
|
* [Jasmine](https://jasmine.github.io/)
|
||||||
* [Nightmare](http://www.nightmarejs.org/)
|
* [Puppeteer](https://pptr.dev/)
|
||||||
|
|
|
@ -15,20 +15,6 @@ module.exports = Self => {
|
||||||
Self.observe('before save', async function(ctx) {
|
Self.observe('before save', async function(ctx) {
|
||||||
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && ctx.data.password)
|
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && ctx.data.password)
|
||||||
ctx.data.password = md5(ctx.data.password);
|
ctx.data.password = md5(ctx.data.password);
|
||||||
|
|
||||||
if (!ctx.isNewInstance && ctx.data && (ctx.data.name || ctx.data.active)) {
|
|
||||||
let instance = JSON.parse(JSON.stringify(ctx.currentInstance));
|
|
||||||
let userId = ctx.options.accessToken.userId;
|
|
||||||
let logRecord = {
|
|
||||||
originFk: ctx.currentInstance.id,
|
|
||||||
userFk: userId,
|
|
||||||
action: 'update',
|
|
||||||
changedModel: 'Account',
|
|
||||||
oldInstance: {name: instance.name, active: instance.active},
|
|
||||||
newInstance: ctx.data
|
|
||||||
};
|
|
||||||
await Self.app.models.ClientLog.create(logRecord);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.remoteMethod('getCurrentUserData', {
|
Self.remoteMethod('getCurrentUserData', {
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
const log = require('fancy-log');
|
||||||
|
const dataSources = require('../loopback/server/datasources.json');
|
||||||
|
|
||||||
|
module.exports = class Docker {
|
||||||
|
constructor(name) {
|
||||||
|
Object.assign(this, {
|
||||||
|
id: name,
|
||||||
|
name,
|
||||||
|
isRandom: name == null,
|
||||||
|
dbConf: Object.assign({}, dataSources.vn)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the database image and runs a container. It only rebuilds the
|
||||||
|
* image when fixtures have been modified or when the day on which the
|
||||||
|
* image was built is different to today. Some workarounds have been used
|
||||||
|
* to avoid a bug with OverlayFS driver on MacOS.
|
||||||
|
*
|
||||||
|
* @param {Boolean} ci continuous integration environment argument
|
||||||
|
*/
|
||||||
|
async run(ci) {
|
||||||
|
let d = new Date();
|
||||||
|
let pad = v => v < 10 ? '0' + v : v;
|
||||||
|
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||||
|
await this.execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`);
|
||||||
|
|
||||||
|
let dockerArgs;
|
||||||
|
|
||||||
|
if (this.isRandom)
|
||||||
|
dockerArgs = '-p 3306';
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
await this.rm();
|
||||||
|
} catch (e) {}
|
||||||
|
dockerArgs = `--name ${this.name} -p 3306:${this.dbConf.port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let runChown = process.platform != 'linux';
|
||||||
|
|
||||||
|
let container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
|
||||||
|
this.id = container.stdout;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.isRandom) {
|
||||||
|
let inspect = await this.execP(`docker inspect -f "{{json .NetworkSettings}}" ${this.id}`);
|
||||||
|
let netSettings = JSON.parse(inspect.stdout);
|
||||||
|
|
||||||
|
if (ci)
|
||||||
|
this.dbConf.host = netSettings.Gateway;
|
||||||
|
|
||||||
|
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runChown) await this.wait();
|
||||||
|
} catch (err) {
|
||||||
|
if (this.isRandom)
|
||||||
|
await this.rm();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the minium effort to start the database container, if it doesn't exists
|
||||||
|
* calls the 'docker' task, if it is started does nothing. Keep in mind that when
|
||||||
|
* you do not rebuild the docker you may be using an outdated version of it.
|
||||||
|
* See the 'docker' task for more info.
|
||||||
|
*/
|
||||||
|
async start() {
|
||||||
|
let state;
|
||||||
|
try {
|
||||||
|
let result = await this.execP(`docker inspect -f "{{json .State}}" ${this.id}`);
|
||||||
|
state = JSON.parse(result.stdout);
|
||||||
|
} catch (err) {
|
||||||
|
return await this.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state.Status) {
|
||||||
|
case 'running':
|
||||||
|
return;
|
||||||
|
case 'exited':
|
||||||
|
await this.execP(`docker start ${this.id}`);
|
||||||
|
await this.wait();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown docker status: ${state.Status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wait() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const mysql = require('mysql2');
|
||||||
|
|
||||||
|
let interval = 100;
|
||||||
|
let elapsedTime = 0;
|
||||||
|
let maxInterval = 4 * 60 * 1000;
|
||||||
|
|
||||||
|
let myConf = {
|
||||||
|
user: this.dbConf.username,
|
||||||
|
password: this.dbConf.password,
|
||||||
|
host: this.dbConf.host,
|
||||||
|
port: this.dbConf.port
|
||||||
|
};
|
||||||
|
|
||||||
|
log('Waiting for MySQL init process...');
|
||||||
|
|
||||||
|
async function checker() {
|
||||||
|
elapsedTime += interval;
|
||||||
|
let state;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result = await this.execP(`docker container inspect -f "{{json .State}}" ${this.id}`);
|
||||||
|
state = JSON.parse(result.stdout);
|
||||||
|
} catch (err) {
|
||||||
|
return reject(new Error(err.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.Status === 'exited')
|
||||||
|
return reject(new Error('Docker exited, please see the docker logs for more info'));
|
||||||
|
|
||||||
|
let conn = mysql.createConnection(myConf);
|
||||||
|
conn.on('error', () => {});
|
||||||
|
conn.connect(err => {
|
||||||
|
conn.destroy();
|
||||||
|
if (!err) {
|
||||||
|
log('MySQL process ready.');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsedTime >= maxInterval)
|
||||||
|
reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`));
|
||||||
|
else
|
||||||
|
setTimeout(bindedChecker, interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let bindedChecker = checker.bind(this);
|
||||||
|
bindedChecker();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rm() {
|
||||||
|
return this.execP(`docker rm -fv ${this.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promisified version of exec().
|
||||||
|
*
|
||||||
|
* @param {String} command The exec command
|
||||||
|
* @return {Promise} The promise
|
||||||
|
*/
|
||||||
|
execP(command) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(command, (err, stdout, stderr) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else {
|
||||||
|
resolve({
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -664,12 +664,12 @@ INSERT INTO `vn`.`itemCategory`(`id`, `name`, `display`, `color`, `icon`, `code`
|
||||||
|
|
||||||
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk`, `isPackaging`)
|
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk`, `isPackaging`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'CRI', 'Crisantemo', 2, 31, 5, 0),
|
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0),
|
||||||
(2, 'ITG', 'Anthurium', 1, 31, 5, 0),
|
(2, 'ITG', 'Anthurium', 1, 31, 35, 0),
|
||||||
(3, 'WPN', 'Paniculata', 2, 31, 5, 0),
|
(3, 'WPN', 'Paniculata', 2, 31, 35, 0),
|
||||||
(4, 'PRT', 'Delivery ports', 3, NULL, 5, 1),
|
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1),
|
||||||
(5, 'CON', 'Container', 3, NULL, 5, 1),
|
(5, 'CON', 'Container', 3, NULL, 35, 1),
|
||||||
(6, 'ALS', 'Alstroemeria', 1, 31, 5, 0);
|
(6, 'ALS', 'Alstroemeria', 1, 31, 35, 0);
|
||||||
|
|
||||||
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`)
|
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default {
|
||||||
ticketsButton: '.modules-menu [ui-sref="ticket.index"]',
|
ticketsButton: '.modules-menu [ui-sref="ticket.index"]',
|
||||||
invoiceOutButton: '.modules-menu [ui-sref="invoiceOut.index"]',
|
invoiceOutButton: '.modules-menu [ui-sref="invoiceOut.index"]',
|
||||||
claimsButton: '.modules-menu [ui-sref="claim.index"]',
|
claimsButton: '.modules-menu [ui-sref="claim.index"]',
|
||||||
returnToModuleIndexButton: 'a[ui-sref="order.index"]',
|
returnToModuleIndexButton: 'a[name="goToModuleIndex"]',
|
||||||
homeButton: 'vn-topbar > div.side.start > a',
|
homeButton: 'vn-topbar > div.side.start > a',
|
||||||
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
|
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
|
||||||
userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
|
userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
|
||||||
|
@ -365,7 +365,8 @@ export default {
|
||||||
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3)',
|
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3)',
|
||||||
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6)',
|
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6)',
|
||||||
invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span',
|
invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span',
|
||||||
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button'
|
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button',
|
||||||
|
descriptorTicketId: 'vn-ticket-descriptor > vn-descriptor-content > div > div.body > div.top > div'
|
||||||
},
|
},
|
||||||
ticketsIndex: {
|
ticketsIndex: {
|
||||||
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
|
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
|
||||||
|
|
|
@ -4,6 +4,9 @@ import getBrowser from '../../helpers/puppeteer';
|
||||||
describe('Ticket create path', () => {
|
describe('Ticket create path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
let nextMonth = new Date();
|
||||||
|
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
||||||
|
let stowawayTicketId;
|
||||||
|
|
||||||
beforeAll(async() => {
|
beforeAll(async() => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
|
@ -21,13 +24,9 @@ describe('Ticket create path', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed to create a ticket', async() => {
|
it('should succeed to create a ticket', async() => {
|
||||||
const nextMonth = new Date();
|
await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
|
||||||
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
|
||||||
|
|
||||||
await page.autocompleteSearch(selectors.createTicketView.client, 'Tony Stark');
|
|
||||||
await page.autocompleteSearch(selectors.createTicketView.address, 'Tony Stark');
|
|
||||||
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
|
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
|
||||||
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse One');
|
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse Two');
|
||||||
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
|
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
|
||||||
await page.waitToClick(selectors.createTicketView.createButton);
|
await page.waitToClick(selectors.createTicketView.createButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
@ -37,5 +36,53 @@ describe('Ticket create path', () => {
|
||||||
|
|
||||||
it('should check the url is now the summary of the ticket', async() => {
|
it('should check the url is now the summary of the ticket', async() => {
|
||||||
await page.waitForState('ticket.card.summary');
|
await page.waitForState('ticket.card.summary');
|
||||||
|
stowawayTicketId = await page.waitToGetProperty(selectors.ticketSummary.descriptorTicketId, 'innerText');
|
||||||
|
stowawayTicketId = stowawayTicketId.substring(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should again open the new ticket form', async() => {
|
||||||
|
await page.waitToClick(selectors.globalItems.returnToModuleIndexButton);
|
||||||
|
await page.waitToClick(selectors.ticketsIndex.newTicketButton);
|
||||||
|
await page.waitForState('ticket.create');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should succeed to create another ticket for the same client', async() => {
|
||||||
|
await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
|
||||||
|
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
|
||||||
|
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse One');
|
||||||
|
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
|
||||||
|
await page.waitToClick(selectors.createTicketView.createButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.type).toBe('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the url is now the summary of the created ticket', async() => {
|
||||||
|
await page.waitForState('ticket.card.summary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should make the previously created ticket the stowaway of the current ticket', async() => {
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway);
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.type).toBe('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete the current ticket', async() => {
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.acceptDeleteButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.type).toBe('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search for the stowaway ticket of the previously deleted ticket', async() => {
|
||||||
|
await page.accessToSearchResult(stowawayTicketId);
|
||||||
|
const result = await page.countElement(selectors.ticketDescriptor.shipButton);
|
||||||
|
|
||||||
|
expect(result).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
require('babel-core/register')({presets: ['es2015']});
|
|
||||||
|
|
||||||
process.on('warning', warning => {
|
|
||||||
console.log(warning.name);
|
|
||||||
console.log(warning.message);
|
|
||||||
console.log(warning.stack);
|
|
||||||
});
|
|
||||||
|
|
||||||
let verbose = false;
|
|
||||||
|
|
||||||
if (process.argv[2] === '--v')
|
|
||||||
verbose = true;
|
|
||||||
|
|
||||||
let Jasmine = require('jasmine');
|
|
||||||
let jasmine = new Jasmine();
|
|
||||||
let SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
|
||||||
|
|
||||||
jasmine.loadConfig({
|
|
||||||
spec_files: [
|
|
||||||
`${__dirname}/smokes/**/*[sS]pec.js`,
|
|
||||||
`${__dirname}/helpers/extensions.js`
|
|
||||||
],
|
|
||||||
helpers: []
|
|
||||||
});
|
|
||||||
|
|
||||||
jasmine.addReporter(new SpecReporter({
|
|
||||||
spec: {
|
|
||||||
// displayStacktrace: 'summary',
|
|
||||||
displaySuccessful: verbose,
|
|
||||||
displayFailedSpec: true,
|
|
||||||
displaySpecDuration: true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
jasmine.execute();
|
|
|
@ -1,31 +0,0 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
|
||||||
|
|
||||||
describe('create client path', () => {
|
|
||||||
let browser;
|
|
||||||
let page;
|
|
||||||
beforeAll(async() => {
|
|
||||||
browser = await getBrowser();
|
|
||||||
page = browser.page;
|
|
||||||
await page.loginAndModule('employee', 'client');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should access to the create client view by clicking the create-client floating button', async() => {
|
|
||||||
await page.waitToClick(selectors.clientsIndex.createClientButton);
|
|
||||||
let url = await page.expectURL('#!/client/create');
|
|
||||||
|
|
||||||
expect(url).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should cancel the client creation to go back to clients index', async() => {
|
|
||||||
await page.waitToClick(selectors.globalItems.applicationsMenuButton);
|
|
||||||
await page.waitToClick(selectors.globalItems.clientsButton);
|
|
||||||
let url = await page.expectURL('#!/client/index');
|
|
||||||
|
|
||||||
expect(url).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,7 +1,7 @@
|
||||||
<form ng-submit="$ctrl.onSubmit()">
|
<form ng-submit="$ctrl.onSubmit()">
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
class="dense standout"
|
class="dense standout"
|
||||||
placeholder="{{::'Search by' | translate: {module: $ctrl.baseState} }}"
|
placeholder="{{::$ctrl.placeholder | translate}}"
|
||||||
ng-model="$ctrl.searchString">
|
ng-model="$ctrl.searchString">
|
||||||
<prepend>
|
<prepend>
|
||||||
<vn-icon
|
<vn-icon
|
||||||
|
|
|
@ -21,6 +21,7 @@ export default class Searchbar extends Component {
|
||||||
constructor($element, $) {
|
constructor($element, $) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
this.searchState = '.';
|
this.searchState = '.';
|
||||||
|
this.placeholder = 'Search';
|
||||||
this.autoState = true;
|
this.autoState = true;
|
||||||
|
|
||||||
this.deregisterCallback = this.$transitions.onSuccess(
|
this.deregisterCallback = this.$transitions.onSuccess(
|
||||||
|
@ -35,6 +36,9 @@ export default class Searchbar extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.searchState = `${this.baseState}.index`;
|
this.searchState = `${this.baseState}.index`;
|
||||||
|
this.placeholder = this.$translate.instant('Search by', {
|
||||||
|
module: this.baseState
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchStateFilter(this.autoLoad);
|
this.fetchStateFilter(this.autoLoad);
|
||||||
|
@ -293,7 +297,8 @@ ngModule.vnComponent('vnSearchbar', {
|
||||||
stateParams: '&?',
|
stateParams: '&?',
|
||||||
model: '<?',
|
model: '<?',
|
||||||
exprBuilder: '&?',
|
exprBuilder: '&?',
|
||||||
fetchParams: '&?'
|
fetchParams: '&?',
|
||||||
|
placeholder: '@?'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
|
||||||
|
class Email {
|
||||||
|
constructor($http, $translate, vnApp) {
|
||||||
|
this.$http = $http;
|
||||||
|
this.vnApp = vnApp;
|
||||||
|
this.$t = $translate.instant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an email displaying a notification when it's sent.
|
||||||
|
*
|
||||||
|
* @param {String} template The email report name
|
||||||
|
* @param {Object} params The email parameters
|
||||||
|
* @return {Promise} Promise resolved when it's sent
|
||||||
|
*/
|
||||||
|
send(template, params) {
|
||||||
|
return this.$http.get(`email/${template}`, {params})
|
||||||
|
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Email.$inject = ['$http', '$translate', 'vnApp'];
|
||||||
|
|
||||||
|
ngModule.service('vnEmail', Email);
|
|
@ -0,0 +1,35 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
|
||||||
|
class File {
|
||||||
|
constructor($httpParamSerializer, vnToken) {
|
||||||
|
this.$httpParamSerializer = $httpParamSerializer;
|
||||||
|
this.vnToken = vnToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full download path
|
||||||
|
*
|
||||||
|
* @param {String} dmsUrl The file download path
|
||||||
|
* @return {String} The full download path
|
||||||
|
*/
|
||||||
|
getPath(dmsUrl) {
|
||||||
|
const serializedParams = this.$httpParamSerializer({
|
||||||
|
access_token: this.vnToken.token
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${dmsUrl}?${serializedParams}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a file in another window, automatically adds the authorization
|
||||||
|
* token to params.
|
||||||
|
*
|
||||||
|
* @param {String} dmsUrl The file download path
|
||||||
|
*/
|
||||||
|
download(dmsUrl) {
|
||||||
|
window.open(this.getPath(dmsUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File.$inject = ['$httpParamSerializer', 'vnToken'];
|
||||||
|
|
||||||
|
ngModule.service('vnFile', File);
|
|
@ -7,3 +7,6 @@ import './modules';
|
||||||
import './interceptor';
|
import './interceptor';
|
||||||
import './config';
|
import './config';
|
||||||
import './week-days';
|
import './week-days';
|
||||||
|
import './report';
|
||||||
|
import './email';
|
||||||
|
import './file';
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
|
||||||
|
class Report {
|
||||||
|
constructor($httpParamSerializer, vnToken) {
|
||||||
|
this.$httpParamSerializer = $httpParamSerializer;
|
||||||
|
this.vnToken = vnToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a report in another window, automatically adds the authorization
|
||||||
|
* token to params.
|
||||||
|
*
|
||||||
|
* @param {String} report The report name
|
||||||
|
* @param {Object} params The report parameters
|
||||||
|
*/
|
||||||
|
show(report, params) {
|
||||||
|
params = Object.assign({
|
||||||
|
authorization: this.vnToken.token
|
||||||
|
}, params);
|
||||||
|
const serializedParams = this.$httpParamSerializer(params);
|
||||||
|
window.open(`api/report/${report}?${serializedParams}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Report.$inject = ['$httpParamSerializer', 'vnToken'];
|
||||||
|
|
||||||
|
ngModule.service('vnReport', Report);
|
|
@ -14,7 +14,7 @@
|
||||||
font-family: 'Material Icons';
|
font-family: 'Material Icons';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('./icons/Material-Design-Icons.woff2') format('woff2');
|
src: url('./icons/MaterialIcons-Regular.woff2') format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -7,6 +7,13 @@ import './quick-link';
|
||||||
* Small card with basing entity information and actions.
|
* Small card with basing entity information and actions.
|
||||||
*/
|
*/
|
||||||
export default class Descriptor extends Component {
|
export default class Descriptor extends Component {
|
||||||
|
constructor($element, $, vnReport, vnEmail) {
|
||||||
|
super($element, $);
|
||||||
|
|
||||||
|
this.vnReport = vnReport;
|
||||||
|
this.vnEmail = vnEmail;
|
||||||
|
}
|
||||||
|
|
||||||
$postLink() {
|
$postLink() {
|
||||||
const content = this.element.querySelector('vn-descriptor-content');
|
const content = this.element.querySelector('vn-descriptor-content');
|
||||||
if (!content) throw new Error('Directive vnDescriptorContent not found');
|
if (!content) throw new Error('Directive vnDescriptorContent not found');
|
||||||
|
@ -74,35 +81,10 @@ export default class Descriptor extends Component {
|
||||||
return this.$http.get(url, options)
|
return this.$http.get(url, options)
|
||||||
.finally(() => this.canceler = null);
|
.finally(() => this.canceler = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a report in another window, automatically adds the authorization
|
|
||||||
* token to params.
|
|
||||||
*
|
|
||||||
* @param {String} report The report name
|
|
||||||
* @param {Object} params The report parameters
|
|
||||||
*/
|
|
||||||
showReport(report, params) {
|
|
||||||
params = Object.assign({
|
|
||||||
authorization: this.vnToken.token
|
|
||||||
}, params);
|
|
||||||
const serializedParams = this.$httpParamSerializer(params);
|
|
||||||
window.open(`api/report/${report}?${serializedParams}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an email displaying a notification when it's sent.
|
|
||||||
*
|
|
||||||
* @param {String} report The email report name
|
|
||||||
* @param {Object} params The email parameters
|
|
||||||
* @return {Promise} Promise resolved when it's sent
|
|
||||||
*/
|
|
||||||
sendEmail(report, params) {
|
|
||||||
return this.$http.get(`email/${report}`, {params})
|
|
||||||
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Descriptor.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
|
||||||
|
|
||||||
ngModule.vnComponent('vnDescriptor', {
|
ngModule.vnComponent('vnDescriptor', {
|
||||||
controller: Descriptor,
|
controller: Descriptor,
|
||||||
bindings: {
|
bindings: {
|
||||||
|
|
259
gulpfile.js
259
gulpfile.js
|
@ -1,11 +1,11 @@
|
||||||
require('require-yaml');
|
require('require-yaml');
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const exec = require('child_process').exec;
|
|
||||||
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 request = require('request');
|
||||||
const e2eConfig = require('./e2e/helpers/config.js');
|
const e2eConfig = require('./e2e/helpers/config.js');
|
||||||
|
const Docker = require('./db/docker.js');
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
|
||||||
|
@ -18,10 +18,6 @@ let langs = ['es', 'en'];
|
||||||
let srcDir = './front';
|
let srcDir = './front';
|
||||||
let modulesDir = './modules';
|
let modulesDir = './modules';
|
||||||
let buildDir = 'dist';
|
let buildDir = 'dist';
|
||||||
let containerId = 'salix-db';
|
|
||||||
|
|
||||||
let dataSources = require('./loopback/server/datasources.json');
|
|
||||||
let dbConf = dataSources.vn;
|
|
||||||
|
|
||||||
let backSources = [
|
let backSources = [
|
||||||
'!node_modules',
|
'!node_modules',
|
||||||
|
@ -63,7 +59,7 @@ function backWatch(done) {
|
||||||
done: done
|
done: done
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
backWatch.description = `Starts backend in waching mode`;
|
backWatch.description = `Starts backend in watcher mode`;
|
||||||
|
|
||||||
const back = gulp.series(dockerStart, backWatch);
|
const back = gulp.series(dockerStart, backWatch);
|
||||||
back.description = `Starts backend and database service`;
|
back.description = `Starts backend and database service`;
|
||||||
|
@ -73,13 +69,25 @@ defaultTask.description = `Starts all application services`;
|
||||||
|
|
||||||
// Backend tests
|
// Backend tests
|
||||||
|
|
||||||
async function backTestOnce() {
|
async function backTestOnce(done) {
|
||||||
let bootOptions;
|
let err;
|
||||||
|
let dataSources = require('./loopback/server/datasources.json');
|
||||||
|
|
||||||
if (argv['random'])
|
const container = new Docker();
|
||||||
bootOptions = {dataSources};
|
await container.run(argv.ci);
|
||||||
|
|
||||||
|
dataSources = JSON.parse(JSON.stringify(dataSources));
|
||||||
|
|
||||||
|
Object.assign(dataSources.vn, {
|
||||||
|
host: container.dbConf.host,
|
||||||
|
port: container.dbConf.port
|
||||||
|
});
|
||||||
|
|
||||||
|
let bootOptions = {dataSources};
|
||||||
|
|
||||||
let app = require(`./loopback/server/server`);
|
let app = require(`./loopback/server/server`);
|
||||||
|
|
||||||
|
try {
|
||||||
app.boot(bootOptions);
|
app.boot(bootOptions);
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
@ -92,7 +100,7 @@ async function backTestOnce() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (argv.junit) {
|
if (argv.ci) {
|
||||||
const reporters = require('jasmine-reporters');
|
const reporters = require('jasmine-reporters');
|
||||||
options.reporter = new reporters.JUnitXmlReporter();
|
options.reporter = new reporters.JUnitXmlReporter();
|
||||||
}
|
}
|
||||||
|
@ -109,42 +117,16 @@ async function backTestOnce() {
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
.resume();
|
.resume();
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
await app.disconnect();
|
await app.disconnect();
|
||||||
|
await container.rm();
|
||||||
|
done();
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
backTestOnce.description = `Runs the backend tests once, can receive --junit arg to save reports on a xml file`;
|
backTestOnce.description = `Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`;
|
||||||
|
|
||||||
async function backTestDockerOnce() {
|
|
||||||
let containerId = await docker();
|
|
||||||
let err;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await backTestOnce();
|
|
||||||
} catch (e) {
|
|
||||||
err = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv['random'])
|
|
||||||
await execP(`docker rm -fv ${containerId}`);
|
|
||||||
if (err) throw err;
|
|
||||||
}
|
|
||||||
backTestDockerOnce.description = `Runs backend tests using in site container once`;
|
|
||||||
|
|
||||||
async function backTestDocker() {
|
|
||||||
let containerId = await docker();
|
|
||||||
let err;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await backTest();
|
|
||||||
} catch (e) {
|
|
||||||
err = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv['random'])
|
|
||||||
await execP(`docker rm -fv ${containerId}`);
|
|
||||||
if (err) throw err;
|
|
||||||
}
|
|
||||||
backTestDocker.description = `Runs backend tests restoring fixtures first`;
|
|
||||||
|
|
||||||
function backTest(done) {
|
function backTest(done) {
|
||||||
const nodemon = require('gulp-nodemon');
|
const nodemon = require('gulp-nodemon');
|
||||||
|
@ -208,7 +190,14 @@ function e2eSingleRun() {
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
e2eSingleRun.description = `Runs the e2e tests just once`;
|
|
||||||
|
e2e = gulp.series(docker, async function isBackendReady() {
|
||||||
|
const attempts = await backendStatus();
|
||||||
|
log(`Backend ready after ${attempts} attempt(s)`);
|
||||||
|
|
||||||
|
return attempts;
|
||||||
|
}, e2eSingleRun);
|
||||||
|
e2e.description = `Restarts database and runs the e2e tests`;
|
||||||
|
|
||||||
async function backendStatus() {
|
async function backendStatus() {
|
||||||
const milliseconds = 250;
|
const milliseconds = 250;
|
||||||
|
@ -231,24 +220,6 @@ async function backendStatus() {
|
||||||
}
|
}
|
||||||
backendStatus.description = `Performs a simple requests to check the backend status`;
|
backendStatus.description = `Performs a simple requests to check the backend status`;
|
||||||
|
|
||||||
e2e = gulp.series(docker, async function isBackendReady() {
|
|
||||||
const attempts = await backendStatus();
|
|
||||||
log(`Backend ready after ${attempts} attempt(s)`);
|
|
||||||
|
|
||||||
return attempts;
|
|
||||||
}, e2eSingleRun);
|
|
||||||
e2e.description = `Restarts database and runs the e2e tests`;
|
|
||||||
|
|
||||||
function smokesOnly() {
|
|
||||||
const jasmine = require('gulp-jasmine');
|
|
||||||
return gulp.src('./e2e/smokes-tests.js')
|
|
||||||
.pipe(jasmine({reporter: 'none'}));
|
|
||||||
}
|
|
||||||
smokesOnly.description = `Runs the smokes tests only`;
|
|
||||||
|
|
||||||
smokes = gulp.series(docker, smokesOnly);
|
|
||||||
smokes.description = `Restarts database and runs the smokes tests`;
|
|
||||||
|
|
||||||
function install() {
|
function install() {
|
||||||
const install = require('gulp-install');
|
const install = require('gulp-install');
|
||||||
const print = require('gulp-print');
|
const print = require('gulp-print');
|
||||||
|
@ -414,156 +385,17 @@ function watch(done) {
|
||||||
watch.description = `Watches for changes in routes and locale files`;
|
watch.description = `Watches for changes in routes and locale files`;
|
||||||
|
|
||||||
// Docker
|
// Docker
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the database image and runs a container. It only rebuilds the
|
|
||||||
* image when fixtures have been modified or when the day on which the
|
|
||||||
* image was built is different to today. Some workarounds have been used
|
|
||||||
* to avoid a bug with OverlayFS driver on MacOS.
|
|
||||||
*/
|
|
||||||
async function docker() {
|
|
||||||
let d = new Date();
|
|
||||||
let pad = v => v < 10 ? '0' + v : v;
|
|
||||||
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
||||||
await execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`);
|
|
||||||
|
|
||||||
let dockerArgs = `--name ${containerId} -p 3306:${dbConf.port}`;
|
|
||||||
|
|
||||||
if (argv['random'])
|
|
||||||
dockerArgs = '-p 3306';
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
await execP(`docker rm -fv ${containerId}`);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let runChown = process.platform != 'linux';
|
|
||||||
if (argv['run-chown']) runChown = true;
|
|
||||||
|
|
||||||
let result = await execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
|
|
||||||
containerId = result.stdout;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (argv['random']) {
|
|
||||||
let inspect = await execP(`docker inspect -f "{{json .NetworkSettings}}" ${containerId}`);
|
|
||||||
let netSettings = JSON.parse(inspect.stdout);
|
|
||||||
|
|
||||||
dbConf.host = netSettings.Gateway;
|
|
||||||
dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runChown) await dockerWait();
|
|
||||||
} catch (err) {
|
|
||||||
if (argv['random'])
|
|
||||||
await execP(`docker rm -fv ${containerId}`);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return containerId;
|
|
||||||
}
|
|
||||||
docker.description = `Builds the database image and runs a container`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the minium effort to start the database container, if it doesn't exists
|
|
||||||
* calls the 'docker' task, if it is started does nothing. Keep in mind that when
|
|
||||||
* you do not rebuild the docker you may be using an outdated version of it.
|
|
||||||
* See the 'docker' task for more info.
|
|
||||||
*/
|
|
||||||
async function dockerStart() {
|
async function dockerStart() {
|
||||||
let state;
|
const container = new Docker('salix-db');
|
||||||
try {
|
await container.start();
|
||||||
let result = await execP(`docker inspect -f "{{json .State}}" ${containerId}`);
|
|
||||||
state = JSON.parse(result.stdout);
|
|
||||||
} catch (err) {
|
|
||||||
return await docker();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state.Status) {
|
|
||||||
case 'running':
|
|
||||||
return;
|
|
||||||
case 'exited':
|
|
||||||
await execP(`docker start ${containerId}`);
|
|
||||||
await dockerWait();
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown docker status: ${state.Status}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dockerStart.description = `Starts the database container`;
|
dockerStart.description = `Starts the salix-db container`;
|
||||||
|
|
||||||
function dockerWait() {
|
async function docker() {
|
||||||
return new Promise((resolve, reject) => {
|
const container = new Docker('salix-db');
|
||||||
const mysql = require('mysql2');
|
await container.run();
|
||||||
|
|
||||||
let interval = 100;
|
|
||||||
let elapsedTime = 0;
|
|
||||||
let maxInterval = 4 * 60 * 1000;
|
|
||||||
|
|
||||||
let myConf = {
|
|
||||||
user: dbConf.username,
|
|
||||||
password: dbConf.password,
|
|
||||||
host: dbConf.host,
|
|
||||||
port: dbConf.port
|
|
||||||
};
|
|
||||||
|
|
||||||
log('Waiting for MySQL init process...');
|
|
||||||
checker();
|
|
||||||
|
|
||||||
async function checker() {
|
|
||||||
elapsedTime += interval;
|
|
||||||
let state;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result = await execP(`docker container inspect -f "{{json .State}}" ${containerId}`);
|
|
||||||
state = JSON.parse(result.stdout);
|
|
||||||
} catch (err) {
|
|
||||||
return reject(new Error(err.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.Status === 'exited')
|
|
||||||
return reject(new Error('Docker exited, please see the docker logs for more info'));
|
|
||||||
|
|
||||||
let conn = mysql.createConnection(myConf);
|
|
||||||
conn.on('error', () => {});
|
|
||||||
conn.connect(err => {
|
|
||||||
conn.destroy();
|
|
||||||
if (!err) {
|
|
||||||
log('MySQL process ready.');
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elapsedTime >= maxInterval)
|
|
||||||
reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`));
|
|
||||||
else
|
|
||||||
setTimeout(checker, interval);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dockerWait.description = `Waits until database service is ready`;
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Promisified version of exec().
|
|
||||||
*
|
|
||||||
* @param {String} command The exec command
|
|
||||||
* @return {Promise} The promise
|
|
||||||
*/
|
|
||||||
function execP(command) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
exec(command, (err, stdout, stderr) => {
|
|
||||||
if (err)
|
|
||||||
reject(err);
|
|
||||||
else {
|
|
||||||
resolve({
|
|
||||||
stdout: stdout,
|
|
||||||
stderr: stderr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
docker.description = `Runs the salix-db container`;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
default: defaultTask,
|
default: defaultTask,
|
||||||
|
@ -572,13 +404,8 @@ module.exports = {
|
||||||
backOnly,
|
backOnly,
|
||||||
backWatch,
|
backWatch,
|
||||||
backTestOnce,
|
backTestOnce,
|
||||||
backTestDockerOnce,
|
|
||||||
backTest,
|
backTest,
|
||||||
backTestDocker,
|
|
||||||
e2e,
|
e2e,
|
||||||
e2eSingleRun,
|
|
||||||
smokes,
|
|
||||||
smokesOnly,
|
|
||||||
i,
|
i,
|
||||||
install,
|
install,
|
||||||
build,
|
build,
|
||||||
|
@ -590,7 +417,5 @@ module.exports = {
|
||||||
localesRoutes,
|
localesRoutes,
|
||||||
watch,
|
watch,
|
||||||
docker,
|
docker,
|
||||||
dockerStart,
|
|
||||||
dockerWait,
|
|
||||||
backendStatus,
|
backendStatus,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,14 +11,14 @@ class Controller extends Descriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
showPickupOrder() {
|
showPickupOrder() {
|
||||||
this.showReport('claim-pickup-order', {
|
this.vnReport.show('claim-pickup-order', {
|
||||||
recipientId: this.claim.clientFk,
|
recipientId: this.claim.clientFk,
|
||||||
claimId: this.claim.id
|
claimId: this.claim.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPickupOrder() {
|
sendPickupOrder() {
|
||||||
return this.sendEmail('claim-pickup-order', {
|
return this.vnEmail.send('claim-pickup-order', {
|
||||||
recipient: this.claim.client.email,
|
recipient: this.claim.client.email,
|
||||||
recipientId: this.claim.clientFk,
|
recipientId: this.claim.clientFk,
|
||||||
claimId: this.claim.id
|
claimId: this.claim.id
|
||||||
|
|
|
@ -20,21 +20,22 @@ describe('Item Component vnClaimDescriptor', () => {
|
||||||
|
|
||||||
describe('showPickupOrder()', () => {
|
describe('showPickupOrder()', () => {
|
||||||
it('should open a new window showing a pickup order PDF document', () => {
|
it('should open a new window showing a pickup order PDF document', () => {
|
||||||
controller.showReport = jest.fn();
|
jest.spyOn(controller.vnReport, 'show');
|
||||||
|
|
||||||
|
window.open = jasmine.createSpy('open');
|
||||||
const params = {
|
const params = {
|
||||||
recipientId: claim.clientFk,
|
recipientId: claim.clientFk,
|
||||||
claimId: claim.id
|
claimId: claim.id
|
||||||
};
|
};
|
||||||
controller.showPickupOrder();
|
controller.showPickupOrder();
|
||||||
|
|
||||||
expect(controller.showReport).toHaveBeenCalledWith('claim-pickup-order', params);
|
expect(controller.vnReport.show).toHaveBeenCalledWith('claim-pickup-order', params);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sendPickupOrder()', () => {
|
describe('sendPickupOrder()', () => {
|
||||||
it('should make a query and call vnApp.showMessage() if the response is accept', () => {
|
it('should make a query and call vnApp.showMessage() if the response is accept', () => {
|
||||||
jest.spyOn(controller, 'sendEmail');
|
jest.spyOn(controller.vnEmail, 'send');
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
recipient: claim.client.email,
|
recipient: claim.client.email,
|
||||||
|
@ -43,7 +44,7 @@ describe('Item Component vnClaimDescriptor', () => {
|
||||||
};
|
};
|
||||||
controller.sendPickupOrder();
|
controller.sendPickupOrder();
|
||||||
|
|
||||||
expect(controller.sendEmail).toHaveBeenCalledWith('claim-pickup-order', params);
|
expect(controller.vnEmail.send).toHaveBeenCalledWith('claim-pickup-order', params);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
</section>
|
</section>
|
||||||
<section class="photo" ng-repeat="photo in $ctrl.photos">
|
<section class="photo" ng-repeat="photo in $ctrl.photos">
|
||||||
<section class="image vn-shadow" on-error-src
|
<section class="image vn-shadow" on-error-src
|
||||||
ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.vnToken.token + ')'}"
|
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
||||||
zoom-image="/api/dms/{{::photo.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}">
|
||||||
</section>
|
</section>
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
<vn-button
|
<vn-button
|
||||||
|
|
|
@ -3,6 +3,11 @@ import Section from 'salix/components/section';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
|
constructor($element, $, vnFile) {
|
||||||
|
super($element, $);
|
||||||
|
this.vnFile = vnFile;
|
||||||
|
}
|
||||||
|
|
||||||
deleteDms(index) {
|
deleteDms(index) {
|
||||||
const dmsFk = this.photos[index].dmsFk;
|
const dmsFk = this.photos[index].dmsFk;
|
||||||
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
|
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
|
||||||
|
@ -80,8 +85,14 @@ class Controller extends Section {
|
||||||
this.$.model.refresh();
|
this.$.model.refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getImagePath(dmsId) {
|
||||||
|
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||||
|
|
||||||
ngModule.component('vnClaimPhotos', {
|
ngModule.component('vnClaimPhotos', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
|
|
|
@ -90,8 +90,8 @@
|
||||||
<vn-horizontal class="photo-list">
|
<vn-horizontal class="photo-list">
|
||||||
<section class="photo" ng-repeat="photo in photos">
|
<section class="photo" ng-repeat="photo in photos">
|
||||||
<section class="image" on-error-src
|
<section class="image" on-error-src
|
||||||
ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.vnToken.token + ')'}"
|
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
||||||
zoom-image="/api/dms/{{::photo.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}">
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
|
|
|
@ -3,6 +3,11 @@ import Section from 'salix/components/section';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
|
constructor($element, $, vnFile) {
|
||||||
|
super($element, $);
|
||||||
|
this.vnFile = vnFile;
|
||||||
|
}
|
||||||
|
|
||||||
$onChanges() {
|
$onChanges() {
|
||||||
if (this.claim && this.claim.id)
|
if (this.claim && this.claim.id)
|
||||||
this.getSummary();
|
this.getSummary();
|
||||||
|
@ -32,8 +37,14 @@ class Controller extends Section {
|
||||||
this.summary = response.data;
|
this.summary = response.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getImagePath(dmsId) {
|
||||||
|
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||||
|
|
||||||
ngModule.component('vnClaimSummary', {
|
ngModule.component('vnClaimSummary', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
|
||||||
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||||
|
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||||
|
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('consumption', {
|
||||||
|
description: 'Find all instances of the model matched by filter from the data source.',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'Object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
|
||||||
|
}, {
|
||||||
|
arg: 'search',
|
||||||
|
type: 'String',
|
||||||
|
description: `If it's and integer searchs by id, otherwise it searchs by name`
|
||||||
|
}, {
|
||||||
|
arg: 'itemFk',
|
||||||
|
type: 'Integer',
|
||||||
|
description: 'Item id'
|
||||||
|
}, {
|
||||||
|
arg: 'categoryFk',
|
||||||
|
type: 'Integer',
|
||||||
|
description: 'Category id'
|
||||||
|
}, {
|
||||||
|
arg: 'typeFk',
|
||||||
|
type: 'Integer',
|
||||||
|
description: 'Item type id',
|
||||||
|
}, {
|
||||||
|
arg: 'buyerFk',
|
||||||
|
type: 'Integer',
|
||||||
|
description: 'Buyer id'
|
||||||
|
}, {
|
||||||
|
arg: 'from',
|
||||||
|
type: 'Date',
|
||||||
|
description: `The from date filter`
|
||||||
|
}, {
|
||||||
|
arg: 'to',
|
||||||
|
type: 'Date',
|
||||||
|
description: `The to date filter`
|
||||||
|
}, {
|
||||||
|
arg: 'grouped',
|
||||||
|
type: 'Boolean',
|
||||||
|
description: 'Group by item'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['Object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/consumption`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.consumption = async(ctx, filter) => {
|
||||||
|
const conn = Self.dataSource.connector;
|
||||||
|
const args = ctx.args;
|
||||||
|
const where = buildFilter(ctx.args, (param, value) => {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return /^\d+$/.test(value)
|
||||||
|
? {'i.id': value}
|
||||||
|
: {'i.name': {like: `%${value}%`}};
|
||||||
|
case 'itemId':
|
||||||
|
return {'i.id': value};
|
||||||
|
case 'description':
|
||||||
|
return {'i.description': {like: `%${value}%`}};
|
||||||
|
case 'categoryId':
|
||||||
|
return {'it.categoryFk': value};
|
||||||
|
case 'typeId':
|
||||||
|
return {'it.id': value};
|
||||||
|
case 'buyerId':
|
||||||
|
return {'it.workerFk': value};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filter = mergeFilters(filter, {where});
|
||||||
|
|
||||||
|
let stmt = new ParameterizedSQL('SELECT');
|
||||||
|
if (args.grouped)
|
||||||
|
stmt.merge(`SUM(s.quantity) AS quantity,`);
|
||||||
|
else
|
||||||
|
stmt.merge(`s.quantity,`);
|
||||||
|
|
||||||
|
stmt.merge(`s.itemFk,
|
||||||
|
s.concept,
|
||||||
|
s.ticketFk,
|
||||||
|
t.shipped,
|
||||||
|
i.name AS itemName,
|
||||||
|
i.size AS itemSize,
|
||||||
|
i.typeFk AS itemTypeFk,
|
||||||
|
i.subName,
|
||||||
|
i.tag5,
|
||||||
|
i.value5,
|
||||||
|
i.tag6,
|
||||||
|
i.value6,
|
||||||
|
i.tag7,
|
||||||
|
i.value7,
|
||||||
|
i.tag8,
|
||||||
|
i.value8,
|
||||||
|
i.tag9,
|
||||||
|
i.value9,
|
||||||
|
i.tag10,
|
||||||
|
i.value10
|
||||||
|
FROM sale s
|
||||||
|
JOIN ticket t ON t.id = s.ticketFk
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN itemType it ON it.id = i.typeFk`, [args.grouped]);
|
||||||
|
|
||||||
|
stmt.merge(conn.makeWhere(filter.where));
|
||||||
|
|
||||||
|
if (args.grouped)
|
||||||
|
stmt.merge(`GROUP BY s.itemFk`);
|
||||||
|
|
||||||
|
stmt.merge(conn.makePagination(filter));
|
||||||
|
|
||||||
|
return conn.executeStmt(stmt);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('client consumption() filter', () => {
|
||||||
|
it('should return a list of buyed items by ticket', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 9}}, args: {}};
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
clientFk: 101
|
||||||
|
},
|
||||||
|
order: 'itemTypeFk, itemName, itemSize'
|
||||||
|
};
|
||||||
|
const result = await app.models.Client.consumption(ctx, filter);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of tickets grouped by item', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 9}},
|
||||||
|
args: {
|
||||||
|
grouped: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
clientFk: 101
|
||||||
|
},
|
||||||
|
order: 'itemTypeFk, itemName, itemSize'
|
||||||
|
};
|
||||||
|
const result = await app.models.Client.consumption(ctx, filter);
|
||||||
|
|
||||||
|
const firstRow = result[0];
|
||||||
|
const secondRow = result[1];
|
||||||
|
const thirdRow = result[2];
|
||||||
|
|
||||||
|
expect(result.length).toEqual(3);
|
||||||
|
expect(firstRow.quantity).toEqual(10);
|
||||||
|
expect(secondRow.quantity).toEqual(15);
|
||||||
|
expect(thirdRow.quantity).toEqual(20);
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,7 +2,6 @@ let request = require('request-promise-native');
|
||||||
let UserError = require('vn-loopback/util/user-error');
|
let UserError = require('vn-loopback/util/user-error');
|
||||||
let getFinalState = require('vn-loopback/util/hook').getFinalState;
|
let getFinalState = require('vn-loopback/util/hook').getFinalState;
|
||||||
let isMultiple = require('vn-loopback/util/hook').isMultiple;
|
let isMultiple = require('vn-loopback/util/hook').isMultiple;
|
||||||
const httpParamSerializer = require('vn-loopback/util/http').httpParamSerializer;
|
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
@ -27,6 +26,7 @@ module.exports = Self => {
|
||||||
require('../methods/client/sendSms')(Self);
|
require('../methods/client/sendSms')(Self);
|
||||||
require('../methods/client/createAddress')(Self);
|
require('../methods/client/createAddress')(Self);
|
||||||
require('../methods/client/updateAddress')(Self);
|
require('../methods/client/updateAddress')(Self);
|
||||||
|
require('../methods/client/consumption')(Self);
|
||||||
|
|
||||||
// Validations
|
// Validations
|
||||||
|
|
||||||
|
@ -218,6 +218,36 @@ module.exports = Self => {
|
||||||
await Self.app.models.ClientCredit.create(newCredit);
|
await Self.app.models.ClientCredit.create(newCredit);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
app.on('started', function() {
|
||||||
|
let account = app.models.Account;
|
||||||
|
|
||||||
|
account.observe('before save', async ctx => {
|
||||||
|
if (ctx.isNewInstance) return;
|
||||||
|
ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance));
|
||||||
|
});
|
||||||
|
|
||||||
|
account.observe('after save', async ctx => {
|
||||||
|
let changes = ctx.data || ctx.instance;
|
||||||
|
if (!ctx.isNewInstance && changes) {
|
||||||
|
let oldData = ctx.hookState.oldInstance;
|
||||||
|
let hasChanges = oldData.name != changes.name || oldData.active != changes.active;
|
||||||
|
if (!hasChanges) return;
|
||||||
|
|
||||||
|
let userId = ctx.options.accessToken.userId;
|
||||||
|
let logRecord = {
|
||||||
|
originFk: oldData.id,
|
||||||
|
userFk: userId,
|
||||||
|
action: 'update',
|
||||||
|
changedModel: 'Account',
|
||||||
|
oldInstance: {name: oldData.name, active: oldData.active},
|
||||||
|
newInstance: {name: changes.name, active: changes.active}
|
||||||
|
};
|
||||||
|
await Self.app.models.ClientLog.create(logRecord);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Self.observe('after save', async ctx => {
|
Self.observe('after save', async ctx => {
|
||||||
if (ctx.isNewInstance) return;
|
if (ctx.isNewInstance) return;
|
||||||
|
|
|
@ -6,6 +6,15 @@
|
||||||
data="$ctrl.addresses"
|
data="$ctrl.addresses"
|
||||||
auto-load="true">
|
auto-load="true">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
|
<vn-portal slot="topbar">
|
||||||
|
<vn-searchbar
|
||||||
|
placeholder="Search by address"
|
||||||
|
info="You can search by address id or name"
|
||||||
|
model="model"
|
||||||
|
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||||
|
auto-state="false">
|
||||||
|
</vn-searchbar>
|
||||||
|
</vn-portal>
|
||||||
<vn-data-viewer
|
<vn-data-viewer
|
||||||
model="model"
|
model="model"
|
||||||
class="vn-w-md">
|
class="vn-w-md">
|
||||||
|
@ -35,7 +44,7 @@
|
||||||
</vn-none>
|
</vn-none>
|
||||||
<vn-one
|
<vn-one
|
||||||
style="overflow: hidden; min-width: 14em;">
|
style="overflow: hidden; min-width: 14em;">
|
||||||
<div class="ellipsize"><b>{{::address.nickname}}</b></div>
|
<div class="ellipsize"><b>{{::address.nickname}} - #{{::address.id}}</b></div>
|
||||||
<div class="ellipsize" name="street">{{::address.street}}</div>
|
<div class="ellipsize" name="street">{{::address.street}}</div>
|
||||||
<div class="ellipsize">{{::address.city}}, {{::address.province.name}}</div>
|
<div class="ellipsize">{{::address.city}}, {{::address.province.name}}</div>
|
||||||
<div class="ellipsize">
|
<div class="ellipsize">
|
||||||
|
|
|
@ -68,6 +68,15 @@ class Controller extends Section {
|
||||||
return this.isDefaultAddress(b) - this.isDefaultAddress(a);
|
return this.isDefaultAddress(b) - this.isDefaultAddress(a);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exprBuilder(param, value) {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return /^\d+$/.test(value)
|
||||||
|
? {id: value}
|
||||||
|
: {nickname: {like: `%${value}%`}};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Controller.$inject = ['$element', '$scope'];
|
Controller.$inject = ['$element', '$scope'];
|
||||||
|
|
||||||
|
|
|
@ -67,5 +67,19 @@ describe('Client', () => {
|
||||||
expect(controller.addresses[0].id).toEqual(123);
|
expect(controller.addresses[0].id).toEqual(123);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('exprBuilder()', () => {
|
||||||
|
it('should return a filter based on a search by id', () => {
|
||||||
|
const filter = controller.exprBuilder('search', '123');
|
||||||
|
|
||||||
|
expect(filter).toEqual({id: '123'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a filter based on a search by name', () => {
|
||||||
|
const filter = controller.exprBuilder('search', 'Bruce Wayne');
|
||||||
|
|
||||||
|
expect(filter).toEqual({nickname: {like: '%Bruce Wayne%'}});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Index
|
# Index
|
||||||
Set as default: Establecer como predeterminado
|
Set as default: Establecer como predeterminado
|
||||||
Active first to set as default: Active primero para marcar como predeterminado
|
Active first to set as default: Active primero para marcar como predeterminado
|
||||||
|
Search by address: Buscar por consignatario
|
||||||
|
You can search by address id or name: Puedes buscar por el id o nombre del consignatario
|
||||||
# Edit
|
# Edit
|
||||||
Enabled: Activo
|
Enabled: Activo
|
||||||
Is equalizated: Recargo de equivalencia
|
Is equalizated: Recargo de equivalencia
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<div class="search-panel">
|
||||||
|
<form class="vn-pa-lg" ng-submit="$ctrl.onSearch()">
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield vn-focus
|
||||||
|
vn-one
|
||||||
|
label="General search"
|
||||||
|
ng-model="filter.search"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Item id"
|
||||||
|
ng-model="filter.itemId">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete
|
||||||
|
vn-one
|
||||||
|
ng-model="filter.buyerId"
|
||||||
|
url="Clients/activeWorkersWithRole"
|
||||||
|
search-function="{firstName: $search}"
|
||||||
|
value-field="id"
|
||||||
|
where="{role: 'employee'}"
|
||||||
|
label="Buyer">
|
||||||
|
<tpl-item>{{nickname}}</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
ng-model="filter.typeId"
|
||||||
|
url="ItemTypes"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
label="Type"
|
||||||
|
fields="['categoryFk']"
|
||||||
|
include="'category'">
|
||||||
|
<tpl-item>
|
||||||
|
<div>{{name}}</div>
|
||||||
|
<div class="text-caption text-secondary">
|
||||||
|
{{category.name}}
|
||||||
|
</div>
|
||||||
|
</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
url="ItemCategories"
|
||||||
|
label="Category"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
ng-model="filter.categoryId">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="From"
|
||||||
|
ng-model="filter.from">
|
||||||
|
</vn-date-picker>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="To"
|
||||||
|
ng-model="filter.to">
|
||||||
|
</vn-date-picker>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal class="vn-mt-lg">
|
||||||
|
<vn-submit label="Search"></vn-submit>
|
||||||
|
</vn-horizontal>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -0,0 +1,7 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||||
|
|
||||||
|
ngModule.component('vnConsumptionSearchPanel', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: SearchPanel
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
Item id: Id artículo
|
||||||
|
From: Desde
|
||||||
|
To: Hasta
|
|
@ -0,0 +1,91 @@
|
||||||
|
<vn-crud-model vn-id="model"
|
||||||
|
url="Clients/consumption"
|
||||||
|
link="{clientFk: $ctrl.$params.id}"
|
||||||
|
filter="::$ctrl.filter"
|
||||||
|
user-params="::$ctrl.filterParams"
|
||||||
|
data="sales"
|
||||||
|
order="itemTypeFk, itemName, itemSize">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-portal slot="topbar">
|
||||||
|
<vn-searchbar
|
||||||
|
panel="vn-consumption-search-panel"
|
||||||
|
suggested-filter="$ctrl.filterParams"
|
||||||
|
info="Search by item id or name"
|
||||||
|
model="model"
|
||||||
|
auto-state="false">
|
||||||
|
</vn-searchbar>
|
||||||
|
</vn-portal>
|
||||||
|
<vn-data-viewer model="model">
|
||||||
|
<vn-card class="vn-pa-lg vn-w-lg">
|
||||||
|
<section class="header">
|
||||||
|
<vn-tool-bar class="vn-mb-md">
|
||||||
|
<vn-button disabled="!model.userParams.from || !model.userParams.to"
|
||||||
|
icon="picture_as_pdf"
|
||||||
|
ng-click="$ctrl.showReport()"
|
||||||
|
vn-tooltip="Open as PDF">
|
||||||
|
</vn-button>
|
||||||
|
<vn-button disabled="!model.userParams.from || !model.userParams.to"
|
||||||
|
icon="email"
|
||||||
|
ng-click="confirm.show()"
|
||||||
|
vn-tooltip="Send to email">
|
||||||
|
</vn-button>
|
||||||
|
<vn-check
|
||||||
|
label="Group by item"
|
||||||
|
on-change="$ctrl.changeGrouped(value)">
|
||||||
|
</vn-check>
|
||||||
|
</vn-tool-bar>
|
||||||
|
</section>
|
||||||
|
<vn-table model="model">
|
||||||
|
<vn-thead>
|
||||||
|
<vn-tr>
|
||||||
|
<vn-th field="itemFk" number>Item</vn-th>
|
||||||
|
<vn-th field="ticketFk" number>Ticket</vn-th>
|
||||||
|
<vn-th field="shipped">Fecha</vn-th>
|
||||||
|
<vn-th expand>Description</vn-th>
|
||||||
|
<vn-th field="quantity" number>Quantity</vn-th>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-thead>
|
||||||
|
<vn-tbody>
|
||||||
|
<vn-tr
|
||||||
|
ng-repeat="sale in sales">
|
||||||
|
<vn-td number>
|
||||||
|
<span
|
||||||
|
ng-click="itemDescriptor.show($event, sale.itemFk)"
|
||||||
|
class="link">
|
||||||
|
{{::sale.itemFk}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td number>
|
||||||
|
<span
|
||||||
|
ng-click="ticketDescriptor.show($event, sale.ticketFk)"
|
||||||
|
class="link">
|
||||||
|
{{::sale.ticketFk}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td>{{::sale.shipped | date: 'dd/MM/yyyy'}}</vn-td>
|
||||||
|
<vn-td expand>
|
||||||
|
<vn-fetched-tags
|
||||||
|
max-length="6"
|
||||||
|
item="::sale"
|
||||||
|
name="::sale.concept"
|
||||||
|
sub-name="::sale.subName">
|
||||||
|
</vn-fetched-tags>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td number>{{::sale.quantity | dashIfEmpty}}</vn-td>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-tbody>
|
||||||
|
</vn-table>
|
||||||
|
</vn-card>
|
||||||
|
</vn-data-viewer>
|
||||||
|
<vn-item-descriptor-popover
|
||||||
|
vn-id="item-descriptor">
|
||||||
|
</vn-item-descriptor-popover>
|
||||||
|
<vn-ticket-descriptor-popover
|
||||||
|
vn-id="ticket-descriptor">
|
||||||
|
</vn-ticket-descriptor-popover>
|
||||||
|
<vn-confirm
|
||||||
|
vn-id="confirm"
|
||||||
|
question="Please, confirm"
|
||||||
|
message="The consumption report will be sent"
|
||||||
|
on-accept="$ctrl.sendEmail()">
|
||||||
|
</vn-confirm>
|
|
@ -0,0 +1,69 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
class Controller extends Section {
|
||||||
|
constructor($element, $, vnReport, vnEmail) {
|
||||||
|
super($element, $);
|
||||||
|
this.vnReport = vnReport;
|
||||||
|
this.vnEmail = vnEmail;
|
||||||
|
|
||||||
|
this.filter = {
|
||||||
|
where: {
|
||||||
|
isPackaging: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const minDate = new Date();
|
||||||
|
minDate.setHours(0, 0, 0, 0);
|
||||||
|
minDate.setMonth(minDate.getMonth() - 2);
|
||||||
|
|
||||||
|
const maxDate = new Date();
|
||||||
|
maxDate.setHours(23, 59, 59, 59);
|
||||||
|
|
||||||
|
this.filterParams = {
|
||||||
|
from: minDate,
|
||||||
|
to: maxDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get reportParams() {
|
||||||
|
const userParams = this.$.model.userParams;
|
||||||
|
return Object.assign({
|
||||||
|
authorization: this.vnToken.token,
|
||||||
|
recipientId: this.client.id
|
||||||
|
}, userParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTicketDescriptor(event, sale) {
|
||||||
|
if (!sale.isTicket) return;
|
||||||
|
|
||||||
|
this.$.ticketDescriptor.show(event.target, sale.origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
showReport() {
|
||||||
|
this.vnReport.show('campaign-metrics', this.reportParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEmail() {
|
||||||
|
this.vnEmail.send('campaign-metrics', this.reportParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeGrouped(value) {
|
||||||
|
const model = this.$.model;
|
||||||
|
|
||||||
|
model.addFilter({}, {grouped: value});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
|
||||||
|
|
||||||
|
ngModule.component('vnClientConsumption', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
client: '<'
|
||||||
|
},
|
||||||
|
require: {
|
||||||
|
card: '^vnClientCard'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,72 @@
|
||||||
|
import './index.js';
|
||||||
|
import crudModel from 'core/mocks/crud-model';
|
||||||
|
|
||||||
|
describe('Client', () => {
|
||||||
|
describe('Component vnClientConsumption', () => {
|
||||||
|
let $scope;
|
||||||
|
let controller;
|
||||||
|
let $httpParamSerializer;
|
||||||
|
let $httpBackend;
|
||||||
|
|
||||||
|
beforeEach(ngModule('client'));
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
|
||||||
|
$scope = $rootScope.$new();
|
||||||
|
$httpParamSerializer = _$httpParamSerializer_;
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
const $element = angular.element('<vn-client-consumption></vn-client-consumption');
|
||||||
|
controller = $componentController('vnClientConsumption', {$element, $scope});
|
||||||
|
controller.$.model = crudModel;
|
||||||
|
controller.client = {
|
||||||
|
id: 101
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('showReport()', () => {
|
||||||
|
it('should call the window.open function', () => {
|
||||||
|
jest.spyOn(window, 'open').mockReturnThis();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
controller.$.model.userParams = {
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.showReport();
|
||||||
|
|
||||||
|
const expectedParams = {
|
||||||
|
recipientId: 101,
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
const serializedParams = $httpParamSerializer(expectedParams);
|
||||||
|
const path = `api/report/campaign-metrics?${serializedParams}`;
|
||||||
|
|
||||||
|
expect(window.open).toHaveBeenCalledWith(path);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendEmail()', () => {
|
||||||
|
it('should make a GET query sending the report', () => {
|
||||||
|
const now = new Date();
|
||||||
|
controller.$.model.userParams = {
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
const expectedParams = {
|
||||||
|
recipientId: 101,
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
|
||||||
|
const serializedParams = $httpParamSerializer(expectedParams);
|
||||||
|
const path = `email/campaign-metrics?${serializedParams}`;
|
||||||
|
|
||||||
|
$httpBackend.expect('GET', path).respond({});
|
||||||
|
controller.sendEmail();
|
||||||
|
$httpBackend.flush();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
Group by item: Agrupar por artículo
|
||||||
|
Open as PDF: Abrir como PDF
|
||||||
|
Send to email: Enviar por email
|
||||||
|
Search by item id or name: Buscar por id de artículo o nombre
|
||||||
|
The consumption report will be sent: Se enviará el informe de consumo
|
||||||
|
Please, confirm: Por favor, confirma
|
|
@ -13,11 +13,6 @@
|
||||||
translate>
|
translate>
|
||||||
Send SMS
|
Send SMS
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
|
||||||
ng-click="consumerReportDialog.show()"
|
|
||||||
translate>
|
|
||||||
View consumer report
|
|
||||||
</vn-item>
|
|
||||||
</slot-menu>
|
</slot-menu>
|
||||||
<slot-body>
|
<slot-body>
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
|
@ -95,27 +90,3 @@
|
||||||
vn-id="sms"
|
vn-id="sms"
|
||||||
sms="$ctrl.newSMS">
|
sms="$ctrl.newSMS">
|
||||||
</vn-client-sms>
|
</vn-client-sms>
|
||||||
<vn-dialog
|
|
||||||
vn-id="consumerReportDialog"
|
|
||||||
on-accept="$ctrl.onConsumerReportAccept()"
|
|
||||||
message="Send consumer report">
|
|
||||||
<tpl-body>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-id="from"
|
|
||||||
vn-one
|
|
||||||
ng-model="$ctrl.from"
|
|
||||||
label="From date"
|
|
||||||
vn-focus>
|
|
||||||
</vn-date-picker>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-id="to"
|
|
||||||
vn-one
|
|
||||||
ng-model="$ctrl.to"
|
|
||||||
label="To date">
|
|
||||||
</vn-date-picker>
|
|
||||||
</tpl-body>
|
|
||||||
<tpl-buttons>
|
|
||||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
|
||||||
<button response="accept" translate>Accept</button>
|
|
||||||
</tpl-buttons>
|
|
||||||
</vn-dialog>
|
|
|
@ -39,14 +39,6 @@ class Controller extends Descriptor {
|
||||||
};
|
};
|
||||||
this.$.sms.open();
|
this.$.sms.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
onConsumerReportAccept() {
|
|
||||||
this.showReport('campaign-metrics', {
|
|
||||||
recipientId: this.id,
|
|
||||||
from: this.from,
|
|
||||||
to: this.to,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnClientDescriptor', {
|
ngModule.vnComponent('vnClientDescriptor', {
|
||||||
|
|
|
@ -54,11 +54,10 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<span title="{{'Download file' | translate}}" class="link"
|
||||||
title="{{'Download file' | translate}}"
|
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
{{::document.dms.file}}
|
{{::document.dms.file}}
|
||||||
</a>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<span class="link"
|
<span class="link"
|
||||||
|
@ -69,13 +68,10 @@
|
||||||
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<vn-icon-button title="{{'Download file' | translate}}"
|
||||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
<vn-icon-button
|
|
||||||
icon="cloud_download"
|
icon="cloud_download"
|
||||||
title="{{'Download file' | translate}}">
|
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</a>
|
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-icon-button ui-sref="client.card.dms.edit({dmsId: {{::document.dmsFk}}})"
|
<vn-icon-button ui-sref="client.card.dms.edit({dmsId: {{::document.dmsFk}}})"
|
||||||
|
|
|
@ -3,8 +3,9 @@ import Section from 'salix/components/section';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
constructor($element, $) {
|
constructor($element, $, vnFile) {
|
||||||
super($element, $);
|
super($element, $, vnFile);
|
||||||
|
this.vnFile = vnFile;
|
||||||
this.filter = {
|
this.filter = {
|
||||||
include: {
|
include: {
|
||||||
relation: 'dms',
|
relation: 'dms',
|
||||||
|
@ -49,9 +50,13 @@ class Controller extends Section {
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadFile(dmsId) {
|
||||||
|
this.vnFile.download(`api/dms/${dmsId}/downloadFile`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope'];
|
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||||
|
|
||||||
ngModule.component('vnClientDmsIndex', {
|
ngModule.component('vnClientDmsIndex', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
|
|
|
@ -40,3 +40,5 @@ import './postcode';
|
||||||
import './dms/index';
|
import './dms/index';
|
||||||
import './dms/create';
|
import './dms/create';
|
||||||
import './dms/edit';
|
import './dms/edit';
|
||||||
|
import './consumption';
|
||||||
|
import './consumption-search-panel';
|
||||||
|
|
|
@ -57,3 +57,4 @@ Contacts: Contactos
|
||||||
Samples: Plantillas
|
Samples: Plantillas
|
||||||
Send sample: Enviar plantilla
|
Send sample: Enviar plantilla
|
||||||
Log: Historial
|
Log: Historial
|
||||||
|
Consumption: Consumo
|
|
@ -18,16 +18,17 @@
|
||||||
{"state": "client.card.greuge.index", "icon": "work"},
|
{"state": "client.card.greuge.index", "icon": "work"},
|
||||||
{"state": "client.card.balance.index", "icon": "icon-invoices"},
|
{"state": "client.card.balance.index", "icon": "icon-invoices"},
|
||||||
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
|
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
|
||||||
|
{"state": "client.card.webAccess", "icon": "cloud"},
|
||||||
{"state": "client.card.log", "icon": "history"},
|
{"state": "client.card.log", "icon": "history"},
|
||||||
{
|
{
|
||||||
"description": "Others",
|
"description": "Others",
|
||||||
"icon": "more",
|
"icon": "more",
|
||||||
"childs": [
|
"childs": [
|
||||||
{"state": "client.card.webAccess", "icon": "cloud"},
|
{"state": "client.card.sample.index", "icon": "mail"},
|
||||||
|
{"state": "client.card.consumption", "icon": "show_chart"},
|
||||||
{"state": "client.card.mandate", "icon": "pan_tool"},
|
{"state": "client.card.mandate", "icon": "pan_tool"},
|
||||||
{"state": "client.card.creditInsurance.index", "icon": "icon-solunion"},
|
{"state": "client.card.creditInsurance.index", "icon": "icon-solunion"},
|
||||||
{"state": "client.card.contact", "icon": "contact_phone"},
|
{"state": "client.card.contact", "icon": "contact_phone"},
|
||||||
{"state": "client.card.sample.index", "icon": "mail"},
|
|
||||||
{"state": "client.card.webPayment", "icon": "icon-onlinepayment"},
|
{"state": "client.card.webPayment", "icon": "icon-onlinepayment"},
|
||||||
{"state": "client.card.dms.index", "icon": "cloud_upload"}
|
{"state": "client.card.dms.index", "icon": "cloud_upload"}
|
||||||
]
|
]
|
||||||
|
@ -350,6 +351,15 @@
|
||||||
"params": {
|
"params": {
|
||||||
"client": "$ctrl.client"
|
"client": "$ctrl.client"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/consumption",
|
||||||
|
"state": "client.card.consumption",
|
||||||
|
"component": "vn-client-consumption",
|
||||||
|
"description": "Consumption",
|
||||||
|
"params": {
|
||||||
|
"client": "$ctrl.client"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class Controller extends Descriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
showEntryReport() {
|
showEntryReport() {
|
||||||
this.showReport('entry-order', {
|
this.vnReport.show('entry-order', {
|
||||||
entryId: this.entry.id
|
entryId: this.entry.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,16 @@ describe('Entry Component vnEntryDescriptor', () => {
|
||||||
|
|
||||||
describe('showEntryReport()', () => {
|
describe('showEntryReport()', () => {
|
||||||
it('should open a new window showing a delivery note PDF document', () => {
|
it('should open a new window showing a delivery note PDF document', () => {
|
||||||
controller.showReport = jest.fn();
|
jest.spyOn(controller.vnReport, 'show');
|
||||||
|
|
||||||
|
window.open = jasmine.createSpy('open');
|
||||||
const params = {
|
const params = {
|
||||||
clientId: controller.vnConfig.storage.currentUserWorkerId,
|
clientId: controller.vnConfig.storage.currentUserWorkerId,
|
||||||
entryId: entry.id
|
entryId: entry.id
|
||||||
};
|
};
|
||||||
controller.showEntryReport();
|
controller.showEntryReport();
|
||||||
|
|
||||||
expect(controller.showReport).toHaveBeenCalledWith('entry-order', params);
|
expect(controller.vnReport.show).toHaveBeenCalledWith('entry-order', params);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,14 +11,14 @@ class Controller extends Descriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
showRouteReport() {
|
showRouteReport() {
|
||||||
this.showReport('driver-route', {
|
this.vnReport.show('driver-route', {
|
||||||
routeId: this.id
|
routeId: this.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRouteReport() {
|
sendRouteReport() {
|
||||||
const workerUser = this.route.worker.user;
|
const workerUser = this.route.worker.user;
|
||||||
this.sendEmail('driver-route', {
|
this.vnEmail.send('driver-route', {
|
||||||
recipient: workerUser.emailUser.email,
|
recipient: workerUser.emailUser.email,
|
||||||
routeId: this.id
|
routeId: this.id
|
||||||
});
|
});
|
||||||
|
|
|
@ -100,34 +100,27 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.filter = async(ctx, filter) => {
|
Self.filter = async(ctx, filter) => {
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
const conn = Self.dataSource.connector;
|
const conn = Self.dataSource.connector;
|
||||||
|
const models = Self.app.models;
|
||||||
const args = ctx.args;
|
const args = ctx.args;
|
||||||
|
|
||||||
let worker = await Self.app.models.Worker.findOne({
|
// Apply filter by team
|
||||||
where: {userFk: ctx.req.accessToken.userId},
|
const teamMembersId = [];
|
||||||
include: [
|
if (args.myTeam != null) {
|
||||||
{relation: 'collegues'}
|
const worker = await models.Worker.findById(userId, {
|
||||||
]
|
include: {
|
||||||
});
|
relation: 'collegues'
|
||||||
|
|
||||||
let teamIds = [];
|
|
||||||
|
|
||||||
if (worker.collegues().length && args.myTeam) {
|
|
||||||
worker.collegues().forEach(collegue => {
|
|
||||||
teamIds.push(collegue.collegueFk);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.mine || (worker.collegues().length === 0 && args.myTeam)) {
|
|
||||||
worker = await Self.app.models.Worker.findOne({
|
|
||||||
fields: ['id'],
|
|
||||||
where: {userFk: ctx.req.accessToken.userId}
|
|
||||||
});
|
});
|
||||||
teamIds = [worker && worker.id];
|
const collegues = worker.collegues() || [];
|
||||||
}
|
collegues.forEach(collegue => {
|
||||||
|
teamMembersId.push(collegue.collegueFk);
|
||||||
|
});
|
||||||
|
|
||||||
if (ctx.args && (args.mine || args.myTeam))
|
if (teamMembersId.length == 0)
|
||||||
args.teamIds = teamIds;
|
teamMembersId.push(userId);
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.args && args.to) {
|
if (ctx.args && args.to) {
|
||||||
const dateTo = args.to;
|
const dateTo = args.to;
|
||||||
|
@ -156,7 +149,11 @@ module.exports = Self => {
|
||||||
return {'ts.stateFk': value};
|
return {'ts.stateFk': value};
|
||||||
case 'mine':
|
case 'mine':
|
||||||
case 'myTeam':
|
case 'myTeam':
|
||||||
return {'c.salesPersonFk': {inq: teamIds}};
|
if (value)
|
||||||
|
return {'c.salesPersonFk': {inq: teamMembersId}};
|
||||||
|
else
|
||||||
|
return {'c.salesPersonFk': {nin: teamMembersId}};
|
||||||
|
|
||||||
case 'alertLevel':
|
case 'alertLevel':
|
||||||
return {'ts.alertLevel': value};
|
return {'ts.alertLevel': value};
|
||||||
case 'pending':
|
case 'pending':
|
||||||
|
|
|
@ -20,7 +20,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.getVolume = async ticketFk => {
|
Self.getVolume = async ticketFk => {
|
||||||
let [volume] = await Self.rawSql(`CALL vn.ticketListVolume(?)`, [ticketFk]);
|
return Self.rawSql(`SELECT * FROM vn.saleVolume
|
||||||
return volume;
|
WHERE ticketFk = ?`, [ticketFk]);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,18 +43,6 @@ module.exports = Self => {
|
||||||
if (hasItemShelvingSales && !isSalesAssistant)
|
if (hasItemShelvingSales && !isSalesAssistant)
|
||||||
throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
|
throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
|
||||||
|
|
||||||
if (hasItemShelvingSales && isSalesAssistant) {
|
|
||||||
const promises = [];
|
|
||||||
for (let sale of sales) {
|
|
||||||
if (sale.itemShelvingSale()) {
|
|
||||||
const itemShelvingSale = sale.itemShelvingSale();
|
|
||||||
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id);
|
|
||||||
promises.push(destroyedShelving);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing claim
|
// Check for existing claim
|
||||||
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}});
|
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}});
|
||||||
if (claimOfATicket)
|
if (claimOfATicket)
|
||||||
|
@ -69,10 +57,23 @@ module.exports = Self => {
|
||||||
if (hasPurchaseRequests)
|
if (hasPurchaseRequests)
|
||||||
throw new UserError('You must delete all the buy requests first');
|
throw new UserError('You must delete all the buy requests first');
|
||||||
|
|
||||||
|
// removes item shelvings
|
||||||
|
if (hasItemShelvingSales && isSalesAssistant) {
|
||||||
|
const promises = [];
|
||||||
|
for (let sale of sales) {
|
||||||
|
if (sale.itemShelvingSale()) {
|
||||||
|
const itemShelvingSale = sale.itemShelvingSale();
|
||||||
|
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id);
|
||||||
|
promises.push(destroyedShelving);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove ticket greuges
|
// Remove ticket greuges
|
||||||
const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}});
|
const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}});
|
||||||
const ownGreuges = ticketGreuges.every(greuge => {
|
const ownGreuges = ticketGreuges.every(greuge => {
|
||||||
return greuge.ticketFk = id;
|
return greuge.ticketFk == id;
|
||||||
});
|
});
|
||||||
if (ownGreuges) {
|
if (ownGreuges) {
|
||||||
for (const greuge of ticketGreuges) {
|
for (const greuge of ticketGreuges) {
|
||||||
|
@ -104,7 +105,7 @@ module.exports = Self => {
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Change state to "fixing" if contains an stowaway
|
// Change state to "fixing" if contains an stowaway and removed the link between them
|
||||||
let otherTicketId;
|
let otherTicketId;
|
||||||
if (ticket.stowaway())
|
if (ticket.stowaway())
|
||||||
otherTicketId = ticket.stowaway().shipFk;
|
otherTicketId = ticket.stowaway().shipFk;
|
||||||
|
@ -112,6 +113,7 @@ module.exports = Self => {
|
||||||
otherTicketId = ticket.ship().id;
|
otherTicketId = ticket.ship().id;
|
||||||
|
|
||||||
if (otherTicketId) {
|
if (otherTicketId) {
|
||||||
|
await models.Ticket.deleteStowaway(ctx, otherTicketId);
|
||||||
await models.TicketTracking.changeState(ctx, {
|
await models.TicketTracking.changeState(ctx, {
|
||||||
ticketFk: otherTicketId,
|
ticketFk: otherTicketId,
|
||||||
code: 'FIXING'
|
code: 'FIXING'
|
||||||
|
|
|
@ -71,4 +71,20 @@ describe('ticket filter()', () => {
|
||||||
expect(secondRow.state).toEqual('Entregado');
|
expect(secondRow.state).toEqual('Entregado');
|
||||||
expect(thirdRow.state).toEqual('Entregado');
|
expect(thirdRow.state).toEqual('Entregado');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return the tickets from the worker team', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 9}}, args: {myTeam: true}};
|
||||||
|
const filter = {};
|
||||||
|
const result = await app.models.Ticket.filter(ctx, filter);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(17);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the tickets that are not from the worker team', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 9}}, args: {myTeam: false}};
|
||||||
|
const filter = {};
|
||||||
|
const result = await app.models.Ticket.filter(ctx, filter);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(7);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ describe('ticket getVolume()', () => {
|
||||||
let ticketFk = 1;
|
let ticketFk = 1;
|
||||||
await app.models.Ticket.getVolume(ticketFk)
|
await app.models.Ticket.getVolume(ticketFk)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response[0].m3).toEqual(1.09);
|
expect(response[0].volume).toEqual(1.09);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,116 +1,7 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
const models = app.models;
|
const models = app.models;
|
||||||
|
|
||||||
// 2301 Failing tests
|
describe('ticket setDeleted()', () => {
|
||||||
xdescribe('ticket deleted()', () => {
|
|
||||||
let ticket;
|
|
||||||
let sale;
|
|
||||||
let deletedClaim;
|
|
||||||
|
|
||||||
beforeAll(async done => {
|
|
||||||
let originalTicket = await models.Ticket.findOne({where: {id: 16}});
|
|
||||||
originalTicket.id = null;
|
|
||||||
ticket = await models.Ticket.create(originalTicket);
|
|
||||||
sale = await models.Sale.create({
|
|
||||||
ticketFk: ticket.id,
|
|
||||||
itemFk: 4,
|
|
||||||
concept: 'Melee weapon',
|
|
||||||
quantity: 10
|
|
||||||
});
|
|
||||||
|
|
||||||
await models.ItemShelvingSale.create({
|
|
||||||
itemShelvingFk: 1,
|
|
||||||
saleFk: sale.id,
|
|
||||||
quantity: 10,
|
|
||||||
userFk: 106
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async done => {
|
|
||||||
const ticketId = 16;
|
|
||||||
const stowawayTicketId = 17;
|
|
||||||
const ctx = {
|
|
||||||
req: {
|
|
||||||
accessToken: {userId: 106},
|
|
||||||
headers: {
|
|
||||||
origin: 'http://localhost:5000'
|
|
||||||
},
|
|
||||||
__: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await models.Ticket.destroyById(ticket.id);
|
|
||||||
const stowaway = await models.Stowaway.findOne({
|
|
||||||
where: {
|
|
||||||
id: stowawayTicketId,
|
|
||||||
shipFk: ticketId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await stowaway.destroy();
|
|
||||||
await models.Claim.create(deletedClaim);
|
|
||||||
await models.TicketTracking.changeState(ctx, {
|
|
||||||
ticketFk: ticketId,
|
|
||||||
code: 'OK'
|
|
||||||
});
|
|
||||||
await models.TicketTracking.changeState(ctx, {
|
|
||||||
ticketFk: stowawayTicketId,
|
|
||||||
code: 'OK'
|
|
||||||
});
|
|
||||||
const orgTicket = await models.Ticket.findById(ticketId);
|
|
||||||
await orgTicket.updateAttribute('isDeleted', false);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should make sure the ticket is not deleted yet', async() => {
|
|
||||||
expect(ticket.isDeleted).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should make sure the ticket sale has an item shelving', async() => {
|
|
||||||
const sales = await models.Sale.find({
|
|
||||||
include: {relation: 'itemShelvingSale'},
|
|
||||||
where: {ticketFk: ticket.id}
|
|
||||||
});
|
|
||||||
const hasItemShelvingSales = sales.some(sale => {
|
|
||||||
return sale.itemShelvingSale();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(hasItemShelvingSales).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set a ticket to deleted and remove all item shelvings', async() => {
|
|
||||||
const salesAssistantId = 21;
|
|
||||||
const ctx = {
|
|
||||||
req: {
|
|
||||||
accessToken: {userId: salesAssistantId},
|
|
||||||
headers: {
|
|
||||||
origin: 'http://localhost:5000'
|
|
||||||
},
|
|
||||||
__: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await app.models.Ticket.setDeleted(ctx, ticket.id);
|
|
||||||
|
|
||||||
let deletedTicket = await app.models.Ticket.findOne({
|
|
||||||
where: {id: ticket.id},
|
|
||||||
fields: ['isDeleted']
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deletedTicket.isDeleted).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not have any item shelving', async() => {
|
|
||||||
const sales = await models.Sale.find({
|
|
||||||
include: {relation: 'itemShelvingSale'},
|
|
||||||
where: {ticketFk: ticket.id}
|
|
||||||
});
|
|
||||||
const hasItemShelvingSales = sales.some(sale => {
|
|
||||||
return sale.itemShelvingSale();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(hasItemShelvingSales).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error if the given ticket has a claim', async() => {
|
it('should throw an error if the given ticket has a claim', async() => {
|
||||||
const ticketId = 16;
|
const ticketId = 16;
|
||||||
const ctx = {
|
const ctx = {
|
||||||
|
@ -134,13 +25,11 @@ xdescribe('ticket deleted()', () => {
|
||||||
expect(error.message).toEqual('You must delete the claim id %d first');
|
expect(error.message).toEqual('You must delete the claim id %d first');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the ticket and change the state to "FIXING" to the stowaway ticket', async() => {
|
it('should delete the ticket, remove the stowaway link and change the stowaway ticket state to "FIXING" and get ride of the itemshelving', async() => {
|
||||||
const ticketId = 16;
|
const employeeUser = 110;
|
||||||
const claimIdToRemove = 2;
|
|
||||||
const stowawayTicketId = 17;
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
req: {
|
req: {
|
||||||
accessToken: {userId: 106},
|
accessToken: {userId: employeeUser},
|
||||||
headers: {
|
headers: {
|
||||||
origin: 'http://localhost:5000'
|
origin: 'http://localhost:5000'
|
||||||
},
|
},
|
||||||
|
@ -148,20 +37,66 @@ xdescribe('ticket deleted()', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await app.models.Stowaway.rawSql(`
|
let sampleTicket = await models.Ticket.findById(12);
|
||||||
|
let sampleStowaway = await models.Ticket.findById(13);
|
||||||
|
|
||||||
|
sampleTicket.id = undefined;
|
||||||
|
let shipTicket = await models.Ticket.create(sampleTicket);
|
||||||
|
|
||||||
|
sampleStowaway.id = undefined;
|
||||||
|
let stowawayTicket = await models.Ticket.create(sampleStowaway);
|
||||||
|
|
||||||
|
await models.Stowaway.rawSql(`
|
||||||
INSERT INTO vn.stowaway(id, shipFk)
|
INSERT INTO vn.stowaway(id, shipFk)
|
||||||
VALUES (?, ?)`, [stowawayTicketId, ticketId]);
|
VALUES (?, ?)`, [stowawayTicket.id, shipTicket.id]);
|
||||||
|
|
||||||
deletedClaim = await app.models.Claim.findById(claimIdToRemove);
|
const boardingState = await models.State.findOne({
|
||||||
await app.models.Claim.destroyById(claimIdToRemove);
|
|
||||||
await app.models.Ticket.setDeleted(ctx, ticketId);
|
|
||||||
|
|
||||||
const stowawayTicket = await app.models.TicketState.findOne({
|
|
||||||
where: {
|
where: {
|
||||||
ticketFk: stowawayTicketId
|
code: 'BOARDING'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await models.TicketTracking.create({
|
||||||
|
ticketFk: stowawayTicket.id,
|
||||||
|
stateFk: boardingState.id,
|
||||||
|
workerFk: ctx.req.accessToken.userId
|
||||||
|
});
|
||||||
|
|
||||||
|
const okState = await models.State.findOne({
|
||||||
|
where: {
|
||||||
|
code: 'OK'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await models.TicketTracking.create({
|
||||||
|
ticketFk: shipTicket.id,
|
||||||
|
stateFk: okState.id,
|
||||||
|
workerFk: ctx.req.accessToken.userId
|
||||||
|
});
|
||||||
|
|
||||||
|
let stowawayTicketState = await models.TicketState.findOne({
|
||||||
|
where: {
|
||||||
|
ticketFk: stowawayTicket.id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stowawayTicket.code).toEqual('FIXING');
|
let stowaway = await models.Stowaway.findById(shipTicket.id);
|
||||||
|
|
||||||
|
expect(stowaway).toBeDefined();
|
||||||
|
expect(stowawayTicketState.code).toEqual('BOARDING');
|
||||||
|
|
||||||
|
await models.Ticket.setDeleted(ctx, shipTicket.id);
|
||||||
|
|
||||||
|
stowawayTicketState = await models.TicketState.findOne({
|
||||||
|
where: {
|
||||||
|
ticketFk: stowawayTicket.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stowaway = await models.Stowaway.findById(shipTicket.id);
|
||||||
|
|
||||||
|
expect(stowaway).toBeNull();
|
||||||
|
expect(stowawayTicketState.code).toEqual('FIXING');
|
||||||
|
|
||||||
|
await shipTicket.destroy();
|
||||||
|
await stowawayTicket.destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -101,14 +101,14 @@ class Controller extends Descriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
showDeliveryNote() {
|
showDeliveryNote() {
|
||||||
this.showReport('delivery-note', {
|
this.vnReport.show('delivery-note', {
|
||||||
recipientId: this.ticket.client.id,
|
recipientId: this.ticket.client.id,
|
||||||
ticketId: this.id,
|
ticketId: this.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDeliveryNote() {
|
sendDeliveryNote() {
|
||||||
return this.sendEmail('delivery-note', {
|
return this.vnEmail.send('delivery-note', {
|
||||||
recipientId: this.ticket.client.id,
|
recipientId: this.ticket.client.id,
|
||||||
recipient: this.ticket.client.email,
|
recipient: this.ticket.client.email,
|
||||||
ticketId: this.id
|
ticketId: this.id
|
||||||
|
|
|
@ -64,21 +64,22 @@ describe('Ticket Component vnTicketDescriptor', () => {
|
||||||
|
|
||||||
describe('showDeliveryNote()', () => {
|
describe('showDeliveryNote()', () => {
|
||||||
it('should open a new window showing a delivery note PDF document', () => {
|
it('should open a new window showing a delivery note PDF document', () => {
|
||||||
jest.spyOn(controller, 'showReport');
|
jest.spyOn(controller.vnReport, 'show');
|
||||||
|
|
||||||
|
window.open = jasmine.createSpy('open');
|
||||||
const params = {
|
const params = {
|
||||||
clientId: ticket.client.id,
|
clientId: ticket.client.id,
|
||||||
ticketId: ticket.id
|
ticketId: ticket.id
|
||||||
};
|
};
|
||||||
controller.showDeliveryNote();
|
controller.showDeliveryNote();
|
||||||
|
|
||||||
expect(controller.showReport).toHaveBeenCalledWith('delivery-note', params);
|
expect(controller.vnReport.show).toHaveBeenCalledWith('delivery-note', params);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sendDeliveryNote()', () => {
|
describe('sendDeliveryNote()', () => {
|
||||||
it('should make a query and call vnApp.showMessage()', () => {
|
it('should make a query and call vnApp.showMessage()', () => {
|
||||||
jest.spyOn(controller, 'sendEmail');
|
jest.spyOn(controller.vnEmail, 'send');
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
recipient: ticket.client.email,
|
recipient: ticket.client.email,
|
||||||
|
@ -87,7 +88,7 @@ describe('Ticket Component vnTicketDescriptor', () => {
|
||||||
};
|
};
|
||||||
controller.sendDeliveryNote();
|
controller.sendDeliveryNote();
|
||||||
|
|
||||||
expect(controller.sendEmail).toHaveBeenCalledWith('delivery-note', params);
|
expect(controller.vnEmail.send).toHaveBeenCalledWith('delivery-note', params);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,11 +52,10 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<span title="{{'Download file' | translate}}" class="link"
|
||||||
title="{{'Download file' | translate}}"
|
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
{{::document.dms.file}}
|
{{::document.dms.file}}
|
||||||
</a>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<span class="link"
|
<span class="link"
|
||||||
|
@ -67,13 +66,10 @@
|
||||||
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<vn-icon-button title="{{'Download file' | translate}}"
|
||||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
<vn-icon-button
|
|
||||||
icon="cloud_download"
|
icon="cloud_download"
|
||||||
title="{{'Download file' | translate}}">
|
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</a>
|
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-icon-button icon="edit"
|
<vn-icon-button icon="edit"
|
||||||
|
|
|
@ -3,8 +3,9 @@ import Section from 'salix/components/section';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
constructor($element, $) {
|
constructor($element, $, vnFile) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
|
this.vnFile = vnFile;
|
||||||
this.filter = {
|
this.filter = {
|
||||||
include: {
|
include: {
|
||||||
relation: 'dms',
|
relation: 'dms',
|
||||||
|
@ -50,8 +51,14 @@ class Controller extends Section {
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadFile(dmsId) {
|
||||||
|
this.vnFile.download(`api/dms/${dmsId}/downloadFile`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||||
|
|
||||||
ngModule.component('vnTicketDmsIndex', {
|
ngModule.component('vnTicketDmsIndex', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
sub-name="::sale.item.subName"/>
|
sub-name="::sale.item.subName"/>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>{{::sale.quantity}}</vn-td>
|
<vn-td number>{{::sale.quantity}}</vn-td>
|
||||||
<vn-td number>{{::sale.volume.m3 | number:3}}</vn-td>
|
<vn-td number>{{::sale.saleVolume.volume | number:3}}</vn-td>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-tbody>
|
</vn-tbody>
|
||||||
</vn-table>
|
</vn-table>
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Controller extends Section {
|
||||||
this.sales.forEach(sale => {
|
this.sales.forEach(sale => {
|
||||||
this.volumes.forEach(volume => {
|
this.volumes.forEach(volume => {
|
||||||
if (sale.id === volume.saleFk)
|
if (sale.id === volume.saleFk)
|
||||||
sale.volume = volume;
|
sale.saleVolume = volume;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,10 +59,10 @@ describe('ticket', () => {
|
||||||
|
|
||||||
it(`should apply volumes to the sales if sales and volumes properties are defined on controller`, () => {
|
it(`should apply volumes to the sales if sales and volumes properties are defined on controller`, () => {
|
||||||
controller.sales = [{id: 1, name: 'Sale one'}, {id: 2, name: 'Sale two'}];
|
controller.sales = [{id: 1, name: 'Sale one'}, {id: 2, name: 'Sale two'}];
|
||||||
controller.volumes = [{saleFk: 1, m3: 0.012}, {saleFk: 2, m3: 0.015}];
|
controller.volumes = [{saleFk: 1, volume: 0.012}, {saleFk: 2, volume: 0.015}];
|
||||||
|
|
||||||
expect(controller.sales[0].volume.m3).toEqual(0.012);
|
expect(controller.sales[0].saleVolume.volume).toEqual(0.012);
|
||||||
expect(controller.sales[1].volume.m3).toEqual(0.015);
|
expect(controller.sales[1].saleVolume.volume).toEqual(0.015);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-portal slot="topbar">
|
<vn-portal slot="topbar">
|
||||||
<vn-searchbar
|
<vn-searchbar
|
||||||
|
placeholder="Search by weekly ticket"
|
||||||
info="Search weekly ticket by id or client id"
|
info="Search weekly ticket by id or client id"
|
||||||
auto-state="false"
|
auto-state="false"
|
||||||
model="model">
|
model="model">
|
||||||
|
|
|
@ -3,3 +3,4 @@ Weekly tickets: Tickets programados
|
||||||
You are going to delete this weekly ticket: Vas a eliminar este ticket programado
|
You are going to delete this weekly ticket: Vas a eliminar este ticket programado
|
||||||
This ticket will be removed from weekly tickets! Continue anyway?: Este ticket se eliminará de tickets programados! ¿Continuar de todas formas?
|
This ticket will be removed from weekly tickets! Continue anyway?: Este ticket se eliminará de tickets programados! ¿Continuar de todas formas?
|
||||||
Search weekly ticket by id or client id: Busca tickets programados por el identificador o el identificador del cliente
|
Search weekly ticket by id or client id: Busca tickets programados por el identificador o el identificador del cliente
|
||||||
|
Search by weekly ticket: Buscar por tickets programados
|
|
@ -29,13 +29,10 @@
|
||||||
<vn-td>{{::thermograph.warehouse.name}}</vn-td>
|
<vn-td>{{::thermograph.warehouse.name}}</vn-td>
|
||||||
<vn-td>{{::thermograph.created | date: 'dd/MM/yyyy'}}</vn-td>
|
<vn-td>{{::thermograph.created | date: 'dd/MM/yyyy'}}</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<vn-icon-button title="{{'Download file' | translate}}"
|
||||||
href="api/dms/{{::thermograph.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
<vn-icon-button
|
|
||||||
icon="cloud_download"
|
icon="cloud_download"
|
||||||
title="{{'Download file' | translate}}">
|
ng-click="$ctrl.downloadFile(thermograph.dmsFk)">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</a>
|
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-icon-button ui-sref="travel.card.thermograph.edit({thermographId: {{::thermograph.id}}})"
|
<vn-icon-button ui-sref="travel.card.thermograph.edit({thermographId: {{::thermograph.id}}})"
|
||||||
|
|
|
@ -3,7 +3,9 @@ import Section from 'salix/components/section';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
$onInit() {
|
constructor($element, $, vnFile) {
|
||||||
|
super($element, $);
|
||||||
|
this.vnFile = vnFile;
|
||||||
this.filter = {
|
this.filter = {
|
||||||
include:
|
include:
|
||||||
{relation: 'warehouse',
|
{relation: 'warehouse',
|
||||||
|
@ -29,8 +31,14 @@ class Controller extends Section {
|
||||||
this.thermographIndex = null;
|
this.thermographIndex = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadFile(dmsId) {
|
||||||
|
this.vnFile.download(`api/dms/${dmsId}/downloadFile`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||||
|
|
||||||
ngModule.component('vnTravelThermographIndex', {
|
ngModule.component('vnTravelThermographIndex', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
|
|
|
@ -38,22 +38,19 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td >
|
</vn-td >
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<span title="{{'Download file' | translate}}" class="link"
|
||||||
title="{{'Download file' | translate}}"
|
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||||
href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">{{::document.file}}
|
{{::document.file}}
|
||||||
</a>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td>
|
<vn-td>
|
||||||
{{::document.created | date:'dd/MM/yyyy HH:mm'}}
|
{{::document.created | date:'dd/MM/yyyy HH:mm'}}
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<a target="_blank"
|
<vn-icon-button title="{{'Download file' | translate}}"
|
||||||
href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
<vn-icon-button
|
|
||||||
icon="cloud_download"
|
icon="cloud_download"
|
||||||
title="{{'Download file' | translate}}">
|
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</a>
|
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-icon-button ui-sref="worker.card.edit({dmsId: {{::document.dmsFk}}})"
|
<vn-icon-button ui-sref="worker.card.edit({dmsId: {{::document.dmsFk}}})"
|
||||||
|
|
|
@ -3,8 +3,9 @@ import Component from 'core/lib/component';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Component {
|
class Controller extends Component {
|
||||||
constructor($element, $) {
|
constructor($element, $, vnFile) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
|
this.vnFile = vnFile;
|
||||||
this.filter = {
|
this.filter = {
|
||||||
include: {
|
include: {
|
||||||
relation: 'dms',
|
relation: 'dms',
|
||||||
|
@ -51,8 +52,14 @@ class Controller extends Component {
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadFile(dmsId) {
|
||||||
|
this.vnFile.download(`api/workerDms/${dmsId}/downloadFile`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||||
|
|
||||||
ngModule.component('vnWorkerDmsIndex', {
|
ngModule.component('vnWorkerDmsIndex', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
|
|
|
@ -3265,7 +3265,7 @@
|
||||||
},
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -4081,7 +4081,7 @@
|
||||||
"base": {
|
"base": {
|
||||||
"version": "0.11.2",
|
"version": "0.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
|
||||||
"integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=",
|
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cache-base": "^1.0.1",
|
"cache-base": "^1.0.1",
|
||||||
|
@ -4532,7 +4532,7 @@
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -4544,7 +4544,7 @@
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "0.10.31",
|
"version": "0.10.31",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
|
@ -4604,7 +4604,7 @@
|
||||||
"cache-base": {
|
"cache-base": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||||
"integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=",
|
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"collection-visit": "^1.0.0",
|
"collection-visit": "^1.0.0",
|
||||||
|
@ -4674,7 +4674,7 @@
|
||||||
},
|
},
|
||||||
"camelcase-keys": {
|
"camelcase-keys": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||||
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
|
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -4813,7 +4813,7 @@
|
||||||
"class-utils": {
|
"class-utils": {
|
||||||
"version": "0.3.6",
|
"version": "0.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
|
||||||
"integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=",
|
"integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"arr-union": "^3.1.0",
|
"arr-union": "^3.1.0",
|
||||||
|
@ -5899,7 +5899,7 @@
|
||||||
"dot-prop": {
|
"dot-prop": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
|
||||||
"integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=",
|
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-obj": "^1.0.0"
|
"is-obj": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -6856,7 +6856,7 @@
|
||||||
},
|
},
|
||||||
"file-loader": {
|
"file-loader": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz",
|
"resolved": "http://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz",
|
||||||
"integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==",
|
"integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -7813,7 +7813,7 @@
|
||||||
},
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -8023,7 +8023,7 @@
|
||||||
"global-modules": {
|
"global-modules": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
|
||||||
"integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=",
|
"integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"global-prefix": "^1.0.1",
|
"global-prefix": "^1.0.1",
|
||||||
|
@ -10201,7 +10201,7 @@
|
||||||
"is-plain-object": {
|
"is-plain-object": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||||
"integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=",
|
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"isobject": "^3.0.1"
|
"isobject": "^3.0.1"
|
||||||
|
@ -10563,7 +10563,7 @@
|
||||||
"jasmine-spec-reporter": {
|
"jasmine-spec-reporter": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
|
||||||
"integrity": "sha1-HWMq7ANBZwrTJPkrqEtLMrNeniI=",
|
"integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"colors": "1.1.2"
|
"colors": "1.1.2"
|
||||||
|
@ -10740,7 +10740,8 @@
|
||||||
},
|
},
|
||||||
"yargs-parser": {
|
"yargs-parser": {
|
||||||
"version": "13.1.1",
|
"version": "13.1.1",
|
||||||
"resolved": "",
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
|
||||||
|
"integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"camelcase": "^5.0.0",
|
"camelcase": "^5.0.0",
|
||||||
|
@ -12568,7 +12569,7 @@
|
||||||
},
|
},
|
||||||
"meow": {
|
"meow": {
|
||||||
"version": "3.7.0",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||||
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
|
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -13209,7 +13210,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
"resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
|
@ -13359,7 +13360,7 @@
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -13826,7 +13827,7 @@
|
||||||
},
|
},
|
||||||
"os-homedir": {
|
"os-homedir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
"resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
|
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
@ -14999,7 +15000,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsesc": {
|
"jsesc": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
||||||
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
|
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
|
@ -15386,7 +15387,7 @@
|
||||||
},
|
},
|
||||||
"safe-regex": {
|
"safe-regex": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||||
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
|
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -15578,7 +15579,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||||
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
|
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -15930,7 +15931,7 @@
|
||||||
"snapdragon-node": {
|
"snapdragon-node": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
|
||||||
"integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=",
|
"integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-property": "^1.0.0",
|
"define-property": "^1.0.0",
|
||||||
|
@ -15981,7 +15982,7 @@
|
||||||
"snapdragon-util": {
|
"snapdragon-util": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
|
||||||
"integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=",
|
"integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^3.2.0"
|
"kind-of": "^3.2.0"
|
||||||
|
@ -16265,7 +16266,7 @@
|
||||||
"split-string": {
|
"split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
"integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=",
|
"integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"extend-shallow": "^3.0.0"
|
"extend-shallow": "^3.0.0"
|
||||||
|
@ -17509,7 +17510,7 @@
|
||||||
"touch": {
|
"touch": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||||
"integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=",
|
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"nopt": "~1.0.10"
|
"nopt": "~1.0.10"
|
||||||
|
@ -18753,7 +18754,7 @@
|
||||||
},
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -18766,7 +18767,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
|
@ -19227,7 +19228,7 @@
|
||||||
},
|
},
|
||||||
"xmlbuilder": {
|
"xmlbuilder": {
|
||||||
"version": "9.0.7",
|
"version": "9.0.7",
|
||||||
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||||
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
||||||
},
|
},
|
||||||
"xmlcreate": {
|
"xmlcreate": {
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.verdnatura.es/verdnatura/salix"
|
"url": "https://gitea.verdnatura.es/verdnatura/salix"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compression": "^1.7.3",
|
"compression": "^1.7.3",
|
||||||
"fs-extra": "^5.0.0",
|
"fs-extra": "^5.0.0",
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.privacy {
|
.privacy {
|
||||||
|
text-align: center;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 100
|
font-weight: 100
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
header {
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
header .logo {
|
header .logo {
|
||||||
margin-bottom: 15px;
|
margin-top: 25px;
|
||||||
|
margin-bottom: 25px
|
||||||
}
|
}
|
||||||
|
|
||||||
header .logo img {
|
header .logo img {
|
||||||
|
|
|
@ -2,5 +2,6 @@ const Vue = require('vue');
|
||||||
const strftime = require('strftime');
|
const strftime = require('strftime');
|
||||||
|
|
||||||
Vue.filter('date', function(value, specifiers = '%d-%m-%Y') {
|
Vue.filter('date', function(value, specifiers = '%d-%m-%Y') {
|
||||||
|
if (!(value instanceof Date)) value = new Date(value);
|
||||||
return strftime(specifiers, value);
|
return strftime(specifiers, value);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"filename": "campaing-metrics",
|
"filename": "campaign-metrics.pdf",
|
||||||
"component": "campaign-metrics"
|
"component": "campaign-metrics"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -25,7 +25,7 @@
|
||||||
<div class="grid-block vn-pa-lg">
|
<div class="grid-block vn-pa-lg">
|
||||||
<h1>{{ $t('title') }}</h1>
|
<h1>{{ $t('title') }}</h1>
|
||||||
<p>{{$t('dear')}},</p>
|
<p>{{$t('dear')}},</p>
|
||||||
<p>{{$t('description')}}</p>
|
<p v-html="$t('description', [minDate, maxDate])"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Footer block -->
|
<!-- Footer block -->
|
||||||
|
|
|
@ -4,7 +4,17 @@ const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'campaign-metrics',
|
name: 'campaign-metrics',
|
||||||
|
created() {
|
||||||
|
this.filters = this.$options.filters;
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
minDate: function() {
|
||||||
|
return this.filters.date(this.from, '%d-%m-%Y');
|
||||||
|
},
|
||||||
|
maxDate: function() {
|
||||||
|
return this.filters.date(this.to, '%d-%m-%Y');
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
'email-header': emailHeader.build(),
|
'email-header': emailHeader.build(),
|
||||||
'email-footer': emailFooter.build()
|
'email-footer': emailFooter.build()
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
subject: Informe consumo campaña
|
subject: Informe de consumo
|
||||||
title: Informe consumo campaña
|
title: Informe de consumo
|
||||||
dear: Estimado cliente
|
dear: Estimado cliente
|
||||||
description: Con motivo de esta próxima campaña, me complace
|
description: Tal y como nos ha solicitado nos complace
|
||||||
relacionarle a continuación el consumo que nos consta en su cuenta para las
|
relacionarle a continuación el consumo que nos consta en su cuenta para las
|
||||||
mismas fechas del año pasado. Espero le sea de utilidad para preparar su pedido.
|
fechas comprendidas entre <strong>{0}</strong> y <strong>{1}</strong>.
|
||||||
|
Espero le sea de utilidad para preparar su pedido.<br/><br/>
|
||||||
Al mismo tiempo aprovecho la ocasión para saludarle cordialmente.
|
Al mismo tiempo aprovecho la ocasión para saludarle cordialmente.
|
||||||
|
|
|
@ -6,9 +6,6 @@ const reportFooter = new Component('report-footer');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'campaign-metrics',
|
name: 'campaign-metrics',
|
||||||
async serverPrefetch() {
|
async serverPrefetch() {
|
||||||
this.to = new Date(this.to);
|
|
||||||
this.from = new Date(this.from);
|
|
||||||
|
|
||||||
this.client = await this.fetchClient(this.recipientId);
|
this.client = await this.fetchClient(this.recipientId);
|
||||||
this.sales = await this.fetchSales(this.recipientId, this.from, this.to);
|
this.sales = await this.fetchSales(this.recipientId, this.from, this.to);
|
||||||
|
|
||||||
|
@ -54,7 +51,7 @@ module.exports = {
|
||||||
t.clientFk = ? AND it.isPackaging = FALSE
|
t.clientFk = ? AND it.isPackaging = FALSE
|
||||||
AND DATE(t.shipped) BETWEEN ? AND ?
|
AND DATE(t.shipped) BETWEEN ? AND ?
|
||||||
GROUP BY s.itemFk
|
GROUP BY s.itemFk
|
||||||
ORDER BY i.typeFk , i.name , i.size`, [clientId, from, to]);
|
ORDER BY i.typeFk , i.name`, [clientId, from, to]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -66,12 +63,10 @@ module.exports = {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
from: {
|
from: {
|
||||||
required: true,
|
required: true
|
||||||
type: Date
|
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
required: true,
|
required: true
|
||||||
type: Date
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
title: Consumo de campaña
|
title: Consumo
|
||||||
Client: Cliente
|
Client: Cliente
|
||||||
clientData: Datos del cliente
|
clientData: Datos del cliente
|
||||||
dated: Fecha
|
dated: Fecha
|
||||||
|
|
Loading…
Reference in New Issue