Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2290-client_consumption
This commit is contained in:
commit
ed83fd5f73
|
@ -68,7 +68,7 @@ pipeline {
|
|||
stage('Backend') {
|
||||
steps {
|
||||
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.
|
||||
|
||||
* Visual Studio Code
|
||||
* Node.js = 12.17.0 LTS
|
||||
* 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.
|
||||
```
|
||||
# sudo npm install -g jest gulp-cli nodemon
|
||||
# sudo npm install -g jest gulp-cli
|
||||
```
|
||||
## Linux Only Prerequisites
|
||||
|
||||
|
@ -65,6 +59,15 @@ For end-to-end tests run from project's root.
|
|||
$ 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
|
||||
|
||||
* [angularjs](https://angularjs.org/)
|
||||
|
@ -75,4 +78,4 @@ $ gulp e2e
|
|||
* [gulp.js](https://gulpjs.com/)
|
||||
* [jest](https://jestjs.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) {
|
||||
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && 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', {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
|
@ -10,7 +10,7 @@ export default {
|
|||
ticketsButton: '.modules-menu [ui-sref="ticket.index"]',
|
||||
invoiceOutButton: '.modules-menu [ui-sref="invoiceOut.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',
|
||||
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
|
||||
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)',
|
||||
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',
|
||||
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: {
|
||||
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
|
||||
|
|
|
@ -4,6 +4,9 @@ import getBrowser from '../../helpers/puppeteer';
|
|||
describe('Ticket create path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
let nextMonth = new Date();
|
||||
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
||||
let stowawayTicketId;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
|
@ -21,13 +24,9 @@ describe('Ticket create path', () => {
|
|||
});
|
||||
|
||||
it('should succeed to create a ticket', async() => {
|
||||
const nextMonth = new Date();
|
||||
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
||||
|
||||
await page.autocompleteSearch(selectors.createTicketView.client, 'Tony Stark');
|
||||
await page.autocompleteSearch(selectors.createTicketView.address, 'Tony Stark');
|
||||
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.warehouse, 'Warehouse Two');
|
||||
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
|
||||
await page.waitToClick(selectors.createTicketView.createButton);
|
||||
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() => {
|
||||
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()">
|
||||
<vn-textfield
|
||||
class="dense standout"
|
||||
placeholder="{{::'Search by' | translate: {module: $ctrl.baseState} }}"
|
||||
placeholder="{{::$ctrl.placeholder | translate}}"
|
||||
ng-model="$ctrl.searchString">
|
||||
<prepend>
|
||||
<vn-icon
|
||||
|
|
|
@ -21,6 +21,7 @@ export default class Searchbar extends Component {
|
|||
constructor($element, $) {
|
||||
super($element, $);
|
||||
this.searchState = '.';
|
||||
this.placeholder = 'Search';
|
||||
this.autoState = true;
|
||||
|
||||
this.deregisterCallback = this.$transitions.onSuccess(
|
||||
|
@ -35,6 +36,9 @@ export default class Searchbar extends Component {
|
|||
}
|
||||
|
||||
this.searchState = `${this.baseState}.index`;
|
||||
this.placeholder = this.$translate.instant('Search by', {
|
||||
module: this.baseState
|
||||
});
|
||||
}
|
||||
|
||||
this.fetchStateFilter(this.autoLoad);
|
||||
|
@ -293,7 +297,8 @@ ngModule.vnComponent('vnSearchbar', {
|
|||
stateParams: '&?',
|
||||
model: '<?',
|
||||
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 './config';
|
||||
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);
|
|
@ -7,6 +7,13 @@ import './quick-link';
|
|||
* Small card with basing entity information and actions.
|
||||
*/
|
||||
export default class Descriptor extends Component {
|
||||
constructor($element, $, vnReport, vnEmail) {
|
||||
super($element, $);
|
||||
|
||||
this.vnReport = vnReport;
|
||||
this.vnEmail = vnEmail;
|
||||
}
|
||||
|
||||
$postLink() {
|
||||
const content = this.element.querySelector('vn-descriptor-content');
|
||||
if (!content) throw new Error('Directive vnDescriptorContent not found');
|
||||
|
@ -74,34 +81,9 @@ export default class Descriptor extends Component {
|
|||
return this.$http.get(url, options)
|
||||
.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', {
|
||||
controller: Descriptor,
|
||||
|
|
259
gulpfile.js
259
gulpfile.js
|
@ -1,11 +1,11 @@
|
|||
require('require-yaml');
|
||||
const gulp = require('gulp');
|
||||
const exec = require('child_process').exec;
|
||||
const PluginError = require('plugin-error');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
const log = require('fancy-log');
|
||||
const request = require('request');
|
||||
const e2eConfig = require('./e2e/helpers/config.js');
|
||||
const Docker = require('./db/docker.js');
|
||||
|
||||
// Configuration
|
||||
|
||||
|
@ -18,10 +18,6 @@ let langs = ['es', 'en'];
|
|||
let srcDir = './front';
|
||||
let modulesDir = './modules';
|
||||
let buildDir = 'dist';
|
||||
let containerId = 'salix-db';
|
||||
|
||||
let dataSources = require('./loopback/server/datasources.json');
|
||||
let dbConf = dataSources.vn;
|
||||
|
||||
let backSources = [
|
||||
'!node_modules',
|
||||
|
@ -63,7 +59,7 @@ function backWatch(done) {
|
|||
done: done
|
||||
});
|
||||
}
|
||||
backWatch.description = `Starts backend in waching mode`;
|
||||
backWatch.description = `Starts backend in watcher mode`;
|
||||
|
||||
const back = gulp.series(dockerStart, backWatch);
|
||||
back.description = `Starts backend and database service`;
|
||||
|
@ -73,13 +69,25 @@ defaultTask.description = `Starts all application services`;
|
|||
|
||||
// Backend tests
|
||||
|
||||
async function backTestOnce() {
|
||||
let bootOptions;
|
||||
async function backTestOnce(done) {
|
||||
let err;
|
||||
let dataSources = require('./loopback/server/datasources.json');
|
||||
|
||||
if (argv['random'])
|
||||
bootOptions = {dataSources};
|
||||
const container = new Docker();
|
||||
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`);
|
||||
|
||||
try {
|
||||
app.boot(bootOptions);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
|
@ -92,7 +100,7 @@ async function backTestOnce() {
|
|||
}
|
||||
};
|
||||
|
||||
if (argv.junit) {
|
||||
if (argv.ci) {
|
||||
const reporters = require('jasmine-reporters');
|
||||
options.reporter = new reporters.JUnitXmlReporter();
|
||||
}
|
||||
|
@ -109,42 +117,16 @@ async function backTestOnce() {
|
|||
.on('error', reject)
|
||||
.resume();
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
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`;
|
||||
|
||||
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`;
|
||||
backTestOnce.description = `Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`;
|
||||
|
||||
function backTest(done) {
|
||||
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() {
|
||||
const milliseconds = 250;
|
||||
|
@ -231,24 +220,6 @@ async function backendStatus() {
|
|||
}
|
||||
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() {
|
||||
const install = require('gulp-install');
|
||||
const print = require('gulp-print');
|
||||
|
@ -414,156 +385,17 @@ function watch(done) {
|
|||
watch.description = `Watches for changes in routes and locale files`;
|
||||
|
||||
// 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() {
|
||||
let state;
|
||||
try {
|
||||
let result = await execP(`docker inspect -f "{{json .State}}" ${containerId}`);
|
||||
state = JSON.parse(result.stdout);
|
||||
} catch (err) {
|
||||
return await docker();
|
||||
const container = new Docker('salix-db');
|
||||
await container.start();
|
||||
}
|
||||
dockerStart.description = `Starts the salix-db container`;
|
||||
|
||||
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`;
|
||||
|
||||
function dockerWait() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const mysql = require('mysql2');
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
async function docker() {
|
||||
const container = new Docker('salix-db');
|
||||
await container.run();
|
||||
}
|
||||
docker.description = `Runs the salix-db container`;
|
||||
|
||||
module.exports = {
|
||||
default: defaultTask,
|
||||
|
@ -572,13 +404,8 @@ module.exports = {
|
|||
backOnly,
|
||||
backWatch,
|
||||
backTestOnce,
|
||||
backTestDockerOnce,
|
||||
backTest,
|
||||
backTestDocker,
|
||||
e2e,
|
||||
e2eSingleRun,
|
||||
smokes,
|
||||
smokesOnly,
|
||||
i,
|
||||
install,
|
||||
build,
|
||||
|
@ -590,7 +417,5 @@ module.exports = {
|
|||
localesRoutes,
|
||||
watch,
|
||||
docker,
|
||||
dockerStart,
|
||||
dockerWait,
|
||||
backendStatus,
|
||||
};
|
||||
|
|
|
@ -11,14 +11,14 @@ class Controller extends Descriptor {
|
|||
}
|
||||
|
||||
showPickupOrder() {
|
||||
this.showReport('claim-pickup-order', {
|
||||
this.vnReport.show('claim-pickup-order', {
|
||||
recipientId: this.claim.clientFk,
|
||||
claimId: this.claim.id
|
||||
});
|
||||
}
|
||||
|
||||
sendPickupOrder() {
|
||||
return this.sendEmail('claim-pickup-order', {
|
||||
return this.vnEmail.send('claim-pickup-order', {
|
||||
recipient: this.claim.client.email,
|
||||
recipientId: this.claim.clientFk,
|
||||
claimId: this.claim.id
|
||||
|
|
|
@ -20,21 +20,22 @@ describe('Item Component vnClaimDescriptor', () => {
|
|||
|
||||
describe('showPickupOrder()', () => {
|
||||
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 = {
|
||||
recipientId: claim.clientFk,
|
||||
claimId: claim.id
|
||||
};
|
||||
controller.showPickupOrder();
|
||||
|
||||
expect(controller.showReport).toHaveBeenCalledWith('claim-pickup-order', params);
|
||||
expect(controller.vnReport.show).toHaveBeenCalledWith('claim-pickup-order', params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendPickupOrder()', () => {
|
||||
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 = {
|
||||
recipient: claim.client.email,
|
||||
|
@ -43,7 +44,7 @@ describe('Item Component vnClaimDescriptor', () => {
|
|||
};
|
||||
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 class="photo" ng-repeat="photo in $ctrl.photos">
|
||||
<section class="image vn-shadow" on-error-src
|
||||
ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.vnToken.token + ')'}"
|
||||
zoom-image="/api/dms/{{::photo.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
||||
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}">
|
||||
</section>
|
||||
<section class="actions">
|
||||
<vn-button
|
||||
|
|
|
@ -3,6 +3,11 @@ import Section from 'salix/components/section';
|
|||
import './style.scss';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
}
|
||||
|
||||
deleteDms(index) {
|
||||
const dmsFk = this.photos[index].dmsFk;
|
||||
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
|
||||
|
@ -80,7 +85,13 @@ class Controller extends Section {
|
|||
this.$.model.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
getImagePath(dmsId) {
|
||||
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||
|
||||
ngModule.component('vnClaimPhotos', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -90,8 +90,8 @@
|
|||
<vn-horizontal class="photo-list">
|
||||
<section class="photo" ng-repeat="photo in photos">
|
||||
<section class="image" on-error-src
|
||||
ng-style="{'background': 'url(/api/dms/' + photo.dmsFk + '/downloadFile?access_token=' + $ctrl.vnToken.token + ')'}"
|
||||
zoom-image="/api/dms/{{::photo.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
||||
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}">
|
||||
</section>
|
||||
</section>
|
||||
</vn-horizontal>
|
||||
|
|
|
@ -3,6 +3,11 @@ import Section from 'salix/components/section';
|
|||
import './style.scss';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
}
|
||||
|
||||
$onChanges() {
|
||||
if (this.claim && this.claim.id)
|
||||
this.getSummary();
|
||||
|
@ -32,7 +37,13 @@ class Controller extends Section {
|
|||
this.summary = response.data;
|
||||
});
|
||||
}
|
||||
|
||||
getImagePath(dmsId) {
|
||||
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||
|
||||
ngModule.component('vnClaimSummary', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -218,6 +218,36 @@ module.exports = Self => {
|
|||
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 => {
|
||||
if (ctx.isNewInstance) return;
|
||||
|
|
|
@ -6,6 +6,15 @@
|
|||
data="$ctrl.addresses"
|
||||
auto-load="true">
|
||||
</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
|
||||
model="model"
|
||||
class="vn-w-md">
|
||||
|
@ -35,7 +44,7 @@
|
|||
</vn-none>
|
||||
<vn-one
|
||||
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">{{::address.city}}, {{::address.province.name}}</div>
|
||||
<div class="ellipsize">
|
||||
|
|
|
@ -68,6 +68,15 @@ class Controller extends Section {
|
|||
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'];
|
||||
|
||||
|
|
|
@ -67,5 +67,19 @@ describe('Client', () => {
|
|||
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
|
||||
Set as default: Establecer 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
|
||||
Enabled: Activo
|
||||
Is equalizated: Recargo de equivalencia
|
||||
|
|
|
@ -41,7 +41,7 @@ class Controller extends Descriptor {
|
|||
}
|
||||
|
||||
onConsumerReportAccept() {
|
||||
this.showReport('campaign-metrics', {
|
||||
this.vnReport.show('campaign-metrics', {
|
||||
recipientId: this.id,
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
|
|
|
@ -54,11 +54,10 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
title="{{'Download file' | translate}}"
|
||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
<span title="{{'Download file' | translate}}" class="link"
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
{{::document.dms.file}}
|
||||
</a>
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<span class="link"
|
||||
|
@ -69,13 +68,10 @@
|
|||
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
<vn-icon-button
|
||||
<vn-icon-button title="{{'Download file' | translate}}"
|
||||
icon="cloud_download"
|
||||
title="{{'Download file' | translate}}">
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
</vn-icon-button>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<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';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
constructor($element, $, vnFile) {
|
||||
super($element, $, vnFile);
|
||||
this.vnFile = vnFile;
|
||||
this.filter = {
|
||||
include: {
|
||||
relation: 'dms',
|
||||
|
@ -49,9 +50,13 @@ class Controller extends Section {
|
|||
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', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -36,7 +36,7 @@ class Controller extends Descriptor {
|
|||
}
|
||||
|
||||
showEntryReport() {
|
||||
this.showReport('entry-order', {
|
||||
this.vnReport.show('entry-order', {
|
||||
entryId: this.entry.id
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,15 +12,16 @@ describe('Entry Component vnEntryDescriptor', () => {
|
|||
|
||||
describe('showEntryReport()', () => {
|
||||
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 = {
|
||||
clientId: controller.vnConfig.storage.currentUserWorkerId,
|
||||
entryId: entry.id
|
||||
};
|
||||
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() {
|
||||
this.showReport('driver-route', {
|
||||
this.vnReport.show('driver-route', {
|
||||
routeId: this.id
|
||||
});
|
||||
}
|
||||
|
||||
sendRouteReport() {
|
||||
const workerUser = this.route.worker.user;
|
||||
this.sendEmail('driver-route', {
|
||||
this.vnEmail.send('driver-route', {
|
||||
recipient: workerUser.emailUser.email,
|
||||
routeId: this.id
|
||||
});
|
||||
|
|
|
@ -100,34 +100,27 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.filter = async(ctx, filter) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const conn = Self.dataSource.connector;
|
||||
const models = Self.app.models;
|
||||
const args = ctx.args;
|
||||
|
||||
let worker = await Self.app.models.Worker.findOne({
|
||||
where: {userFk: ctx.req.accessToken.userId},
|
||||
include: [
|
||||
{relation: 'collegues'}
|
||||
]
|
||||
});
|
||||
|
||||
let teamIds = [];
|
||||
|
||||
if (worker.collegues().length && args.myTeam) {
|
||||
worker.collegues().forEach(collegue => {
|
||||
teamIds.push(collegue.collegueFk);
|
||||
});
|
||||
// Apply filter by team
|
||||
const teamMembersId = [];
|
||||
if (args.myTeam != null) {
|
||||
const worker = await models.Worker.findById(userId, {
|
||||
include: {
|
||||
relation: 'collegues'
|
||||
}
|
||||
|
||||
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))
|
||||
args.teamIds = teamIds;
|
||||
if (teamMembersId.length == 0)
|
||||
teamMembersId.push(userId);
|
||||
}
|
||||
|
||||
if (ctx.args && args.to) {
|
||||
const dateTo = args.to;
|
||||
|
@ -156,7 +149,11 @@ module.exports = Self => {
|
|||
return {'ts.stateFk': value};
|
||||
case 'mine':
|
||||
case 'myTeam':
|
||||
return {'c.salesPersonFk': {inq: teamIds}};
|
||||
if (value)
|
||||
return {'c.salesPersonFk': {inq: teamMembersId}};
|
||||
else
|
||||
return {'c.salesPersonFk': {nin: teamMembersId}};
|
||||
|
||||
case 'alertLevel':
|
||||
return {'ts.alertLevel': value};
|
||||
case 'pending':
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.getVolume = async ticketFk => {
|
||||
let [volume] = await Self.rawSql(`CALL vn.ticketListVolume(?)`, [ticketFk]);
|
||||
return volume;
|
||||
return Self.rawSql(`SELECT * FROM vn.saleVolume
|
||||
WHERE ticketFk = ?`, [ticketFk]);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -43,18 +43,6 @@ module.exports = Self => {
|
|||
if (hasItemShelvingSales && !isSalesAssistant)
|
||||
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
|
||||
const claimOfATicket = await models.Claim.findOne({where: {ticketFk: id}});
|
||||
if (claimOfATicket)
|
||||
|
@ -69,10 +57,23 @@ module.exports = Self => {
|
|||
if (hasPurchaseRequests)
|
||||
throw new UserError('You must delete all the buy requests first');
|
||||
|
||||
// removes item shelvings
|
||||
if (hasItemShelvingSales && isSalesAssistant) {
|
||||
const promises = [];
|
||||
for (let sale of sales) {
|
||||
if (sale.itemShelvingSale()) {
|
||||
const itemShelvingSale = sale.itemShelvingSale();
|
||||
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id);
|
||||
promises.push(destroyedShelving);
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
// Remove ticket greuges
|
||||
const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}});
|
||||
const ownGreuges = ticketGreuges.every(greuge => {
|
||||
return greuge.ticketFk = id;
|
||||
return greuge.ticketFk == id;
|
||||
});
|
||||
if (ownGreuges) {
|
||||
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;
|
||||
if (ticket.stowaway())
|
||||
otherTicketId = ticket.stowaway().shipFk;
|
||||
|
@ -112,6 +113,7 @@ module.exports = Self => {
|
|||
otherTicketId = ticket.ship().id;
|
||||
|
||||
if (otherTicketId) {
|
||||
await models.Ticket.deleteStowaway(ctx, otherTicketId);
|
||||
await models.TicketTracking.changeState(ctx, {
|
||||
ticketFk: otherTicketId,
|
||||
code: 'FIXING'
|
||||
|
|
|
@ -71,4 +71,20 @@ describe('ticket filter()', () => {
|
|||
expect(secondRow.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;
|
||||
await app.models.Ticket.getVolume(ticketFk)
|
||||
.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 models = app.models;
|
||||
|
||||
// 2301 Failing tests
|
||||
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);
|
||||
});
|
||||
|
||||
describe('ticket setDeleted()', () => {
|
||||
it('should throw an error if the given ticket has a claim', async() => {
|
||||
const ticketId = 16;
|
||||
const ctx = {
|
||||
|
@ -134,13 +25,11 @@ xdescribe('ticket deleted()', () => {
|
|||
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() => {
|
||||
const ticketId = 16;
|
||||
const claimIdToRemove = 2;
|
||||
const stowawayTicketId = 17;
|
||||
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 employeeUser = 110;
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: 106},
|
||||
accessToken: {userId: employeeUser},
|
||||
headers: {
|
||||
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)
|
||||
VALUES (?, ?)`, [stowawayTicketId, ticketId]);
|
||||
VALUES (?, ?)`, [stowawayTicket.id, shipTicket.id]);
|
||||
|
||||
deletedClaim = await app.models.Claim.findById(claimIdToRemove);
|
||||
await app.models.Claim.destroyById(claimIdToRemove);
|
||||
await app.models.Ticket.setDeleted(ctx, ticketId);
|
||||
|
||||
const stowawayTicket = await app.models.TicketState.findOne({
|
||||
const boardingState = await models.State.findOne({
|
||||
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() {
|
||||
this.showReport('delivery-note', {
|
||||
this.vnReport.show('delivery-note', {
|
||||
recipientId: this.ticket.client.id,
|
||||
ticketId: this.id,
|
||||
});
|
||||
}
|
||||
|
||||
sendDeliveryNote() {
|
||||
return this.sendEmail('delivery-note', {
|
||||
return this.vnEmail.send('delivery-note', {
|
||||
recipientId: this.ticket.client.id,
|
||||
recipient: this.ticket.client.email,
|
||||
ticketId: this.id
|
||||
|
|
|
@ -64,21 +64,22 @@ describe('Ticket Component vnTicketDescriptor', () => {
|
|||
|
||||
describe('showDeliveryNote()', () => {
|
||||
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 = {
|
||||
clientId: ticket.client.id,
|
||||
ticketId: ticket.id
|
||||
};
|
||||
controller.showDeliveryNote();
|
||||
|
||||
expect(controller.showReport).toHaveBeenCalledWith('delivery-note', params);
|
||||
expect(controller.vnReport.show).toHaveBeenCalledWith('delivery-note', params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendDeliveryNote()', () => {
|
||||
it('should make a query and call vnApp.showMessage()', () => {
|
||||
jest.spyOn(controller, 'sendEmail');
|
||||
jest.spyOn(controller.vnEmail, 'send');
|
||||
|
||||
const params = {
|
||||
recipient: ticket.client.email,
|
||||
|
@ -87,7 +88,7 @@ describe('Ticket Component vnTicketDescriptor', () => {
|
|||
};
|
||||
controller.sendDeliveryNote();
|
||||
|
||||
expect(controller.sendEmail).toHaveBeenCalledWith('delivery-note', params);
|
||||
expect(controller.vnEmail.send).toHaveBeenCalledWith('delivery-note', params);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -52,11 +52,10 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
title="{{'Download file' | translate}}"
|
||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
<span title="{{'Download file' | translate}}" class="link"
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
{{::document.dms.file}}
|
||||
</a>
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<span class="link"
|
||||
|
@ -67,13 +66,10 @@
|
|||
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
<vn-icon-button
|
||||
<vn-icon-button title="{{'Download file' | translate}}"
|
||||
icon="cloud_download"
|
||||
title="{{'Download file' | translate}}">
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
</vn-icon-button>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button icon="edit"
|
||||
|
|
|
@ -3,8 +3,9 @@ import Section from 'salix/components/section';
|
|||
import './style.scss';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $) {
|
||||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
this.filter = {
|
||||
include: {
|
||||
relation: 'dms',
|
||||
|
@ -50,7 +51,13 @@ class Controller extends Section {
|
|||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(dmsId) {
|
||||
this.vnFile.download(`api/dms/${dmsId}/downloadFile`);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||
|
||||
ngModule.component('vnTicketDmsIndex', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
sub-name="::sale.item.subName"/>
|
||||
</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-tbody>
|
||||
</vn-table>
|
||||
|
|
|
@ -39,7 +39,7 @@ class Controller extends Section {
|
|||
this.sales.forEach(sale => {
|
||||
this.volumes.forEach(volume => {
|
||||
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`, () => {
|
||||
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[1].volume.m3).toEqual(0.015);
|
||||
expect(controller.sales[0].saleVolume.volume).toEqual(0.012);
|
||||
expect(controller.sales[1].saleVolume.volume).toEqual(0.015);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
</vn-crud-model>
|
||||
<vn-portal slot="topbar">
|
||||
<vn-searchbar
|
||||
placeholder="Search by weekly ticket"
|
||||
info="Search weekly ticket by id or client id"
|
||||
auto-state="false"
|
||||
model="model">
|
||||
|
|
|
@ -3,3 +3,4 @@ Weekly tickets: Tickets programados
|
|||
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?
|
||||
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.created | date: 'dd/MM/yyyy'}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
href="api/dms/{{::thermograph.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
<vn-icon-button
|
||||
<vn-icon-button title="{{'Download file' | translate}}"
|
||||
icon="cloud_download"
|
||||
title="{{'Download file' | translate}}">
|
||||
ng-click="$ctrl.downloadFile(thermograph.dmsFk)">
|
||||
</vn-icon-button>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<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';
|
||||
|
||||
class Controller extends Section {
|
||||
$onInit() {
|
||||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
this.filter = {
|
||||
include:
|
||||
{relation: 'warehouse',
|
||||
|
@ -29,7 +31,13 @@ class Controller extends Section {
|
|||
this.thermographIndex = null;
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(dmsId) {
|
||||
this.vnFile.download(`api/dms/${dmsId}/downloadFile`);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||
|
||||
ngModule.component('vnTravelThermographIndex', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -38,22 +38,19 @@
|
|||
</span>
|
||||
</vn-td >
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
title="{{'Download file' | translate}}"
|
||||
href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">{{::document.file}}
|
||||
</a>
|
||||
<span title="{{'Download file' | translate}}" class="link"
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
{{::document.file}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
{{::document.created | date:'dd/MM/yyyy HH:mm'}}
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
|
||||
<vn-icon-button
|
||||
<vn-icon-button title="{{'Download file' | translate}}"
|
||||
icon="cloud_download"
|
||||
title="{{'Download file' | translate}}">
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
</vn-icon-button>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button ui-sref="worker.card.edit({dmsId: {{::document.dmsFk}}})"
|
||||
|
|
|
@ -3,8 +3,9 @@ import Component from 'core/lib/component';
|
|||
import './style.scss';
|
||||
|
||||
class Controller extends Component {
|
||||
constructor($element, $) {
|
||||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
this.filter = {
|
||||
include: {
|
||||
relation: 'dms',
|
||||
|
@ -51,7 +52,13 @@ class Controller extends Component {
|
|||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(dmsId) {
|
||||
this.vnFile.download(`api/workerDms/${dmsId}/downloadFile`);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||
|
||||
ngModule.component('vnWorkerDmsIndex', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -3265,7 +3265,7 @@
|
|||
},
|
||||
"util": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -4081,7 +4081,7 @@
|
|||
"base": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
|
||||
"integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=",
|
||||
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cache-base": "^1.0.1",
|
||||
|
@ -4532,7 +4532,7 @@
|
|||
},
|
||||
"readable-stream": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -4544,7 +4544,7 @@
|
|||
},
|
||||
"string_decoder": {
|
||||
"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=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -4604,7 +4604,7 @@
|
|||
"cache-base": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||
"integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=",
|
||||
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"collection-visit": "^1.0.0",
|
||||
|
@ -4674,7 +4674,7 @@
|
|||
},
|
||||
"camelcase-keys": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -4813,7 +4813,7 @@
|
|||
"class-utils": {
|
||||
"version": "0.3.6",
|
||||
"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,
|
||||
"requires": {
|
||||
"arr-union": "^3.1.0",
|
||||
|
@ -5899,7 +5899,7 @@
|
|||
"dot-prop": {
|
||||
"version": "4.2.0",
|
||||
"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": {
|
||||
"is-obj": "^1.0.0"
|
||||
}
|
||||
|
@ -6856,7 +6856,7 @@
|
|||
},
|
||||
"file-loader": {
|
||||
"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==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -7813,7 +7813,7 @@
|
|||
},
|
||||
"string-width": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -8023,7 +8023,7 @@
|
|||
"global-modules": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
|
||||
"integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=",
|
||||
"integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"global-prefix": "^1.0.1",
|
||||
|
@ -10201,7 +10201,7 @@
|
|||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"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,
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
|
@ -10563,7 +10563,7 @@
|
|||
"jasmine-spec-reporter": {
|
||||
"version": "4.2.1",
|
||||
"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,
|
||||
"requires": {
|
||||
"colors": "1.1.2"
|
||||
|
@ -10740,7 +10740,8 @@
|
|||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
|
||||
"integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
|
@ -12568,7 +12569,7 @@
|
|||
},
|
||||
"meow": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -13209,7 +13210,7 @@
|
|||
"dependencies": {
|
||||
"semver": {
|
||||
"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=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -13359,7 +13360,7 @@
|
|||
},
|
||||
"chalk": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -13826,7 +13827,7 @@
|
|||
},
|
||||
"os-homedir": {
|
||||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -14999,7 +15000,7 @@
|
|||
"dependencies": {
|
||||
"jsesc": {
|
||||
"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=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -15386,7 +15387,7 @@
|
|||
},
|
||||
"safe-regex": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -15578,7 +15579,7 @@
|
|||
"dependencies": {
|
||||
"source-map": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -15930,7 +15931,7 @@
|
|||
"snapdragon-node": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
|
||||
"integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=",
|
||||
"integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-property": "^1.0.0",
|
||||
|
@ -15981,7 +15982,7 @@
|
|||
"snapdragon-util": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
|
||||
"integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=",
|
||||
"integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.2.0"
|
||||
|
@ -16265,7 +16266,7 @@
|
|||
"split-string": {
|
||||
"version": "3.1.0",
|
||||
"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,
|
||||
"requires": {
|
||||
"extend-shallow": "^3.0.0"
|
||||
|
@ -17509,7 +17510,7 @@
|
|||
"touch": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||
"integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=",
|
||||
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nopt": "~1.0.10"
|
||||
|
@ -18753,7 +18754,7 @@
|
|||
},
|
||||
"globby": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -18766,7 +18767,7 @@
|
|||
"dependencies": {
|
||||
"pify": {
|
||||
"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=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -19227,7 +19228,7 @@
|
|||
},
|
||||
"xmlbuilder": {
|
||||
"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="
|
||||
},
|
||||
"xmlcreate": {
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
"type": "git",
|
||||
"url": "https://gitea.verdnatura.es/verdnatura/salix"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"compression": "^1.7.3",
|
||||
"fs-extra": "^5.0.0",
|
||||
|
|
Loading…
Reference in New Issue