This commit is contained in:
Carlos Jimenez Ruiz 2019-01-26 13:04:20 +01:00
commit 20bea9679c
126 changed files with 3859 additions and 3527 deletions

View File

@ -46,7 +46,7 @@ module.exports = Self => {
let filter = {where: {name: user}};
let instance = await Self.findOne(filter);
if (!instance || instance.password !== md5(password))
if (!instance || instance.password !== md5(password || ''))
throw err;
let where = {id: instance.id};

View File

@ -1,14 +1,6 @@
module.exports = Self => {
Self.remoteMethod('validateToken', {
description: 'Get the user information and permissions',
accepts: [
{
arg: 'token',
type: 'String',
description: 'The token to validate',
required: true
}
],
description: 'Validates the current logged user token',
returns: {
type: 'Boolean',
root: true
@ -19,13 +11,7 @@ module.exports = Self => {
}
});
Self.validateToken = function(tokenId, cb) {
Self.app.models.AccessToken.findById(tokenId, (err, token) => {
if (err) return cb(err);
if (token)
token.validate((_, isValid) => cb(null, isValid === true));
else
cb(null, false);
});
Self.validateToken = async function() {
return true;
};
};

View File

@ -52,7 +52,7 @@
"property": "validateToken",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]

View File

@ -169,9 +169,9 @@ export default class Watcher extends Component {
*/
check() {
if (this.form && this.form.$invalid)
throw new UserError(this._.instant('Some fields are invalid'));
throw new UserError('Some fields are invalid');
if (!this.dirty)
throw new UserError(this._.instant('No changes to save'));
throw new UserError('No changes to save');
}
/**

View File

@ -1,6 +1,5 @@
import './module-loader';
import './crud';
import './app';
import './acl-service';
import './storage-services';
import './template';

View File

@ -41,7 +41,7 @@ export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $t
if (validations) {
promises.push(new Promise(resolve => {
$http.get(`/${moduleName}/validations`).then(
$http.get(`/${moduleName}/api/modelInfo`).then(
json => this.onValidationsReady(json, resolve),
() => resolve()
);

View File

@ -13,18 +13,18 @@ export default class App {
}
showMessage(message) {
if (this.snackbar)
this.snackbar.show({message: message});
if (this.logger)
this.logger.showMessage(message);
}
showSuccess(message) {
if (this.snackbar)
this.snackbar.showSuccess({message: message});
if (this.logger)
this.logger.showSuccess(message);
}
showError(message) {
if (this.snackbar)
this.snackbar.showError({message: message});
if (this.logger)
this.logger.showError(message);
}
pushLoader() {

View File

@ -7,99 +7,89 @@ import UserError from 'core/lib/user-error';
* @property {Boolean} loggedIn Whether the user is currently logged
*/
export default class Auth {
constructor($http, $state, $transitions, $window, vnToken, vnModules, aclService) {
constructor($http, $q, $state, $transitions, $window, vnToken, vnModules, aclService) {
Object.assign(this, {
$http,
$q,
$state,
$transitions,
$window,
vnToken,
vnModules,
aclService,
token: null,
loggedIn: false,
dataLoaded: false
loggedIn: false
});
}
initialize() {
this.loggedIn = this.vnToken.token != null;
let criteria = {
to: state => state.name != 'login'
};
this.$transitions.onStart(criteria, transition => {
if (!this.loggedIn) {
let params = {continue: this.$window.location.hash};
return transition.router.stateService.target('login', params);
}
if (!this.dataLoaded) {
this.resetData();
this.dataLoaded = true;
return this.aclService.load();
}
if (this.loggedIn)
return true;
return true;
let redirectToLogin = () => {
return transition.router.stateService.target('login', {
continue: this.$window.location.hash
});
};
if (this.vnToken.token) {
return this.loadAcls()
.then(() => true)
.catch(redirectToLogin);
} else
return redirectToLogin();
});
}
login(user, password, remember) {
if (!user)
throw new UserError('Please insert your user and password');
return this.$q.reject(new UserError('Please enter your username'));
let params = {
user,
password: password || undefined
};
return this.$http.post('/api/Accounts/login', params).then(
json => this.onLoginOk(json, remember),
json => this.onLoginErr(json)
);
json => this.onLoginOk(json, remember));
}
onLoginOk(json, remember) {
this.resetData();
this.vnToken.set(json.data.token, remember);
this.loggedIn = true;
let continueHash = this.$state.params.continue;
if (continueHash)
this.$window.location = continueHash;
else
this.$state.go('home');
}
onLoginErr(json) {
let message;
switch (json.status) {
case 401:
message = 'Invalid credentials';
break;
case -1:
message = 'Can\'t contact with server';
break;
default:
message = 'Something went wrong';
}
throw new UserError(message);
return this.loadAcls().then(() => {
let continueHash = this.$state.params.continue;
if (continueHash)
this.$window.location = continueHash;
else
this.$state.go('home');
});
}
logout() {
let promise = this.$http.post('/api/Accounts/logout', null, {
headers: {Authorization: this.vnToken.token}
});
}).catch(() => {});
this.vnToken.unset();
this.loggedIn = false;
this.resetData();
this.vnModules.reset();
this.aclService.reset();
this.$state.go('login');
return promise;
}
resetData() {
this.aclService.reset();
this.vnModules.reset();
this.dataLoaded = false;
loadAcls() {
return this.aclService.load()
.then(() => {
this.loggedIn = true;
this.vnModules.reset();
})
.catch(err => {
this.vnToken.unset();
throw err;
});
}
}
Auth.$inject = ['$http', '$state', '$transitions', '$window', 'vnToken', 'vnModules', 'aclService'];
Auth.$inject = ['$http', '$q', '$state', '$transitions', '$window', 'vnToken', 'vnModules', 'aclService'];
ngModule.service('vnAuth', Auth);

View File

@ -1,3 +1,5 @@
import './app';
import './auth';
import './token';
import './modules';

View File

@ -0,0 +1,6 @@
Invalid login: Invalid login, remember that distinction is made between uppercase and lowercase
Could not contact the server: Could not contact the server, make sure you have a connection to the network
Please enter your username: Please enter your username
It seems that the server has fall down: It seems that the server has fall down, wait a few minutes and try again
Session has expired: Your session has expired, please login again
Access denied: Access denied

View File

@ -0,0 +1,6 @@
Invalid login: Usuario o contraseña incorrectos, recuerda que se hace distinción entre mayúsculas y minúsculas
Could not contact the server: No se ha podido contactar con el servidor, asegurate de que dispones de conexión con la red
Please enter your username: Por favor introduce tu nombre de usuario
It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo
Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
Access denied: Acción no permitida

View File

@ -6,17 +6,12 @@ import ngModule from '../module';
* @property {String} token The current login token or %null
*/
export default class Token {
constructor($cookies) {
this.$cookies = $cookies;
constructor() {
try {
this.token = sessionStorage.getItem('vnToken');
if (!this.token)
this.token = localStorage.getItem('vnToken');
} catch (e) {}
if (!this.token)
this.token = this.$cookies.get('vnToken');
}
set(value, remember) {
this.unset();
@ -25,27 +20,15 @@ export default class Token {
localStorage.setItem('vnToken', value);
else
sessionStorage.setItem('vnToken', value);
} catch (e) {
let options = {};
} catch (e) {}
if (location.protocol == 'https:')
options.secure = true;
if (remember) {
let now = new Date().getTime();
options.expires = new Date(now + 7 * 86400000);
}
this.$cookies.put('vnToken', value, options);
}
this.token = value;
}
unset() {
localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken');
this.$cookies.remove('vnToken');
this.token = null;
}
}
Token.$inject = ['$cookies'];
ngModule.service('vnToken', Token);

View File

@ -3,7 +3,6 @@ import '@babel/polyfill';
import * as ng from 'angular';
export {ng};
import 'angular-cookies';
import 'angular-translate';
import 'angular-translate-loader-partial';
import '@uirouter/angularjs';
@ -11,7 +10,6 @@ import 'mg-crud';
import 'oclazyload';
export const ngDeps = [
'ngCookies',
'pascalprecht.translate',
'ui.router',
'mgCrud',

View File

@ -31,11 +31,6 @@
"resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz",
"integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw=="
},
"angular-cookies": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz",
"integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA=="
},
"angular-translate": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz",

View File

@ -11,7 +11,6 @@
"@babel/polyfill": "^7.2.5",
"@uirouter/angularjs": "^1.0.20",
"angular": "^1.7.5",
"angular-cookies": "^1.7.5",
"angular-translate": "^2.18.1",
"angular-translate-loader-partial": "^2.18.1",
"flatpickr": "^4.5.2",

View File

@ -1,4 +1,4 @@
<vn-topbar ng-if="$ctrl.inApp">
<vn-topbar ng-if="$ctrl.showTopbar">
<a class="logo" ui-sref="home" title="{{'Home' | translate}}">
<img src="./logo.svg" alt="Logo"></img>
</a>
@ -12,7 +12,7 @@
</vn-topbar>
<div ui-view
class="main-view"
ng-class="{'padding': $ctrl.inApp}">
ng-class="{'padding': $ctrl.showTopbar}">
<vn-home></vn-home>
</div>
<div

View File

@ -2,42 +2,63 @@ import ngModule from '../../module';
import './style.scss';
export default class App {
constructor($element, $scope, vnApp, $state, $transitions) {
this.$element = $element;
this.$ = $scope;
this.vnApp = vnApp;
this.$state = $state;
constructor($, $element, vnApp, $state, $transitions) {
Object.assign(this, {
$,
$element,
vnApp,
$state
});
$transitions.onStart({}, () => {
if (this.menuShown) this.hideMenu();
});
}
$postLink() {
this.background = this.$element[0].querySelector('.background');
this.vnApp.snackbar = this.$.snackbar;
this.vnApp.logger = this;
}
// TODO: Temporary fix to hide the topbar when login is displayed
get inApp() {
$onDestroy() {
this.vnApp.logger = null;
}
get showTopbar() {
let state = this.$state.current.name;
return state && state != 'login';
}
get leftBlock() {
return this.$element[0].querySelector('.left-block');
}
showMenu() {
let leftBlock = this.leftBlock;
if (!leftBlock) return;
leftBlock.classList.add('shown');
this.menuShown = true;
}
hideMenu() {
this.menuShown = false;
let leftBlock = this.leftBlock;
if (!leftBlock) return;
leftBlock.classList.remove('shown');
}
showMessage(message) {
this.$.snackbar.show({message: message});
}
showSuccess(message) {
this.$.snackbar.showSuccess({message: message});
}
showError(message) {
this.$.snackbar.showError({message: message});
}
}
App.$inject = ['$element', '$scope', 'vnApp', '$state', '$transitions'];
App.$inject = ['$scope', '$element', 'vnApp', '$state', '$transitions'];
ngModule.component('vnApp', {
template: require('./app.html'),

View File

@ -5,11 +5,14 @@ import './style.scss';
* A simple login form.
*/
export default class Controller {
constructor($element, $scope, vnAuth) {
this.$element = $element;
this.$ = $scope;
this.vnAuth = vnAuth;
this.user = localStorage.getItem('lastUser');
constructor($, $element, vnAuth) {
Object.assign(this, {
$,
$element,
vnAuth,
user: localStorage.getItem('lastUser'),
remember: true
});
}
submit() {
this.loading = true;
@ -18,11 +21,11 @@ export default class Controller {
localStorage.setItem('lastUser', this.user);
this.loading = false;
})
.catch(error => {
.catch(err => {
this.loading = false;
this.password = '';
this.focusUser();
throw error;
throw err;
});
}
focusUser() {
@ -30,7 +33,7 @@ export default class Controller {
this.$.userField.focus();
}
}
Controller.$inject = ['$element', '$scope', 'vnAuth'];
Controller.$inject = ['$scope', '$element', 'vnAuth'];
ngModule.component('vnLogin', {
template: require('./login.html'),

View File

@ -41,7 +41,9 @@
data="banksData"
select-fields="['id','bank']"
show-field="bank"
order="id"
value-field="id">
<tpl-item>{{id}}: {{bank}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>

View File

@ -43,6 +43,9 @@ class Controller {
set warehouseFk(value) {
this.warehouse = value;
if (value &&
(!window.localStorage.localWarehouseFk || window.localStorage.localWarehouseFk === 'null'))
window.localStorage.defaultWarehouseFk = value;
this.setUserConfig('warehouseFk', value);
}
@ -52,6 +55,9 @@ class Controller {
set companyFk(value) {
this.company = value;
if (value &&
(!window.localStorage.localCompanyFk || window.localStorage.localCompanyFk === 'null'))
window.localStorage.defaultCompanyFk = value;
this.setUserConfig('companyFk', value);
}
@ -76,14 +82,14 @@ class Controller {
.then(res => {
if (res.data && res.data.warehouseFk) {
this.warehouse = res.data.warehouseFk;
if (!localStorage.getItem('localWarehouseFk'))
localStorage.setItem('defaultWarehouseFk', res.data.warehouseFk);
if (res.data.warehouseFk && !window.localStorage.localWarehouseFk)
window.localStorage.defaultWarehouseFk = res.data.warehouseFk;
}
if (res.data && res.data.companyFk) {
this.company = res.data.companyFk;
if (!localStorage.getItem('localCompanyFk'))
localStorage.setItem('defaultCompanyFk', res.data.companyFk);
if (res.data.companyFk && !window.localStorage.localCompanyFk)
window.localStorage.defaultCompanyFk = res.data.companyFk;
}
});
}
@ -99,8 +105,10 @@ class Controller {
}
$onInit() {
if (localStorage.getItem('localBankFk'))
window.localStorage.defaultBankFk = localStorage.getItem('localBankFk');
if (window.localStorage.localBankFk && window.localStorage.localBankFk !== 'null')
window.localStorage.defaultBankFk = window.localStorage.localBankFk;
else
localStorage.removeItem('defaultBankFk');
}
}

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<html ng-app="salix">
<head>
<meta charset="utf-8">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=no"/>
<meta name="mobile-web-app-capable" content="yes"/>
<title vn-title translate></title>
<script src="/acl"></script>
</head>

View File

@ -5,7 +5,6 @@ Logout: Logout
Change language: Change language
Profile: Profile
Data saved!: Data saved!
Can't contact with server: Can't contact with server
Push on applications menu: To open a module push on applications menu
Clients: Clients
Modules access: Modules access

View File

@ -1,5 +1,4 @@
Applications: Aplicaciones
Can't contact with server: No se pudo contactar con el servidor
Change language: Cambiar idioma
Client Frozen: Cliente congelado
Client has debt: Cliente con riesgo
@ -25,6 +24,6 @@ Return to module index: Volver a la página principal del módulo
Routes: Rutas
What is new: Novedades de la versión
Web Account inactive: Sin acceso Web
Orders: Pedidos
Orders: Cesta
Agencies: Agencias
Travels: Envíos

View File

@ -66,10 +66,12 @@ ngModule.config(config);
// Unhandled exceptions
$exceptionHandler.$inject = ['vnApp', '$window', '$state'];
function $exceptionHandler(vnApp, $window, $state) {
$exceptionHandler.$inject = ['vnApp', '$window', '$state', '$injector'];
function $exceptionHandler(vnApp, $window, $state, $injector) {
return function(exception, cause) {
let message;
let messageT;
let $translate = $injector.get('$translate');
if (exception.name == 'HttpError') {
switch (exception.xhrStatus) {
@ -78,27 +80,44 @@ function $exceptionHandler(vnApp, $window, $state) {
return;
}
let data = exception.data;
switch (exception.status) {
case 401:
if ($state.current.name != 'login') {
messageT = 'Session has expired';
let params = {continue: $window.location.hash};
$state.go('login', params);
} else
messageT = 'Invalid login';
break;
case 403:
messageT = 'Access denied';
break;
case 502:
messageT = 'It seems that the server has fall down';
break;
case -1:
messageT = 'Could not contact the server';
break;
}
if (data && data.error instanceof Object)
message = data.error.message;
else if (exception.status === -1)
message = `Can't contact with server`;
else
message = `${exception.status}: ${exception.statusText}`;
if (!messageT) {
let data = exception.data;
if (exception.status === 401 && $state.current.name != 'login') {
let params = {continue: $window.location.hash};
$state.go('login', params);
if (data && data.error instanceof Object)
message = data.error.message;
else
message = `${exception.status}: ${exception.statusText}`;
}
} else if (exception.name == 'UserError')
message = exception.message;
messageT = exception.message;
else {
vnApp.showError('Ups! Something went wrong');
console.error(exception);
throw exception;
}
if (messageT)
message = $translate.instant(messageT);
vnApp.showError(message);
};
}

View File

@ -91,9 +91,12 @@ dockerAndBackTest.description = `Restarts database and runs the backend tests`;
function backTest(done) {
const nodemon = require('gulp-nodemon');
let gulpBin = isWindows
? 'node_modules/.bin/gulp.cmd'
: 'node_modules/.bin/gulp';
nodemon({
script: 'node_modules/.bin/gulp',
exec: gulpBin,
args: ['dockerAndBackTest'],
watch: ['loopback', 'modules/*/back/**', 'back'],
done: done

View File

@ -2,6 +2,8 @@
<html ng-app="salix">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=no"/>
<meta name="mobile-web-app-capable" content="yes"/>
<title vn-title translate></title>
</head>
<body>

View File

@ -1,4 +1,63 @@
module.exports = function(app) {
module.exports = Self => {
Self.remoteMethod('modelInfo', {
description: 'Gets all models information',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}
],
returns: {
type: 'Object',
root: true
},
http: {
path: `/modelInfo`,
verb: 'GET'
}
});
Self.modelInfo = async function(ctx) {
let json = {};
let models = Self.app.models;
for (let modelName in models) {
let model = models[modelName];
let validations = model.validations;
let jsonValidations = {};
for (let fieldName in validations) {
let jsonField = [];
for (let validation of validations[fieldName]) {
let options = validation.options;
if ((options && options.async) ||
(validation.validation == 'custom' && !validation.isExportable))
continue;
let validationCp = Object.assign({}, validation);
if (validationCp.message)
validationCp.message = ctx.req.__(validationCp.message);
jsonField.push(toJson(validationCp));
}
jsonValidations[fieldName] = jsonField;
}
json[modelName] = {
properties: model.definition.rawProperties,
validations: jsonValidations
};
}
return json;
};
function toJson(object) {
let json = {};
@ -20,44 +79,4 @@ module.exports = function(app) {
return json;
}
app.get('/validations', function(req, res) {
let json = {};
let models = app.models;
for (let modelName in models) {
let model = models[modelName];
let validations = model.validations;
let jsonValidations = {};
for (let fieldName in validations) {
let jsonField = [];
for (let validation of validations[fieldName]) {
let options = validation.options;
if ((options && options.async) ||
(validation.validation == 'custom' && !validation.isExportable))
continue;
let validationCp = Object.assign({}, validation);
if (validationCp.message)
validationCp.message = req.__(validationCp.message);
jsonField.push(toJson(validationCp));
}
jsonValidations[fieldName] = jsonField;
}
json[modelName] = {
properties: model.definition.rawProperties,
validations: jsonValidations
};
}
res.set('Content-Type', 'application/json');
res.send(JSON.stringify(json));
});
};

View File

@ -0,0 +1,4 @@
module.exports = function(Self) {
require('../methods/schema/model-info')(Self);
};

View File

@ -0,0 +1,13 @@
{
"name": "Schema",
"base": "PersistedModel",
"acls": [
{
"property": "validations",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,4 +0,0 @@
{
"default": "/login",
"salix": "/login"
}

View File

@ -7,4 +7,12 @@ module.exports = function(app) {
version: bootTimestamp
});
});
// FIXME: Fix until the original can be used
app.get('/api/modelInfo', function(req, res) {
app.models.Schema.modelInfo({req}).then(json => {
res.set('Content-Type', 'application/json');
res.send(JSON.stringify(json));
});
});
};

View File

@ -43,5 +43,8 @@
},
"user": {
"dataSource": "vn"
},
"Schema": {
"dataSource": "vn"
}
}

View File

@ -36,11 +36,6 @@ let buildDir = `${appDir}/dist`;
let servicesDir = `${appDir}/modules`;
let modulesPath = `${appDir}/modules.yml`;
// TODO: It should be stored at some config file
app.set('api key', 'salix');
app.set('url auth', '/auth');
app.set('applications', require('./application.json'));
app.use(cookieParser());
// Internationalization

View File

@ -0,0 +1,28 @@
module.exports = Self => {
Self.remoteMethod('getLanded', {
description: 'Returns the first shipped and landed possible for params',
accessType: 'READ',
accepts: [{
arg: 'params',
type: 'object',
required: true,
description: 'shipped, addressFk, agencyModeFk, warehouseFk'
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/getLanded`,
verb: 'get'
}
});
Self.getLanded = async params => {
let query = `CALL vn.agencyHourGetLanded(?, ?, ?, ?);
SELECT * FROM tmp.agencyHourGetLanded`;
let result = await Self.rawSql(query, [params.shipped, params.addressFk || null, params.agencyModeFk, params.warehouseFk]);
return result[1][0].landed;
};
};

View File

@ -0,0 +1,28 @@
module.exports = Self => {
Self.remoteMethod('getShipped', {
description: 'Returns the first shipped possible for params',
accessType: 'READ',
accepts: [{
arg: 'params',
type: 'object',
required: true,
description: 'landed, addressFk, agencyModeFk'
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/getShipped`,
verb: 'get'
}
});
Self.getShipped = async params => {
let query = `CALL vn.agencyHourGetShipped(?, ?, ?);
SELECT * FROM tmp.agencyHourGetShipped`;
let result = await Self.rawSql(query, [params.landed, params.addressFk, params.agencyModeFk]);
return result[1][0].shipped;
};
};

View File

@ -2,4 +2,6 @@ module.exports = Self => {
require('../methods/agency/landsThatDay')(Self);
require('../methods/agency/getFirstShipped')(Self);
require('../methods/agency/getAgenciesWithWarehouse')(Self);
require('../methods/agency/getLanded')(Self);
require('../methods/agency/getShipped')(Self);
};

View File

@ -24,6 +24,7 @@ class Controller {
this.$state.go('zone.index');
});
}
console.log('res', response);
}
}

View File

@ -6,7 +6,15 @@
<a translate-attr="{title: 'Preview'}" ui-sref="claim.card.summary({id: $ctrl.claim.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<div></div>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)">
</vn-icon-menu>
</div>
<div class="body">
<div class="attributes">
@ -58,3 +66,9 @@
</div>
</div>
</div>
<vn-confirm
vn-id="confirm-dialog"
on-response="$ctrl.returnDialog(response)"
question="Pickup order"
message="Do you want to send it directly?">
</vn-confirm>

View File

@ -1,8 +1,15 @@
import ngModule from '../module';
class Controller {
constructor($state) {
constructor($scope, $state, $http, $translate, vnApp) {
this.$scope = $scope;
this.$state = $state;
this.$http = $http;
this.$translate = $translate;
this.vnApp = vnApp;
this.moreOptions = [
{callback: this.showConfirmDialog, name: 'Pickup order'}
];
}
get claim() {
@ -30,9 +37,28 @@ class Controller {
get quicklinks() {
return this._quicklinks;
}
onMoreChange(callback) {
callback.call(this);
}
showConfirmDialog() {
this.$scope.confirmDialog.show();
}
returnDialog(response) {
if (response === 'CANCEL') {
let url = `/report/rpt-claim-pickup-order?claimFk=${this.claim.id}`;
window.open(url);
} else if (response === 'ACCEPT') {
this.$http.post(`/email/claim-pickup-order`, {claimFk: this.claim.id}).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
);
}
}
}
Controller.$inject = ['$state'];
Controller.$inject = ['$scope', '$state', '$http', '$translate', 'vnApp'];
ngModule.component('vnClaimDescriptor', {
template: require('./index.html'),

View File

@ -7,6 +7,8 @@ Remove sale: Borrar linea
Claim Id: Id reclamación
Created: Creado
Enter a new search: Introduce una nueva búsqueda
Pickup order: Orden de recogida
Do you want to send it directly?: ¿Quieres enviarlo directamente?
#sections
Claims: Reclamaciones

View File

@ -23,9 +23,8 @@ class Controller {
if (this.$stateParams.amountPaid)
this.receipt.amountPaid = this.$stateParams.amountPaid;
if (this.$stateParams.companyFk) {
if (this.$stateParams.companyFk)
this.receipt.companyFk = this.$stateParams.companyFk;
}
}
$onInit() {

View File

@ -3,12 +3,13 @@
url="/client/api/receipts/filter"
params="$ctrl.params"
limit="20"
data="$ctrl.risks">
data="$ctrl.risks"
auto-load="true">
</vn-crud-model>
<vn-crud-model
vn-id="riskModel"
url="/client/api/ClientRisks"
filter="::$ctrl.filter"
filter="$ctrl.filter"
data="riskTotal"
auto-load="true">
</vn-crud-model>

View File

@ -18,6 +18,7 @@ class Controller {
},
where: {
clientFk: $stateParams.id,
companyFk: this.companyFk
},
};
this.params = {
@ -29,7 +30,9 @@ class Controller {
}
setOrder(value) {
this.params.params.companyFk = value;
this.filter.where.companyFk = value;
this.$.model.refresh();
this.$.riskModel.refresh();
}
set risks(value) {

View File

@ -57,6 +57,24 @@ module.exports = Self => {
throw new UserError(`You can't create a ticket for a client that has a debt`);
}
if (!params.shipped && params.landed) {
params.shipped = await Self.app.models.Agency.getShipped({
landed: params.landed,
addressFk: address.id,
agencyModeFk: params.agencyModeFk
});
}
if (params.shipped && !params.landed) {
params.landed = await Self.app.models.Agency.getLanded({
shipped: params.shipped,
addressFk: address.id,
agencyModeFk: params.agencyModeFk,
warehouseFk: params.warehouseFk
});
}
if (!params.userId && ctx.req && ctx.req.accessToken.userId)
params.userId = ctx.req.accessToken.userId;

View File

@ -1,7 +1,7 @@
const app = require('vn-loopback/server/server');
describe('ticket filter()', () => {
it('should call the filter method', async () => {
it('should call the filter method', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let filter = {order: 'shipped DESC'};

View File

@ -27,7 +27,7 @@
ini-options="{enableTime: false}">
</vn-date-picker>
<vn-autocomplete
disabled="!$ctrl.clientFk || !$ctrl.landed"
disabled="!$ctrl.warehouseFk && (!$ctrl.clientFk || !$ctrl.landed)"
field="$ctrl.warehouseFk"
url="/agency/api/Warehouses"
show-field="name"

View File

@ -14,6 +14,9 @@ class Controller {
$onInit() {
if (this.$stateParams && this.$stateParams.clientFk)
this.clientFk = this.$stateParams.clientFk;
if (window.localStorage && window.localStorage.defaultWarehouseFk)
this.warehouseFk = parseInt(window.localStorage.defaultWarehouseFk);
}
set ticket(value) {
@ -37,6 +40,7 @@ class Controller {
});
} else
this.addressFk = null;
this.getAvailableAgencies();
}
get clientFk() {
@ -45,6 +49,7 @@ class Controller {
set addressFk(value) {
this.ticket.addressFk = value;
this.getAvailableAgencies();
}
get addressFk() {
@ -53,6 +58,7 @@ class Controller {
set landed(value) {
this.ticket.landed = value;
this.getAvailableAgencies();
}
get landed() {
@ -67,10 +73,9 @@ class Controller {
return this.ticket.warehouseFk;
}
getAvailableAgencies() {
this.ticket.agencyModeFk = null;
if (this.ticket.landed && this.ticket.addressFk) {
if (this.ticket.warehouseFk && this.ticket.addressFk && this.ticket.landed && this.ticket.clientFk) {
this.ticket.agencyModeFk = null;
let filter = {warehouseFk: this.ticket.warehouseFk, addressFk: this.ticket.addressFk, landed: this.ticket.landed};
filter = encodeURIComponent(JSON.stringify(filter));
let query = `/api/Agencies/getAgenciesWithWarehouse?filter=${filter}`;

6326
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,9 @@
.container {
font-family: verdana, sans-serif;
font-size: 16px;
zoom: 0.55
}
.columns {
overflow: hidden
}
@ -27,46 +33,6 @@
float: left
}
.pull-left {
float: left
}
.pull-right {
float: right
}
.grid {
border-bottom: 3px solid #888888
}
.grid .row {
padding: 5px;
margin-bottom: 0
}
.grid .header {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
background-color: #c0c0c0;
}
.grid .row.inline > div {
float: left;
}
.panel {
border: 1px solid #DDD;
margin-bottom: 10px;
position: relative;
padding:10px
}
.panel .header {
font-weight: bold
}
.row {
margin-bottom: 15px;
overflow: hidden
@ -139,6 +105,37 @@
overflow: visible
}
.grid {
border-bottom: 3px solid #888888
}
.grid .row {
padding: 5px;
margin-bottom: 0
}
.grid .header {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
font-weight: bold
}
.grid .row.inline > div {
float: left;
}
.panel {
border: 1px solid #DDD;
margin-bottom: 10px;
position: relative;
padding:10px
}
.panel .header {
font-weight: bold
}
.box {
border-top: 1px solid #CCC;
border-right: 1px solid #CCC;
@ -163,23 +160,4 @@
.pull-right {
float: right
}
.grid {
border-bottom: 3px solid #888888
}
.grid .row {
padding: 5px;
margin-bottom: 0
}
.grid .header {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
background-color: #c0c0c0;
}
.grid .row.inline > div {
float: left;
}

View File

@ -4,9 +4,10 @@
{"type": "email", "name": "payment-update"},
{"type": "email", "name": "letter-debtor-st"},
{"type": "email", "name": "letter-debtor-nd"},
{"type": "email", "name": "claim-pickup-order"},
{"type": "report", "name": "delivery-note"},
{"type": "report", "name": "invoice"},
{"type": "report", "name": "claim-pickup"},
{"type": "report", "name": "rpt-claim-pickup-order"},
{"type": "static", "name": "email-header"},
{"type": "static", "name": "email-footer"},
{"type": "static", "name": "report-header"},

View File

@ -4,18 +4,16 @@ const renderer = require('vue-server-renderer').createRenderer();
const fs = require('fs-extra');
const juice = require('juice');
const smtp = require('./smtp');
const i18n = new VueI18n({
locale: 'es',
});
Vue.use(VueI18n);
const fallbackLocale = 'es';
if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/';
Vue.use(VueI18n);
module.exports = {
path: `${appPath}/reports`,
path: `${appPath}/report`,
/**
* Renders a report component
@ -25,17 +23,18 @@ module.exports = {
*/
async render(name, ctx) {
const component = require(`${this.path}/${name}`);
const prefetchedData = await this.preFetch(component, ctx);
const app = new Vue({
i18n,
render: h => h(component),
const result = await this.preFetch(component, ctx);
const i18n = new VueI18n({
locale: 'es',
fallbackLocale
});
const app = new Vue({i18n,
render: h => h(result.component)});
return renderer.renderToString(app).then(renderedHtml => {
return {
html: renderedHtml,
data: prefetchedData,
data: result.mergedData,
};
});
},
@ -43,10 +42,11 @@ module.exports = {
/**
* Prefetch all component data from asyncData method
*
* @param {Object} component - Component object
* @param {Object} orgComponent - Component object
* @param {Object} ctx - Request context
*/
async preFetch(component, ctx) {
async preFetch(orgComponent, ctx) {
let component = Object.create(orgComponent);
let mergedData = {attachments: []};
let asyncData = {};
let data = {};
@ -60,14 +60,18 @@ module.exports = {
await this.attachAssets(component);
if (component.hasOwnProperty('data'))
data = component.data();
if (orgComponent.hasOwnProperty('data'))
data = orgComponent.data();
if (component.hasOwnProperty('asyncData')) {
asyncData = await component.asyncData(ctx, params);
if (orgComponent.hasOwnProperty('asyncData')) {
asyncData = await orgComponent.asyncData(ctx, params);
if (asyncData.locale) {
const locale = component.i18n.messages[asyncData.locale];
let locale = component.i18n.messages[asyncData.locale];
if (!locale)
locale = component.i18n.messages[fallbackLocale];
mergedData.subject = locale.subject;
}
}
@ -81,7 +85,7 @@ module.exports = {
if (data.hasOwnProperty('files')) {
const files = data.files;
files.forEach(file => {
const componentPath = `${this.path}/${component.name}`;
const componentPath = `${this.path}/${orgComponent.name}`;
let fileSrc = componentPath + file;
if (file.slice(0, 4) === 'http' || file.slice(0, 4) === 'https')
@ -96,25 +100,30 @@ module.exports = {
});
}
if (component.components) {
const components = component.components;
const promises = [];
const components = orgComponent.components;
Object.keys(components).forEach(component => {
promises.push(this.preFetch(components[component], ctx));
if (components) {
const promises = [];
const childNames = [];
component.components = {};
Object.keys(components).forEach(childName => {
childNames.push(childName);
promises.push(this.preFetch(components[childName], ctx));
});
return Promise.all(promises).then(results => {
results.forEach(result => {
result.attachments.forEach(atth => {
await Promise.all(promises).then(results => {
results.forEach((result, i) => {
result.mergedData.attachments.forEach(atth => {
mergedData.attachments.push(atth);
});
component.components[childNames[i]] = result.component;
});
return mergedData;
});
}
return mergedData;
return {component, mergedData};
},
async attachAssets(component) {

View File

@ -13,7 +13,7 @@ if (!process.env.OPENSSL_CONF)
module.exports = {
path: `${appPath}/reports`,
path: `${appPath}/report`,
/**
* Renders a report component
@ -23,16 +23,13 @@ module.exports = {
*/
async render(name, ctx) {
const component = require(`${this.path}/${name}`);
await this.preFetch(component, ctx);
const result = await this.preFetch(component, ctx);
const i18n = new VueI18n({
locale: 'es',
fallbackLocale: 'en'
});
const app = new Vue({
i18n,
render: h => h(component),
});
const app = new Vue({i18n,
render: h => h(result.component)});
return renderer.renderToString(app);
},
@ -40,10 +37,11 @@ module.exports = {
/**
* Prefetch all component data from asyncData method
*
* @param {Object} component - Component object
* @param {Object} orgComponent - Component object
* @param {Object} ctx - Request context
*/
async preFetch(component, ctx) {
async preFetch(orgComponent, ctx) {
let component = Object.create(orgComponent);
let mergedData = {};
let asyncData = {};
let data = {};
@ -57,11 +55,11 @@ module.exports = {
await this.attachAssets(component);
if (component.hasOwnProperty('data'))
data = component.data();
if (orgComponent.hasOwnProperty('data'))
data = orgComponent.data();
if (component.hasOwnProperty('asyncData'))
asyncData = await component.asyncData(ctx, params);
if (orgComponent.hasOwnProperty('asyncData'))
asyncData = await orgComponent.asyncData(ctx, params);
mergedData = Object.assign(mergedData, data, asyncData);
@ -69,16 +67,25 @@ module.exports = {
return mergedData;
};
if (component.components) {
const components = component.components;
const components = orgComponent.components;
if (components) {
const promises = [];
const childNames = [];
component.components = {};
Object.keys(components).forEach(component => {
promises.push(this.preFetch(components[component], ctx));
Object.keys(components).forEach(childName => {
childNames.push(childName);
promises.push(this.preFetch(components[childName], ctx));
});
return Promise.all(promises);
await Promise.all(promises).then(results => {
results.forEach((result, i) => {
component.components[childNames[i]] = result.component;
});
});
}
return {component};
},
async attachAssets(component) {

View File

@ -4,7 +4,7 @@ const express = require('express');
const routes = require(`../config/routes.json`);
module.exports = app => {
this.path = `${appPath}/reports`;
this.path = `${appPath}/report`;
/**
* Enables a report
@ -15,10 +15,9 @@ module.exports = app => {
if (!name) throw new Error('Report name required');
app.get(`/report/${name}`, (request, response, next) => {
response.setHeader('Content-Disposition', `attachment; filename="${name}.pdf"`);
response.setHeader('Content-type', 'application/pdf');
reportEngine.toPdf(name, request).then(stream => {
response.setHeader('Content-Disposition', `attachment; filename="${name}.pdf"`);
response.setHeader('Content-type', 'application/pdf');
stream.pipe(response);
}).catch(e => {
next(e);

View File

@ -3,10 +3,8 @@ body {
}
.container {
font-family: arial, sans-serif;
max-width: 600px;
min-width: 320px;
font-size: 16px;
margin: 0 auto;
color: #555
}

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="es">
<head>
<title>{{ $t('subject') }}</title>
</head>
<body>
<section class="container">
<email-header></email-header>
<section class="main">
<!-- Title block -->
<div class="title">
<h1>{{ $t('title') }}</h1>
</div>
<!-- Title block end -->
<p>{{$t('description.dear')}},</p>
<p>{{$t('description.instructions')}}</p>
<!-- <h1>{{$t('sections.howToBuy.title')}}</h1>
<p>{{$t('sections.howToBuy.description')}}</p>
<ol>
<li v-for="requeriment in $t('sections.howToBuy.requeriments')">
<span v-html="requeriment"></span>
</li>
</ol>
<p>{{$t('sections.howToBuy.stock')}}</p>
<p>{{$t('sections.howToBuy.delivery')}}</p> -->
</section>
<email-footer></email-footer>
</section>
</body>
</html>

View File

@ -0,0 +1,52 @@
const UserException = require(`${appPath}/lib/exceptions/userException`);
const reportEngine = require(`${appPath}/lib/reportEngine`);
const database = require(`${appPath}/lib/database`);
const emailHeader = require('../email-header');
const emailFooter = require('../email-footer');
module.exports = {
name: 'claim-pickup-order',
async asyncData(ctx, params) {
const promises = [];
const data = {
isPreview: ctx.method === 'GET',
};
if (!params.claimFk)
throw new UserException('No claim id specified');
promises.push(reportEngine.toPdf('rpt-claim-pickup-order', ctx));
promises.push(this.methods.fetchClient(params.claimFk));
return Promise.all(promises).then(result => {
const stream = result[0];
const [[client]] = result[1];
Object.assign(data, client);
Object.assign(data, {attachments: [{filename: 'claim-pickup-order.pdf', content: stream}]});
return data;
});
},
created() {
this.$i18n.locale = this.locale;
},
methods: {
fetchClient(claimFk) {
return database.pool.query(`
SELECT
c.id,
u.lang locale,
c.email recipient
FROM claim cl
JOIN client c ON c.id = cl.clientFk
JOIN account.user u ON u.id = c.id
WHERE cl.id = ?`, [claimFk]);
},
},
components: {
emailHeader,
emailFooter,
},
};

View File

@ -0,0 +1,25 @@
module.exports = {
messages: {
es: {
subject: 'Orden de recogida',
title: 'Orden de recogida',
description: {
dear: 'Estimado cliente',
instructions: 'Aqui tienes tu orden de recogida.'
},
sections: {
howToBuy: {
title: 'Cómo hacer un pedido',
description: `Para realizar un pedido en nuestra web,
debes configurarlo indicando:`,
requeriments: [
'Si quieres recibir el pedido (por agencia o por nuestro propio reparto) o si lo prefieres recoger en alguno de nuestros almacenes.',
'La fecha en la que quieres recibir el pedido (se preparará el día anterior).',
'La dirección de entrega o el almacén donde quieres recoger el pedido.'],
stock: 'En nuestra web y aplicaciones puedes visualizar el stock disponible de flor cortada, verdes, plantas, complementos y artificial. Ten en cuenta que dicho stock puede variar en función de la fecha seleccionada al configurar el pedido. Es importante CONFIRMAR los pedidos para que la mercancía quede reservada.',
delivery: 'El reparto se realiza de lunes a sábado según la zona en la que te encuentres. Por regla general, los pedidos que se entregan por agencia, deben estar confirmados y pagados antes de las 17h del día en que se preparan (el día anterior a recibirlos), aunque esto puede variar si el pedido se envía a través de nuestro reparto y según la zona.',
}
}
},
},
};

View File

@ -3,10 +3,8 @@ body {
}
.container {
font-family: arial, sans-serif;
max-width: 600px;
min-width: 320px;
font-size: 16px;
margin: 0 auto;
color: #555
}

View File

@ -15,6 +15,8 @@ module.exports = {
return this.methods.fetchClient(params.clientFk)
.then(([result]) => {
if (!result)
throw new UserException('No client data found');
return Object.assign(data, result[0]);
});
},

View File

@ -1,11 +1,3 @@
footer {
font-family: arial, sans-serif;
max-width: 600px;
min-width: 320px;
font-size: 16px;
margin: 0 auto
}
.buttons {
background-color: #FFF;
text-align: center;

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,7 @@
header {
color: #555
}
header img {
width: 100%
}

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,6 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([
`${appPath}/common/css/layout.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,39 @@
body {
background-color: #EEE
}
.container {
max-width: 600px;
min-width: 320px;
margin: 0 auto;
color: #555
}
.main {
background-color: #FFF;
padding: 20px
}
.main a {
color: #8dba25
}
.main h1 {
color: #999
}
.main h3 {
font-size: 16px
}
.title {
background-color: #95d831;
text-transform: uppercase;
text-align: center;
padding: 35px 0
}
.title h1 {
font-size: 32px;
color: #333;
margin: 0
}

View File

@ -15,6 +15,8 @@ module.exports = {
return this.methods.fetchClientData(params.clientFk)
.then(([result]) => {
if (!result)
throw new UserException('No client data found');
return Object.assign(data, result[0]);
});
},

View File

@ -0,0 +1,6 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([
`${appPath}/common/css/layout.css`,
`${__dirname}/style.css`])
.mergeStyles();

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