diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb85e4ffe..76527ac83 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,29 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2330.01] - 2023-07-27
+
+### Added
+
+### Changed
+
+### 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
### Added
diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js
new file mode 100644
index 000000000..73cc705de
--- /dev/null
+++ b/back/methods/vn-user/sign-in.js
@@ -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);
+ }
+ };
+};
diff --git a/back/methods/vn-user/signIn.js b/back/methods/vn-user/signIn.js
deleted file mode 100644
index e52d68df5..000000000
--- a/back/methods/vn-user/signIn.js
+++ /dev/null
@@ -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, ttl: token.ttl};
- };
-};
diff --git a/back/methods/vn-user/specs/sign-in.spec.js b/back/methods/vn-user/specs/sign-in.spec.js
new file mode 100644
index 000000000..f4cad88b9
--- /dev/null
+++ b/back/methods/vn-user/specs/sign-in.spec.js
@@ -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');
+ });
+ });
+});
diff --git a/back/methods/vn-user/specs/signIn.spec.js b/back/methods/vn-user/specs/signIn.spec.js
deleted file mode 100644
index c3f4630c6..000000000
--- a/back/methods/vn-user/specs/signIn.spec.js
+++ /dev/null
@@ -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');
- });
- });
-});
diff --git a/back/methods/vn-user/specs/validate-auth.spec.js b/back/methods/vn-user/specs/validate-auth.spec.js
new file mode 100644
index 000000000..8018bd3e1
--- /dev/null
+++ b/back/methods/vn-user/specs/validate-auth.spec.js
@@ -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');
+ });
+ });
+});
diff --git a/back/methods/vn-user/validate-auth.js b/back/methods/vn-user/validate-auth.js
new file mode 100644
index 000000000..beab43417
--- /dev/null
+++ b/back/methods/vn-user/validate-auth.js
@@ -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);
+ };
+};
diff --git a/back/model-config.json b/back/model-config.json
index d945f3250..0e37bf527 100644
--- a/back/model-config.json
+++ b/back/model-config.json
@@ -1,7 +1,4 @@
{
- "AccountingType": {
- "dataSource": "vn"
- },
"AccessTokenConfig": {
"dataSource": "vn",
"options": {
@@ -10,6 +7,12 @@
}
}
},
+ "AccountingType": {
+ "dataSource": "vn"
+ },
+ "AuthCode": {
+ "dataSource": "vn"
+ },
"Bank": {
"dataSource": "vn"
},
diff --git a/back/models/auth-code.json b/back/models/auth-code.json
new file mode 100644
index 000000000..b6a89115f
--- /dev/null
+++ b/back/models/auth-code.json
@@ -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"
+ }
+ }
+}
diff --git a/back/models/vn-user.js b/back/models/vn-user.js
index b58395acc..a7ce12073 100644
--- a/back/models/vn-user.js
+++ b/back/models/vn-user.js
@@ -5,11 +5,12 @@ const {Email} = require('vn-print');
module.exports = function(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/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self);
+ require('../methods/vn-user/validate-auth')(Self);
require('../methods/vn-user/renew-token')(Self);
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
@@ -111,6 +112,18 @@ module.exports = function(Self) {
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;
Self.prototype.setPassword = async function(newPassword, options, cb) {
if (cb === undefined && typeof options === 'function') {
@@ -143,8 +156,9 @@ module.exports = function(Self) {
}
};
- Self.sharedClass._methods.find(method => method.name == 'changePassword')
- .accessScopes = ['change-password'];
+ Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls =
+ Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
+ .filter(acl => acl.property != 'changePassword');
// FIXME: https://redmine.verdnatura.es/issues/5761
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
diff --git a/back/models/vn-user.json b/back/models/vn-user.json
index 61e42f77a..9131c9134 100644
--- a/back/models/vn-user.json
+++ b/back/models/vn-user.json
@@ -59,7 +59,10 @@
},
"passExpired": {
"type": "date"
- }
+ },
+ "twoFactor": {
+ "type": "string"
+ }
},
"relations": {
"role": {
@@ -111,6 +114,13 @@
"principalId": "$authenticated",
"permission": "ALLOW"
},
+ {
+ "property": "validateAuth",
+ "accessType": "EXECUTE",
+ "principalType": "ROLE",
+ "principalId": "$everyone",
+ "permission": "ALLOW"
+ },
{
"property": "privileges",
"accessType": "*",
diff --git a/db/changes/232801/00-authCode.sql b/db/changes/232801/00-authCode.sql
new file mode 100644
index 000000000..a256db43f
--- /dev/null
+++ b/db/changes/232801/00-authCode.sql
@@ -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
+);
diff --git a/db/changes/232801/00-client_create.sql b/db/changes/232801/00-client_create.sql
new file mode 100644
index 000000000..d21094dad
--- /dev/null
+++ b/db/changes/232801/00-client_create.sql
@@ -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 ;
diff --git a/db/changes/232801/00-client_create2.sql b/db/changes/232801/00-client_create2.sql
new file mode 100644
index 000000000..f2e660351
--- /dev/null
+++ b/db/changes/232801/00-client_create2.sql
@@ -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;
diff --git a/db/changes/232801/00-department.sql b/db/changes/232801/00-department.sql
new file mode 100644
index 000000000..3dcb8501d
--- /dev/null
+++ b/db/changes/232801/00-department.sql
@@ -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 ;
diff --git a/db/changes/232801/00-user.sql b/db/changes/232801/00-user.sql
new file mode 100644
index 000000000..376b3dbb1
--- /dev/null
+++ b/db/changes/232801/00-user.sql
@@ -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';
diff --git a/db/changes/233001/.gitkeep b/db/changes/233001/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/db/changes/233001/00-ticketWeight.sql b/db/changes/233001/00-ticketWeight.sql
new file mode 100644
index 000000000..0b727b434
--- /dev/null
+++ b/db/changes/233001/00-ticketWeight.sql
@@ -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;
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index a5cc2ddbe..172c7943a 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -77,7 +77,10 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `
ORDER BY 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`)
VALUES
@@ -382,9 +385,16 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
(1103, 0, 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
- (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`)
VALUES
@@ -700,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),
(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
- (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)),
- (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)),
- (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)),
- (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)),
- (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)),
- (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)),
- (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()),
- (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()),
- (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()),
- (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()),
- (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()),
- (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()),
- (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()),
- (14, 1, 2, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 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()),
- (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()),
- (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()),
- (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)),
- (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()),
- (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)),
- (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)),
- (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)),
- (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()),
- (24 ,NULL, 8, 1, 7, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 5, 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()),
- (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()),
- (27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, 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()),
- (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()),
- (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()),
- (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()),
- (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());
+ (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, 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, 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, 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, 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, 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(), 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(), 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(), 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(), 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(), 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(), 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(), NULL),
+ (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(), 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(), 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(), 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), 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(), 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), 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), 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), 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(), NULL),
+ (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(), 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(), NULL),
+ (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(), 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(), 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(), 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(), 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(), NULL);
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
@@ -824,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),
(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`)
VALUES
(1, 1102, 442, '1-1', util.VN_CURDATE(), 2);
@@ -2857,8 +2861,8 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
- ('lilium', 'dev', 'http://localhost:9000/#/'),
- ('salix', 'dev', 'http://localhost:5000/#!/');
+ ('lilium', 'development', 'http://localhost:9000/#/'),
+ ('salix', 'development', 'http://localhost:5000/#!/');
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
VALUES
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index d4a8a316f..977c8a837 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -547,6 +547,7 @@ export default {
moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]',
moreMenuRegenerateInvoice: '.vn-menu [name="regenerateInvoice"]',
moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]',
+ moreMenuSMSOptions: '.vn-menu [name="smsOptions"]',
moreMenuPaymentSMS: '.vn-menu [name="sendPaymentSms"]',
moreMenuSendImportSms: '.vn-menu [name="sendImportSms"]',
SMStext: 'textarea[name="message"]',
@@ -894,6 +895,18 @@ export default {
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: {
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
@@ -901,6 +914,13 @@ export default {
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
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: {
addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]',
note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]',
diff --git a/e2e/paths/01-salix/05_changePassword.spec.js b/e2e/paths/01-salix/05_changePassword.spec.js
index 6e4cfb7f3..950f773dd 100644
--- a/e2e/paths/01-salix/05_changePassword.spec.js
+++ b/e2e/paths/01-salix/05_changePassword.spec.js
@@ -16,6 +16,7 @@ describe('ChangePassword path', async() => {
await browser.close();
});
+ const badPassword = 'badpass';
const oldPassword = 'nightmare';
const newPassword = 'newPass.1234';
describe('Bad login', async() => {
@@ -37,13 +38,22 @@ describe('ChangePassword path', async() => {
expect(message.text).toContain('Invalid current password');
// 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, {
oldPassword: oldPassword,
newPassword: 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
message = await page.sendForm($.form, {
diff --git a/e2e/paths/03-worker/01-department/01_summary.spec.js b/e2e/paths/03-worker/01-department/01_summary.spec.js
new file mode 100644
index 000000000..e4bf8fc2d
--- /dev/null
+++ b/e2e/paths/03-worker/01-department/01_summary.spec.js
@@ -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('-');
+ });
+});
diff --git a/e2e/paths/03-worker/01-department/02-basicData.spec.js b/e2e/paths/03-worker/01-department/02-basicData.spec.js
new file mode 100644
index 000000000..219d1426c
--- /dev/null
+++ b/e2e/paths/03-worker/01-department/02-basicData.spec.js
@@ -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);
+ });
+});
diff --git a/e2e/paths/05-ticket/12_descriptor.spec.js b/e2e/paths/05-ticket/12_descriptor.spec.js
index ca6fb8290..95a114c45 100644
--- a/e2e/paths/05-ticket/12_descriptor.spec.js
+++ b/e2e/paths/05-ticket/12_descriptor.spec.js
@@ -124,6 +124,7 @@ describe('Ticket descriptor path', () => {
describe('SMS', () => {
it('should send the payment SMS using the descriptor menu', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
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() => {
- await page.waitToClick(selectors.ticketDescriptor.moreMenu);
- await page.waitForContentLoaded();
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms);
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 144);
diff --git a/front/core/services/auth.js b/front/core/services/auth.js
index 92ff4b061..844a5145d 100644
--- a/front/core/services/auth.js
+++ b/front/core/services/auth.js
@@ -24,7 +24,7 @@ export default class Auth {
initialize() {
let criteria = {
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);
}
};
@@ -60,7 +60,25 @@ export default class Auth {
};
const now = new Date();
- return this.$http.post('VnUsers/signIn', params)
+ return this.$http.post('VnUsers/sign-in', params).then(
+ json => this.onLoginOk(json, now, remember));
+ }
+
+ validateCode(user, password, code, 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));
}
diff --git a/front/core/services/token.js b/front/core/services/token.js
index 8f9f80e5c..c4b644a89 100644
--- a/front/core/services/token.js
+++ b/front/core/services/token.js
@@ -34,7 +34,6 @@ export default class Token {
remember
});
this.vnInterceptor.setToken(token);
-
try {
if (remember)
this.setStorage(localStorage, token, created, ttl);
diff --git a/front/salix/components/change-password/index.html b/front/salix/components/change-password/index.html
index 8d338d411..04f66976e 100644
--- a/front/salix/components/change-password/index.html
+++ b/front/salix/components/change-password/index.html
@@ -21,6 +21,14 @@
type="password"
autocomplete="false">
+