Merge branch 'dev' into 5066-vehicleSorted
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
commit
b2b335326e
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [2328.01] - 2023-07-13
|
## [2330.01] - 2023-07-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
@ -14,15 +14,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
||||||
|
## [2328.01] - 2023-07-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- (Clientes -> Morosos) Añadida columna "es trabajador"
|
||||||
|
- (Trabajadores -> Departamentos) Nueva sección
|
||||||
|
- (Trabajadores -> Departamentos) Añadido listado de Trabajadores por departamento
|
||||||
|
- (Trabajadores -> Departamentos) Añadido características de departamento e información
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- (Trabajadores -> Departamentos) Arreglado búscador
|
||||||
|
|
||||||
|
|
||||||
## [2326.01] - 2023-06-29
|
## [2326.01] - 2023-06-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas
|
- (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas
|
||||||
|
- (General -> Históricos) Botón para ver el estado del registro en cada punto
|
||||||
|
- (General -> Históricos) Al filtar por registro se muestra todo el histórial desde que fue creado
|
||||||
|
- (Tickets -> Índice) Permite enviar varios albaranes a Docuware
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- (General -> Históricos) Los registros se muestran agrupados por usuario y entidad
|
||||||
|
- (Facturas -> Facturación global) Optimizada, generación de PDFs y notificaciones en paralelo
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
-
|
- (General -> Históricos) Duplicidades eliminadas
|
||||||
|
- (Facturas -> Facturación global) Solucionados fallos que paran el proceso
|
||||||
|
|
||||||
## [2324.01] - 2023-06-15
|
## [2324.01] - 2023-06-15
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('addAlias', {
|
||||||
|
description: 'Add an alias if the user has the grant',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'ctx',
|
||||||
|
type: 'Object',
|
||||||
|
http: {source: 'context'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The user id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'mailAlias',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The new alias for user',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/:id/addAlias`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.addAlias = async function(ctx, id, mailAlias, options) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
|
||||||
|
|
||||||
|
if (!user.hasGrant)
|
||||||
|
throw new UserError(`You don't have grant privilege`);
|
||||||
|
|
||||||
|
const account = await models.Account.findById(userId, {
|
||||||
|
fields: ['id'],
|
||||||
|
include: {
|
||||||
|
relation: 'aliases',
|
||||||
|
scope: {
|
||||||
|
fields: ['mailAlias']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const aliases = account.aliases().map(alias => alias.mailAlias);
|
||||||
|
|
||||||
|
const hasAlias = aliases.includes(mailAlias);
|
||||||
|
if (!hasAlias)
|
||||||
|
throw new UserError(`You cannot assign an alias that you are not assigned to`);
|
||||||
|
|
||||||
|
return models.MailAliasAccount.create({
|
||||||
|
mailAlias: mailAlias,
|
||||||
|
account: id
|
||||||
|
}, myOptions);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,55 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('removeAlias', {
|
||||||
|
description: 'Remove alias if the user has the grant',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'ctx',
|
||||||
|
type: 'Object',
|
||||||
|
http: {source: 'context'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The user id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'mailAlias',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The alias to delete',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/:id/removeAlias`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.removeAlias = async function(ctx, id, mailAlias, options) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE');
|
||||||
|
|
||||||
|
if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`);
|
||||||
|
|
||||||
|
const mailAliasAccount = await models.MailAliasAccount.findOne({
|
||||||
|
where: {
|
||||||
|
mailAlias: mailAlias,
|
||||||
|
account: id
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
await mailAliasAccount.destroy(myOptions);
|
||||||
|
};
|
||||||
|
};
|
|
@ -17,23 +17,22 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.renewToken = async function(ctx) {
|
Self.renewToken = async function(ctx) {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const userId = ctx.req.accessToken.userId;
|
const token = ctx.req.accessToken;
|
||||||
const created = ctx.req.accessToken.created;
|
|
||||||
const tokenId = ctx.req.accessToken.id;
|
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const differenceMilliseconds = now - created;
|
const differenceMilliseconds = now - token.created;
|
||||||
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
||||||
|
|
||||||
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod']});
|
const fields = ['renewPeriod', 'courtesyTime'];
|
||||||
|
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields});
|
||||||
|
|
||||||
if (differenceSeconds <= accessTokenConfig.renewPeriod)
|
if (differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime)
|
||||||
throw new UserError(`The renew period has not been exceeded`);
|
throw new UserError(`The renew period has not been exceeded`, 'periodNotExceeded');
|
||||||
|
|
||||||
await Self.logout(tokenId);
|
await Self.logout(token.id);
|
||||||
const user = await Self.findById(userId);
|
const user = await Self.findById(token.userId);
|
||||||
const accessToken = await user.createAccessToken();
|
const accessToken = await user.createAccessToken();
|
||||||
|
|
||||||
return {token: accessToken.id, created: accessToken.created};
|
return {id: accessToken.id, ttl: accessToken.ttl};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('signIn', {
|
||||||
|
description: 'Login a user with username/email and password',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'user',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The user name or email',
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
arg: 'password',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The password'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/sign-in`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.signIn = async function(ctx, user, password, options) {
|
||||||
|
const myOptions = {};
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const where = Self.userUses(user);
|
||||||
|
const vnUser = await Self.findOne({
|
||||||
|
fields: ['id', 'name', 'password', 'active', 'email', 'passExpired', 'twoFactor'],
|
||||||
|
where
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const validCredentials = vnUser
|
||||||
|
&& await vnUser.hasPassword(password);
|
||||||
|
|
||||||
|
if (validCredentials) {
|
||||||
|
if (!vnUser.active)
|
||||||
|
throw new UserError('User disabled');
|
||||||
|
await Self.sendTwoFactor(ctx, vnUser, myOptions);
|
||||||
|
await Self.passExpired(vnUser, myOptions);
|
||||||
|
|
||||||
|
if (vnUser.twoFactor)
|
||||||
|
throw new ForbiddenError(null, 'REQUIRES_2FA');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Self.validateLogin(user, password);
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.passExpired = async(vnUser, myOptions) => {
|
||||||
|
const today = Date.vnNew();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
|
||||||
|
const $ = Self.app.models;
|
||||||
|
const changePasswordToken = await $.AccessToken.create({
|
||||||
|
scopes: ['changePassword'],
|
||||||
|
userId: vnUser.id
|
||||||
|
}, myOptions);
|
||||||
|
const err = new UserError('Pass expired', 'passExpired');
|
||||||
|
changePasswordToken.twoFactor = vnUser.twoFactor ? true : false;
|
||||||
|
err.details = {token: changePasswordToken};
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.sendTwoFactor = async(ctx, vnUser, myOptions) => {
|
||||||
|
if (vnUser.twoFactor === 'email') {
|
||||||
|
const $ = Self.app.models;
|
||||||
|
|
||||||
|
const code = String(Math.floor(Math.random() * 999999));
|
||||||
|
const maxTTL = ((60 * 1000) * 5); // 5 min
|
||||||
|
await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
|
||||||
|
userFk: vnUser.id,
|
||||||
|
code: code,
|
||||||
|
expires: Date.vnNow() + maxTTL
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const headers = ctx.req.headers;
|
||||||
|
const platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
|
||||||
|
const browser = headers['sec-ch-ua']?.replace(/['"=]+/g, '');
|
||||||
|
const params = {
|
||||||
|
args: {
|
||||||
|
recipientId: vnUser.id,
|
||||||
|
recipient: vnUser.email,
|
||||||
|
code: code,
|
||||||
|
ip: ctx.req?.connection?.remoteAddress,
|
||||||
|
device: platform && browser ? platform + ', ' + browser : headers['user-agent'],
|
||||||
|
},
|
||||||
|
req: {getLocale: ctx.req.getLocale},
|
||||||
|
};
|
||||||
|
|
||||||
|
await Self.sendTemplate(params, 'auth-code', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,81 +0,0 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethod('signIn', {
|
|
||||||
description: 'Login a user with username/email and password',
|
|
||||||
accepts: [
|
|
||||||
{
|
|
||||||
arg: 'user',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The user name or email',
|
|
||||||
http: {source: 'form'},
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
arg: 'password',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The password'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
returns: {
|
|
||||||
type: 'object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/signIn`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.signIn = async function(user, password) {
|
|
||||||
const models = Self.app.models;
|
|
||||||
const usesEmail = user.indexOf('@') !== -1;
|
|
||||||
let token;
|
|
||||||
|
|
||||||
const userInfo = usesEmail
|
|
||||||
? {email: user}
|
|
||||||
: {username: user};
|
|
||||||
const instance = await Self.findOne({
|
|
||||||
fields: ['username', 'password'],
|
|
||||||
where: userInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
const where = usesEmail
|
|
||||||
? {email: user}
|
|
||||||
: {name: user};
|
|
||||||
const vnUser = await Self.findOne({
|
|
||||||
fields: ['id', 'active', 'passExpired'],
|
|
||||||
where
|
|
||||||
});
|
|
||||||
|
|
||||||
const today = Date.vnNew();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const validCredentials = instance
|
|
||||||
&& await instance.hasPassword(password);
|
|
||||||
|
|
||||||
if (validCredentials) {
|
|
||||||
if (!vnUser.active)
|
|
||||||
throw new UserError('User disabled');
|
|
||||||
|
|
||||||
if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
|
|
||||||
const changePasswordToken = await models.AccessToken.create({
|
|
||||||
scopes: ['change-password'],
|
|
||||||
userId: vnUser.id
|
|
||||||
});
|
|
||||||
const err = new UserError('Pass expired', 'passExpired');
|
|
||||||
err.details = {token: changePasswordToken};
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await models.Account.sync(instance.username, password);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let loginInfo = Object.assign({password}, userInfo);
|
|
||||||
token = await Self.login(loginInfo, 'user');
|
|
||||||
return {token: token.id, created: token.created};
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('VnUser Sign-in()', () => {
|
||||||
|
const employeeId = 1;
|
||||||
|
const unauthCtx = {
|
||||||
|
req: {
|
||||||
|
headers: {},
|
||||||
|
connection: {
|
||||||
|
remoteAddress: '127.0.0.1'
|
||||||
|
},
|
||||||
|
getLocale: () => 'en'
|
||||||
|
},
|
||||||
|
args: {}
|
||||||
|
};
|
||||||
|
const {VnUser, AccessToken} = models;
|
||||||
|
describe('when credentials are correct', () => {
|
||||||
|
it('should return the token', async() => {
|
||||||
|
let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare');
|
||||||
|
let accessToken = await AccessToken.findById(login.token);
|
||||||
|
let ctx = {req: {accessToken: accessToken}};
|
||||||
|
|
||||||
|
expect(login.token).toBeDefined();
|
||||||
|
|
||||||
|
await VnUser.logout(ctx.req.accessToken.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the token if the user doesnt exist but the client does', async() => {
|
||||||
|
let login = await VnUser.signIn(unauthCtx, 'PetterParker', 'nightmare');
|
||||||
|
let accessToken = await AccessToken.findById(login.token);
|
||||||
|
let ctx = {req: {accessToken: accessToken}};
|
||||||
|
|
||||||
|
expect(login.token).toBeDefined();
|
||||||
|
|
||||||
|
await VnUser.logout(ctx.req.accessToken.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when credentials are incorrect', () => {
|
||||||
|
it('should throw a 401 error', async() => {
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await VnUser.signIn(unauthCtx, 'IDontExist', 'TotallyWrongPassword');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(401);
|
||||||
|
expect(error.code).toBe('LOGIN_FAILED');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when two-factor auth is required', () => {
|
||||||
|
it('should throw a 403 error', async() => {
|
||||||
|
const employee = await VnUser.findById(employeeId);
|
||||||
|
const tx = await VnUser.beginTransaction({});
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
await employee.updateAttribute('twoFactor', 'email', options);
|
||||||
|
|
||||||
|
await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(403);
|
||||||
|
expect(error.code).toBe('REQUIRES_2FA');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when passExpired', () => {
|
||||||
|
it('should throw a passExpired error', async() => {
|
||||||
|
const tx = await VnUser.beginTransaction({});
|
||||||
|
const employee = await VnUser.findById(employeeId);
|
||||||
|
const yesterday = Date.vnNew();
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
await employee.updateAttribute('passExpired', yesterday, options);
|
||||||
|
|
||||||
|
await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(400);
|
||||||
|
expect(error.message).toBe('Pass expired');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,41 +0,0 @@
|
||||||
const {models} = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('VnUser signIn()', () => {
|
|
||||||
describe('when credentials are correct', () => {
|
|
||||||
it('should return the token', async() => {
|
|
||||||
let login = await models.VnUser.signIn('salesAssistant', 'nightmare');
|
|
||||||
let accessToken = await models.AccessToken.findById(login.token);
|
|
||||||
let ctx = {req: {accessToken: accessToken}};
|
|
||||||
|
|
||||||
expect(login.token).toBeDefined();
|
|
||||||
|
|
||||||
await models.VnUser.logout(ctx.req.accessToken.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the token if the user doesnt exist but the client does', async() => {
|
|
||||||
let login = await models.VnUser.signIn('PetterParker', 'nightmare');
|
|
||||||
let accessToken = await models.AccessToken.findById(login.token);
|
|
||||||
let ctx = {req: {accessToken: accessToken}};
|
|
||||||
|
|
||||||
expect(login.token).toBeDefined();
|
|
||||||
|
|
||||||
await models.VnUser.logout(ctx.req.accessToken.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when credentials are incorrect', () => {
|
|
||||||
it('should throw a 401 error', async() => {
|
|
||||||
let error;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await models.VnUser.signIn('IDontExist', 'TotallyWrongPassword');
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.statusCode).toBe(401);
|
|
||||||
expect(error.code).toBe('LOGIN_FAILED');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('VnUser validate-auth()', () => {
|
||||||
|
describe('validateAuth', () => {
|
||||||
|
it('should signin if data is correct', async() => {
|
||||||
|
await models.AuthCode.create({
|
||||||
|
userFk: 9,
|
||||||
|
code: '555555',
|
||||||
|
expires: Date.vnNow() + (60 * 1000)
|
||||||
|
});
|
||||||
|
const token = await models.VnUser.validateAuth('developer', 'nightmare', '555555');
|
||||||
|
|
||||||
|
expect(token.token).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateCode', () => {
|
||||||
|
it('should throw an error for a non existent code', async() => {
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
await models.VnUser.validateCode('developer', '123456');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(400);
|
||||||
|
expect(error.message).toEqual('Invalid or expired verification code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when a code doesn`t match the login username', async() => {
|
||||||
|
let error;
|
||||||
|
let authCode;
|
||||||
|
try {
|
||||||
|
authCode = await models.AuthCode.create({
|
||||||
|
userFk: 1,
|
||||||
|
code: '555555',
|
||||||
|
expires: Date.vnNow() + (60 * 1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
await models.VnUser.validateCode('developer', '555555');
|
||||||
|
} catch (e) {
|
||||||
|
authCode && await authCode.destroy();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.statusCode).toBe(400);
|
||||||
|
expect(error.message).toEqual('Authentication failed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('validateAuth', {
|
||||||
|
description: 'Login a user with username/email and password',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'user',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The user name or email',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'password',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The password'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'code',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The auth code'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/validate-auth`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validateAuth = async(username, password, code, options) => {
|
||||||
|
const myOptions = {};
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const token = Self.validateLogin(username, password);
|
||||||
|
await Self.validateCode(username, code, myOptions);
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.validateCode = async(username, code, myOptions) => {
|
||||||
|
const {AuthCode} = Self.app.models;
|
||||||
|
|
||||||
|
const authCode = await AuthCode.findOne({
|
||||||
|
where: {
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const expired = authCode && Date.vnNow() > authCode.expires;
|
||||||
|
if (!authCode || expired)
|
||||||
|
throw new UserError('Invalid or expired verification code');
|
||||||
|
|
||||||
|
const user = await Self.findById(authCode.userFk, {
|
||||||
|
fields: ['name', 'twoFactor']
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
if (user.name !== username)
|
||||||
|
throw new UserError('Authentication failed');
|
||||||
|
|
||||||
|
await authCode.destroy(myOptions);
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"AccountingType": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"AccessTokenConfig": {
|
"AccessTokenConfig": {
|
||||||
"dataSource": "vn",
|
"dataSource": "vn",
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -10,6 +7,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AccountingType": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"AuthCode": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"Bank": {
|
"Bank": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
"courtesyTime": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"renewInterval": {
|
"renewInterval": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"required": true
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "AuthCode",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "salix.authCode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"userFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true,
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Account",
|
||||||
|
"foreignKey": "userFk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,15 @@ const {Email} = require('vn-print');
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
vnModel(Self);
|
vnModel(Self);
|
||||||
|
|
||||||
require('../methods/vn-user/signIn')(Self);
|
require('../methods/vn-user/sign-in')(Self);
|
||||||
require('../methods/vn-user/acl')(Self);
|
require('../methods/vn-user/acl')(Self);
|
||||||
require('../methods/vn-user/recover-password')(Self);
|
require('../methods/vn-user/recover-password')(Self);
|
||||||
require('../methods/vn-user/validate-token')(Self);
|
require('../methods/vn-user/validate-token')(Self);
|
||||||
require('../methods/vn-user/privileges')(Self);
|
require('../methods/vn-user/privileges')(Self);
|
||||||
|
require('../methods/vn-user/validate-auth')(Self);
|
||||||
require('../methods/vn-user/renew-token')(Self);
|
require('../methods/vn-user/renew-token')(Self);
|
||||||
|
require('../methods/vn-user/addAlias')(Self);
|
||||||
|
require('../methods/vn-user/removeAlias')(Self);
|
||||||
|
|
||||||
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
||||||
|
|
||||||
|
@ -111,6 +114,18 @@ module.exports = function(Self) {
|
||||||
return email.send();
|
return email.send();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self.validateLogin = async function(user, password) {
|
||||||
|
let loginInfo = Object.assign({password}, Self.userUses(user));
|
||||||
|
token = await Self.login(loginInfo, 'user');
|
||||||
|
return {token: token.id, ttl: token.ttl};
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.userUses = function(user) {
|
||||||
|
return user.indexOf('@') !== -1
|
||||||
|
? {email: user}
|
||||||
|
: {username: user};
|
||||||
|
};
|
||||||
|
|
||||||
const _setPassword = Self.prototype.setPassword;
|
const _setPassword = Self.prototype.setPassword;
|
||||||
Self.prototype.setPassword = async function(newPassword, options, cb) {
|
Self.prototype.setPassword = async function(newPassword, options, cb) {
|
||||||
if (cb === undefined && typeof options === 'function') {
|
if (cb === undefined && typeof options === 'function') {
|
||||||
|
@ -143,8 +158,9 @@ module.exports = function(Self) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.sharedClass._methods.find(method => method.name == 'changePassword')
|
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls =
|
||||||
.accessScopes = ['change-password'];
|
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
|
||||||
|
.filter(acl => acl.property != 'changePassword');
|
||||||
|
|
||||||
// FIXME: https://redmine.verdnatura.es/issues/5761
|
// FIXME: https://redmine.verdnatura.es/issues/5761
|
||||||
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
||||||
|
|
|
@ -59,7 +59,10 @@
|
||||||
},
|
},
|
||||||
"passExpired": {
|
"passExpired": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
}
|
},
|
||||||
|
"twoFactor": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
"role": {
|
"role": {
|
||||||
|
@ -111,6 +114,13 @@
|
||||||
"principalId": "$authenticated",
|
"principalId": "$authenticated",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"property": "validateAuth",
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"property": "privileges",
|
"property": "privileges",
|
||||||
"accessType": "*",
|
"accessType": "*",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
CREATE TABLE `vn`.`travelConfig` (
|
||||||
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`warehouseInFk` smallint(6) unsigned NOT NULL DEFAULT 8 COMMENT 'Warehouse de origen',
|
||||||
|
`warehouseOutFk` smallint(6) unsigned NOT NULL DEFAULT 60 COMMENT 'Warehouse destino',
|
||||||
|
`agencyFk` int(11) NOT NULL DEFAULT 1378 COMMENT 'Agencia por defecto',
|
||||||
|
`companyFk` int(10) unsigned NOT NULL DEFAULT 442 COMMENT 'Compañía por defecto',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `travelConfig_FK` (`warehouseInFk`),
|
||||||
|
KEY `travelConfig_FK_1` (`warehouseOutFk`),
|
||||||
|
KEY `travelConfig_FK_2` (`agencyFk`),
|
||||||
|
KEY `travelConfig_FK_3` (`companyFk`),
|
||||||
|
CONSTRAINT `travelConfig_FK` FOREIGN KEY (`warehouseInFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `travelConfig_FK_1` FOREIGN KEY (`warehouseOutFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `travelConfig_FK_2` FOREIGN KEY (`agencyFk`) REFERENCES `agencyMode` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `travelConfig_FK_3` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
|
VALUES
|
||||||
|
('Entry', 'addFromPackaging', 'WRITE', 'ALLOW', 'ROLE', 'production'),
|
||||||
|
('Entry', 'addFromBuy', 'WRITE', 'ALLOW', 'ROLE', 'production'),
|
||||||
|
('Supplier', 'getItemsPackaging', 'READ', 'ALLOW', 'ROLE', 'production');
|
|
@ -0,0 +1,11 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
|
|
@ -0,0 +1,2 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
|
@ -1,10 +1,11 @@
|
||||||
CREATE TABLE `salix`.`accessTokenConfig` (
|
CREATE TABLE `salix`.`accessTokenConfig` (
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`renewPeriod` int(10) unsigned DEFAULT NULL,
|
`renewPeriod` int(10) unsigned DEFAULT NULL,
|
||||||
|
`courtesyTime` int(10) unsigned DEFAULT NULL,
|
||||||
`renewInterval` int(10) unsigned DEFAULT NULL,
|
`renewInterval` int(10) unsigned DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||||
|
|
||||||
INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`)
|
INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `courtesyTime`, `renewInterval`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 21600, 300);
|
(1, 21600, 5, 300);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
|
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
|
||||||
VALUES
|
VALUES
|
||||||
('InvoiceOut','makePdfAndNotify','WRITE','ALLOW','ROLE','invoicing'),
|
('InvoiceOut','makePdfAndNotify','WRITE','ALLOW','ROLE','invoicing'),
|
||||||
('InvoiceOutConfig','*','READ','ALLOW','ROLE','invoicing');
|
('InvoiceOutConfig','*','READ','ALLOW','ROLE','invoicing');
|
||||||
|
|
||||||
CREATE OR REPLACE TABLE vn.invoiceOutConfig (
|
CREATE OR REPLACE TABLE `vn`.`invoiceOutConfig` (
|
||||||
id INT UNSIGNED auto_increment NOT NULL,
|
id INT UNSIGNED auto_increment NOT NULL,
|
||||||
parallelism int UNSIGNED DEFAULT 1 NOT NULL,
|
parallelism int UNSIGNED DEFAULT 1 NOT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
create table `salix`.`authCode`
|
||||||
|
(
|
||||||
|
userFk int UNSIGNED not null,
|
||||||
|
code int not null,
|
||||||
|
expires bigint not null,
|
||||||
|
constraint authCode_pk
|
||||||
|
primary key (userFk),
|
||||||
|
constraint authCode_unique
|
||||||
|
unique (code),
|
||||||
|
constraint authCode_user_id_fk
|
||||||
|
foreign key (userFk) references `account`.`user` (id)
|
||||||
|
on update cascade on delete cascade
|
||||||
|
);
|
|
@ -0,0 +1,89 @@
|
||||||
|
DROP PROCEDURE IF EXISTS `vn`.`clientCreate`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`(
|
||||||
|
vFirstname VARCHAR(50),
|
||||||
|
vSurnames VARCHAR(50),
|
||||||
|
vFi VARCHAR(9),
|
||||||
|
vAddress TEXT,
|
||||||
|
vPostcode CHAR(5),
|
||||||
|
vCity VARCHAR(25),
|
||||||
|
vProvinceFk SMALLINT(5),
|
||||||
|
vCompanyFk SMALLINT(5),
|
||||||
|
vPhone VARCHAR(11),
|
||||||
|
vEmail VARCHAR(255),
|
||||||
|
vUserFk INT
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Create new client
|
||||||
|
*
|
||||||
|
* @params vFirstname firstName
|
||||||
|
* @params vSurnames surnames
|
||||||
|
* @params vFi company code from accounting transactions
|
||||||
|
* @params vAddress address
|
||||||
|
* @params vPostcode postCode
|
||||||
|
* @params vCity city
|
||||||
|
* @params vProvinceFk province
|
||||||
|
* @params vCompanyFk company in which he has become a client
|
||||||
|
* @params vPhone telephone number
|
||||||
|
* @params vEmail email address
|
||||||
|
* @params vUserFk user id
|
||||||
|
*/
|
||||||
|
DECLARE vPayMethodFk INT;
|
||||||
|
DECLARE vDueDay INT;
|
||||||
|
DECLARE vDefaultCredit DECIMAL(10, 2);
|
||||||
|
DECLARE vIsTaxDataChecked TINYINT(1);
|
||||||
|
DECLARE vHasCoreVnl BOOLEAN;
|
||||||
|
DECLARE vMandateTypeFk INT;
|
||||||
|
|
||||||
|
SELECT defaultPayMethodFk,
|
||||||
|
defaultDueDay,
|
||||||
|
defaultCredit,
|
||||||
|
defaultIsTaxDataChecked,
|
||||||
|
defaultHasCoreVnl,
|
||||||
|
defaultMandateTypeFk
|
||||||
|
INTO vPayMethodFk,
|
||||||
|
vDueDay,
|
||||||
|
vDefaultCredit,
|
||||||
|
vIsTaxDataChecked,
|
||||||
|
vHasCoreVnl,
|
||||||
|
vMandateTypeFk
|
||||||
|
FROM clientConfig;
|
||||||
|
|
||||||
|
INSERT INTO `client`
|
||||||
|
SET id = vUserFk,
|
||||||
|
name = CONCAT(vFirstname, ' ', vSurnames),
|
||||||
|
street = vAddress,
|
||||||
|
fi = TRIM(vFi),
|
||||||
|
phone = vPhone,
|
||||||
|
email = vEmail,
|
||||||
|
provinceFk = vProvinceFk,
|
||||||
|
city = vCity,
|
||||||
|
postcode = vPostcode,
|
||||||
|
socialName = CONCAT(vSurnames, ' ', vFirstname),
|
||||||
|
payMethodFk = vPayMethodFk,
|
||||||
|
dueDay = vDueDay,
|
||||||
|
credit = vDefaultCredit,
|
||||||
|
isTaxDataChecked = vIsTaxDataChecked,
|
||||||
|
hasCoreVnl = vHasCoreVnl,
|
||||||
|
isEqualizated = FALSE
|
||||||
|
ON duplicate KEY UPDATE
|
||||||
|
payMethodFk = vPayMethodFk,
|
||||||
|
dueDay = vDueDay,
|
||||||
|
credit = vDefaultCredit,
|
||||||
|
isTaxDataChecked = vIsTaxDataChecked,
|
||||||
|
hasCoreVnl = vHasCoreVnl,
|
||||||
|
isActive = TRUE;
|
||||||
|
|
||||||
|
INSERT INTO mandate (clientFk, companyFk, mandateTypeFk)
|
||||||
|
SELECT vUserFk, vCompanyFk, vMandateTypeFk
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT id
|
||||||
|
FROM mandate
|
||||||
|
WHERE clientFk = vUserFk
|
||||||
|
AND companyFk = vCompanyFk
|
||||||
|
AND mandateTypeFk = vMandateTypeFk
|
||||||
|
);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,17 @@
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD defaultPayMethodFk tinyint(3) unsigned NULL;
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD defaultDueDay int unsigned NULL;
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD defaultCredit decimal(10, 2) NULL;
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD defaultIsTaxDataChecked tinyint(1) NULL;
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD defaultHasCoreVnl boolean NULL;
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD defaultMandateTypeFk smallint(5) NULL;
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD CONSTRAINT clientNewConfigPayMethod_FK FOREIGN KEY (defaultPayMethodFk) REFERENCES vn.payMethod(id);
|
||||||
|
ALTER TABLE `vn`.`clientConfig` ADD CONSTRAINT clientNewConfigMandateType_FK FOREIGN KEY (defaultMandateTypeFk) REFERENCES vn.mandateType(id);
|
||||||
|
|
||||||
|
UPDATE `vn`.`clientConfig`
|
||||||
|
SET defaultPayMethodFk = 4,
|
||||||
|
defaultDueDay = 5,
|
||||||
|
defaultCredit = 300.0,
|
||||||
|
defaultIsTaxDataChecked = 1,
|
||||||
|
defaultHasCoreVnl = 1,
|
||||||
|
defaultMandateTypeFk = 2
|
||||||
|
WHERE id = 1;
|
|
@ -0,0 +1,24 @@
|
||||||
|
alter table `vn`.`department`
|
||||||
|
add `twoFactor` ENUM ('email') null comment 'Default user two-factor auth type';
|
||||||
|
|
||||||
|
drop trigger `vn`.`department_afterUpdate`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
create definer = root@localhost trigger `vn`.`department_afterUpdate`
|
||||||
|
after update
|
||||||
|
on department
|
||||||
|
for each row
|
||||||
|
BEGIN
|
||||||
|
IF !(OLD.parentFk <=> NEW.parentFk) THEN
|
||||||
|
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF !(OLD.twoFactor <=> NEW.twoFactor) THEN
|
||||||
|
UPDATE account.user u
|
||||||
|
JOIN vn.workerDepartment wd ON wd.workerFk = u.id
|
||||||
|
SET u.twoFactor = NEW.twoFactor
|
||||||
|
WHERE wd.departmentFk = NEW.id;
|
||||||
|
END IF;
|
||||||
|
END;$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,13 @@
|
||||||
|
UPDATE `salix`.`ACL`
|
||||||
|
SET principalId='financialBoss'
|
||||||
|
WHERE
|
||||||
|
model = 'Client'
|
||||||
|
AND property = 'editCredit';
|
||||||
|
|
||||||
|
UPDATE `salix`.`ACL`
|
||||||
|
SET property='zeroCreditEditor'
|
||||||
|
WHERE
|
||||||
|
model = 'Client'
|
||||||
|
AND property = 'isNotEditableCredit';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
ALTER TABLE `vn`.`roadmap` COMMENT='Troncales diarios que se contratan';
|
||||||
|
ALTER TABLE `vn`.`roadmap` ADD price decimal(10,2) NULL;
|
||||||
|
ALTER TABLE `vn`.`roadmap` ADD driverName varchar(45) NULL;
|
||||||
|
ALTER TABLE `vn`.`roadmap` ADD name varchar(45) NOT NULL;
|
||||||
|
ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL AFTER id;
|
||||||
|
ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales';
|
||||||
|
ALTER TABLE `vn`.`expeditionTruck` DROP FOREIGN KEY expeditionTruck_FK_2;
|
||||||
|
ALTER TABLE `vn`.`expeditionTruck` ADD CONSTRAINT expeditionTruck_FK_2 FOREIGN KEY (roadmapFk) REFERENCES vn.roadmap(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -0,0 +1,6 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
|
VALUES
|
||||||
|
('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'),
|
||||||
|
('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'productionBoss'),
|
||||||
|
('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'),
|
||||||
|
('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'productionBoss');
|
|
@ -0,0 +1,5 @@
|
||||||
|
alter table `account`.`user`
|
||||||
|
add `twoFactor` ENUM ('email') null comment 'Two-factor auth type';
|
||||||
|
|
||||||
|
DELETE FROM `salix`.`ACL`
|
||||||
|
WHERE model = 'VnUser' AND property = 'changePassword';
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE `vn`.`item` ADD recycledPlastic INT NULL;
|
||||||
|
ALTER TABLE `vn`.`item` ADD nonRecycledPlastic INT NULL;
|
|
@ -0,0 +1,7 @@
|
||||||
|
UPDATE `vn`.`ticket` t
|
||||||
|
JOIN `vn`.`ticketObservation` o ON o.ticketFk = t.id
|
||||||
|
SET t.weight = cast(REPLACE(o.description, ',', '.') as decimal(10,2))
|
||||||
|
WHERE o.observationTypeFk = 6;
|
||||||
|
|
||||||
|
DELETE FROM `vn`.`ticketObservation` WHERE observationTypeFk = 6;
|
||||||
|
DELETE FROM `vn`.`observationType` WHERE id = 6;
|
File diff suppressed because one or more lines are too long
|
@ -77,7 +77,10 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `
|
||||||
ORDER BY id;
|
ORDER BY id;
|
||||||
|
|
||||||
INSERT INTO `account`.`account`(`id`)
|
INSERT INTO `account`.`account`(`id`)
|
||||||
SELECT id FROM `account`.`user`;
|
SELECT `u`.`id`
|
||||||
|
FROM `account`.`user` `u`
|
||||||
|
JOIN `account`.`role` `r` ON `u`.`role` = `r`.`id`
|
||||||
|
WHERE `r`.`name` <> 'customer';
|
||||||
|
|
||||||
INSERT INTO `vn`.`educationLevel` (`id`, `name`)
|
INSERT INTO `vn`.`educationLevel` (`id`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -144,17 +147,17 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
|
||||||
(3, 'GBP', 'Libra', 1),
|
(3, 'GBP', 'Libra', 1),
|
||||||
(4, 'JPY', 'Yen Japones', 1);
|
(4, 'JPY', 'Yen Japones', 1);
|
||||||
|
|
||||||
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`, `politicalCountryFk`)
|
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'España', 1, 'ES', 1, 24, 4, 0, 1, 1),
|
(1, 'España', 1, 'ES', 1, 24, 4, 0, 1),
|
||||||
(2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1, 2),
|
(2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1),
|
||||||
(3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1, 3),
|
(3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1),
|
||||||
(4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1, 4),
|
(4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1),
|
||||||
(5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1, 5),
|
(5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1),
|
||||||
(8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1, 8),
|
(8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1),
|
||||||
(13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2, 13),
|
(13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2),
|
||||||
(19,'Francia', 1, 'FR', 1, 27, 4, 0, 1, 19),
|
(19,'Francia', 1, 'FR', 1, 27, 4, 0, 1),
|
||||||
(30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2, 30);
|
(30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2);
|
||||||
|
|
||||||
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
|
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -185,13 +188,13 @@ INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAd
|
||||||
|
|
||||||
UPDATE `vn`.`sector` SET mainPrinterFk = 1 WHERE id = 1;
|
UPDATE `vn`.`sector` SET mainPrinterFk = 1 WHERE id = 1;
|
||||||
|
|
||||||
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
|
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
|
||||||
VALUES
|
VALUES
|
||||||
(1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106, NULL, NULL),
|
(1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106),
|
||||||
(1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107, NULL, NULL),
|
(1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107),
|
||||||
(1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108, 1, NULL),
|
(1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108),
|
||||||
(1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109, 1, NULL),
|
(1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109),
|
||||||
(1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110, 2, NULL);
|
(1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110);
|
||||||
|
|
||||||
INSERT INTO `vn`.`parking` (`id`, `column`, `row`, `sectorFk`, `code`, `pickingOrder`)
|
INSERT INTO `vn`.`parking` (`id`, `column`, `row`, `sectorFk`, `code`, `pickingOrder`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -382,9 +385,16 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
|
||||||
(1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
(1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
||||||
(1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH));
|
(1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH));
|
||||||
|
|
||||||
INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`)
|
INSERT INTO `vn`.`mandateType`(`id`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
(200, 10);
|
(1, 'B2B'),
|
||||||
|
(2, 'CORE'),
|
||||||
|
(3, 'LCR');
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`clientConfig`(`id`, `riskTolerance`, `maxCreditRows`, `maxPriceIncreasingRatio`, `riskScope`, `defaultPayMethodFk`, `defaultDueDay`, `defaultCredit`, `defaultIsTaxDataChecked`, `defaultHasCoreVnl`, `defaultMandateTypeFk`)
|
||||||
|
VALUES
|
||||||
|
(1, 200, 10, 0.25, 2, 4, 5, 300.00, 1, 1, 2);
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
|
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -550,10 +560,13 @@ INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `pr
|
||||||
|
|
||||||
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`)
|
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
|
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
|
||||||
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
|
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
|
||||||
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
|
(69, 'Packaging', 'Packaging nick', 4100000069, 1, '94935005K', 0, util.VN_CURDATE(), 1, 'supplier address 5', 'ASGARD', 3, 46600, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
|
||||||
(1381, 'Ornamentales', 'Ornamentales', 7185000440, 1, '03815934E', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
|
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
|
||||||
|
(567, 'Holland', 'Holland nick', 4000020567, 1, '14364089Z', 0, util.VN_CURDATE(), 1, 'supplier address 6', 'ASGARD', 3, 46600, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
|
||||||
|
(791, 'Bros SL', 'Bros nick', 5115000791, 1, '37718083S', 0, util.VN_CURDATE(), 1, 'supplier address 7', 'ASGARD', 3, 46600, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
|
||||||
|
(1381, 'Ornamentales', 'Ornamentales', 7185001381, 1, '07972486L', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
|
||||||
|
|
||||||
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
|
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -697,40 +710,40 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen
|
||||||
(6, NULL, 57, util.VN_CURDATE(), 5, 7, 'sixth route', 1.7, 60, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 3),
|
(6, NULL, 57, util.VN_CURDATE(), 5, 7, 'sixth route', 1.7, 60, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 3),
|
||||||
(7, NULL, 57, util.VN_CURDATE(), 6, 8, 'seventh route', 0, 70, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 5);
|
(7, NULL, 57, util.VN_CURDATE(), 6, 8, 'seventh route', 0, 70, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 5);
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`)
|
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`, `weight`)
|
||||||
VALUES
|
VALUES
|
||||||
(1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, 'T1111111', 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
(1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1),
|
||||||
(2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T1111111', 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
(2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2),
|
||||||
(3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T2222222', 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
|
(3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL),
|
||||||
(4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T3333333', 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH)),
|
(4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL),
|
||||||
(5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T4444444', 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH)),
|
(5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL),
|
||||||
(6 , 1, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
(6 , 1, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL),
|
||||||
(7 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(7 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(8 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Bat cave', 121, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(8 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Bat cave', 121, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(9 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(9 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(10, 1, 1, 5, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(10, 1, 1, 5, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(11, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(11, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(12, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(12, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(13, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(13, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(14, 1, 2, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, util.VN_CURDATE()),
|
(14, 1, 2, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(15, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(15, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(16, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(16, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(17, 1, 7, 2, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
|
(17, 1, 7, 2, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(18, 1, 4, 4, 4, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1108, 'Cerebro', 128, NULL, 0, 12, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +12 HOUR)),
|
(18, 1, 4, 4, 4, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1108, 'Cerebro', 128, NULL, 0, 12, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +12 HOUR), NULL),
|
||||||
(19, 1, 5, 5, NULL, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 1, NULL, 5, 1, util.VN_CURDATE()),
|
(19, 1, 5, 5, NULL, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 1, NULL, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(20, 1, 5, 5, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
|
(20, 1, 5, 5, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), NULL),
|
||||||
(21, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Holland', 102, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
|
(21, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Holland', 102, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), NULL),
|
||||||
(22, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Japan', 103, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
|
(22, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Japan', 103, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), NULL),
|
||||||
(23, NULL, 8, 1, 7, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'address 21', 121, NULL, 0, 5, 5, 1, util.VN_CURDATE()),
|
(23, NULL, 8, 1, 7, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'address 21', 121, NULL, 0, 5, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(24 ,NULL, 8, 1, 7, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, util.VN_CURDATE()),
|
(24 ,NULL, 8, 1, 7, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(25 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(25 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(26 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'An incredibly long alias for testing purposes', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(26 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'An incredibly long alias for testing purposes', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
|
||||||
(32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE());
|
(32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL);
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
|
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -821,12 +834,6 @@ INSERT INTO `vn`.`greuge`(`id`, `clientFk`, `description`, `amount`, `shipped`,
|
||||||
(11, 1101, 'some heritage charges', -15.99, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 5, 1),
|
(11, 1101, 'some heritage charges', -15.99, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 5, 1),
|
||||||
(12, 1101, 'some miscellaneous charges', 58.00, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 6, 1);
|
(12, 1101, 'some miscellaneous charges', 58.00, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 6, 1);
|
||||||
|
|
||||||
INSERT INTO `vn`.`mandateType`(`id`, `name`)
|
|
||||||
VALUES
|
|
||||||
(1, 'B2B'),
|
|
||||||
(2, 'CORE'),
|
|
||||||
(3, 'LCR');
|
|
||||||
|
|
||||||
INSERT INTO `vn`.`mandate`(`id`, `clientFk`, `companyFk`, `code`, `created`, `mandateTypeFk`)
|
INSERT INTO `vn`.`mandate`(`id`, `clientFk`, `companyFk`, `code`, `created`, `mandateTypeFk`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 1102, 442, '1-1', util.VN_CURDATE(), 2);
|
(1, 1102, 442, '1-1', util.VN_CURDATE(), 2);
|
||||||
|
@ -2572,6 +2579,26 @@ INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
|
||||||
|
|
||||||
CALL `vn`.`ticket_doRecalc`();
|
CALL `vn`.`ticket_doRecalc`();
|
||||||
|
|
||||||
|
UPDATE `vn`.`ticket`
|
||||||
|
SET refFk = 'T1111111'
|
||||||
|
WHERE id IN (1,2);
|
||||||
|
|
||||||
|
UPDATE `vn`.`ticket`
|
||||||
|
SET refFk = 'T2222222'
|
||||||
|
WHERE id = 3;
|
||||||
|
|
||||||
|
UPDATE `vn`.`ticket`
|
||||||
|
SET refFk = 'T3333333'
|
||||||
|
WHERE id = 4;
|
||||||
|
|
||||||
|
UPDATE `vn`.`ticket`
|
||||||
|
SET refFk = 'T4444444'
|
||||||
|
WHERE id = 5;
|
||||||
|
|
||||||
|
UPDATE `vn`.`ticket`
|
||||||
|
SET refFk = 'A1111111'
|
||||||
|
WHERE id = 6;
|
||||||
|
|
||||||
INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
|
INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 1, 1),
|
(1, 1, 1),
|
||||||
|
@ -2579,9 +2606,18 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
|
||||||
(3, 6, 5),
|
(3, 6, 5),
|
||||||
(4, 7, 1);
|
(4, 7, 1);
|
||||||
|
|
||||||
INSERT INTO `vn`.`expeditionTruck` (`id`, `ETD`, `description`)
|
INSERT INTO `vn`.`roadmap` (`id`, `name`, `tractorPlate`, `trailerPlate`, `phone`, `supplierFk`, `etd`, `observations`, `userFk`, `price`, `driverName`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet');
|
(1, 'val-algemesi', 'RE-001', 'PO-001', '111111111', 1, util.VN_NOW(), 'this is test observation', 1, 15, 'Batman'),
|
||||||
|
(2, 'alg-valencia', 'RE-002', 'PO-002', '111111111', 1, util.VN_NOW(), 'test observation', 1, 20, 'Robin'),
|
||||||
|
(3, 'alz-algemesi', 'RE-003', 'PO-003', '222222222', 2, DATE_ADD(util.VN_NOW(), INTERVAL 2 DAY), 'observations...', 2, 25, 'Driverman');
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`expeditionTruck` (`id`, `roadmapFk`, `warehouseFk`, `eta`, `description`, `userFk`)
|
||||||
|
VALUES
|
||||||
|
(1, 1, 1, DATE_ADD(util.VN_NOW(), INTERVAL 1 DAY), 'Best truck in fleet', 1),
|
||||||
|
(2, 1, 2, DATE_ADD(util.VN_NOW(), INTERVAL '1 2' DAY_HOUR), 'Second truck in fleet', 1),
|
||||||
|
(3, 1, 3, DATE_ADD(util.VN_NOW(), INTERVAL '1 4' DAY_HOUR), 'Third truck in fleet', 1),
|
||||||
|
(4, 2, 1, DATE_ADD(util.VN_NOW(), INTERVAL 3 DAY), 'Truck red', 1);
|
||||||
|
|
||||||
INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`)
|
INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -2825,8 +2861,8 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
|
||||||
|
|
||||||
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
|
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
|
||||||
VALUES
|
VALUES
|
||||||
('lilium', 'dev', 'http://localhost:9000/#/'),
|
('lilium', 'development', 'http://localhost:9000/#/'),
|
||||||
('salix', 'dev', 'http://localhost:5000/#!/');
|
('salix', 'development', 'http://localhost:5000/#!/');
|
||||||
|
|
||||||
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
|
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -2842,7 +2878,8 @@ INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`)
|
INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 12);
|
(1, 12),
|
||||||
|
(8, 10);
|
||||||
|
|
||||||
INSERT INTO `vn`.`deviceProductionModels` (`code`)
|
INSERT INTO `vn`.`deviceProductionModels` (`code`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -312,8 +312,8 @@ export default {
|
||||||
clientDefaulter: {
|
clientDefaulter: {
|
||||||
anyClient: 'vn-client-defaulter tbody > tr',
|
anyClient: 'vn-client-defaulter tbody > tr',
|
||||||
firstClientName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(2) > span',
|
firstClientName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(2) > span',
|
||||||
firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(3) > span',
|
firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(4) > span',
|
||||||
firstObservation: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]',
|
firstObservation: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(9) > vn-textarea[ng-model="defaulter.observation"]',
|
||||||
allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check',
|
allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check',
|
||||||
addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]',
|
addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]',
|
||||||
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',
|
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',
|
||||||
|
@ -479,9 +479,6 @@ export default {
|
||||||
fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance > span',
|
fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance > span',
|
||||||
firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance'
|
firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance'
|
||||||
},
|
},
|
||||||
itemLog: {
|
|
||||||
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
|
|
||||||
},
|
|
||||||
ticketSummary: {
|
ticketSummary: {
|
||||||
header: 'vn-ticket-summary > vn-card > h5',
|
header: 'vn-ticket-summary > vn-card > h5',
|
||||||
state: 'vn-ticket-summary vn-label-value[label="State"] > section > span',
|
state: 'vn-ticket-summary vn-label-value[label="State"] > section > span',
|
||||||
|
@ -550,6 +547,7 @@ export default {
|
||||||
moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]',
|
moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]',
|
||||||
moreMenuRegenerateInvoice: '.vn-menu [name="regenerateInvoice"]',
|
moreMenuRegenerateInvoice: '.vn-menu [name="regenerateInvoice"]',
|
||||||
moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]',
|
moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]',
|
||||||
|
moreMenuSMSOptions: '.vn-menu [name="smsOptions"]',
|
||||||
moreMenuPaymentSMS: '.vn-menu [name="sendPaymentSms"]',
|
moreMenuPaymentSMS: '.vn-menu [name="sendPaymentSms"]',
|
||||||
moreMenuSendImportSms: '.vn-menu [name="sendImportSms"]',
|
moreMenuSendImportSms: '.vn-menu [name="sendImportSms"]',
|
||||||
SMStext: 'textarea[name="message"]',
|
SMStext: 'textarea[name="message"]',
|
||||||
|
@ -667,15 +665,6 @@ export default {
|
||||||
thirdRemoveRequestButton: 'vn-ticket-request-index vn-tr:nth-child(3) vn-icon[icon="delete"]',
|
thirdRemoveRequestButton: 'vn-ticket-request-index vn-tr:nth-child(3) vn-icon[icon="delete"]',
|
||||||
thirdRequestQuantity: 'vn-ticket-request-index vn-table vn-tr:nth-child(3) > vn-td:nth-child(6) vn-input-number',
|
thirdRequestQuantity: 'vn-ticket-request-index vn-table vn-tr:nth-child(3) > vn-td:nth-child(6) vn-input-number',
|
||||||
saveButton: 'vn-ticket-request-create button[type=submit]',
|
saveButton: 'vn-ticket-request-create button[type=submit]',
|
||||||
|
|
||||||
},
|
|
||||||
ticketLog: {
|
|
||||||
firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)',
|
|
||||||
logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]',
|
|
||||||
user: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(2)',
|
|
||||||
action: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(4)',
|
|
||||||
changes: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr table tr:nth-child(2) td.after',
|
|
||||||
id: 'vn-ticket-log vn-tr:nth-child(1) table tr:nth-child(1) td.before'
|
|
||||||
},
|
},
|
||||||
ticketService: {
|
ticketService: {
|
||||||
addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button',
|
addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button',
|
||||||
|
@ -906,6 +895,18 @@ export default {
|
||||||
extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span',
|
extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span',
|
||||||
|
|
||||||
},
|
},
|
||||||
|
department: {
|
||||||
|
firstDepartment: 'vn-worker-department-index vn-card > vn-treeview vn-treeview-childs vn-treeview-childs vn-treeview-childs a'
|
||||||
|
},
|
||||||
|
departmentSummary: {
|
||||||
|
header: 'vn-worker-department-summary h5',
|
||||||
|
name: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(1) > section > span',
|
||||||
|
code: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(2) > section > span',
|
||||||
|
chat: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(3) > section > span',
|
||||||
|
bossDepartment: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(4) > section > span',
|
||||||
|
email: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(5) > section > span',
|
||||||
|
clientFk: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(6) > section > span',
|
||||||
|
},
|
||||||
workerBasicData: {
|
workerBasicData: {
|
||||||
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
|
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
|
||||||
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
|
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
|
||||||
|
@ -913,6 +914,13 @@ export default {
|
||||||
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
|
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
|
||||||
saveButton: 'vn-worker-basic-data button[type=submit]'
|
saveButton: 'vn-worker-basic-data button[type=submit]'
|
||||||
},
|
},
|
||||||
|
departmentBasicData: {
|
||||||
|
Name: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.name"]',
|
||||||
|
Code: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.code"]',
|
||||||
|
Chat: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.chat"]',
|
||||||
|
Email: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.notificationEmail"]',
|
||||||
|
saveButton: 'vn-worker-department-basic-data button[type=submit]'
|
||||||
|
},
|
||||||
workerNotes: {
|
workerNotes: {
|
||||||
addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]',
|
addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]',
|
||||||
note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]',
|
note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]',
|
||||||
|
|
|
@ -16,6 +16,7 @@ describe('ChangePassword path', async() => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const badPassword = 'badpass';
|
||||||
const oldPassword = 'nightmare';
|
const oldPassword = 'nightmare';
|
||||||
const newPassword = 'newPass.1234';
|
const newPassword = 'newPass.1234';
|
||||||
describe('Bad login', async() => {
|
describe('Bad login', async() => {
|
||||||
|
@ -37,13 +38,22 @@ describe('ChangePassword path', async() => {
|
||||||
expect(message.text).toContain('Invalid current password');
|
expect(message.text).toContain('Invalid current password');
|
||||||
|
|
||||||
// Bad attempt: password not meet requirements
|
// Bad attempt: password not meet requirements
|
||||||
|
message = await page.sendForm($.form, {
|
||||||
|
oldPassword: oldPassword,
|
||||||
|
newPassword: badPassword,
|
||||||
|
repeatPassword: badPassword
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(message.text).toContain('Password does not meet requirements');
|
||||||
|
|
||||||
|
// Bad attempt: same password
|
||||||
message = await page.sendForm($.form, {
|
message = await page.sendForm($.form, {
|
||||||
oldPassword: oldPassword,
|
oldPassword: oldPassword,
|
||||||
newPassword: oldPassword,
|
newPassword: oldPassword,
|
||||||
repeatPassword: oldPassword
|
repeatPassword: oldPassword
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(message.text).toContain('Password does not meet requirements');
|
expect(message.text).toContain('You can not use the same password');
|
||||||
|
|
||||||
// Correct attempt: change password
|
// Correct attempt: change password
|
||||||
message = await page.sendForm($.form, {
|
message = await page.sendForm($.form, {
|
||||||
|
|
|
@ -5,8 +5,8 @@ const $ = {
|
||||||
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
|
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
|
||||||
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
|
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
|
||||||
saveButton: 'vn-client-web-access button[type=submit]',
|
saveButton: 'vn-client-web-access button[type=submit]',
|
||||||
nameValue: 'vn-client-log .change:nth-child(1) .basic-json:nth-child(2) vn-json-value',
|
nameValue: 'vn-client-log .changes-log:nth-child(2) .basic-json:nth-child(2) vn-json-value',
|
||||||
activeValue: 'vn-client-log .change:nth-child(2) .basic-json:nth-child(1) vn-json-value'
|
activeValue: 'vn-client-log .changes-log:nth-child(3) .basic-json:nth-child(1) vn-json-value'
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Client web access path', () => {
|
describe('Client web access path', () => {
|
||||||
|
@ -39,9 +39,9 @@ describe('Client web access path', () => {
|
||||||
const userName = await page.getValue($.userName);
|
const userName = await page.getValue($.userName);
|
||||||
const email = await page.getValue($.email);
|
const email = await page.getValue($.email);
|
||||||
|
|
||||||
await page.accessToSection('client.card.log');
|
// await page.accessToSection('client.card.log');
|
||||||
const logName = await page.innerText($.nameValue);
|
// const logName = await page.innerText($.nameValue);
|
||||||
const logActive = await page.innerText($.activeValue);
|
// const logActive = await page.innerText($.activeValue);
|
||||||
|
|
||||||
expect(enableMessage.type).toBe('success');
|
expect(enableMessage.type).toBe('success');
|
||||||
expect(modifyMessage.type).toBe('success');
|
expect(modifyMessage.type).toBe('success');
|
||||||
|
@ -50,7 +50,7 @@ describe('Client web access path', () => {
|
||||||
expect(userName).toEqual('Legion');
|
expect(userName).toEqual('Legion');
|
||||||
expect(email).toEqual('legion@marvel.com');
|
expect(email).toEqual('legion@marvel.com');
|
||||||
|
|
||||||
expect(logName).toEqual('Legion');
|
// expect(logName).toEqual('Legion');
|
||||||
expect(logActive).toEqual('✗');
|
// expect(logActive).toEqual('✗');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import selectors from '../../../helpers/selectors.js';
|
||||||
|
import getBrowser from '../../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('department summary path', () => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
await page.loginAndModule('hr', 'worker');
|
||||||
|
await page.accessToSection('worker.department');
|
||||||
|
await page.doSearch('INFORMATICA');
|
||||||
|
await page.click(selectors.department.firstDepartment);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reach the employee summary section and check all properties', async() => {
|
||||||
|
expect(await page.waitToGetProperty(selectors.departmentSummary.header, 'innerText')).toEqual('INFORMATICA');
|
||||||
|
expect(await page.getProperty(selectors.departmentSummary.name, 'innerText')).toEqual('INFORMATICA');
|
||||||
|
expect(await page.getProperty(selectors.departmentSummary.code, 'innerText')).toEqual('it');
|
||||||
|
expect(await page.getProperty(selectors.departmentSummary.chat, 'innerText')).toEqual('informatica-cau');
|
||||||
|
expect(await page.getProperty(selectors.departmentSummary.bossDepartment, 'innerText')).toEqual('');
|
||||||
|
expect(await page.getProperty(selectors.departmentSummary.email, 'innerText')).toEqual('-');
|
||||||
|
expect(await page.getProperty(selectors.departmentSummary.clientFk, 'innerText')).toEqual('-');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
import getBrowser from '../../../helpers/puppeteer';
|
||||||
|
import selectors from '../../../helpers/selectors.js';
|
||||||
|
|
||||||
|
const $ = {
|
||||||
|
form: 'vn-worker-department-basic-data form',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('department summary path', () => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
await page.loginAndModule('hr', 'worker');
|
||||||
|
await page.accessToSection('worker.department');
|
||||||
|
await page.doSearch('INFORMATICA');
|
||||||
|
await page.click(selectors.department.firstDepartment);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async() => {
|
||||||
|
await page.accessToSection('worker.department.card.basicData');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should edit the department basic data and confirm the department data was edited`, async() => {
|
||||||
|
const values = {
|
||||||
|
Name: 'Informatica',
|
||||||
|
Code: 'IT',
|
||||||
|
Chat: 'informatica-cau',
|
||||||
|
Email: 'it@verdnatura.es',
|
||||||
|
};
|
||||||
|
|
||||||
|
await page.fillForm($.form, values);
|
||||||
|
const formValues = await page.fetchForm($.form, Object.keys(values));
|
||||||
|
const message = await page.sendForm($.form, values);
|
||||||
|
|
||||||
|
expect(message.isSuccess).toBeTrue();
|
||||||
|
expect(formValues).toEqual(values);
|
||||||
|
});
|
||||||
|
});
|
|
@ -19,7 +19,6 @@ describe('Item edit tax path', () => {
|
||||||
it(`should add the item tax to all countries`, async() => {
|
it(`should add the item tax to all countries`, async() => {
|
||||||
await page.autocompleteSearch(selectors.itemTax.firstClass, 'General VAT');
|
await page.autocompleteSearch(selectors.itemTax.firstClass, 'General VAT');
|
||||||
await page.autocompleteSearch(selectors.itemTax.secondClass, 'General VAT');
|
await page.autocompleteSearch(selectors.itemTax.secondClass, 'General VAT');
|
||||||
await page.autocompleteSearch(selectors.itemTax.thirdClass, 'General VAT');
|
|
||||||
await page.waitToClick(selectors.itemTax.submitTaxButton);
|
await page.waitToClick(selectors.itemTax.submitTaxButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
@ -40,13 +39,6 @@ describe('Item edit tax path', () => {
|
||||||
expect(secondVatType).toEqual('General VAT');
|
expect(secondVatType).toEqual('General VAT');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should confirm the third item tax class was edited`, async() => {
|
|
||||||
const thirdVatType = await page
|
|
||||||
.waitToGetProperty(selectors.itemTax.thirdClass, 'value');
|
|
||||||
|
|
||||||
expect(thirdVatType).toEqual('General VAT');
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should edit the first class without saving the form`, async() => {
|
it(`should edit the first class without saving the form`, async() => {
|
||||||
await page.autocompleteSearch(selectors.itemTax.firstClass, 'Reduced VAT');
|
await page.autocompleteSearch(selectors.itemTax.firstClass, 'Reduced VAT');
|
||||||
const firstVatType = await page.waitToGetProperty(selectors.itemTax.firstClass, 'value');
|
const firstVatType = await page.waitToGetProperty(selectors.itemTax.firstClass, 'value');
|
||||||
|
|
|
@ -124,6 +124,7 @@ describe('Ticket descriptor path', () => {
|
||||||
describe('SMS', () => {
|
describe('SMS', () => {
|
||||||
it('should send the payment SMS using the descriptor menu', async() => {
|
it('should send the payment SMS using the descriptor menu', async() => {
|
||||||
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
||||||
|
await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
|
||||||
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
|
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
|
||||||
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
|
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
|
||||||
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 128);
|
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 128);
|
||||||
|
@ -134,8 +135,7 @@ describe('Ticket descriptor path', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send the import SMS using the descriptor menu', async() => {
|
it('should send the import SMS using the descriptor menu', async() => {
|
||||||
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
|
await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
|
||||||
await page.waitForContentLoaded();
|
|
||||||
await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms);
|
await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms);
|
||||||
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
|
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
|
||||||
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 144);
|
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 144);
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('Travel descriptor path', () => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
await page.loginAndModule('buyer', 'travel');
|
await page.loginAndModule('buyer', 'travel');
|
||||||
await page.write(selectors.travelIndex.generalSearchFilter, '1');
|
await page.write(selectors.travelIndex.generalSearchFilter, '3');
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForState('travel.card.summary');
|
await page.waitForState('travel.card.summary');
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@ describe('Travel descriptor path', () => {
|
||||||
await page.waitForState('travel.index');
|
await page.waitForState('travel.index');
|
||||||
const result = await page.countElement(selectors.travelIndex.anySearchResult);
|
const result = await page.countElement(selectors.travelIndex.anySearchResult);
|
||||||
|
|
||||||
expect(result).toBeGreaterThanOrEqual(7);
|
expect(result).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate to the first search result', async() => {
|
it('should navigate to the first search result', async() => {
|
||||||
|
|
|
@ -81,6 +81,6 @@ describe('Account Role create and basic data path', () => {
|
||||||
await page.accessToSection('account.role.card.inherited');
|
await page.accessToSection('account.role.card.inherited');
|
||||||
const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult);
|
const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult);
|
||||||
|
|
||||||
expect(rolesCount).toEqual(6);
|
expect(rolesCount).toEqual(7);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,8 @@ vn-avatar {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 36px;
|
height: 38px;
|
||||||
width: 36px;
|
width: 38px;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
background-color: $color-main;
|
background-color: $color-main;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default class Auth {
|
||||||
initialize() {
|
initialize() {
|
||||||
let criteria = {
|
let criteria = {
|
||||||
to: state => {
|
to: state => {
|
||||||
const outLayout = ['login', 'recover-password', 'reset-password', 'change-password'];
|
const outLayout = ['login', 'recover-password', 'reset-password', 'change-password', 'validate-email'];
|
||||||
return !outLayout.some(ol => ol == state.name);
|
return !outLayout.some(ol => ol == state.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -59,12 +59,31 @@ export default class Auth {
|
||||||
password: password || undefined
|
password: password || undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.$http.post('VnUsers/signIn', params)
|
const now = new Date();
|
||||||
.then(json => this.onLoginOk(json, remember));
|
return this.$http.post('VnUsers/sign-in', params).then(
|
||||||
|
json => this.onLoginOk(json, now, remember));
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoginOk(json, remember) {
|
validateCode(user, password, code, remember) {
|
||||||
this.vnToken.set(json.data.token, json.data.created, remember);
|
if (!user) {
|
||||||
|
let err = new UserError('Please enter your username');
|
||||||
|
err.code = 'EmptyLogin';
|
||||||
|
return this.$q.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
user: user,
|
||||||
|
password: password || undefined,
|
||||||
|
code: code
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
return this.$http.post('VnUsers/validate-auth', params)
|
||||||
|
.then(json => this.onLoginOk(json, now, remember));
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginOk(json, now, remember) {
|
||||||
|
this.vnToken.set(json.data.token, now, json.data.ttl, remember);
|
||||||
|
|
||||||
return this.loadAcls().then(() => {
|
return this.loadAcls().then(() => {
|
||||||
let continueHash = this.$state.params.continue;
|
let continueHash = this.$state.params.continue;
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
import HttpError from 'core/lib/http-error';
|
import HttpError from 'core/lib/http-error';
|
||||||
|
|
||||||
interceptor.$inject = ['$q', 'vnApp', 'vnToken', '$translate'];
|
interceptor.$inject = ['$q', 'vnApp', '$translate'];
|
||||||
function interceptor($q, vnApp, vnToken, $translate) {
|
function interceptor($q, vnApp, $translate) {
|
||||||
let apiPath = 'api/';
|
let apiPath = 'api/';
|
||||||
|
let token = sessionStorage.getItem('vnToken')
|
||||||
|
?? localStorage.getItem('vnToken');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
setToken(newToken) {
|
||||||
|
token = newToken;
|
||||||
|
},
|
||||||
setApiPath(path) {
|
setApiPath(path) {
|
||||||
apiPath = path;
|
apiPath = path;
|
||||||
},
|
},
|
||||||
|
@ -14,8 +19,8 @@ function interceptor($q, vnApp, vnToken, $translate) {
|
||||||
|
|
||||||
if (config.url.charAt(0) !== '/' && apiPath)
|
if (config.url.charAt(0) !== '/' && apiPath)
|
||||||
config.url = `${apiPath}${config.url}`;
|
config.url = `${apiPath}${config.url}`;
|
||||||
if (vnToken.token)
|
if (token)
|
||||||
config.headers.Authorization = vnToken.token;
|
config.headers.Authorization = token;
|
||||||
if ($translate.use())
|
if ($translate.use())
|
||||||
config.headers['Accept-Language'] = $translate.use();
|
config.headers['Accept-Language'] = $translate.use();
|
||||||
if (config.filter) {
|
if (config.filter) {
|
||||||
|
|
|
@ -6,37 +6,116 @@ 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() {
|
constructor(vnInterceptor, $http, $rootScope) {
|
||||||
try {
|
Object.assign(this, {
|
||||||
this.token = sessionStorage.getItem('vnToken');
|
vnInterceptor,
|
||||||
this.created = sessionStorage.getItem('vnTokenCreated');
|
$http,
|
||||||
if (!this.token) {
|
$rootScope
|
||||||
this.token = localStorage.getItem('vnToken');
|
});
|
||||||
this.created = localStorage.getItem('vnTokenCreated');
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
set(token, created, remember) {
|
|
||||||
this.unset();
|
|
||||||
try {
|
|
||||||
if (remember) {
|
|
||||||
localStorage.setItem('vnToken', token);
|
|
||||||
localStorage.setItem('vnTokenCreated', created);
|
|
||||||
} else {
|
|
||||||
sessionStorage.setItem('vnToken', token);
|
|
||||||
sessionStorage.setItem('vnTokenCreated', created);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
this.token = token;
|
try {
|
||||||
this.created = created;
|
this.getStorage(sessionStorage);
|
||||||
|
this.remember = true;
|
||||||
|
|
||||||
|
if (!this.token) {
|
||||||
|
this.getStorage(localStorage);
|
||||||
|
this.remember = false;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set(token, created, ttl, remember) {
|
||||||
|
this.unset();
|
||||||
|
|
||||||
|
Object.assign(this, {
|
||||||
|
token,
|
||||||
|
created,
|
||||||
|
ttl,
|
||||||
|
remember
|
||||||
|
});
|
||||||
|
this.vnInterceptor.setToken(token);
|
||||||
|
try {
|
||||||
|
if (remember)
|
||||||
|
this.setStorage(localStorage, token, created, ttl);
|
||||||
|
else
|
||||||
|
this.setStorage(sessionStorage, token, created, ttl);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unset() {
|
unset() {
|
||||||
localStorage.removeItem('vnToken');
|
|
||||||
sessionStorage.removeItem('vnToken');
|
|
||||||
this.token = null;
|
this.token = null;
|
||||||
this.created = null;
|
this.created = null;
|
||||||
|
this.ttl = null;
|
||||||
|
this.remember = null;
|
||||||
|
this.vnInterceptor.setToken(null);
|
||||||
|
|
||||||
|
this.removeStorage(localStorage);
|
||||||
|
this.removeStorage(sessionStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStorage(storage) {
|
||||||
|
this.token = storage.getItem('vnToken');
|
||||||
|
if (!this.token) return;
|
||||||
|
const created = storage.getItem('vnTokenCreated');
|
||||||
|
this.created = created && new Date(created);
|
||||||
|
this.renewPeriod = storage.getItem('vnTokenRenewPeriod');
|
||||||
|
}
|
||||||
|
|
||||||
|
setStorage(storage, token, created, ttl) {
|
||||||
|
storage.setItem('vnToken', token);
|
||||||
|
storage.setItem('vnTokenCreated', created.toJSON());
|
||||||
|
storage.setItem('vnTokenTtl', ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeStorage(storage) {
|
||||||
|
storage.removeItem('vnToken');
|
||||||
|
storage.removeItem('vnTokenCreated');
|
||||||
|
storage.removeItem('vnTokenTtl');
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchConfig() {
|
||||||
|
const filter = {fields: ['renewInterval', 'renewPeriod']};
|
||||||
|
this.$http.get('AccessTokenConfigs/findOne', {filter}).then(res => {
|
||||||
|
const data = res.data;
|
||||||
|
if (!data) return;
|
||||||
|
this.renewPeriod = data.renewPeriod;
|
||||||
|
this.stopRenewer();
|
||||||
|
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkValidity() {
|
||||||
|
if (this.checking || !this.created) return;
|
||||||
|
this.checking = true;
|
||||||
|
const renewPeriod = Math.min(this.ttl, this.renewPeriod) * 1000;
|
||||||
|
const maxDate = this.created.getTime() + renewPeriod;
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (now.getTime() <= maxDate) {
|
||||||
|
this.checking = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$http.post('VnUsers/renewToken')
|
||||||
|
.then(res => {
|
||||||
|
const token = res.data;
|
||||||
|
this.set(token.id, now, token.ttl, this.remember);
|
||||||
|
})
|
||||||
|
.catch(res => {
|
||||||
|
if (res.data?.error?.code !== 'periodNotExceeded')
|
||||||
|
throw res;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.checking = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRenewer() {
|
||||||
|
clearInterval(this.inservalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Token.$inject = ['vnInterceptor', '$http', '$rootScope'];
|
||||||
|
|
||||||
ngModule.service('vnToken', Token);
|
ngModule.service('vnToken', Token);
|
||||||
|
|
|
@ -1,402 +1,411 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'salixfont';
|
font-family: 'salixfont';
|
||||||
src:
|
src:
|
||||||
url('./salixfont.ttf?wtrl3') format('truetype'),
|
url('./salixfont.ttf?wtrl3') format('truetype'),
|
||||||
url('./salixfont.woff?wtrl3') format('woff'),
|
url('./salixfont.woff?wtrl3') format('woff'),
|
||||||
url('./salixfont.svg?wtrl3#salixfont') format('svg');
|
url('./salixfont.svg?wtrl3#salixfont') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class^="icon-"], [class*=" icon-"] {
|
[class^="icon-"], [class*=" icon-"] {
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
font-family: 'salixfont' !important;
|
font-family: 'salixfont' !important;
|
||||||
speak: none;
|
speak: none;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
/* Better Font Rendering =========== */
|
/* Better Font Rendering =========== */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-agency-term:before {
|
.icon-trailer:before {
|
||||||
content: "\e950";
|
content: "\e967";
|
||||||
}
|
}
|
||||||
.icon-defaulter:before {
|
.icon-grafana:before {
|
||||||
content: "\e94b";
|
content: "\e965";
|
||||||
}
|
}
|
||||||
.icon-100:before {
|
.icon-trolley:before {
|
||||||
content: "\e95a";
|
content: "\e95c";
|
||||||
}
|
}
|
||||||
.icon-clientUnpaid:before {
|
.icon-agency-term:before {
|
||||||
content: "\e95b";
|
content: "\e950";
|
||||||
}
|
}
|
||||||
.icon-history:before {
|
.icon-defaulter:before {
|
||||||
content: "\e968";
|
content: "\e94b";
|
||||||
}
|
}
|
||||||
.icon-Person:before {
|
.icon-100:before {
|
||||||
content: "\e901";
|
content: "\e95a";
|
||||||
}
|
}
|
||||||
.icon-accessory:before {
|
.icon-clientUnpaid:before {
|
||||||
content: "\e90a";
|
content: "\e95b";
|
||||||
}
|
}
|
||||||
.icon-account:before {
|
.icon-history:before {
|
||||||
content: "\e92a";
|
content: "\e968";
|
||||||
}
|
}
|
||||||
.icon-actions:before {
|
.icon-Person:before {
|
||||||
content: "\e960";
|
content: "\e901";
|
||||||
}
|
}
|
||||||
.icon-addperson:before {
|
.icon-accessory:before {
|
||||||
content: "\e90e";
|
content: "\e90a";
|
||||||
}
|
}
|
||||||
.icon-agency:before {
|
.icon-account:before {
|
||||||
content: "\e938";
|
content: "\e92a";
|
||||||
}
|
}
|
||||||
.icon-albaran:before {
|
.icon-actions:before {
|
||||||
content: "\e94d";
|
content: "\e960";
|
||||||
}
|
}
|
||||||
.icon-anonymous:before {
|
.icon-addperson:before {
|
||||||
content: "\e930";
|
content: "\e90e";
|
||||||
}
|
}
|
||||||
.icon-apps:before {
|
.icon-agency:before {
|
||||||
content: "\e951";
|
content: "\e938";
|
||||||
}
|
}
|
||||||
.icon-artificial:before {
|
.icon-albaran:before {
|
||||||
content: "\e90b";
|
content: "\e94d";
|
||||||
}
|
}
|
||||||
.icon-attach:before {
|
.icon-anonymous:before {
|
||||||
content: "\e92e";
|
content: "\e930";
|
||||||
}
|
}
|
||||||
.icon-barcode:before {
|
.icon-apps:before {
|
||||||
content: "\e971";
|
content: "\e951";
|
||||||
}
|
}
|
||||||
.icon-basket:before {
|
.icon-artificial:before {
|
||||||
content: "\e914";
|
content: "\e90b";
|
||||||
}
|
}
|
||||||
.icon-basketadd:before {
|
.icon-attach:before {
|
||||||
content: "\e913";
|
content: "\e92e";
|
||||||
}
|
}
|
||||||
.icon-bin:before {
|
.icon-barcode:before {
|
||||||
content: "\e96f";
|
content: "\e971";
|
||||||
}
|
}
|
||||||
.icon-botanical:before {
|
.icon-basket:before {
|
||||||
content: "\e972";
|
content: "\e914";
|
||||||
}
|
}
|
||||||
.icon-bucket:before {
|
.icon-basketadd:before {
|
||||||
content: "\e97a";
|
content: "\e913";
|
||||||
}
|
}
|
||||||
.icon-buscaman:before {
|
.icon-bin:before {
|
||||||
content: "\e93b";
|
content: "\e96f";
|
||||||
}
|
}
|
||||||
.icon-buyrequest:before {
|
.icon-botanical:before {
|
||||||
content: "\e932";
|
content: "\e972";
|
||||||
}
|
}
|
||||||
.icon-calc_volum .path1:before {
|
.icon-bucket:before {
|
||||||
content: "\e915";
|
content: "\e97a";
|
||||||
}
|
}
|
||||||
.icon-calc_volum .path2:before {
|
.icon-buscaman:before {
|
||||||
content: "\e916";
|
content: "\e93b";
|
||||||
margin-left: -1em;
|
}
|
||||||
}
|
.icon-buyrequest:before {
|
||||||
.icon-calc_volum .path3:before {
|
content: "\e932";
|
||||||
content: "\e917";
|
}
|
||||||
margin-left: -1em;
|
.icon-calc_volum .path1:before {
|
||||||
}
|
content: "\e915";
|
||||||
.icon-calc_volum .path4:before {
|
}
|
||||||
content: "\e918";
|
.icon-calc_volum .path2:before {
|
||||||
margin-left: -1em;
|
content: "\e916";
|
||||||
}
|
margin-left: -1em;
|
||||||
.icon-calc_volum .path5:before {
|
}
|
||||||
content: "\e919";
|
.icon-calc_volum .path3:before {
|
||||||
margin-left: -1em;
|
content: "\e917";
|
||||||
}
|
margin-left: -1em;
|
||||||
.icon-calc_volum .path6:before {
|
}
|
||||||
content: "\e91a";
|
.icon-calc_volum .path4:before {
|
||||||
margin-left: -1em;
|
content: "\e918";
|
||||||
}
|
margin-left: -1em;
|
||||||
.icon-calendar:before {
|
}
|
||||||
content: "\e93d";
|
.icon-calc_volum .path5:before {
|
||||||
}
|
content: "\e919";
|
||||||
.icon-catalog:before {
|
margin-left: -1em;
|
||||||
content: "\e937";
|
}
|
||||||
}
|
.icon-calc_volum .path6:before {
|
||||||
.icon-claims:before {
|
content: "\e91a";
|
||||||
content: "\e963";
|
margin-left: -1em;
|
||||||
}
|
}
|
||||||
.icon-client:before {
|
.icon-calendar:before {
|
||||||
content: "\e928";
|
content: "\e93d";
|
||||||
}
|
}
|
||||||
.icon-clone:before {
|
.icon-catalog:before {
|
||||||
content: "\e973";
|
content: "\e937";
|
||||||
}
|
}
|
||||||
.icon-columnadd:before {
|
.icon-claims:before {
|
||||||
content: "\e954";
|
content: "\e963";
|
||||||
}
|
}
|
||||||
.icon-columndelete:before {
|
.icon-client:before {
|
||||||
content: "\e953";
|
content: "\e928";
|
||||||
}
|
}
|
||||||
.icon-components:before {
|
.icon-clone:before {
|
||||||
content: "\e946";
|
content: "\e973";
|
||||||
}
|
}
|
||||||
.icon-consignatarios:before {
|
.icon-columnadd:before {
|
||||||
content: "\e93f";
|
content: "\e954";
|
||||||
}
|
}
|
||||||
.icon-control:before {
|
.icon-columndelete:before {
|
||||||
content: "\e949";
|
content: "\e953";
|
||||||
}
|
}
|
||||||
.icon-credit:before {
|
.icon-components:before {
|
||||||
content: "\e927";
|
content: "\e946";
|
||||||
}
|
}
|
||||||
.icon-deletedTicket:before {
|
.icon-consignatarios:before {
|
||||||
content: "\e935";
|
content: "\e93f";
|
||||||
}
|
}
|
||||||
.icon-deleteline:before {
|
.icon-control:before {
|
||||||
content: "\e955";
|
content: "\e949";
|
||||||
}
|
}
|
||||||
.icon-delivery:before {
|
.icon-credit:before {
|
||||||
content: "\e939";
|
content: "\e927";
|
||||||
}
|
}
|
||||||
.icon-deliveryprices:before {
|
.icon-deletedTicket:before {
|
||||||
content: "\e91c";
|
content: "\e935";
|
||||||
}
|
}
|
||||||
.icon-details:before {
|
.icon-deleteline:before {
|
||||||
content: "\e961";
|
content: "\e955";
|
||||||
}
|
}
|
||||||
.icon-dfiscales:before {
|
.icon-delivery:before {
|
||||||
content: "\e984";
|
content: "\e939";
|
||||||
}
|
}
|
||||||
.icon-disabled:before {
|
.icon-deliveryprices:before {
|
||||||
content: "\e921";
|
content: "\e91c";
|
||||||
}
|
}
|
||||||
.icon-doc:before {
|
.icon-details:before {
|
||||||
content: "\e977";
|
content: "\e961";
|
||||||
}
|
}
|
||||||
.icon-entry:before {
|
.icon-dfiscales:before {
|
||||||
content: "\e934";
|
content: "\e984";
|
||||||
}
|
}
|
||||||
.icon-exit:before {
|
.icon-disabled:before {
|
||||||
content: "\e92f";
|
content: "\e921";
|
||||||
}
|
}
|
||||||
.icon-eye:before {
|
.icon-doc:before {
|
||||||
content: "\e976";
|
content: "\e977";
|
||||||
}
|
}
|
||||||
.icon-fixedPrice:before {
|
.icon-entry:before {
|
||||||
content: "\e90d";
|
content: "\e934";
|
||||||
}
|
}
|
||||||
.icon-flower:before {
|
.icon-exit:before {
|
||||||
content: "\e90c";
|
content: "\e92f";
|
||||||
}
|
}
|
||||||
.icon-frozen:before {
|
.icon-eye:before {
|
||||||
content: "\e900";
|
content: "\e976";
|
||||||
}
|
}
|
||||||
.icon-fruit:before {
|
.icon-fixedPrice:before {
|
||||||
content: "\e903";
|
content: "\e90d";
|
||||||
}
|
}
|
||||||
.icon-funeral:before {
|
.icon-flower:before {
|
||||||
content: "\e904";
|
content: "\e90c";
|
||||||
}
|
}
|
||||||
.icon-greenery:before {
|
.icon-frozen:before {
|
||||||
content: "\e907";
|
content: "\e900";
|
||||||
}
|
}
|
||||||
.icon-greuge:before {
|
.icon-fruit:before {
|
||||||
content: "\e944";
|
content: "\e903";
|
||||||
}
|
}
|
||||||
.icon-grid:before {
|
.icon-funeral:before {
|
||||||
content: "\e980";
|
content: "\e904";
|
||||||
}
|
}
|
||||||
.icon-handmade:before {
|
.icon-greenery:before {
|
||||||
content: "\e909";
|
content: "\e907";
|
||||||
}
|
}
|
||||||
.icon-handmadeArtificial:before {
|
.icon-greuge:before {
|
||||||
content: "\e902";
|
content: "\e944";
|
||||||
}
|
}
|
||||||
.icon-headercol:before {
|
.icon-grid:before {
|
||||||
content: "\e958";
|
content: "\e980";
|
||||||
}
|
}
|
||||||
.icon-info:before {
|
.icon-handmade:before {
|
||||||
content: "\e952";
|
content: "\e909";
|
||||||
}
|
}
|
||||||
.icon-inventory:before {
|
.icon-handmadeArtificial:before {
|
||||||
content: "\e92b";
|
content: "\e902";
|
||||||
}
|
}
|
||||||
.icon-invoice:before {
|
.icon-headercol:before {
|
||||||
content: "\e923";
|
content: "\e958";
|
||||||
}
|
}
|
||||||
.icon-invoice-in:before {
|
.icon-info:before {
|
||||||
content: "\e911";
|
content: "\e952";
|
||||||
}
|
}
|
||||||
.icon-invoice-in-create:before {
|
.icon-inventory:before {
|
||||||
content: "\e912";
|
content: "\e92b";
|
||||||
}
|
}
|
||||||
.icon-invoice-out:before {
|
.icon-invoice:before {
|
||||||
content: "\e910";
|
content: "\e923";
|
||||||
}
|
}
|
||||||
.icon-isTooLittle:before {
|
.icon-invoice-in:before {
|
||||||
content: "\e91b";
|
content: "\e911";
|
||||||
}
|
}
|
||||||
.icon-item:before {
|
.icon-invoice-in-create:before {
|
||||||
content: "\e956";
|
content: "\e912";
|
||||||
}
|
}
|
||||||
.icon-languaje:before {
|
.icon-invoice-out:before {
|
||||||
content: "\e926";
|
content: "\e910";
|
||||||
}
|
}
|
||||||
.icon-lines:before {
|
.icon-isTooLittle:before {
|
||||||
content: "\e942";
|
content: "\e91b";
|
||||||
}
|
}
|
||||||
.icon-linesprepaired:before {
|
.icon-item:before {
|
||||||
content: "\e948";
|
content: "\e956";
|
||||||
}
|
}
|
||||||
.icon-logout:before {
|
.icon-languaje:before {
|
||||||
content: "\e936";
|
content: "\e926";
|
||||||
}
|
}
|
||||||
.icon-mana:before {
|
.icon-lines:before {
|
||||||
content: "\e96a";
|
content: "\e942";
|
||||||
}
|
}
|
||||||
.icon-mandatory:before {
|
.icon-linesprepaired:before {
|
||||||
content: "\e97b";
|
content: "\e948";
|
||||||
}
|
}
|
||||||
.icon-net:before {
|
.icon-logout:before {
|
||||||
content: "\e931";
|
content: "\e936";
|
||||||
}
|
}
|
||||||
.icon-niche:before {
|
.icon-mana:before {
|
||||||
content: "\e96c";
|
content: "\e96a";
|
||||||
}
|
}
|
||||||
.icon-no036:before {
|
.icon-mandatory:before {
|
||||||
content: "\e920";
|
content: "\e97b";
|
||||||
}
|
}
|
||||||
.icon-noPayMethod:before {
|
.icon-net:before {
|
||||||
content: "\e905";
|
content: "\e931";
|
||||||
}
|
}
|
||||||
.icon-notes:before {
|
.icon-niche:before {
|
||||||
content: "\e941";
|
content: "\e96c";
|
||||||
}
|
}
|
||||||
.icon-noweb:before {
|
.icon-no036:before {
|
||||||
content: "\e91f";
|
content: "\e920";
|
||||||
}
|
}
|
||||||
.icon-onlinepayment:before {
|
.icon-noPayMethod:before {
|
||||||
content: "\e91d";
|
content: "\e905";
|
||||||
}
|
}
|
||||||
.icon-package:before {
|
.icon-notes:before {
|
||||||
content: "\e978";
|
content: "\e941";
|
||||||
}
|
}
|
||||||
.icon-payment:before {
|
.icon-noweb:before {
|
||||||
content: "\e97e";
|
content: "\e91f";
|
||||||
}
|
}
|
||||||
.icon-pbx:before {
|
.icon-onlinepayment:before {
|
||||||
content: "\e93c";
|
content: "\e91d";
|
||||||
}
|
}
|
||||||
.icon-pets:before {
|
.icon-package:before {
|
||||||
content: "\e947";
|
content: "\e978";
|
||||||
}
|
}
|
||||||
.icon-photo:before {
|
.icon-payment:before {
|
||||||
content: "\e924";
|
content: "\e97e";
|
||||||
}
|
}
|
||||||
.icon-plant:before {
|
.icon-pbx:before {
|
||||||
content: "\e908";
|
content: "\e93c";
|
||||||
}
|
}
|
||||||
.icon-polizon:before {
|
.icon-pets:before {
|
||||||
content: "\e95e";
|
content: "\e947";
|
||||||
}
|
}
|
||||||
.icon-preserved:before {
|
.icon-photo:before {
|
||||||
content: "\e906";
|
content: "\e924";
|
||||||
}
|
}
|
||||||
.icon-recovery:before {
|
.icon-plant:before {
|
||||||
content: "\e97c";
|
content: "\e908";
|
||||||
}
|
}
|
||||||
.icon-regentry:before {
|
.icon-polizon:before {
|
||||||
content: "\e964";
|
content: "\e95e";
|
||||||
}
|
}
|
||||||
.icon-reserva:before {
|
.icon-preserved:before {
|
||||||
content: "\e959";
|
content: "\e906";
|
||||||
}
|
}
|
||||||
.icon-revision:before {
|
.icon-recovery:before {
|
||||||
content: "\e94a";
|
content: "\e97c";
|
||||||
}
|
}
|
||||||
.icon-risk:before {
|
.icon-regentry:before {
|
||||||
content: "\e91e";
|
content: "\e964";
|
||||||
}
|
}
|
||||||
.icon-services:before {
|
.icon-reserva:before {
|
||||||
content: "\e94c";
|
content: "\e959";
|
||||||
}
|
}
|
||||||
.icon-settings:before {
|
.icon-revision:before {
|
||||||
content: "\e979";
|
content: "\e94a";
|
||||||
}
|
}
|
||||||
.icon-shipment-01:before {
|
.icon-risk:before {
|
||||||
content: "\e929";
|
content: "\e91e";
|
||||||
}
|
}
|
||||||
.icon-sign:before {
|
.icon-services:before {
|
||||||
content: "\e95d";
|
content: "\e94c";
|
||||||
}
|
}
|
||||||
.icon-sms:before {
|
.icon-settings:before {
|
||||||
content: "\e975";
|
content: "\e979";
|
||||||
}
|
}
|
||||||
.icon-solclaim:before {
|
.icon-shipment-01:before {
|
||||||
content: "\e95f";
|
content: "\e929";
|
||||||
}
|
}
|
||||||
.icon-solunion:before {
|
.icon-sign:before {
|
||||||
content: "\e94e";
|
content: "\e95d";
|
||||||
}
|
}
|
||||||
.icon-stowaway:before {
|
.icon-sms:before {
|
||||||
content: "\e94f";
|
content: "\e975";
|
||||||
}
|
}
|
||||||
.icon-splitline:before {
|
.icon-solclaim:before {
|
||||||
content: "\e93e";
|
content: "\e95f";
|
||||||
}
|
}
|
||||||
.icon-splur:before {
|
.icon-solunion:before {
|
||||||
content: "\e970";
|
content: "\e94e";
|
||||||
}
|
}
|
||||||
.icon-supplier:before {
|
.icon-stowaway:before {
|
||||||
content: "\e925";
|
content: "\e94f";
|
||||||
}
|
}
|
||||||
.icon-supplierfalse:before {
|
.icon-splitline:before {
|
||||||
content: "\e90f";
|
content: "\e93e";
|
||||||
}
|
}
|
||||||
.icon-tags:before {
|
.icon-splur:before {
|
||||||
content: "\e96d";
|
content: "\e970";
|
||||||
}
|
}
|
||||||
.icon-tax:before {
|
.icon-supplier:before {
|
||||||
content: "\e940";
|
content: "\e925";
|
||||||
}
|
}
|
||||||
.icon-thermometer:before {
|
.icon-supplierfalse:before {
|
||||||
content: "\e933";
|
content: "\e90f";
|
||||||
}
|
}
|
||||||
.icon-ticket:before {
|
.icon-tags:before {
|
||||||
content: "\e96b";
|
content: "\e96d";
|
||||||
}
|
}
|
||||||
.icon-ticketAdd:before {
|
.icon-tax:before {
|
||||||
content: "\e945";
|
content: "\e940";
|
||||||
}
|
}
|
||||||
.icon-traceability:before {
|
.icon-thermometer:before {
|
||||||
content: "\e962";
|
content: "\e933";
|
||||||
}
|
}
|
||||||
.icon-transaction:before {
|
.icon-ticket:before {
|
||||||
content: "\e966";
|
content: "\e96b";
|
||||||
}
|
}
|
||||||
.icon-treatments:before {
|
.icon-ticketAdd:before {
|
||||||
content: "\e922";
|
content: "\e945";
|
||||||
}
|
}
|
||||||
.icon-unavailable:before {
|
.icon-traceability:before {
|
||||||
content: "\e92c";
|
content: "\e962";
|
||||||
}
|
}
|
||||||
.icon-volume:before {
|
.icon-transaction:before {
|
||||||
content: "\e96e";
|
content: "\e966";
|
||||||
}
|
}
|
||||||
.icon-wand:before {
|
.icon-treatments:before {
|
||||||
content: "\e93a";
|
content: "\e922";
|
||||||
}
|
}
|
||||||
.icon-web:before {
|
.icon-unavailable:before {
|
||||||
content: "\e982";
|
content: "\e92c";
|
||||||
}
|
}
|
||||||
.icon-wiki:before {
|
.icon-volume:before {
|
||||||
content: "\e92d";
|
content: "\e96e";
|
||||||
}
|
}
|
||||||
.icon-worker:before {
|
.icon-wand:before {
|
||||||
content: "\e957";
|
content: "\e93a";
|
||||||
}
|
}
|
||||||
.icon-zone:before {
|
.icon-web:before {
|
||||||
content: "\e943";
|
content: "\e982";
|
||||||
}
|
}
|
||||||
|
.icon-wiki:before {
|
||||||
|
content: "\e92d";
|
||||||
|
}
|
||||||
|
.icon-worker:before {
|
||||||
|
content: "\e957";
|
||||||
|
}
|
||||||
|
.icon-zone:before {
|
||||||
|
content: "\e943";
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 173 KiB |
Binary file not shown.
Binary file not shown.
|
@ -21,6 +21,14 @@
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="false">
|
autocomplete="false">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
ng-if="$ctrl.$state.params.twoFactor == 'true'"
|
||||||
|
label="Verification code"
|
||||||
|
ng-model="$ctrl.code"
|
||||||
|
vn-name="code"
|
||||||
|
autocomplete="false"
|
||||||
|
class="vn-mt-md">
|
||||||
|
</vn-textfield>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<vn-submit label="Change password" ng-click="$ctrl.submit()"></vn-submit>
|
<vn-submit label="Change password" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
<div class="spinner-wrapper">
|
<div class="spinner-wrapper">
|
||||||
|
|
|
@ -26,11 +26,13 @@ export default class Controller {
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
const userId = this.$state.params.userId;
|
const userId = this.$state.params.userId;
|
||||||
const newPassword = this.newPassword;
|
|
||||||
const oldPassword = this.oldPassword;
|
const oldPassword = this.oldPassword;
|
||||||
|
const newPassword = this.newPassword;
|
||||||
|
const repeatPassword = this.repeatPassword;
|
||||||
|
const code = this.code;
|
||||||
|
|
||||||
if (!newPassword)
|
if (!oldPassword || !newPassword || !repeatPassword)
|
||||||
throw new UserError(`You must enter a new password`);
|
throw new UserError(`You must fill all the fields`);
|
||||||
if (newPassword != this.repeatPassword)
|
if (newPassword != this.repeatPassword)
|
||||||
throw new UserError(`Passwords don't match`);
|
throw new UserError(`Passwords don't match`);
|
||||||
|
|
||||||
|
@ -38,11 +40,12 @@ export default class Controller {
|
||||||
Authorization: this.$state.params.id
|
Authorization: this.$state.params.id
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$http.post('VnUsers/change-password',
|
this.$http.patch('Accounts/change-password',
|
||||||
{
|
{
|
||||||
id: userId,
|
id: userId,
|
||||||
oldPassword,
|
oldPassword,
|
||||||
newPassword
|
newPassword,
|
||||||
|
code
|
||||||
},
|
},
|
||||||
{headers}
|
{headers}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
|
|
@ -2,6 +2,10 @@ Change password: Cambiar contraseña
|
||||||
Old password: Antigua contraseña
|
Old password: Antigua contraseña
|
||||||
New password: Nueva contraseña
|
New password: Nueva contraseña
|
||||||
Repeat password: Repetir contraseña
|
Repeat password: Repetir contraseña
|
||||||
|
Passwords don't match: Las contraseñas no coinciden
|
||||||
|
You must fill all the fields: Debes rellenar todos los campos
|
||||||
|
You can not use the same password: No puedes usar la misma contraseña
|
||||||
|
Verification code: Código de verificación
|
||||||
Password updated!: ¡Contraseña actualizada!
|
Password updated!: ¡Contraseña actualizada!
|
||||||
Password requirements: >
|
Password requirements: >
|
||||||
La contraseña debe tener al menos {{ length }} caracteres de longitud,
|
La contraseña debe tener al menos {{ length }} caracteres de longitud,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import './login';
|
||||||
import './outLayout';
|
import './outLayout';
|
||||||
import './recover-password';
|
import './recover-password';
|
||||||
import './reset-password';
|
import './reset-password';
|
||||||
|
import './validate-email';
|
||||||
import './change-password';
|
import './change-password';
|
||||||
import './module-card';
|
import './module-card';
|
||||||
import './module-main';
|
import './module-main';
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
<button class="buttonAccount">
|
<button class="buttonAccount">
|
||||||
<img
|
<img
|
||||||
id="user"
|
id="user"
|
||||||
ng-src="{{$ctrl.getImageUrl()}}"
|
ng-src="{{::$ctrl.getImageUrl()}}"
|
||||||
ng-click="userPopover.show($event)"
|
ng-click="userPopover.show($event)"
|
||||||
translate-attr="{title: 'Account'}"
|
translate-attr="{title: 'Account'}"
|
||||||
on-error-src/>
|
on-error-src/>
|
||||||
|
@ -93,4 +93,4 @@
|
||||||
</vn-list>
|
</vn-list>
|
||||||
</vn-portal>
|
</vn-portal>
|
||||||
<ui-view class="main-view"></ui-view>
|
<ui-view class="main-view"></ui-view>
|
||||||
<vn-scroll-up></vn-scroll-up>
|
<vn-scroll-up></vn-scroll-up>
|
||||||
|
|
|
@ -3,14 +3,13 @@ import Component from 'core/lib/component';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export class Layout extends Component {
|
export class Layout extends Component {
|
||||||
constructor($element, $, vnModules, vnToken) {
|
constructor($element, $, vnModules) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
this.modules = vnModules.get();
|
this.modules = vnModules.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
this.getUserData();
|
this.getUserData();
|
||||||
this.getAccessTokenConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserData() {
|
getUserData() {
|
||||||
|
@ -32,41 +31,11 @@ export class Layout extends Component {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccessTokenConfig() {
|
|
||||||
this.$http.get('AccessTokenConfigs').then(json => {
|
|
||||||
const firtsResult = json.data[0];
|
|
||||||
if (!firtsResult) return;
|
|
||||||
this.renewPeriod = firtsResult.renewPeriod;
|
|
||||||
this.renewInterval = firtsResult.renewInterval;
|
|
||||||
|
|
||||||
const intervalMilliseconds = firtsResult.renewInterval * 1000;
|
|
||||||
this.inservalId = setInterval(this.checkTokenValidity.bind(this), intervalMilliseconds);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTokenValidity() {
|
|
||||||
const now = new Date();
|
|
||||||
const differenceMilliseconds = now - new Date(this.vnToken.created);
|
|
||||||
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
|
||||||
|
|
||||||
if (differenceSeconds > this.renewPeriod) {
|
|
||||||
this.$http.post('VnUsers/renewToken')
|
|
||||||
.then(json => {
|
|
||||||
if (json.data.token) {
|
|
||||||
let remember = true;
|
|
||||||
if (window.sessionStorage.vnToken) remember = false;
|
|
||||||
|
|
||||||
this.vnToken.set(json.data.token, json.data.created, remember);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
clearInterval(this.inservalId);
|
this.vnToken.stopRenewer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout.$inject = ['$element', '$scope', 'vnModules', 'vnToken'];
|
Layout.$inject = ['$element', '$scope', 'vnModules'];
|
||||||
|
|
||||||
ngModule.vnComponent('vnLayout', {
|
ngModule.vnComponent('vnLayout', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
|
|
|
@ -37,49 +37,4 @@ describe('Component vnLayout', () => {
|
||||||
expect(url).not.toBeDefined();
|
expect(url).not.toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAccessTokenConfig()', () => {
|
|
||||||
it(`should set the renewPeriod and renewInterval properties in localStorage`, () => {
|
|
||||||
const response = [{
|
|
||||||
renewPeriod: 100,
|
|
||||||
renewInterval: 5
|
|
||||||
}];
|
|
||||||
|
|
||||||
$httpBackend.expect('GET', `AccessTokenConfigs`).respond(response);
|
|
||||||
controller.getAccessTokenConfig();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.renewPeriod).toBe(100);
|
|
||||||
expect(controller.renewInterval).toBe(5);
|
|
||||||
expect(controller.inservalId).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('checkTokenValidity()', () => {
|
|
||||||
it(`should not call renewToken and not set vnToken in the controller`, () => {
|
|
||||||
controller.renewPeriod = 100;
|
|
||||||
controller.vnToken.created = new Date();
|
|
||||||
|
|
||||||
controller.checkTokenValidity();
|
|
||||||
|
|
||||||
expect(controller.vnToken.token).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should call renewToken and set vnToken properties in the controller`, () => {
|
|
||||||
const response = {
|
|
||||||
token: 999,
|
|
||||||
created: new Date()
|
|
||||||
};
|
|
||||||
controller.renewPeriod = 100;
|
|
||||||
const oneHourBefore = new Date(Date.now() - (60 * 60 * 1000));
|
|
||||||
controller.vnToken.created = oneHourBefore;
|
|
||||||
|
|
||||||
$httpBackend.expect('POST', `VnUsers/renewToken`).respond(response);
|
|
||||||
controller.checkTokenValidity();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.vnToken.token).toBe(999);
|
|
||||||
expect(controller.vnToken.created).toEqual(response.created);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
url="{{$ctrl.url}}"
|
url="{{$ctrl.url}}"
|
||||||
filter="$ctrl.filter"
|
filter="$ctrl.filter"
|
||||||
link="{originFk: $ctrl.originId}"
|
|
||||||
where="{changedModel: $ctrl.changedModel, changedModelId: $ctrl.changedModelId}"
|
|
||||||
data="$ctrl.logs"
|
data="$ctrl.logs"
|
||||||
order="creationDate DESC, id DESC"
|
order="creationDate DESC, id DESC"
|
||||||
limit="20">
|
limit="20">
|
||||||
|
@ -17,90 +15,110 @@
|
||||||
<vn-data-viewer
|
<vn-data-viewer
|
||||||
model="model"
|
model="model"
|
||||||
class="vn-w-sm vn-px-sm vn-pb-xl">
|
class="vn-w-sm vn-px-sm vn-pb-xl">
|
||||||
<div class="change vn-mb-sm" ng-repeat="log in $ctrl.logs">
|
<div class="origin-log" ng-repeat="originLog in $ctrl.logTree">
|
||||||
<div class="left">
|
<div class="origin-info vn-mb-md" ng-if="::$ctrl.logTree.length > 1">
|
||||||
<vn-avatar class="vn-mt-xs"
|
<h6 class="origin-id">
|
||||||
ng-class="::{system: !log.user}"
|
{{::$ctrl.modelI18n}} #{{::originLog.originFk}}
|
||||||
val="{{::log.user ? log.user.nickname : $ctrl.$t('System')}}"
|
</h6>
|
||||||
ng-click="$ctrl.showWorkerDescriptor($event, log)">
|
|
||||||
<img
|
|
||||||
ng-if="::log.user.image"
|
|
||||||
ng-src="/api/Images/user/160x160/{{::log.userFk}}/download?access_token={{::$ctrl.vnToken.token}}">
|
|
||||||
</img>
|
|
||||||
</vn-avatar>
|
|
||||||
<div class="arrow bg-panel"></div>
|
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
</div>
|
</div>
|
||||||
<vn-card class="detail">
|
<div class="user-log vn-mb-sm" ng-repeat="userLog in ::originLog.logs">
|
||||||
<div class="header vn-pa-sm">
|
<div class="timeline">
|
||||||
<div class="action-model">
|
<div class="user-avatar">
|
||||||
<span class="model-name"
|
<vn-avatar
|
||||||
ng-if="::$ctrl.showModelName && log.changedModel"
|
ng-class="::{system: !userLog.user}"
|
||||||
ng-style="::{backgroundColor: $ctrl.hashToColor(log.changedModel)}"
|
val="{{::userLog.user ? userLog.user.nickname : $ctrl.$t('System')}}"
|
||||||
title="{{::log.changedModel}}">
|
ng-click="$ctrl.showWorkerDescriptor($event, userLog)">
|
||||||
{{::log.changedModelI18n}}
|
<img
|
||||||
</span>
|
ng-if="::userLog.user.image"
|
||||||
</div>
|
ng-src="/api/Images/user/160x160/{{::userLog.userFk}}/download?access_token={{::$ctrl.vnToken.token}}">
|
||||||
<div
|
</img>
|
||||||
class="action-date text-secondary text-caption vn-ml-sm"
|
</vn-avatar>
|
||||||
title="{{::log.creationDate | date:'dd/MM/yyyy HH:mm:ss'}}">
|
|
||||||
{{::$ctrl.relativeDate(log.creationDate)}}
|
|
||||||
<vn-icon
|
|
||||||
class="action vn-ml-xs"
|
|
||||||
ng-class="::$ctrl.actionsClass[log.action]"
|
|
||||||
icon="{{::$ctrl.actionsIcon[log.action]}}"
|
|
||||||
translate-attr="::{title: $ctrl.actionsText[log.action]}">
|
|
||||||
</vn-icon>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="arrow bg-panel" ng-if="::$ctrl.byRecord"></div>
|
||||||
|
<div class="line"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="model vn-pb-sm vn-px-sm"
|
<div class="user-changes">
|
||||||
ng-if="::$ctrl.showModelName">
|
<div class="model-log" ng-repeat="modelLog in ::userLog.logs">
|
||||||
<span class="model-id" ng-if="::log.changedModelId">#{{::log.changedModelId}}</span>
|
<div class="model-info vn-my-sm" ng-if="::!$ctrl.byRecord">
|
||||||
<vn-icon
|
<vn-icon
|
||||||
icon="filter_alt"
|
icon="filter_alt"
|
||||||
translate-attr="{title: 'Show all record changes'}"
|
translate-attr="{title: 'Show all record changes'}"
|
||||||
ng-click="$ctrl.filterByEntity(log)">
|
ng-click="$ctrl.filterByRecord(modelLog)">
|
||||||
</vn-icon>
|
</vn-icon>
|
||||||
<span class="model-value" title="{{::log.changedModelValue}}">{{::log.changedModelValue}}</span>
|
<span class="model-name"
|
||||||
</div>
|
ng-if="::$ctrl.showModelName && modelLog.model"
|
||||||
<div class="changes vn-pa-sm"
|
ng-style="::{backgroundColor: $ctrl.hashToColor(modelLog.model)}"
|
||||||
ng-class="{expanded: log.expand}"
|
title="{{::modelLog.model}}">
|
||||||
ng-if="::log.props.length || log.description">
|
{{::modelLog.modelI18n}}
|
||||||
<vn-icon
|
|
||||||
icon="expand_more"
|
|
||||||
translate-attr="{title: 'Details'}"
|
|
||||||
ng-click="log.expand = !log.expand">
|
|
||||||
</vn-icon>
|
|
||||||
<span ng-if="::log.props.length"
|
|
||||||
class="attributes">
|
|
||||||
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
|
|
||||||
class="basic-json">
|
|
||||||
<span class="json-field" title="{{::prop.name}}">
|
|
||||||
{{::prop.nameI18n}}:
|
|
||||||
</span>
|
</span>
|
||||||
<vn-json-value value="::prop.val.val"></vn-json-value><span ng-if="::!$last">,</span>
|
<span class="model-id" ng-if="::modelLog.id">#{{::modelLog.id}}</span>
|
||||||
</span>
|
<span class="model-value" title="{{::modelLog.showValue}}">{{::modelLog.showValue}}</span>
|
||||||
<div ng-if="log.expand" class="expanded-json">
|
|
||||||
<div ng-repeat="prop in ::log.props">
|
|
||||||
<span class="json-field" title="{{::prop.name}}">
|
|
||||||
{{::prop.nameI18n}}:
|
|
||||||
</span>
|
|
||||||
<vn-log-value val="::prop.val"></vn-log-value>
|
|
||||||
<span ng-if="::log.action == 'update'">
|
|
||||||
← <vn-log-value val="::prop.old"></vn-log-value>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<vn-card class="changes-log vn-mb-xs" ng-repeat="log in ::modelLog.logs">
|
||||||
<span ng-if="::!log.props.length" class="description">
|
<div class="change-info vn-pa-sm">
|
||||||
{{::log.description}}
|
<div
|
||||||
</span>
|
class="date text-secondary text-caption vn-mr-sm"
|
||||||
</vn-card>
|
title="{{::log.creationDate | date:'dd/MM/yyyy HH:mm:ss'}}">
|
||||||
|
{{::$ctrl.relativeDate(log.creationDate)}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<vn-icon
|
||||||
|
class="pit vn-ml-xs"
|
||||||
|
icon="preview"
|
||||||
|
translate-attr="::{title: 'View record at this point in time'}"
|
||||||
|
ng-show="::log.action != 'insert'"
|
||||||
|
ng-click="$ctrl.viewPitInstance($event, log.id, modelLog)">
|
||||||
|
</vn-icon>
|
||||||
|
<vn-icon
|
||||||
|
class="action vn-ml-xs"
|
||||||
|
ng-class="::$ctrl.actionsClass[log.action]"
|
||||||
|
icon="{{::$ctrl.actionsIcon[log.action]}}"
|
||||||
|
translate-attr="::{title: $ctrl.actionsText[log.action]}">
|
||||||
|
</vn-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="change-detail vn-pa-sm"
|
||||||
|
ng-class="{expanded: log.expand}"
|
||||||
|
ng-if="::log.props.length || log.description">
|
||||||
|
<vn-icon
|
||||||
|
icon="expand_more"
|
||||||
|
translate-attr="{title: 'Details'}"
|
||||||
|
ng-click="log.expand = !log.expand">
|
||||||
|
</vn-icon>
|
||||||
|
<span ng-if="::log.props.length"
|
||||||
|
class="attributes">
|
||||||
|
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
|
||||||
|
class="basic-json">
|
||||||
|
<span class="json-field" title="{{::prop.name}}">
|
||||||
|
{{::prop.nameI18n}}:
|
||||||
|
</span>
|
||||||
|
<vn-json-value value="::prop.val.val"></vn-json-value><span ng-if="::!$last">,</span>
|
||||||
|
</span>
|
||||||
|
<div ng-if="log.expand" class="expanded-json">
|
||||||
|
<div ng-repeat="prop in ::log.props">
|
||||||
|
<span class="json-field" title="{{::prop.name}}">
|
||||||
|
{{::prop.nameI18n}}:
|
||||||
|
</span>
|
||||||
|
<vn-log-value val="::prop.val"></vn-log-value>
|
||||||
|
<span ng-if="::log.action == 'update'">
|
||||||
|
← <vn-log-value val="::prop.old"></vn-log-value>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span ng-if="::!log.props.length" class="description">
|
||||||
|
{{::log.description}}
|
||||||
|
</span>
|
||||||
|
</vn-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</vn-data-viewer>
|
</vn-data-viewer>
|
||||||
<vn-float-button
|
<vn-float-button
|
||||||
ng-if="model.userFilter"
|
ng-if="$ctrl.hasFilter"
|
||||||
icon="filter_alt_off"
|
icon="filter_alt_off"
|
||||||
translate-attr="{title: 'Quit filter'}"
|
translate-attr="{title: 'Quit filter'}"
|
||||||
ng-click="$ctrl.resetFilter()"
|
ng-click="$ctrl.resetFilter()"
|
||||||
|
@ -212,5 +230,33 @@
|
||||||
</vn-date-picker>
|
</vn-date-picker>
|
||||||
</form>
|
</form>
|
||||||
</vn-side-menu>
|
</vn-side-menu>
|
||||||
<vn-worker-descriptor-popover vn-id="workerDescriptor">
|
<vn-popover vn-id="instance-popover">
|
||||||
|
<tpl-body class="vn-log-instance">
|
||||||
|
<vn-spinner
|
||||||
|
ng-if="$ctrl.instance.canceler"
|
||||||
|
class="loading vn-pa-sm"
|
||||||
|
enable="true">
|
||||||
|
</vn-spinner>
|
||||||
|
<div
|
||||||
|
ng-if="!$ctrl.instance.canceler" class="instance">
|
||||||
|
<h6 class="header vn-pa-sm">
|
||||||
|
{{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}}
|
||||||
|
</h6>
|
||||||
|
<div class="change-detail vn-pa-sm">
|
||||||
|
<div ng-if="$ctrl.instance.props"
|
||||||
|
ng-repeat="prop in $ctrl.instance.props">
|
||||||
|
<span class="json-field" title="{{::prop.name}}">
|
||||||
|
{{::prop.nameI18n}}:
|
||||||
|
</span>
|
||||||
|
<vn-log-value val="::prop.val"></vn-log-value>
|
||||||
|
</div>
|
||||||
|
<div ng-if="!$ctrl.instance.props" translate>
|
||||||
|
No data
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</tpl-body>
|
||||||
|
</vn-popover>
|
||||||
|
<vn-worker-descriptor-popover
|
||||||
|
vn-id="worker-descriptor">
|
||||||
</vn-worker-descriptor-popover>
|
</vn-worker-descriptor-popover>
|
||||||
|
|
|
@ -3,7 +3,10 @@ import Section from '../section';
|
||||||
import {hashToColor} from 'core/lib/string';
|
import {hashToColor} from 'core/lib/string';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
const validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/;
|
const validDate = new RegExp(
|
||||||
|
/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source
|
||||||
|
+ /T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source
|
||||||
|
);
|
||||||
|
|
||||||
export default class Controller extends Section {
|
export default class Controller extends Section {
|
||||||
constructor($element, $) {
|
constructor($element, $) {
|
||||||
|
@ -28,6 +31,20 @@ export default class Controller extends Section {
|
||||||
select: 'visibility'
|
select: 'visibility'
|
||||||
};
|
};
|
||||||
this.filter = {
|
this.filter = {
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'originFk',
|
||||||
|
'userFk',
|
||||||
|
'action',
|
||||||
|
'changedModel',
|
||||||
|
'oldInstance',
|
||||||
|
'newInstance',
|
||||||
|
'creationDate',
|
||||||
|
'changedModel',
|
||||||
|
'changedModelId',
|
||||||
|
'changedModelValue',
|
||||||
|
'description'
|
||||||
|
],
|
||||||
include: [{
|
include: [{
|
||||||
relation: 'user',
|
relation: 'user',
|
||||||
scope: {
|
scope: {
|
||||||
|
@ -48,6 +65,11 @@ export default class Controller extends Section {
|
||||||
this.today.setHours(0, 0, 0, 0);
|
this.today.setHours(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
const match = this.url?.match(/(.*)Logs$/);
|
||||||
|
this.modelI18n = match && this.translateModel(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
$postLink() {
|
$postLink() {
|
||||||
this.resetFilter();
|
this.resetFilter();
|
||||||
this.$.$watch(
|
this.$.$watch(
|
||||||
|
@ -63,47 +85,75 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
set logs(value) {
|
set logs(value) {
|
||||||
this._logs = value;
|
this._logs = value;
|
||||||
|
this.logTree = [];
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
const empty = {};
|
const empty = {};
|
||||||
const validations = window.validations;
|
const validations = window.validations;
|
||||||
const castJsonValue = this.castJsonValue;
|
|
||||||
|
|
||||||
for (const log of value) {
|
let originLog;
|
||||||
|
let userLog;
|
||||||
|
let modelLog;
|
||||||
|
let nLogs;
|
||||||
|
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const log = value[i];
|
||||||
|
const prevLog = i > 0 ? value[i - 1] : null;
|
||||||
|
const locale = validations[log.changedModel]?.locale || empty;
|
||||||
|
|
||||||
|
// Origin
|
||||||
|
|
||||||
|
const originChanged = !prevLog
|
||||||
|
|| log.originFk != prevLog.originFk;
|
||||||
|
if (originChanged) {
|
||||||
|
this.logTree.push(originLog = {
|
||||||
|
originFk: log.originFk,
|
||||||
|
logs: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// User
|
||||||
|
|
||||||
|
const userChanged = originChanged
|
||||||
|
|| log.userFk != prevLog.userFk;
|
||||||
|
if (userChanged) {
|
||||||
|
originLog.logs.push(userLog = {
|
||||||
|
user: log.user,
|
||||||
|
userFk: log.userFk,
|
||||||
|
logs: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model
|
||||||
|
|
||||||
|
const modelChanged = userChanged
|
||||||
|
|| log.changedModel != prevLog.changedModel
|
||||||
|
|| log.changedModelId != prevLog.changedModelId
|
||||||
|
|| nLogs >= 6;
|
||||||
|
if (modelChanged) {
|
||||||
|
userLog.logs.push(modelLog = {
|
||||||
|
model: log.changedModel,
|
||||||
|
modelI18n: firstUpper(locale.name) || log.changedModel,
|
||||||
|
id: log.changedModelId,
|
||||||
|
showValue: log.changedModelValue,
|
||||||
|
logs: []
|
||||||
|
});
|
||||||
|
nLogs = 0;
|
||||||
|
}
|
||||||
|
nLogs++;
|
||||||
|
|
||||||
|
modelLog.logs.push(log);
|
||||||
|
|
||||||
|
// Changes
|
||||||
|
|
||||||
const notDelete = log.action != 'delete';
|
const notDelete = log.action != 'delete';
|
||||||
const olds = (notDelete ? log.oldInstance : null) || empty;
|
const olds = (notDelete ? log.oldInstance : null) || empty;
|
||||||
const vals = (notDelete ? log.newInstance : log.oldInstance) || empty;
|
const vals = (notDelete ? log.newInstance : log.oldInstance) || empty;
|
||||||
const locale = validations[log.changedModel]?.locale || empty;
|
|
||||||
log.changedModelI18n = firstUpper(locale.name) || log.changedModel;
|
|
||||||
|
|
||||||
let props = Object.keys(olds).concat(Object.keys(vals));
|
let propNames = Object.keys(olds).concat(Object.keys(vals));
|
||||||
props = [...new Set(props)];
|
propNames = [...new Set(propNames)];
|
||||||
|
|
||||||
log.props = [];
|
log.props = this.parseProps(propNames, locale, vals, olds);
|
||||||
for (const prop of props) {
|
|
||||||
if (prop.endsWith('$')) continue;
|
|
||||||
log.props.push({
|
|
||||||
name: prop,
|
|
||||||
nameI18n: firstUpper(locale.columns?.[prop]) || prop,
|
|
||||||
old: getVal(olds, prop),
|
|
||||||
val: getVal(vals, prop)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
log.props.sort(
|
|
||||||
(a, b) => a.nameI18n.localeCompare(b.nameI18n));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVal(vals, prop) {
|
|
||||||
let val, id;
|
|
||||||
const showProp = `${prop}$`;
|
|
||||||
|
|
||||||
if (vals[showProp] != null) {
|
|
||||||
val = vals[showProp];
|
|
||||||
id = vals[prop];
|
|
||||||
} else
|
|
||||||
val = vals[prop];
|
|
||||||
|
|
||||||
return {val: castJsonValue(val), id};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,17 +164,76 @@ export default class Controller extends Section {
|
||||||
set models(value) {
|
set models(value) {
|
||||||
this._models = value;
|
this._models = value;
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
for (const model of value) {
|
for (const model of value)
|
||||||
const name = model.changedModel;
|
model.changedModelI18n = this.translateModel(model.changedModel);
|
||||||
model.changedModelI18n =
|
|
||||||
firstUpper(window.validations[name]?.locale?.name) || name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get showModelName() {
|
get showModelName() {
|
||||||
return !(this.changedModel && this.changedModelId);
|
return !(this.changedModel && this.changedModelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseProps(propNames, locale, vals, olds) {
|
||||||
|
const castJsonValue = this.castJsonValue;
|
||||||
|
const props = [];
|
||||||
|
|
||||||
|
for (const prop of propNames) {
|
||||||
|
if (prop.endsWith('$')) continue;
|
||||||
|
props.push({
|
||||||
|
name: prop,
|
||||||
|
nameI18n: firstUpper(locale.columns?.[prop]) || prop,
|
||||||
|
val: getVal(vals, prop),
|
||||||
|
old: olds && getVal(olds, prop)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
props.sort(
|
||||||
|
(a, b) => a.nameI18n.localeCompare(b.nameI18n));
|
||||||
|
|
||||||
|
function getVal(vals, prop) {
|
||||||
|
let val; let id;
|
||||||
|
const showProp = `${prop}$`;
|
||||||
|
|
||||||
|
if (vals[showProp] != null) {
|
||||||
|
val = vals[showProp];
|
||||||
|
id = vals[prop];
|
||||||
|
} else
|
||||||
|
val = vals[prop];
|
||||||
|
|
||||||
|
return {val: castJsonValue(val), id};
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewPitInstance(event, id, modelLog) {
|
||||||
|
if (this.instance?.canceler)
|
||||||
|
this.instance.canceler.resolve();
|
||||||
|
|
||||||
|
const canceler = this.$q.defer();
|
||||||
|
this.instance = {
|
||||||
|
modelLog,
|
||||||
|
canceler
|
||||||
|
};
|
||||||
|
const options = {timeout: canceler.promise};
|
||||||
|
|
||||||
|
this.$http.get(`${this.url}/${id}/pitInstance`, options)
|
||||||
|
.then(res => {
|
||||||
|
const instance = res.data;
|
||||||
|
const propNames = Object.keys(instance);
|
||||||
|
const locale = window.validations[modelLog.model]?.locale || {};
|
||||||
|
this.instance.props = this.parseProps(propNames, locale, instance);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.instance.canceler = null;
|
||||||
|
this.$.$applyAsync(() => this.$.instancePopover.relocate());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$.instancePopover.show(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
translateModel(name) {
|
||||||
|
return firstUpper(window.validations[name]?.locale?.name) || name;
|
||||||
|
}
|
||||||
|
|
||||||
castJsonValue(value) {
|
castJsonValue(value) {
|
||||||
return typeof value === 'string' && validDate.test(value)
|
return typeof value === 'string' && validDate.test(value)
|
||||||
? new Date(value)
|
? new Date(value)
|
||||||
|
@ -160,12 +269,11 @@ export default class Controller extends Section {
|
||||||
applyFilter() {
|
applyFilter() {
|
||||||
const filter = this.$.filter;
|
const filter = this.$.filter;
|
||||||
|
|
||||||
function getParam(prop, value) {
|
const getParam = (prop, value) => {
|
||||||
if (value == null || value == '') return null;
|
if (value == null || value == '') return null;
|
||||||
switch (prop) {
|
switch (prop) {
|
||||||
case 'search':
|
case 'search':
|
||||||
const or = [];
|
if (/^\s*[0-9]+\s*$/.test(value) || this.byRecord)
|
||||||
if (/^\s*[0-9]+\s*$/.test(value))
|
|
||||||
return {changedModelId: value.trim()};
|
return {changedModelId: value.trim()};
|
||||||
else
|
else
|
||||||
return {changedModelValue: {like: `%${value}%`}};
|
return {changedModelValue: {like: `%${value}%`}};
|
||||||
|
@ -177,72 +285,86 @@ export default class Controller extends Section {
|
||||||
]};
|
]};
|
||||||
case 'who':
|
case 'who':
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'all':
|
|
||||||
return null;
|
|
||||||
case 'user':
|
case 'user':
|
||||||
return {userFk: {neq: null}};
|
return {userFk: {neq: null}};
|
||||||
case 'system':
|
case 'system':
|
||||||
return {userFk: null};
|
return {userFk: null};
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
case 'actions':
|
case 'actions': {
|
||||||
const inq = [];
|
const inq = [];
|
||||||
for (const action in value) {
|
for (const action in value) {
|
||||||
if (value[action])
|
if (value[action])
|
||||||
inq.push(action);
|
inq.push(action);
|
||||||
}
|
}
|
||||||
return inq.length ? {action: {inq}} : null;
|
return inq.length ? {action: {inq}} : null;
|
||||||
|
}
|
||||||
case 'from':
|
case 'from':
|
||||||
if (filter.to) {
|
if (filter.to)
|
||||||
return {creationDate: {gte: value}};
|
return {creationDate: {gte: value}};
|
||||||
} else {
|
else {
|
||||||
const to = new Date(value);
|
const to = new Date(value);
|
||||||
to.setHours(23, 59, 59, 999);
|
to.setHours(23, 59, 59, 999);
|
||||||
return {creationDate: {between: [value, to]}};
|
return {creationDate: {between: [value, to]}};
|
||||||
}
|
}
|
||||||
case 'to':
|
case 'to': {
|
||||||
const to = new Date(value);
|
const to = new Date(value);
|
||||||
to.setHours(23, 59, 59, 999);
|
to.setHours(23, 59, 59, 999);
|
||||||
return {creationDate: {lte: to}};
|
return {creationDate: {lte: to}};
|
||||||
|
}
|
||||||
case 'userFk':
|
case 'userFk':
|
||||||
return filter.who != 'system'
|
return filter.who != 'system'
|
||||||
? {[prop]: value} : null;
|
? {[prop]: value} : null;
|
||||||
default:
|
default:
|
||||||
return {[prop]: value};
|
return {[prop]: value};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.hasFilter = false;
|
||||||
const and = [];
|
const and = [];
|
||||||
|
|
||||||
|
if (!filter.search || !filter.changedModel)
|
||||||
|
this.byRecord = false;
|
||||||
|
if (!this.byRecord)
|
||||||
|
and.push({originFk: this.originId});
|
||||||
|
|
||||||
for (const prop in filter) {
|
for (const prop in filter) {
|
||||||
const param = getParam(prop, filter[prop]);
|
const param = getParam(prop, filter[prop]);
|
||||||
if (param) and.push(param);
|
if (param) {
|
||||||
|
and.push(param);
|
||||||
|
this.hasFilter = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lbFilter = and.length ? {where: {and}} : null;
|
const lbFilter = and.length ? {where: {and}} : null;
|
||||||
return this.$.model.applyFilter(lbFilter);
|
return this.$.model.applyFilter(lbFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
filterByEntity(log) {
|
filterByRecord(modelLog) {
|
||||||
|
this.byRecord = true;
|
||||||
this.$.filter = {
|
this.$.filter = {
|
||||||
who: 'all',
|
who: 'all',
|
||||||
search: log.changedModelId,
|
search: modelLog.id,
|
||||||
changedModel: log.changedModel
|
changedModel: modelLog.model
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
searchUser(search) {
|
searchUser(search) {
|
||||||
if (/^[0-9]+$/.test(search)) {
|
if (/^[0-9]+$/.test(search))
|
||||||
return {id: search};
|
return {id: search};
|
||||||
} else {
|
else {
|
||||||
return {or: [
|
return {or: [
|
||||||
{name: search},
|
{name: search},
|
||||||
{nickname: {like: `%${search}%`}}
|
{nickname: {like: `%${search}%`}}
|
||||||
]}
|
]};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showWorkerDescriptor(event, log) {
|
showWorkerDescriptor(event, userLog) {
|
||||||
if (log.user?.worker)
|
if (userLog.user?.worker)
|
||||||
this.$.workerDescriptor.show(event.target, log.userFk);
|
this.$.workerDescriptor.show(event.target, userLog.userFk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,4 +24,5 @@ Changes: Cambios
|
||||||
today: hoy
|
today: hoy
|
||||||
yesterday: ayer
|
yesterday: ayer
|
||||||
Show all record changes: Mostrar todos los cambios realizados en el registro
|
Show all record changes: Mostrar todos los cambios realizados en el registro
|
||||||
|
View record at this point in time: Ver el registro en este punto
|
||||||
Quit filter: Quitar filtro
|
Quit filter: Quitar filtro
|
||||||
|
|
|
@ -1,167 +1,246 @@
|
||||||
@import "util";
|
@import "util";
|
||||||
|
|
||||||
vn-log {
|
vn-log {
|
||||||
.change {
|
.origin-log {
|
||||||
|
&:first-child > .origin-info {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
& > .origin-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 28px;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
& > .origin-id {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: $color-font-secondary;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
& > .line {
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: $color-font-secondary;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.user-log {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
& > .left {
|
& > .timeline {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
|
width: 38px;
|
||||||
|
min-width: 38px;
|
||||||
|
flex-grow: auto;
|
||||||
|
|
||||||
& > vn-avatar {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.system {
|
|
||||||
background-color: $color-main !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& > .arrow {
|
& > .arrow {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: rotateY(0deg) rotate(45deg);
|
transform: rotateY(0deg) rotate(45deg);
|
||||||
top: 18px;
|
top: 15px;
|
||||||
right: -4px;
|
right: -4px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
& > .user-avatar {
|
||||||
|
background-color: $color-bg;
|
||||||
|
padding: $spacing-sm 0;
|
||||||
|
margin-top: -$spacing-sm;
|
||||||
|
position: sticky;
|
||||||
|
top: 64px;
|
||||||
|
|
||||||
|
& > vn-avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&.system {
|
||||||
|
background-color: $color-main !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
& > .line {
|
& > .line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: $color-main;
|
background-color: $color-main;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
left: 17px;
|
left: 18px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
top: 44px;
|
top: 0;
|
||||||
bottom: -8px;
|
bottom: -$spacing-sm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:last-child > .left > .line {
|
&:last-child > .timeline > .line {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.detail {
|
& > .user-changes {
|
||||||
position: relative;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 100%;
|
|
||||||
border-radius: 2px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.model-log {
|
||||||
|
& > .model-info {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
min-height: 22px;
|
||||||
|
|
||||||
& > .header {
|
& > .model-name {
|
||||||
display: flex;
|
display: inline-block;
|
||||||
justify-content: space-between;
|
padding: 2px 5px;
|
||||||
align-items: center;
|
color: $color-font-dark;
|
||||||
overflow: hidden;
|
border-radius: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
& > .model-value {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
& > .model-id {
|
||||||
|
color: $color-font-secondary;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
& > vn-icon[icon="filter_alt"] {
|
||||||
|
@extend %clickable-light;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 18px;
|
||||||
|
color: $color-font-secondary;
|
||||||
|
float: right;
|
||||||
|
display: none;
|
||||||
|
|
||||||
& > .action-model {
|
@include mobile {
|
||||||
display: inline-flex;
|
display: initial;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
& > .model-name {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 5px;
|
|
||||||
color: $color-font-dark;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& > .action-date {
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
& > .action {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: $color-font-bg;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
&.notice {
|
|
||||||
background-color: $color-notice-medium
|
|
||||||
}
|
|
||||||
&.success {
|
|
||||||
background-color: $color-success-medium;
|
|
||||||
}
|
|
||||||
&.warning {
|
|
||||||
background-color: $color-main-medium;
|
|
||||||
}
|
|
||||||
&.alert {
|
|
||||||
background-color: lighten($color-alert, 5%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > .model {
|
}
|
||||||
overflow: hidden;
|
&:hover > .model-info > vn-icon[icon="filter_alt"] {
|
||||||
text-overflow: ellipsis;
|
display: initial;
|
||||||
white-space: nowrap;
|
}
|
||||||
max-height: 18px;
|
}
|
||||||
|
.changes-log {
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
& > vn-icon {
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
& > .change-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > .date {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
& > div {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
& > vn-icon.pit {
|
||||||
@extend %clickable-light;
|
@extend %clickable-light;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: 2px;
|
font-size: 20px;
|
||||||
margin: -2px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: $color-font-secondary;
|
color: $color-font-secondary;
|
||||||
float: right;
|
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@include mobile {
|
@include mobile {
|
||||||
display: initial;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > .model-value {
|
& > .action {
|
||||||
font-style: italic;
|
display: inline-flex;
|
||||||
}
|
align-items: center;
|
||||||
& > .model-id {
|
justify-content: center;
|
||||||
color: $color-font-secondary;
|
color: $color-font-bg;
|
||||||
font-size: .9rem;
|
vertical-align: middle;
|
||||||
}
|
border-radius: 50%;
|
||||||
}
|
width: 24px;
|
||||||
&:hover > .model > vn-icon {
|
height: 24px;
|
||||||
display: initial;
|
font-size: 18px;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.changes {
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: rgba(255, 255, 255, .05);
|
|
||||||
color: $color-font-light;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-height: 34px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
& > vn-icon {
|
&.notice {
|
||||||
@extend %clickable;
|
background-color: $color-notice-medium
|
||||||
float: right;
|
}
|
||||||
position: relative;
|
&.success {
|
||||||
transition-property: transform, background-color;
|
background-color: $color-success-medium;
|
||||||
transition-duration: 150ms;
|
}
|
||||||
margin: -5px;
|
&.warning {
|
||||||
margin-left: 4px;
|
background-color: $color-main-medium;
|
||||||
padding: 1px;
|
}
|
||||||
border-radius: 50%;
|
&.alert {
|
||||||
|
background-color: lighten($color-alert, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover vn-icon.pit {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.expanded {
|
& > .change-detail {
|
||||||
text-overflow: initial;
|
overflow: hidden;
|
||||||
white-space: initial;
|
background-color: rgba(255, 255, 255, .05);
|
||||||
|
color: $color-font-light;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-height: 34px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
& > vn-icon {
|
& > vn-icon {
|
||||||
transform: rotate(180deg);
|
@extend %clickable;
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
transition-property: transform, background-color;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
margin: -5px;
|
||||||
|
margin-left: 4px;
|
||||||
|
padding: 1px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
&.expanded {
|
||||||
|
text-overflow: initial;
|
||||||
|
white-space: initial;
|
||||||
|
|
||||||
|
& > vn-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .no-changes {
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > .no-changes {
|
}
|
||||||
font-style: italic;
|
.id-value {
|
||||||
|
font-size: .9rem;
|
||||||
|
color: $color-font-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vn-log-instance {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& > .loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
& > .instance {
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 400px;
|
||||||
|
|
||||||
|
& > .header {
|
||||||
|
background-color: $color-main;
|
||||||
|
color: $color-font-dark;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
& > .change-detail {
|
||||||
|
color: $color-font-light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vn-log-value > .id-value {
|
|
||||||
font-size: .9rem;
|
|
||||||
color: $color-font-secondary;
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,13 +24,23 @@ export default class Controller {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
})
|
})
|
||||||
.catch(req => {
|
.catch(req => {
|
||||||
this.loading = false;
|
if (req?.data?.error?.code === 'REQUIRES_2FA') {
|
||||||
this.password = '';
|
this.outLayout.login = {
|
||||||
this.focusUser();
|
user: this.user,
|
||||||
|
password: this.password,
|
||||||
|
remember: this.remember
|
||||||
|
};
|
||||||
|
this.$state.go('validate-email');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const err = req.data?.error;
|
const err = req.data?.error;
|
||||||
if (err?.code == 'passExpired')
|
if (err?.code == 'passExpired')
|
||||||
this.$state.go('change-password', err.details.token);
|
this.$state.go('change-password', err.details.token);
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
this.password = '';
|
||||||
|
this.focusUser();
|
||||||
throw req;
|
throw req;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,5 +54,8 @@ Controller.$inject = ['$scope', '$element', '$state', 'vnAuth'];
|
||||||
|
|
||||||
ngModule.vnComponent('vnLogin', {
|
ngModule.vnComponent('vnLogin', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller
|
controller: Controller,
|
||||||
|
require: {
|
||||||
|
outLayout: '^vnOutLayout'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<h5 class="vn-mb-md vn-mt-lg" translate>Enter verification code</h5>
|
||||||
|
<span translate>Please enter the verification code that we have sent to your email address within 5 minutes</span>
|
||||||
|
<vn-textfield label="Code" ng-model="$ctrl.code" type="text" vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<div class="footer">
|
||||||
|
<vn-submit label="Validate" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,43 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default class Controller {
|
||||||
|
constructor($scope, $element, vnAuth, $state) {
|
||||||
|
Object.assign(this, {
|
||||||
|
$scope,
|
||||||
|
$element,
|
||||||
|
vnAuth,
|
||||||
|
user: localStorage.getItem('lastUser'),
|
||||||
|
remember: true,
|
||||||
|
$state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
this.loginData = this.outLayout.login;
|
||||||
|
if (!this.loginData)
|
||||||
|
this.$state.go('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
this.loading = true;
|
||||||
|
this.vnAuth.validateCode(this.loginData.user, this.loginData.password, this.code, this.loginData.remember)
|
||||||
|
.then(() => {
|
||||||
|
localStorage.setItem('lastUser', this.user);
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.loading = false;
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controller.$inject = ['$scope', '$element', 'vnAuth', '$state'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnValidateEmail', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
require: {
|
||||||
|
outLayout: '^vnOutLayout'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
Validate email auth: Autenticar email
|
||||||
|
Enter verification code: Introduce código de verificación
|
||||||
|
Code: Código
|
||||||
|
Please enter the verification code that we have sent to your email address within 5 minutes: Por favor, introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
|
||||||
|
Validate: Validar
|
|
@ -0,0 +1,24 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
vn-validate-email {
|
||||||
|
.footer {
|
||||||
|
margin-top: 32px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
& > .vn-submit {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .spinner-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
top: 3px;
|
||||||
|
right: -8px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ function config($stateProvider, $urlRouterProvider) {
|
||||||
abstract: true,
|
abstract: true,
|
||||||
template: '<vn-layout></vn-layout>',
|
template: '<vn-layout></vn-layout>',
|
||||||
resolve: {
|
resolve: {
|
||||||
config: ['vnConfig', vnConfig => vnConfig.initialize()]
|
config: ['vnConfig', vnConfig => vnConfig.initialize()],
|
||||||
|
token: ['vnToken', vnToken => vnToken.fetchConfig()]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('outLayout', {
|
.state('outLayout', {
|
||||||
|
@ -36,9 +37,15 @@ function config($stateProvider, $urlRouterProvider) {
|
||||||
description: 'Reset password',
|
description: 'Reset password',
|
||||||
template: '<vn-reset-password></vn-reset-password>'
|
template: '<vn-reset-password></vn-reset-password>'
|
||||||
})
|
})
|
||||||
|
.state('validate-email', {
|
||||||
|
parent: 'outLayout',
|
||||||
|
url: '/validate-email',
|
||||||
|
description: 'Validate email auth',
|
||||||
|
template: '<vn-validate-email></vn-validate-email>'
|
||||||
|
})
|
||||||
.state('change-password', {
|
.state('change-password', {
|
||||||
parent: 'outLayout',
|
parent: 'outLayout',
|
||||||
url: '/change-password?id&userId',
|
url: '/change-password?id&userId&twoFactor',
|
||||||
description: 'Change password',
|
description: 'Change password',
|
||||||
template: '<vn-change-password></vn-change-password>'
|
template: '<vn-change-password></vn-change-password>'
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
const NotFoundError = require('vn-loopback/util/not-found-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('pitInstance', {
|
||||||
|
description: 'Gets the status of instance at specific point in time',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'integer',
|
||||||
|
description: 'The log id',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: [Self],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/pitInstance`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.pitInstance = async function(id) {
|
||||||
|
const log = await Self.findById(id, {
|
||||||
|
fields: [
|
||||||
|
'changedModel',
|
||||||
|
'changedModelId',
|
||||||
|
'creationDate'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (!log)
|
||||||
|
throw new NotFoundError();
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
changedModel: log.changedModel,
|
||||||
|
changedModelId: log.changedModelId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch creation and all update logs for record up to requested log
|
||||||
|
|
||||||
|
const createdWhere = {
|
||||||
|
action: 'insert',
|
||||||
|
creationDate: {lte: log.creationDate}
|
||||||
|
};
|
||||||
|
const createdLog = await Self.findOne({
|
||||||
|
fields: ['id', 'creationDate', 'newInstance'],
|
||||||
|
where: Object.assign(createdWhere, where),
|
||||||
|
order: 'creationDate DESC, id DESC'
|
||||||
|
});
|
||||||
|
|
||||||
|
const instance = {};
|
||||||
|
|
||||||
|
let logsWhere = {
|
||||||
|
action: 'update'
|
||||||
|
};
|
||||||
|
if (createdLog) {
|
||||||
|
Object.assign(instance, createdLog.newInstance);
|
||||||
|
Object.assign(logsWhere, {
|
||||||
|
creationDate: {between: [
|
||||||
|
createdLog.creationDate,
|
||||||
|
log.creationDate
|
||||||
|
]},
|
||||||
|
id: {between: [
|
||||||
|
Math.min(id, createdLog.id),
|
||||||
|
Math.max(id, createdLog.id)
|
||||||
|
]}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.assign(logsWhere, {
|
||||||
|
creationDate: {lte: log.creationDate},
|
||||||
|
id: {lte: id}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const logs = await Self.find({
|
||||||
|
fields: ['newInstance'],
|
||||||
|
where: Object.assign(logsWhere, where),
|
||||||
|
order: 'creationDate, id'
|
||||||
|
});
|
||||||
|
if (!logs.length && !createdLog)
|
||||||
|
throw new NotFoundError('No logs found for record');
|
||||||
|
|
||||||
|
// Merge all logs in order into one instance
|
||||||
|
|
||||||
|
for (const log of logs)
|
||||||
|
Object.assign(instance, log.newInstance);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
};
|
|
@ -39,7 +39,7 @@ module.exports = Self => {
|
||||||
return [html, 'text/html', `filename=${fileName}.pdf"`];
|
return [html, 'text/html', `filename=${fileName}.pdf"`];
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.sendTemplate = async function(ctx, templateName) {
|
Self.sendTemplate = async function(ctx, templateName, force) {
|
||||||
const args = Object.assign({}, ctx.args);
|
const args = Object.assign({}, ctx.args);
|
||||||
const params = {
|
const params = {
|
||||||
recipient: args.recipient,
|
recipient: args.recipient,
|
||||||
|
@ -52,6 +52,6 @@ module.exports = Self => {
|
||||||
|
|
||||||
const email = new Email(templateName, params);
|
const email = new Email(templateName, params);
|
||||||
|
|
||||||
return email.send();
|
return email.send({force: force});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ module.exports = function(Self) {
|
||||||
Self.super_.setup.call(this);
|
Self.super_.setup.call(this);
|
||||||
require('../methods/log/editors')(this);
|
require('../methods/log/editors')(this);
|
||||||
require('../methods/log/models')(this);
|
require('../methods/log/models')(this);
|
||||||
|
require('../methods/log/pitInstance')(this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -147,14 +147,12 @@
|
||||||
"Receipt's bank was not found": "Receipt's bank was not found",
|
"Receipt's bank was not found": "Receipt's bank was not found",
|
||||||
"This receipt was not compensated": "This receipt was not compensated",
|
"This receipt was not compensated": "This receipt was not compensated",
|
||||||
"Client's email was not found": "Client's email was not found",
|
"Client's email was not found": "Client's email was not found",
|
||||||
"Tickets with associated refunds": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº {{id}}",
|
"Tickets with associated refunds": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº %d",
|
||||||
"It is not possible to modify tracked sales": "It is not possible to modify tracked sales",
|
"It is not possible to modify tracked sales": "It is not possible to modify tracked sales",
|
||||||
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
|
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
|
||||||
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
|
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
|
||||||
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
|
|
||||||
"Warehouse inventory not set": "Almacén inventario no está establecido",
|
"Warehouse inventory not set": "Almacén inventario no está establecido",
|
||||||
"Component cost not set": "Componente coste no está estabecido",
|
"Component cost not set": "Componente coste no está estabecido",
|
||||||
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2",
|
|
||||||
"Description cannot be blank": "Description cannot be blank",
|
"Description cannot be blank": "Description cannot be blank",
|
||||||
"company": "Company",
|
"company": "Company",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
|
@ -175,6 +173,10 @@
|
||||||
"Pass expired": "The password has expired, change it from Salix",
|
"Pass expired": "The password has expired, change it from Salix",
|
||||||
"Can't transfer claimed sales": "Can't transfer claimed sales",
|
"Can't transfer claimed sales": "Can't transfer claimed sales",
|
||||||
"Invalid quantity": "Invalid quantity",
|
"Invalid quantity": "Invalid quantity",
|
||||||
"Failed to upload delivery note": "Error to upload delivery note {{id}}",
|
"Failed to upload delivery note": "Error to upload delivery note {{id}}",
|
||||||
"Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address"
|
"Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address",
|
||||||
|
"The renew period has not been exceeded": "The renew period has not been exceeded",
|
||||||
|
"You can not use the same password": "You can not use the same password",
|
||||||
|
"Valid priorities": "Valid priorities: %d",
|
||||||
|
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,7 +177,6 @@
|
||||||
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
|
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
|
||||||
"Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
|
"Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
|
||||||
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
|
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
|
||||||
"Sorts whole route": "Reordena ruta entera",
|
|
||||||
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*",
|
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*",
|
||||||
"New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*",
|
"New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*",
|
||||||
"Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío",
|
"Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío",
|
||||||
|
@ -269,7 +268,7 @@
|
||||||
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
|
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
|
||||||
"Warehouse inventory not set": "El almacén inventario no está establecido",
|
"Warehouse inventory not set": "El almacén inventario no está establecido",
|
||||||
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
|
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
|
||||||
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
|
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº %d",
|
||||||
"Not exist this branch": "La rama no existe",
|
"Not exist this branch": "La rama no existe",
|
||||||
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
|
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
|
||||||
"Collection does not exist": "La colección no existe",
|
"Collection does not exist": "La colección no existe",
|
||||||
|
@ -277,6 +276,8 @@
|
||||||
"Insert a date range": "Inserte un rango de fechas",
|
"Insert a date range": "Inserte un rango de fechas",
|
||||||
"Added observation": "{{user}} añadió esta observacion: {{text}}",
|
"Added observation": "{{user}} añadió esta observacion: {{text}}",
|
||||||
"Comment added to client": "Observación añadida al cliente {{clientFk}}",
|
"Comment added to client": "Observación añadida al cliente {{clientFk}}",
|
||||||
|
"Invalid auth code": "Código de verificación incorrecto",
|
||||||
|
"Invalid or expired verification code": "Código de verificación incorrecto o expirado",
|
||||||
"Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen",
|
"Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen",
|
||||||
"company": "Compañía",
|
"company": "Compañía",
|
||||||
"country": "País",
|
"country": "País",
|
||||||
|
@ -294,9 +295,15 @@
|
||||||
"Invalid NIF for VIES": "Invalid NIF for VIES",
|
"Invalid NIF for VIES": "Invalid NIF for VIES",
|
||||||
"Ticket does not exist": "Este ticket no existe",
|
"Ticket does not exist": "Este ticket no existe",
|
||||||
"Ticket is already signed": "Este ticket ya ha sido firmado",
|
"Ticket is already signed": "Este ticket ya ha sido firmado",
|
||||||
|
"Authentication failed": "Autenticación fallida",
|
||||||
|
"You can't use the same password": "No puedes usar la misma contraseña",
|
||||||
|
"You can only add negative amounts in refund tickets": "Solo se puede añadir cantidades negativas en tickets abono",
|
||||||
"Fecha fuera de rango": "Fecha fuera de rango",
|
"Fecha fuera de rango": "Fecha fuera de rango",
|
||||||
"Error while generating PDF": "Error al generar PDF",
|
"Error while generating PDF": "Error al generar PDF",
|
||||||
"Error when sending mail to client": "Error al enviar el correo al cliente",
|
"Error when sending mail to client": "Error al enviar el correo al cliente",
|
||||||
"Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico",
|
"Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico",
|
||||||
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado"
|
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
|
||||||
|
"Valid priorities": "Prioridades válidas: %d",
|
||||||
|
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
|
||||||
|
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = class ForbiddenError extends Error {
|
||||||
|
constructor(message, code, ...translateArgs) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ForbiddenError';
|
||||||
|
this.statusCode = 403;
|
||||||
|
this.code = code;
|
||||||
|
this.translateArgs = translateArgs;
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,15 +1,12 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('changePassword', {
|
Self.remoteMethodCtx('changePassword', {
|
||||||
description: 'Changes the user password',
|
description: 'Changes the user password',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
|
accessScopes: ['changePassword'],
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
description: 'The user id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}, {
|
|
||||||
arg: 'oldPassword',
|
arg: 'oldPassword',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The old password',
|
description: 'The old password',
|
||||||
|
@ -19,15 +16,35 @@ module.exports = Self => {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The new password',
|
description: 'The new password',
|
||||||
required: true
|
required: true
|
||||||
|
}, {
|
||||||
|
arg: 'code',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The 2FA code'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
http: {
|
http: {
|
||||||
path: `/:id/changePassword`,
|
path: `/change-password`,
|
||||||
verb: 'PATCH'
|
verb: 'PATCH'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.changePassword = async function(id, oldPassword, newPassword) {
|
Self.changePassword = async function(ctx, oldPassword, newPassword, code, options) {
|
||||||
await Self.app.models.VnUser.changePassword(id, oldPassword, newPassword);
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
|
||||||
|
const myOptions = {};
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const {VnUser} = Self.app.models;
|
||||||
|
const user = await VnUser.findById(userId, {fields: ['name', 'twoFactor']}, myOptions);
|
||||||
|
await user.hasPassword(oldPassword);
|
||||||
|
|
||||||
|
if (oldPassword == newPassword)
|
||||||
|
throw new UserError(`You can not use the same password`);
|
||||||
|
|
||||||
|
if (user.twoFactor)
|
||||||
|
await VnUser.validateCode(user.name, code, myOptions);
|
||||||
|
|
||||||
|
await VnUser.changePassword(userId, oldPassword, newPassword, myOptions);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('login', {
|
Self.remoteMethodCtx('login', {
|
||||||
description: 'Login a user with username/email and password',
|
description: 'Login a user with username/email and password',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
|
@ -23,5 +23,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.login = async(user, password) => Self.app.models.VnUser.signIn(user, password);
|
Self.login = async(ctx, user, password, options) => Self.app.models.VnUser.signIn(ctx, user, password, options);
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.setPassword = async function(id, newPassword) {
|
Self.setPassword = async function(id, newPassword, options) {
|
||||||
await Self.app.models.VnUser.setPassword(id, newPassword);
|
options = typeof options == 'object' ? options : {};
|
||||||
|
await Self.app.models.VnUser.setPassword(id, newPassword, options);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,99 @@
|
||||||
const {models} = require('vn-loopback/server/server');
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('account changePassword()', () => {
|
describe('account changePassword()', () => {
|
||||||
it('should throw an error when old password is wrong', async() => {
|
const ctx = {req: {accessToken: {userId: 70}}};
|
||||||
let error;
|
const unauthCtx = {
|
||||||
try {
|
req: {
|
||||||
await models.Account.changePassword(1, 'wrongPassword', 'nightmare.9999');
|
headers: {},
|
||||||
} catch (e) {
|
connection: {
|
||||||
error = e.message;
|
remoteAddress: '127.0.0.1'
|
||||||
}
|
},
|
||||||
|
getLocale: () => 'en'
|
||||||
|
},
|
||||||
|
args: {}
|
||||||
|
};
|
||||||
|
describe('Without 2FA', () => {
|
||||||
|
it('should throw an error when old password is wrong', async() => {
|
||||||
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
|
||||||
expect(error).toContain('Invalid current password');
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toContain('Invalid current password');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when old and new password are the same', async() => {
|
||||||
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options);
|
||||||
|
await models.Account.changePassword(ctx, 'nightmare.9999', 'nightmare.9999', null, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toContain('You can not use the same password');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change password', async() => {
|
||||||
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
expect(e).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change password', async() => {
|
describe('With 2FA', () => {
|
||||||
try {
|
it('should change password when code is correct', async() => {
|
||||||
await models.Account.changePassword(70, 'nightmare', 'nightmare.9999');
|
const tx = await models.Account.beginTransaction({});
|
||||||
} catch (e) {
|
const yesterday = Date.vnNew();
|
||||||
expect(e).toBeUndefined();
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
}
|
|
||||||
|
const options = {transaction: tx};
|
||||||
|
try {
|
||||||
|
await models.VnUser.updateAll(
|
||||||
|
{id: 70},
|
||||||
|
{
|
||||||
|
twoFactor: 'email',
|
||||||
|
passExpired: yesterday
|
||||||
|
}
|
||||||
|
, options);
|
||||||
|
await models.VnUser.signIn(unauthCtx, 'trainee', 'nightmare', options);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message != 'Pass expired')
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options);
|
||||||
|
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
expect(e).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,8 +8,18 @@ describe('Account setPassword()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update password when it passes requirements', async() => {
|
it('should update password when it passes requirements', async() => {
|
||||||
let req = models.Account.setPassword(1, 'Very$ecurePa22.');
|
const tx = await models.Account.beginTransaction({});
|
||||||
|
|
||||||
await expectAsync(req).toBeResolved();
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
await models.Account.setPassword(1, 'Very$ecurePa22.', options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,13 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$authenticated",
|
"principalId": "$authenticated",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "changePassword",
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,7 @@
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
icon="delete"
|
icon="delete"
|
||||||
translate-attr="{title: 'Unsubscribe'}"
|
translate-attr="{title: 'Unsubscribe'}"
|
||||||
ng-click="removeConfirm.show(row)"
|
ng-click="removeConfirm.show(row)">
|
||||||
vn-acl="itManagement"
|
|
||||||
vn-acl-action="remove">
|
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</vn-item-section>
|
</vn-item-section>
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
@ -32,9 +30,7 @@
|
||||||
translate-attr="{title: 'Add'}"
|
translate-attr="{title: 'Add'}"
|
||||||
vn-bind="+"
|
vn-bind="+"
|
||||||
ng-click="$ctrl.onAddClick()"
|
ng-click="$ctrl.onAddClick()"
|
||||||
fixed-bottom-right
|
fixed-bottom-right>
|
||||||
vn-acl="itManagement"
|
|
||||||
vn-acl-action="remove">
|
|
||||||
</vn-float-button>
|
</vn-float-button>
|
||||||
<vn-dialog
|
<vn-dialog
|
||||||
vn-id="dialog"
|
vn-id="dialog"
|
||||||
|
|
|
@ -21,12 +21,11 @@ export default class Controller extends Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddClick() {
|
onAddClick() {
|
||||||
this.addData = {account: this.$params.id};
|
|
||||||
this.$.dialog.show();
|
this.$.dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddSave() {
|
onAddSave() {
|
||||||
return this.$http.post(`MailAliasAccounts`, this.addData)
|
return this.$http.post(`VnUsers/${this.$params.id}/addAlias`, this.addData)
|
||||||
.then(() => this.refresh())
|
.then(() => this.refresh())
|
||||||
.then(() => this.vnApp.showSuccess(
|
.then(() => this.vnApp.showSuccess(
|
||||||
this.$t('Subscribed to alias!'))
|
this.$t('Subscribed to alias!'))
|
||||||
|
@ -34,11 +33,12 @@ export default class Controller extends Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(row) {
|
onRemove(row) {
|
||||||
return this.$http.delete(`MailAliasAccounts/${row.id}`)
|
const params = {
|
||||||
.then(() => {
|
mailAlias: row.mailAlias
|
||||||
this.$.data.splice(this.$.data.indexOf(row), 1);
|
};
|
||||||
this.vnApp.showSuccess(this.$t('Unsubscribed from alias!'));
|
return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params)
|
||||||
});
|
.then(() => this.refresh())
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ describe('component vnUserAliases', () => {
|
||||||
describe('onAddSave()', () => {
|
describe('onAddSave()', () => {
|
||||||
it('should add the new row', () => {
|
it('should add the new row', () => {
|
||||||
controller.addData = {account: 1};
|
controller.addData = {account: 1};
|
||||||
|
controller.$params = {id: 1};
|
||||||
|
|
||||||
$httpBackend.expectPOST('MailAliasAccounts').respond();
|
$httpBackend.expectPOST('VnUsers/1/addAlias').respond();
|
||||||
$httpBackend.expectGET('MailAliasAccounts').respond('foo');
|
$httpBackend.expectGET('MailAliasAccounts').respond('foo');
|
||||||
controller.onAddSave();
|
controller.onAddSave();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
@ -41,12 +42,14 @@ describe('component vnUserAliases', () => {
|
||||||
{id: 1, alias: 'foo'},
|
{id: 1, alias: 'foo'},
|
||||||
{id: 2, alias: 'bar'}
|
{id: 2, alias: 'bar'}
|
||||||
];
|
];
|
||||||
|
controller.$params = {id: 1};
|
||||||
|
|
||||||
$httpBackend.expectDELETE('MailAliasAccounts/1').respond();
|
$httpBackend.expectPOST('VnUsers/1/removeAlias').respond();
|
||||||
|
$httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]);
|
||||||
controller.onRemove(controller.$.data[0]);
|
controller.onRemove(controller.$.data[0]);
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]);
|
expect(controller.$.data).toEqual({id: 2, alias: 'bar'});
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -77,14 +77,6 @@ module.exports = Self => {
|
||||||
if (salesPersonId)
|
if (salesPersonId)
|
||||||
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
|
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
|
||||||
|
|
||||||
await models.ClaimLog.create({
|
|
||||||
originFk: args.id,
|
|
||||||
userFk: userId,
|
|
||||||
action: 'insert',
|
|
||||||
description: 'Claim-pickup-order sent',
|
|
||||||
changedModel: 'Mail'
|
|
||||||
});
|
|
||||||
|
|
||||||
const email = new Email('claim-pickup-order', params);
|
const email = new Email('claim-pickup-order', params);
|
||||||
|
|
||||||
return email.send();
|
return email.send();
|
||||||
|
|
|
@ -95,7 +95,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
// When claimState has been changed
|
// When claimState has been changed
|
||||||
if (args.claimStateFk) {
|
if (args.claimStateFk) {
|
||||||
const newState = await models.ClaimState.findById(args.claimStateFk, null, options);
|
const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions);
|
||||||
if (newState.hasToNotify) {
|
if (newState.hasToNotify) {
|
||||||
if (newState.code == 'incomplete')
|
if (newState.code == 'incomplete')
|
||||||
notifyStateChange(ctx, salesPerson.id, claim, newState.code);
|
notifyStateChange(ctx, salesPerson.id, claim, newState.code);
|
||||||
|
|
|
@ -19,9 +19,6 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.confirmTransaction = async(ctx, id, options) => {
|
Self.confirmTransaction = async(ctx, id, options) => {
|
||||||
const models = Self.app.models;
|
|
||||||
const userId = ctx.req.accessToken.userId;
|
|
||||||
|
|
||||||
let tx;
|
let tx;
|
||||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||||
|
|
||||||
|
@ -34,29 +31,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const oldTpvTransaction = await models.TpvTransaction.findById(id, null, myOptions);
|
|
||||||
|
|
||||||
const confirm = await Self.rawSql('CALL hedera.tpvTransaction_confirmById(?)', [id], myOptions);
|
const confirm = await Self.rawSql('CALL hedera.tpvTransaction_confirmById(?)', [id], myOptions);
|
||||||
|
|
||||||
const tpvTransaction = await models.TpvTransaction.findById(id, null, myOptions);
|
|
||||||
|
|
||||||
const oldInstance = {status: oldTpvTransaction.status};
|
|
||||||
const newInstance = {status: tpvTransaction.status};
|
|
||||||
|
|
||||||
const logRecord = {
|
|
||||||
originFk: tpvTransaction.clientFk,
|
|
||||||
userFk: userId,
|
|
||||||
action: 'update',
|
|
||||||
changedModel: 'TpvTransaction',
|
|
||||||
changedModelId: id,
|
|
||||||
oldInstance: oldInstance,
|
|
||||||
newInstance: newInstance
|
|
||||||
};
|
|
||||||
|
|
||||||
await models.ClientLog.create(logRecord, myOptions);
|
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return confirm;
|
return confirm;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (tx) await tx.rollback();
|
if (tx) await tx.rollback();
|
||||||
|
|
|
@ -30,34 +30,9 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.sendSms = async(ctx, id, destination, message, options) => {
|
Self.sendSms = async(ctx, id, destination, message) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const myOptions = {};
|
|
||||||
|
|
||||||
if (typeof options == 'object')
|
|
||||||
Object.assign(myOptions, options);
|
|
||||||
|
|
||||||
const userId = ctx.req.accessToken.userId;
|
|
||||||
|
|
||||||
const sms = await models.Sms.send(ctx, destination, message);
|
const sms = await models.Sms.send(ctx, destination, message);
|
||||||
const logRecord = {
|
|
||||||
originFk: id,
|
|
||||||
userFk: userId,
|
|
||||||
action: 'insert',
|
|
||||||
changedModel: 'sms',
|
|
||||||
newInstance: {
|
|
||||||
destinationFk: id,
|
|
||||||
destination: destination,
|
|
||||||
message: message,
|
|
||||||
statusCode: sms.statusCode,
|
|
||||||
status: sms.status
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const clientLog = await models.ClientLog.create(logRecord, myOptions);
|
|
||||||
|
|
||||||
sms.logId = clientLog.id;
|
|
||||||
|
|
||||||
return sms;
|
return sms;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@ module.exports = Self => {
|
||||||
const isAccount = await models.Account.findById(id);
|
const isAccount = await models.Account.findById(id);
|
||||||
|
|
||||||
if (isClient && !isAccount)
|
if (isClient && !isAccount)
|
||||||
await models.Account.setPassword(id, newPassword);
|
await models.VnUser.setPassword(id, newPassword);
|
||||||
else
|
else
|
||||||
throw new UserError(`Modifiable password only via recovery or by an administrator`);
|
throw new UserError(`Modifiable password only via recovery or by an administrator`);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,10 +13,7 @@ describe('client sendSms()', () => {
|
||||||
|
|
||||||
const sms = await models.Client.sendSms(ctx, id, destination, message, options);
|
const sms = await models.Client.sendSms(ctx, id, destination, message, options);
|
||||||
|
|
||||||
const createdLog = await models.ClientLog.findById(sms.logId, null, options);
|
expect(sms).toBeDefined();
|
||||||
const json = JSON.parse(JSON.stringify(createdLog.newInstance));
|
|
||||||
|
|
||||||
expect(json.message).toEqual(message);
|
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -144,20 +144,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
if (taxDataChecked && !hasSageData)
|
if (taxDataChecked && !hasSageData)
|
||||||
throw new UserError(`You need to fill sage information before you check verified data`);
|
throw new UserError(`You need to fill sage information before you check verified data`);
|
||||||
if (args.despiteOfClient) {
|
|
||||||
const logRecord = {
|
|
||||||
originFk: clientId,
|
|
||||||
userFk: userId,
|
|
||||||
action: 'update',
|
|
||||||
changedModel: 'Client',
|
|
||||||
changedModelId: clientId,
|
|
||||||
description: $t(`Client checked as validated despite of duplication`, {
|
|
||||||
clientId: args.despiteOfClient
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
await models.ClientLog.create(logRecord, myOptions);
|
|
||||||
}
|
|
||||||
// Remove unwanted properties
|
// Remove unwanted properties
|
||||||
delete args.ctx;
|
delete args.ctx;
|
||||||
delete args.id;
|
delete args.id;
|
||||||
|
|
|
@ -60,6 +60,7 @@ module.exports = Self => {
|
||||||
DISTINCT c.id clientFk,
|
DISTINCT c.id clientFk,
|
||||||
c.name clientName,
|
c.name clientName,
|
||||||
c.salesPersonFk,
|
c.salesPersonFk,
|
||||||
|
c.businessTypeFk,
|
||||||
u.name salesPersonName,
|
u.name salesPersonName,
|
||||||
d.amount,
|
d.amount,
|
||||||
co.created,
|
co.created,
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = Self => {
|
||||||
Self.validatesPresenceOf('street', {
|
Self.validatesPresenceOf('street', {
|
||||||
message: 'Street cannot be empty'
|
message: 'Street cannot be empty'
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.validatesPresenceOf('city', {
|
Self.validatesPresenceOf('city', {
|
||||||
message: 'City cannot be empty'
|
message: 'City cannot be empty'
|
||||||
});
|
});
|
||||||
|
@ -89,7 +89,7 @@ module.exports = Self => {
|
||||||
};
|
};
|
||||||
const country = await Self.app.models.Country.findOne(filter);
|
const country = await Self.app.models.Country.findOne(filter);
|
||||||
const code = country ? country.code.toLowerCase() : null;
|
const code = country ? country.code.toLowerCase() : null;
|
||||||
const countryCode = this.fi.toLowerCase().substring(0, 2);
|
const countryCode = this.fi?.toLowerCase().substring(0, 2);
|
||||||
|
|
||||||
if (!this.fi || !validateTin(this.fi, code) || (this.isVies && countryCode == code))
|
if (!this.fi || !validateTin(this.fi, code) || (this.isVies && countryCode == code))
|
||||||
err();
|
err();
|
||||||
|
@ -282,7 +282,7 @@ module.exports = Self => {
|
||||||
await Self.changeCredit(ctx, finalState, changes);
|
await Self.changeCredit(ctx, finalState, changes);
|
||||||
|
|
||||||
// Credit management changes
|
// Credit management changes
|
||||||
if (orgData?.rating != changes.rating || orgData?.recommendedCredit != changes.recommendedCredit)
|
if (changes?.rating || changes?.recommendedCredit)
|
||||||
await Self.changeCreditManagement(ctx, finalState, changes);
|
await Self.changeCreditManagement(ctx, finalState, changes);
|
||||||
|
|
||||||
const oldInstance = {};
|
const oldInstance = {};
|
||||||
|
@ -401,22 +401,32 @@ module.exports = Self => {
|
||||||
Self.changeCredit = async function changeCredit(ctx, finalState, changes) {
|
Self.changeCredit = async function changeCredit(ctx, finalState, changes) {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const userId = ctx.options.accessToken.userId;
|
const userId = ctx.options.accessToken.userId;
|
||||||
const accessToken = {req: {accessToken: ctx.options.accessToken} };
|
const accessToken = {req: {accessToken: ctx.options.accessToken}};
|
||||||
|
|
||||||
const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE');
|
const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE');
|
||||||
if (!canEditCredit) {
|
if (!canEditCredit) {
|
||||||
const lastCredit = await models.ClientCredit.findOne({
|
const lastCredit = await models.ClientCredit.findOne({
|
||||||
|
field: ['workerFk', 'amount'],
|
||||||
where: {
|
where: {
|
||||||
clientFk: finalState.id
|
clientFk: finalState.id
|
||||||
},
|
},
|
||||||
order: 'id DESC'
|
order: 'id DESC'
|
||||||
}, ctx.options);
|
}, ctx.options);
|
||||||
|
|
||||||
const lastAmount = lastCredit && lastCredit.amount;
|
if (lastCredit && lastCredit.amount == 0) {
|
||||||
const lastCreditIsNotEditable = !await models.ACL.checkAccessAcl(accessToken, 'Client', 'isNotEditableCredit', 'WRITE');
|
const zeroCreditEditor =
|
||||||
|
await models.ACL.checkAccessAcl(accessToken, 'Client', 'zeroCreditEditor', 'WRITE');
|
||||||
|
const lastCreditIsNotEditable =
|
||||||
|
await models.ACL.checkAccessAcl(
|
||||||
|
{req: {accessToken: {userId: lastCredit.workerFk}}},
|
||||||
|
'Client',
|
||||||
|
'zeroCreditEditor',
|
||||||
|
'WRITE'
|
||||||
|
);
|
||||||
|
|
||||||
if (lastAmount == 0 && lastCreditIsNotEditable)
|
if (lastCreditIsNotEditable && !zeroCreditEditor)
|
||||||
throw new UserError(`You can't change the credit set to zero from a financialBoss`);
|
throw new UserError(`You can't change the credit set to zero from a financialBoss`);
|
||||||
|
}
|
||||||
|
|
||||||
const creditLimits = await models.ClientCreditLimit.find({
|
const creditLimits = await models.ClientCreditLimit.find({
|
||||||
fields: ['roleFk'],
|
fields: ['roleFk'],
|
||||||
|
@ -479,21 +489,6 @@ module.exports = Self => {
|
||||||
hasChanges = oldData.name != changes.name || oldData.active != changes.active;
|
hasChanges = oldData.name != changes.name || oldData.active != changes.active;
|
||||||
|
|
||||||
if (!hasChanges) return;
|
if (!hasChanges) return;
|
||||||
|
|
||||||
const isClient = await Self.app.models.Client.count({id: oldData.id});
|
|
||||||
if (isClient) {
|
|
||||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
|
||||||
const userId = loopBackContext.active.accessToken.userId;
|
|
||||||
const logRecord = {
|
|
||||||
originFk: oldData.id,
|
|
||||||
userFk: userId,
|
|
||||||
action: 'update',
|
|
||||||
changedModel: 'VnUser',
|
|
||||||
oldInstance: {name: oldData.name, active: oldData.active},
|
|
||||||
newInstance: {name: changes.name, active: changes.active}
|
|
||||||
};
|
|
||||||
await Self.app.models.ClientLog.create(logRecord);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,22 +60,22 @@ describe('Client Model', () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
const context = {options};
|
const ctx = {options};
|
||||||
|
|
||||||
// Set credit to zero by a financialBoss
|
// Set credit to zero by a financialBoss
|
||||||
const financialBoss = await models.VnUser.findOne({
|
const financialBoss = await models.VnUser.findOne({
|
||||||
where: {name: 'financialBoss'}
|
where: {name: 'financialBoss'}
|
||||||
}, options);
|
}, options);
|
||||||
context.options.accessToken = {userId: financialBoss.id};
|
ctx.options.accessToken = {userId: financialBoss.id};
|
||||||
|
|
||||||
await models.Client.changeCredit(context, instance, {credit: 0});
|
await models.Client.changeCredit(ctx, instance, {credit: 0});
|
||||||
|
|
||||||
const salesAssistant = await models.VnUser.findOne({
|
const salesAssistant = await models.VnUser.findOne({
|
||||||
where: {name: 'salesAssistant'}
|
where: {name: 'salesAssistant'}
|
||||||
}, options);
|
}, options);
|
||||||
context.options.accessToken = {userId: salesAssistant.id};
|
ctx.options.accessToken = {userId: salesAssistant.id};
|
||||||
|
|
||||||
await models.Client.changeCredit(context, instance, {credit: 300});
|
await models.Client.changeCredit(ctx, instance, {credit: 300});
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -93,14 +93,14 @@ describe('Client Model', () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
const context = {options};
|
const ctx = {options};
|
||||||
|
|
||||||
const salesAssistant = await models.VnUser.findOne({
|
const salesAssistant = await models.VnUser.findOne({
|
||||||
where: {name: 'salesAssistant'}
|
where: {name: 'salesAssistant'}
|
||||||
}, options);
|
}, options);
|
||||||
context.options.accessToken = {userId: salesAssistant.id};
|
ctx.options.accessToken = {userId: salesAssistant.id};
|
||||||
|
|
||||||
await models.Client.changeCredit(context, instance, {credit: 99999});
|
await models.Client.changeCredit(ctx, instance, {credit: 99999});
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>{{::clientInforma.rating}}</vn-td>
|
<vn-td number>{{::clientInforma.rating}}</vn-td>
|
||||||
<vn-td>{{::clientInforma.recommendedCredit | currency: 'EUR': 2}}</vn-td>
|
<vn-td number>{{::clientInforma.recommendedCredit | currency: 'EUR'}}</vn-td>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-tbody>
|
</vn-tbody>
|
||||||
</vn-table>
|
</vn-table>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
filter="::$ctrl.filter"
|
filter="::$ctrl.filter"
|
||||||
limit="20"
|
limit="20"
|
||||||
order="amount DESC"
|
order="amount DESC"
|
||||||
data="defaulters"
|
data="$ctrl.defaulters"
|
||||||
on-data-change="$ctrl.reCheck()"
|
on-data-change="$ctrl.reCheck()"
|
||||||
auto-load="true">
|
auto-load="true">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="vn-pa-md">
|
<div class="vn-pa-md">
|
||||||
<vn-button
|
<vn-button
|
||||||
ng-show="$ctrl.checked.length > 0"
|
disabled="$ctrl.checked.length == 0"
|
||||||
ng-click="notesDialog.show()"
|
ng-click="notesDialog.show()"
|
||||||
name="notesDialog"
|
name="notesDialog"
|
||||||
vn-tooltip="Add observation"
|
vn-tooltip="Add observation"
|
||||||
|
@ -54,6 +54,9 @@
|
||||||
<th field="clientFk">
|
<th field="clientFk">
|
||||||
<span translate>Client</span>
|
<span translate>Client</span>
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
<span translate>Es trabajador</span>
|
||||||
|
</th>
|
||||||
<th field="salesPersonFk">
|
<th field="salesPersonFk">
|
||||||
<span translate>Comercial</span>
|
<span translate>Comercial</span>
|
||||||
</th>
|
</th>
|
||||||
|
@ -94,7 +97,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="defaulter in defaulters">
|
<tr ng-repeat="defaulter in $ctrl.defaulters">
|
||||||
<td shrink>
|
<td shrink>
|
||||||
<vn-check
|
<vn-check
|
||||||
ng-model="defaulter.checked"
|
ng-model="defaulter.checked"
|
||||||
|
@ -110,6 +113,12 @@
|
||||||
{{::defaulter.clientName}}
|
{{::defaulter.clientName}}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<vn-check
|
||||||
|
ng-model="defaulter.isWorker"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
title="{{::defaulter.salesPersonName}}"
|
title="{{::defaulter.salesPersonName}}"
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default class Controller extends Section {
|
||||||
constructor($element, $) {
|
constructor($element, $) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
this.defaulter = {};
|
this.defaulter = {};
|
||||||
|
this.defaulters = [];
|
||||||
this.checkedDefaulers = [];
|
this.checkedDefaulers = [];
|
||||||
|
|
||||||
this.smartTableOptions = {
|
this.smartTableOptions = {
|
||||||
|
@ -69,6 +70,18 @@ export default class Controller extends Section {
|
||||||
this.getBalanceDueTotal();
|
this.getBalanceDueTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set defaulters(value) {
|
||||||
|
if (!value || !value.length) return;
|
||||||
|
for (let defaulter of value)
|
||||||
|
defaulter.isWorker = defaulter.businessTypeFk === 'worker';
|
||||||
|
|
||||||
|
this._defaulters = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaulters() {
|
||||||
|
return this._defaulters;
|
||||||
|
}
|
||||||
|
|
||||||
get checked() {
|
get checked() {
|
||||||
const clients = this.$.model.data || [];
|
const clients = this.$.model.data || [];
|
||||||
const checkedLines = [];
|
const checkedLines = [];
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
<vn-label-value label="Channel"
|
<vn-label-value label="Channel"
|
||||||
value="{{$ctrl.summary.contactChannel.name}}">
|
value="{{$ctrl.summary.contactChannel.name}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Business type"
|
<vn-label-value label="Business type"
|
||||||
value="{{$ctrl.summary.businessType.description}}">
|
value="{{$ctrl.summary.businessType.description}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
|
@ -270,7 +270,7 @@
|
||||||
info="Invoices minus payments plus orders not yet invoiced">
|
info="Invoices minus payments plus orders not yet invoiced">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Credit"
|
<vn-label-value label="Credit"
|
||||||
value="{{$ctrl.summary.credit | currency: 'EUR':2 }} "
|
value="{{$ctrl.summary.credit | currency: 'EUR'}} "
|
||||||
ng-class="{alert: $ctrl.summary.credit > $ctrl.summary.creditInsurance ||
|
ng-class="{alert: $ctrl.summary.credit > $ctrl.summary.creditInsurance ||
|
||||||
($ctrl.summary.credit && $ctrl.summary.creditInsurance == null)}"
|
($ctrl.summary.credit && $ctrl.summary.creditInsurance == null)}"
|
||||||
info="Verdnatura's maximum risk">
|
info="Verdnatura's maximum risk">
|
||||||
|
@ -296,6 +296,9 @@
|
||||||
value="{{$ctrl.summary.rating}}"
|
value="{{$ctrl.summary.rating}}"
|
||||||
info="Value from 1 to 20. The higher the better value">
|
info="Value from 1 to 20. The higher the better value">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Recommended credit"
|
||||||
|
value="{{$ctrl.summary.recommendedCredit | currency: 'EUR'}}">
|
||||||
|
</vn-label-value>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
|
|
|
@ -8,7 +8,7 @@ vn-client-summary .summary {
|
||||||
}
|
}
|
||||||
|
|
||||||
vn-horizontal h4 .grafana:after {
|
vn-horizontal h4 .grafana:after {
|
||||||
content: 'contact_support';
|
font-family: 'salixfont' !important;
|
||||||
font-size: 17px;
|
content: "\e965";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
|
|
||||||
vn-entry-buy-index vn-card {
|
vn-entry-buy-index vn-card {
|
||||||
max-width: $width-xl;
|
max-width: $width-xl;
|
||||||
|
|
||||||
.dark-row {
|
.dark-row {
|
||||||
background-color: lighten($color-marginal, 10%);
|
background-color: lighten($color-marginal, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
thead tr {
|
thead tr {
|
||||||
border-left: 1px solid white;
|
border: 1px solid white;;
|
||||||
border-right: 1px solid white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:nth-child(1),
|
tbody tr:nth-child(1),
|
||||||
|
@ -22,7 +21,7 @@ vn-entry-buy-index vn-card {
|
||||||
tbody tr:nth-child(2) {
|
tbody tr:nth-child(2) {
|
||||||
border-bottom: 1px solid $color-spacer;
|
border-bottom: 1px solid $color-spacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody{
|
tbody{
|
||||||
border-bottom: 1px solid $color-spacer;
|
border-bottom: 1px solid $color-spacer;
|
||||||
}
|
}
|
||||||
|
@ -40,4 +39,4 @@ vn-entry-buy-index vn-card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$color-font-link-medium: lighten($color-font-link, 20%)
|
$color-font-link-medium: lighten($color-font-link, 20%)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue