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 filter = {where: {name: user}};
let instance = await Self.findOne(filter); let instance = await Self.findOne(filter);
if (!instance || instance.password !== md5(password)) if (!instance || instance.password !== md5(password || ''))
throw err; throw err;
let where = {id: instance.id}; let where = {id: instance.id};

View File

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

View File

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

View File

@ -169,9 +169,9 @@ export default class Watcher extends Component {
*/ */
check() { check() {
if (this.form && this.form.$invalid) 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) 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 './module-loader';
import './crud'; import './crud';
import './app';
import './acl-service'; import './acl-service';
import './storage-services'; import './storage-services';
import './template'; import './template';

View File

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

View File

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

View File

@ -7,99 +7,89 @@ import UserError from 'core/lib/user-error';
* @property {Boolean} loggedIn Whether the user is currently logged * @property {Boolean} loggedIn Whether the user is currently logged
*/ */
export default class Auth { export default class Auth {
constructor($http, $state, $transitions, $window, vnToken, vnModules, aclService) { constructor($http, $q, $state, $transitions, $window, vnToken, vnModules, aclService) {
Object.assign(this, { Object.assign(this, {
$http, $http,
$q,
$state, $state,
$transitions, $transitions,
$window, $window,
vnToken, vnToken,
vnModules, vnModules,
aclService, aclService,
token: null, loggedIn: false
loggedIn: false,
dataLoaded: false
}); });
} }
initialize() { initialize() {
this.loggedIn = this.vnToken.token != null;
let criteria = { let criteria = {
to: state => state.name != 'login' to: state => state.name != 'login'
}; };
this.$transitions.onStart(criteria, transition => { this.$transitions.onStart(criteria, transition => {
if (!this.loggedIn) { 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();
}
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) { login(user, password, remember) {
if (!user) if (!user)
throw new UserError('Please insert your user and password'); return this.$q.reject(new UserError('Please enter your username'));
let params = { let params = {
user, user,
password: password || undefined password: password || undefined
}; };
return this.$http.post('/api/Accounts/login', params).then( return this.$http.post('/api/Accounts/login', params).then(
json => this.onLoginOk(json, remember), json => this.onLoginOk(json, remember));
json => this.onLoginErr(json)
);
} }
onLoginOk(json, remember) { onLoginOk(json, remember) {
this.resetData();
this.vnToken.set(json.data.token, remember); this.vnToken.set(json.data.token, remember);
this.loggedIn = true;
return this.loadAcls().then(() => {
let continueHash = this.$state.params.continue; let continueHash = this.$state.params.continue;
if (continueHash) if (continueHash)
this.$window.location = continueHash; this.$window.location = continueHash;
else else
this.$state.go('home'); 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);
} }
logout() { logout() {
let promise = this.$http.post('/api/Accounts/logout', null, { let promise = this.$http.post('/api/Accounts/logout', null, {
headers: {Authorization: this.vnToken.token} headers: {Authorization: this.vnToken.token}
}); }).catch(() => {});
this.vnToken.unset(); this.vnToken.unset();
this.loggedIn = false; this.loggedIn = false;
this.resetData(); this.vnModules.reset();
this.aclService.reset();
this.$state.go('login'); this.$state.go('login');
return promise; return promise;
} }
resetData() { loadAcls() {
this.aclService.reset(); return this.aclService.load()
.then(() => {
this.loggedIn = true;
this.vnModules.reset(); this.vnModules.reset();
this.dataLoaded = false; })
.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); ngModule.service('vnAuth', Auth);

View File

@ -1,3 +1,5 @@
import './app';
import './auth'; import './auth';
import './token'; import './token';
import './modules'; 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 * @property {String} token The current login token or %null
*/ */
export default class Token { export default class Token {
constructor($cookies) { constructor() {
this.$cookies = $cookies;
try { try {
this.token = sessionStorage.getItem('vnToken'); this.token = sessionStorage.getItem('vnToken');
if (!this.token) if (!this.token)
this.token = localStorage.getItem('vnToken'); this.token = localStorage.getItem('vnToken');
} catch (e) {} } catch (e) {}
if (!this.token)
this.token = this.$cookies.get('vnToken');
} }
set(value, remember) { set(value, remember) {
this.unset(); this.unset();
@ -25,27 +20,15 @@ export default class Token {
localStorage.setItem('vnToken', value); localStorage.setItem('vnToken', value);
else else
sessionStorage.setItem('vnToken', value); sessionStorage.setItem('vnToken', value);
} catch (e) { } catch (e) {}
let options = {};
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; this.token = value;
} }
unset() { unset() {
localStorage.removeItem('vnToken'); localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken'); sessionStorage.removeItem('vnToken');
this.$cookies.remove('vnToken');
this.token = null; this.token = null;
} }
} }
Token.$inject = ['$cookies'];
ngModule.service('vnToken', Token); ngModule.service('vnToken', Token);

View File

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

View File

@ -31,11 +31,6 @@
"resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz",
"integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" "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": { "angular-translate": {
"version": "2.18.1", "version": "2.18.1",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz",

View File

@ -11,7 +11,6 @@
"@babel/polyfill": "^7.2.5", "@babel/polyfill": "^7.2.5",
"@uirouter/angularjs": "^1.0.20", "@uirouter/angularjs": "^1.0.20",
"angular": "^1.7.5", "angular": "^1.7.5",
"angular-cookies": "^1.7.5",
"angular-translate": "^2.18.1", "angular-translate": "^2.18.1",
"angular-translate-loader-partial": "^2.18.1", "angular-translate-loader-partial": "^2.18.1",
"flatpickr": "^4.5.2", "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}}"> <a class="logo" ui-sref="home" title="{{'Home' | translate}}">
<img src="./logo.svg" alt="Logo"></img> <img src="./logo.svg" alt="Logo"></img>
</a> </a>
@ -12,7 +12,7 @@
</vn-topbar> </vn-topbar>
<div ui-view <div ui-view
class="main-view" class="main-view"
ng-class="{'padding': $ctrl.inApp}"> ng-class="{'padding': $ctrl.showTopbar}">
<vn-home></vn-home> <vn-home></vn-home>
</div> </div>
<div <div

View File

@ -2,42 +2,63 @@ import ngModule from '../../module';
import './style.scss'; import './style.scss';
export default class App { export default class App {
constructor($element, $scope, vnApp, $state, $transitions) { constructor($, $element, vnApp, $state, $transitions) {
this.$element = $element; Object.assign(this, {
this.$ = $scope; $,
this.vnApp = vnApp; $element,
this.$state = $state; vnApp,
$state
});
$transitions.onStart({}, () => { $transitions.onStart({}, () => {
if (this.menuShown) this.hideMenu(); if (this.menuShown) this.hideMenu();
}); });
} }
$postLink() { $postLink() {
this.background = this.$element[0].querySelector('.background'); this.vnApp.logger = this;
this.vnApp.snackbar = this.$.snackbar;
} }
// 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; let state = this.$state.current.name;
return state && state != 'login'; return state && state != 'login';
} }
get leftBlock() { get leftBlock() {
return this.$element[0].querySelector('.left-block'); return this.$element[0].querySelector('.left-block');
} }
showMenu() { showMenu() {
let leftBlock = this.leftBlock; let leftBlock = this.leftBlock;
if (!leftBlock) return; if (!leftBlock) return;
leftBlock.classList.add('shown'); leftBlock.classList.add('shown');
this.menuShown = true; this.menuShown = true;
} }
hideMenu() { hideMenu() {
this.menuShown = false; this.menuShown = false;
let leftBlock = this.leftBlock; let leftBlock = this.leftBlock;
if (!leftBlock) return; if (!leftBlock) return;
leftBlock.classList.remove('shown'); leftBlock.classList.remove('shown');
} }
showMessage(message) {
this.$.snackbar.show({message: message});
} }
App.$inject = ['$element', '$scope', 'vnApp', '$state', '$transitions'];
showSuccess(message) {
this.$.snackbar.showSuccess({message: message});
}
showError(message) {
this.$.snackbar.showError({message: message});
}
}
App.$inject = ['$scope', '$element', 'vnApp', '$state', '$transitions'];
ngModule.component('vnApp', { ngModule.component('vnApp', {
template: require('./app.html'), template: require('./app.html'),

View File

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

View File

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

View File

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

View File

@ -1,7 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html ng-app="salix"> <html ng-app="salix">
<head> <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> <title vn-title translate></title>
<script src="/acl"></script> <script src="/acl"></script>
</head> </head>

View File

@ -5,7 +5,6 @@ Logout: Logout
Change language: Change language Change language: Change language
Profile: Profile Profile: Profile
Data saved!: Data saved! 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 Push on applications menu: To open a module push on applications menu
Clients: Clients Clients: Clients
Modules access: Modules access Modules access: Modules access

View File

@ -1,5 +1,4 @@
Applications: Aplicaciones Applications: Aplicaciones
Can't contact with server: No se pudo contactar con el servidor
Change language: Cambiar idioma Change language: Cambiar idioma
Client Frozen: Cliente congelado Client Frozen: Cliente congelado
Client has debt: Cliente con riesgo 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 Routes: Rutas
What is new: Novedades de la versión What is new: Novedades de la versión
Web Account inactive: Sin acceso Web Web Account inactive: Sin acceso Web
Orders: Pedidos Orders: Cesta
Agencies: Agencias Agencies: Agencias
Travels: Envíos Travels: Envíos

View File

@ -66,10 +66,12 @@ ngModule.config(config);
// Unhandled exceptions // Unhandled exceptions
$exceptionHandler.$inject = ['vnApp', '$window', '$state']; $exceptionHandler.$inject = ['vnApp', '$window', '$state', '$injector'];
function $exceptionHandler(vnApp, $window, $state) { function $exceptionHandler(vnApp, $window, $state, $injector) {
return function(exception, cause) { return function(exception, cause) {
let message; let message;
let messageT;
let $translate = $injector.get('$translate');
if (exception.name == 'HttpError') { if (exception.name == 'HttpError') {
switch (exception.xhrStatus) { switch (exception.xhrStatus) {
@ -78,27 +80,44 @@ function $exceptionHandler(vnApp, $window, $state) {
return; return;
} }
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 (!messageT) {
let data = exception.data; let data = exception.data;
if (data && data.error instanceof Object) if (data && data.error instanceof Object)
message = data.error.message; message = data.error.message;
else if (exception.status === -1)
message = `Can't contact with server`;
else else
message = `${exception.status}: ${exception.statusText}`; message = `${exception.status}: ${exception.statusText}`;
if (exception.status === 401 && $state.current.name != 'login') {
let params = {continue: $window.location.hash};
$state.go('login', params);
} }
} else if (exception.name == 'UserError') } else if (exception.name == 'UserError')
message = exception.message; messageT = exception.message;
else { else {
vnApp.showError('Ups! Something went wrong'); vnApp.showError('Ups! Something went wrong');
console.error(exception); console.error(exception);
throw exception; throw exception;
} }
if (messageT)
message = $translate.instant(messageT);
vnApp.showError(message); vnApp.showError(message);
}; };
} }

View File

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

View File

@ -2,6 +2,8 @@
<html ng-app="salix"> <html ng-app="salix">
<head> <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> <title vn-title translate></title>
</head> </head>
<body> <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) { function toJson(object) {
let json = {}; let json = {};
@ -20,44 +79,4 @@ module.exports = function(app) {
return json; 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 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": { "user": {
"dataSource": "vn" "dataSource": "vn"
},
"Schema": {
"dataSource": "vn"
} }
} }

View File

@ -36,11 +36,6 @@ let buildDir = `${appDir}/dist`;
let servicesDir = `${appDir}/modules`; let servicesDir = `${appDir}/modules`;
let modulesPath = `${appDir}/modules.yml`; 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()); app.use(cookieParser());
// Internationalization // 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/landsThatDay')(Self);
require('../methods/agency/getFirstShipped')(Self); require('../methods/agency/getFirstShipped')(Self);
require('../methods/agency/getAgenciesWithWarehouse')(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'); 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})"> <a translate-attr="{title: 'Preview'}" ui-sref="claim.card.summary({id: $ctrl.claim.id})">
<vn-icon icon="desktop_windows"></vn-icon> <vn-icon icon="desktop_windows"></vn-icon>
</a> </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>
<div class="body"> <div class="body">
<div class="attributes"> <div class="attributes">
@ -58,3 +66,9 @@
</div> </div>
</div> </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'; import ngModule from '../module';
class Controller { class Controller {
constructor($state) { constructor($scope, $state, $http, $translate, vnApp) {
this.$scope = $scope;
this.$state = $state; this.$state = $state;
this.$http = $http;
this.$translate = $translate;
this.vnApp = vnApp;
this.moreOptions = [
{callback: this.showConfirmDialog, name: 'Pickup order'}
];
} }
get claim() { get claim() {
@ -30,9 +37,28 @@ class Controller {
get quicklinks() { get quicklinks() {
return this._quicklinks; return this._quicklinks;
} }
onMoreChange(callback) {
callback.call(this);
} }
Controller.$inject = ['$state']; 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 = ['$scope', '$state', '$http', '$translate', 'vnApp'];
ngModule.component('vnClaimDescriptor', { ngModule.component('vnClaimDescriptor', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -7,6 +7,8 @@ Remove sale: Borrar linea
Claim Id: Id reclamación Claim Id: Id reclamación
Created: Creado Created: Creado
Enter a new search: Introduce una nueva búsqueda 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 #sections
Claims: Reclamaciones Claims: Reclamaciones

View File

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

View File

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

View File

@ -18,6 +18,7 @@ class Controller {
}, },
where: { where: {
clientFk: $stateParams.id, clientFk: $stateParams.id,
companyFk: this.companyFk
}, },
}; };
this.params = { this.params = {
@ -29,7 +30,9 @@ class Controller {
} }
setOrder(value) { setOrder(value) {
this.params.params.companyFk = value; this.params.params.companyFk = value;
this.filter.where.companyFk = value;
this.$.model.refresh(); this.$.model.refresh();
this.$.riskModel.refresh();
} }
set risks(value) { 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`); 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) if (!params.userId && ctx.req && ctx.req.accessToken.userId)
params.userId = ctx.req.accessToken.userId; params.userId = ctx.req.accessToken.userId;

View File

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

View File

@ -14,6 +14,9 @@ class Controller {
$onInit() { $onInit() {
if (this.$stateParams && this.$stateParams.clientFk) if (this.$stateParams && this.$stateParams.clientFk)
this.clientFk = this.$stateParams.clientFk; this.clientFk = this.$stateParams.clientFk;
if (window.localStorage && window.localStorage.defaultWarehouseFk)
this.warehouseFk = parseInt(window.localStorage.defaultWarehouseFk);
} }
set ticket(value) { set ticket(value) {
@ -37,6 +40,7 @@ class Controller {
}); });
} else } else
this.addressFk = null; this.addressFk = null;
this.getAvailableAgencies();
} }
get clientFk() { get clientFk() {
@ -45,6 +49,7 @@ class Controller {
set addressFk(value) { set addressFk(value) {
this.ticket.addressFk = value; this.ticket.addressFk = value;
this.getAvailableAgencies();
} }
get addressFk() { get addressFk() {
@ -53,6 +58,7 @@ class Controller {
set landed(value) { set landed(value) {
this.ticket.landed = value; this.ticket.landed = value;
this.getAvailableAgencies();
} }
get landed() { get landed() {
@ -67,10 +73,9 @@ class Controller {
return this.ticket.warehouseFk; return this.ticket.warehouseFk;
} }
getAvailableAgencies() { getAvailableAgencies() {
if (this.ticket.warehouseFk && this.ticket.addressFk && this.ticket.landed && this.ticket.clientFk) {
this.ticket.agencyModeFk = null; this.ticket.agencyModeFk = null;
if (this.ticket.landed && this.ticket.addressFk) {
let filter = {warehouseFk: this.ticket.warehouseFk, addressFk: this.ticket.addressFk, landed: this.ticket.landed}; let filter = {warehouseFk: this.ticket.warehouseFk, addressFk: this.ticket.addressFk, landed: this.ticket.landed};
filter = encodeURIComponent(JSON.stringify(filter)); filter = encodeURIComponent(JSON.stringify(filter));
let query = `/api/Agencies/getAgenciesWithWarehouse?filter=${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 { .columns {
overflow: hidden overflow: hidden
} }
@ -27,46 +33,6 @@
float: left 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 { .row {
margin-bottom: 15px; margin-bottom: 15px;
overflow: hidden overflow: hidden
@ -139,6 +105,37 @@
overflow: visible 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 { .box {
border-top: 1px solid #CCC; border-top: 1px solid #CCC;
border-right: 1px solid #CCC; border-right: 1px solid #CCC;
@ -164,22 +161,3 @@
.pull-right { .pull-right {
float: 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": "payment-update"},
{"type": "email", "name": "letter-debtor-st"}, {"type": "email", "name": "letter-debtor-st"},
{"type": "email", "name": "letter-debtor-nd"}, {"type": "email", "name": "letter-debtor-nd"},
{"type": "email", "name": "claim-pickup-order"},
{"type": "report", "name": "delivery-note"}, {"type": "report", "name": "delivery-note"},
{"type": "report", "name": "invoice"}, {"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-header"},
{"type": "static", "name": "email-footer"}, {"type": "static", "name": "email-footer"},
{"type": "static", "name": "report-header"}, {"type": "static", "name": "report-header"},

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,8 @@ module.exports = {
return this.methods.fetchClient(params.clientFk) return this.methods.fetchClient(params.clientFk)
.then(([result]) => { .then(([result]) => {
if (!result)
throw new UserException('No client data found');
return Object.assign(data, result[0]); 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 { .buttons {
background-color: #FFF; background-color: #FFF;
text-align: center; 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) return this.methods.fetchClientData(params.clientFk)
.then(([result]) => { .then(([result]) => {
if (!result)
throw new UserException('No client data found');
return Object.assign(data, result[0]); 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();

View File

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

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