#6427 - SMS Recover Password #2037
|
@ -1,41 +1,16 @@
|
||||||
const got = require('got');
|
const got = require('got');
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
|
||||||
const isProduction = require('vn-loopback/server/boot/isProduction');
|
const isProduction = require('vn-loopback/server/boot/isProduction');
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('send', {
|
Self.send = async(senderFk, destination, message, options) => {
|
||||||
description: 'Sends SMS to a destination phone',
|
const smsConfig = await models.SmsConfig.findOne();
|
||||||
accessType: 'WRITE',
|
|
||||||
accepts: [
|
|
||||||
{
|
|
||||||
arg: 'destination',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'message',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
returns: {
|
|
||||||
type: 'object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/send`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.send = async(ctx, destination, message) => {
|
const [{prefix: spainPrefix}] = await Self.rawSql(
|
||||||
const userId = ctx.req.accessToken.userId;
|
'SELECT prefix FROM pbx.prefix WHERE country = ?', ['es'], options
|
||||||
const smsConfig = await Self.app.models.SmsConfig.findOne();
|
);
|
||||||
|
if (destination.length == 9)
|
||||||
if (destination.length == 9) {
|
|
||||||
const spainPrefix = '0034';
|
|
||||||
destination = spainPrefix + destination;
|
destination = spainPrefix + destination;
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
api_key: smsConfig.apiKey,
|
api_key: smsConfig.apiKey,
|
||||||
|
@ -51,25 +26,26 @@ module.exports = Self => {
|
||||||
if (!isProduction(false))
|
if (!isProduction(false))
|
||||||
response = {result: [{status: 'ok'}]};
|
response = {result: [{status: 'ok'}]};
|
||||||
else {
|
else {
|
||||||
const jsonTest = {
|
const body = {
|
||||||
json: params
|
json: params
|
||||||
};
|
};
|
||||||
response = await got.post(smsConfig.uri, jsonTest).json();
|
response = await got.post(smsConfig.uri, body).json();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
if (!options?.insert) return;
|
||||||
|
|
||||||
const [result] = response.result;
|
const [result] = response.result;
|
||||||
const error = result.error_id;
|
const error = result.error_id;
|
||||||
|
|
||||||
|
if (senderFk) senderFk = senderFk.req.accessToken.userId;
|
||||||
const newSms = {
|
const newSms = {
|
||||||
senderFk: userId,
|
senderFk,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
message: message,
|
message: message,
|
||||||
status: error
|
status: error
|
||||||
};
|
};
|
||||||
|
|
||||||
const sms = await Self.create(newSms);
|
const sms = await Self.create(newSms);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
|
@ -3,8 +3,15 @@ const app = require('vn-loopback/server/server');
|
||||||
describe('sms send()', () => {
|
describe('sms send()', () => {
|
||||||
it('should not return status error', async() => {
|
it('should not return status error', async() => {
|
||||||
const ctx = {req: {accessToken: {userId: 1}}};
|
const ctx = {req: {accessToken: {userId: 1}}};
|
||||||
const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body');
|
const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body', {insert: true});
|
||||||
|
|
||||||
expect(result.status).toBeUndefined();
|
expect(result.status).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not insert', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 1}}};
|
||||||
|
const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body', {insert: false});
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -0,0 +1,67 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
const isProduction = require('vn-loopback/server/boot/isProduction');
|
||||||
|
const authCode = require('../../models/authCode');
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('recoverPasswordSMS', {
|
||||||
|
description: 'Send SMS to the user',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'user',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The recoveryPhone user\'s',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'verificationCode',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Code tovalidate operation'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/recoverPasswordSMS`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.recoverPasswordSMS = async function(user, verificationCode, options) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const usesEmail = user.indexOf('@') !== -1;
|
||||||
|
const filter = usesEmail ? {email: user} : {name: user};
|
||||||
|
const account = await models.VnUser.findOne({
|
||||||
|
fields: ['id', 'name', 'recoveryPhone'],
|
||||||
|
where: filter
|
||||||
|
});
|
||||||
|
if (!account && !verificationCode) return;
|
||||||
|
user = account;
|
||||||
|
|
||||||
|
if (verificationCode) {
|
||||||
|
if (!account)
|
||||||
|
throw new UserError('Invalid or expired verification code');
|
||||||
|
await Self.validateCode(user.name, verificationCode);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: await user.accessTokens.create({})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = await authCode(user, myOptions);
|
||||||
|
|
||||||
|
if (!isProduction()) {
|
||||||
|
try {
|
||||||
|
await Self.app.models.Sms.send(null, +user.recoveryPhone, code, {insert: false});
|
||||||
|
} catch (e) {
|
||||||
|
throw new UserError(`We weren't able to send this SMS`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {code: true};
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
const authCode = require('../../models/authCode');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('signIn', {
|
Self.remoteMethodCtx('signIn', {
|
||||||
|
@ -65,18 +66,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.sendTwoFactor = async(ctx, vnUser, myOptions) => {
|
Self.sendTwoFactor = async(ctx, vnUser, myOptions) => {
|
||||||
if (vnUser.twoFactor === 'email') {
|
if (vnUser.twoFactor === 'email') {
|
||||||
const $ = Self.app.models;
|
const code = await authCode(vnUser, myOptions);
|
||||||
|
|
||||||
const min = 100000;
|
|
||||||
const max = 999999;
|
|
||||||
const code = String(Math.floor(Math.random() * (max - min + 1)) + min);
|
|
||||||
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 headers = ctx.req.headers;
|
||||||
const platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
|
const platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
|
||||||
const browser = headers['sec-ch-ua']?.replace(/['"=]+/g, '');
|
const browser = headers['sec-ch-ua']?.replace(/['"=]+/g, '');
|
||||||
|
|
|
@ -20,6 +20,10 @@ module.exports = Self => {
|
||||||
arg: 'email',
|
arg: 'email',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The user email'
|
description: 'The user email'
|
||||||
|
}, {
|
||||||
|
arg: 'recoveryPhone',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user email'
|
||||||
}, {
|
}, {
|
||||||
arg: 'lang',
|
arg: 'lang',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -36,8 +40,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updateUser = async(ctx, id, name, nickname, email, lang, twoFactor) => {
|
Self.updateUser = async(ctx, id, name, nickname, email, recoveryPhone, lang, twoFactor) => {
|
||||||
await Self.userSecurity(ctx, id);
|
await Self.userSecurity(ctx, id);
|
||||||
await Self.upsertWithWhere({id}, {name, nickname, email, lang, twoFactor});
|
await Self.upsertWithWhere({id}, {name, nickname, email, recoveryPhone, lang, twoFactor});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const maxTTL = ((60 * 1000) * 5); // 5 min
|
||||||
|
|
||||||
|
module.exports = authCode = async(vnUser, options) => {
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const min = 100000;
|
||||||
|
const max = 999999;
|
||||||
|
const code = String(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||||
|
|
||||||
|
await models.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
|
||||||
|
userFk: vnUser.id,
|
||||||
|
code,
|
||||||
|
expires: Date.vnNow() + maxTTL
|
||||||
|
}, myOptions);
|
||||||
|
return code;
|
||||||
|
};
|
|
@ -4,7 +4,7 @@
|
||||||
"base": "VnModel",
|
"base": "VnModel",
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"table": "smsConfig"
|
"table": "vn.smsConfig"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
|
@ -1,4 +1,3 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
// Methods
|
|
||||||
require('../methods/sms/send')(Self);
|
require('../methods/sms/send')(Self);
|
||||||
};
|
};
|
|
@ -4,7 +4,7 @@
|
||||||
"base": "VnModel",
|
"base": "VnModel",
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"table": "sms"
|
"table": "vn.sms"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
|
@ -1,7 +1,6 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const models = require('vn-loopback/server/server').models;
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
describe('VnUser recoverPassword', () => {
|
||||||
describe('VnUser recoverPassword()', () => {
|
|
||||||
const userId = 1107;
|
const userId = 1107;
|
||||||
|
|
||||||
const activeCtx = {
|
const activeCtx = {
|
||||||
|
@ -19,14 +18,29 @@ describe('VnUser recoverPassword()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send email with token', async() => {
|
describe('By email', () => {
|
||||||
const userId = 1107;
|
it('should send email with token', async() => {
|
||||||
const user = await models.VnUser.findById(userId);
|
const userId = 1107;
|
||||||
|
const user = await models.VnUser.findById(userId);
|
||||||
|
|
||||||
await models.VnUser.recoverPassword(user.email);
|
await models.VnUser.recoverPassword(user.email);
|
||||||
|
|
||||||
const result = await models.AccessToken.findOne({where: {userId: userId}});
|
const result = await models.AccessToken.findOne({where: {userId: userId}});
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('By SMS()', () => {
|
||||||
|
it('should send sms with token', async() => {
|
||||||
|
const userId = 1107;
|
||||||
|
const user = await models.VnUser.findById(userId);
|
||||||
|
|
||||||
|
await models.VnUser.recoverPasswordSMS(user.email);
|
||||||
|
|
||||||
|
const result = await models.AuthCode.findOne({where: {userId: userId}});
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe('loopback model VnUser', () => {
|
||||||
await models.VnUser.userSecurity(ctx, employeeId);
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if you have medium privileges and the users email is verified', async() => {
|
it('should throw an error when update emailVerified field if you have medium privileges and the users email is verified', async() => {
|
||||||
const tx = await models.VnUser.beginTransaction({});
|
const tx = await models.VnUser.beginTransaction({});
|
||||||
const ctx = {options: {accessToken: {userId: hrId}}};
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
try {
|
try {
|
||||||
|
@ -50,5 +50,32 @@ describe('loopback model VnUser', () => {
|
||||||
expect(error).toEqual(new ForbiddenError());
|
expect(error).toEqual(new ForbiddenError());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error when update recoveryPhone if you have medium privileges and the users email is verified', async() => {
|
||||||
|
const tx = await models.VnUser.beginTransaction({});
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const userToUpdate = await models.VnUser.findById(1, null, options);
|
||||||
|
userToUpdate.updateAttribute('recoveryPhone', 123456789, options);
|
||||||
|
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (error) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
expect(error).toEqual(new ForbiddenError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update recoveryPhone if you are the same user', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: employeeId}}};
|
||||||
|
const newRecoveryPhone = 123456789;
|
||||||
|
|
||||||
|
const userToUpdate = await models.VnUser.findById(1, null);
|
||||||
|
userToUpdate.updateAttribute('recoveryPhone', newRecoveryPhone);
|
||||||
|
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@ module.exports = function(Self) {
|
||||||
require('../methods/vn-user/sign-in')(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/recover-passwordSMS')(Self);
|
||||||
require('../methods/vn-user/privileges')(Self);
|
require('../methods/vn-user/privileges')(Self);
|
||||||
require('../methods/vn-user/validate-auth')(Self);
|
require('../methods/vn-user/validate-auth')(Self);
|
||||||
require('../methods/vn-user/renew-token')(Self);
|
require('../methods/vn-user/renew-token')(Self);
|
||||||
|
|
|
@ -61,6 +61,9 @@
|
||||||
},
|
},
|
||||||
"twoFactor": {
|
"twoFactor": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"recoveryPhone": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
@ -106,6 +109,13 @@
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"property": "recoverPasswordSMS",
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"property": "validateAuth",
|
"property": "validateAuth",
|
||||||
"accessType": "EXECUTE",
|
"accessType": "EXECUTE",
|
||||||
|
@ -166,6 +176,7 @@
|
||||||
"realm",
|
"realm",
|
||||||
"email",
|
"email",
|
||||||
"emailVerified",
|
"emailVerified",
|
||||||
|
"recoveryPhone",
|
||||||
"twoFactor"
|
"twoFactor"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,4 +311,10 @@ INSERT INTO mysql.roles_mapping (`User`, `Host`, `Role`, `Admin_option`)
|
||||||
SELECT SUBSTR(`User`, @prefixLen + 1), `Host`, `Role`, `Admin_option`
|
SELECT SUBSTR(`User`, @prefixLen + 1), `Host`, `Role`, `Admin_option`
|
||||||
FROM mysql.roles_mapping
|
FROM mysql.roles_mapping
|
||||||
WHERE `User` LIKE @prefixedLike AND `Host` = @genRoleHost;
|
WHERE `User` LIKE @prefixedLike AND `Host` = @genRoleHost;
|
||||||
|
|
||||||
|
-- Actualiza los valores de la nueva columna con los valores correspondientes de la tabla userInfo
|
||||||
|
UPDATE `account`.`user` as `user`
|
||||||
|
JOIN vn.client `client` ON `user`.id = `client`.id
|
||||||
|
SET `user`.recoveryPhone = `client`.phone;
|
||||||
|
|
||||||
FLUSH PRIVILEGES;
|
FLUSH PRIVILEGES;
|
||||||
|
|
|
@ -3190,6 +3190,10 @@ INSERT INTO `vn`.`cmr` (id,truckPlate,observations,senderInstruccions,paymentIns
|
||||||
(2,'123456N','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet',69,3,4,2,'Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet'),
|
(2,'123456N','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet',69,3,4,2,'Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet'),
|
||||||
(3,'123456B','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet',567,5,6,69,'Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet');
|
(3,'123456B','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet',567,5,6,69,'Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet','Lorem ipsum dolor sit amet');
|
||||||
|
|
||||||
|
UPDATE `vn`.`client`
|
||||||
|
SET phone= 432978106
|
||||||
|
WHERE id=9;
|
||||||
|
|
||||||
UPDATE vn.department
|
UPDATE vn.department
|
||||||
SET workerFk = null;
|
SET workerFk = null;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
#6427 -- Place your SQL code here
|
||||||
|
ALTER TABLE account.`user` ADD recoveryPhone varchar(20) NULL;
|
||||||
|
|
||||||
|
-- ALTER TABLE vn.`sms` MODIFY COLUMN senderFk int(10) unsigned NULL;
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
-- Actualiza los valores de la nueva columna con los valores correspondientes de la tabla userInfo
|
||||||
|
UPDATE `account`.`user` as `user`
|
||||||
|
JOIN vn.client `client` ON `user`.id = `client`.id
|
||||||
|
SET `user`.recoveryPhone = `client`.phone;
|
|
@ -33,7 +33,10 @@ export default {
|
||||||
recoverPassword: {
|
recoverPassword: {
|
||||||
recoverPasswordButton: 'vn-login a[ui-sref="recover-password"]',
|
recoverPasswordButton: 'vn-login a[ui-sref="recover-password"]',
|
||||||
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.user"]',
|
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.user"]',
|
||||||
|
code: 'vn-recover-password vn-textfield[ng-model="$ctrl.verificationCode"]',
|
||||||
sendEmailButton: 'vn-recover-password vn-submit',
|
sendEmailButton: 'vn-recover-password vn-submit',
|
||||||
|
smsOption: 'vn-recover-password vn-radio[val="sms"]',
|
||||||
|
emailOption: 'vn-recover-password vn-radio[val="email"]',
|
||||||
},
|
},
|
||||||
accountIndex: {
|
accountIndex: {
|
||||||
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
||||||
|
|
|
@ -8,6 +8,10 @@ describe('RecoverPassword path', async() => {
|
||||||
beforeAll(async() => {
|
beforeAll(async() => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async() => {
|
||||||
|
await page.waitForState('login');
|
||||||
|
|
||||||
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||||
await page.waitForState('recover-password');
|
await page.waitForState('recover-password');
|
||||||
|
@ -17,36 +21,78 @@ describe('RecoverPassword path', async() => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw error if not exist user', async() => {
|
it('should not throw error if not exist user when select email option', async() => {
|
||||||
await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com');
|
await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.emailOption);
|
||||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
|
const httpDataResponse = await page.waitForResponse(response => {
|
||||||
|
return response.status() === 204 && response.url().includes(`VnUsers/recoverPassword`);
|
||||||
|
});
|
||||||
|
const code = await httpDataResponse.ok();
|
||||||
|
|
||||||
|
expect(code).toBeTrue();
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('Notification sent!');
|
expect(message.text).toContain('Notification sent!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send email using email', async() => {
|
it('should send email using email', async() => {
|
||||||
await page.waitForState('login');
|
|
||||||
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
|
||||||
|
|
||||||
await page.write(selectors.recoverPassword.email, 'BruceWayne@mydomain.com');
|
await page.write(selectors.recoverPassword.email, 'BruceWayne@mydomain.com');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.emailOption);
|
||||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
await page.waitForState('login');
|
|
||||||
|
|
||||||
expect(message.text).toContain('Notification sent!');
|
expect(message.text).toContain('Notification sent!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send email using username', async() => {
|
it('should send email using username', async() => {
|
||||||
await page.waitForState('login');
|
|
||||||
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
|
||||||
|
|
||||||
await page.write(selectors.recoverPassword.email, 'BruceWayne');
|
await page.write(selectors.recoverPassword.email, 'BruceWayne');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.emailOption);
|
||||||
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
await page.waitForState('login');
|
|
||||||
|
|
||||||
expect(message.text).toContain('Notification sent!');
|
expect(message.text).toContain('Notification sent!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should send sms using username', async() => {
|
||||||
|
await page.setRequestInterception(true);
|
||||||
|
|
||||||
|
await page.write(selectors.recoverPassword.email, 'BruceWayne');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.smsOption);
|
||||||
|
page.on('request', request => {
|
||||||
|
if (request.url().includes('recoverPasswordSMS')) {
|
||||||
|
const body = JSON.parse(request.postData());
|
||||||
|
const isVerificationCode = Object.keys(body).includes('verificationCode');
|
||||||
|
if (!isVerificationCode) {
|
||||||
|
request.respond({
|
||||||
|
content: 'application/json',
|
||||||
|
headers: {'Access-Control-Allow-Origin': '*'},
|
||||||
|
body: JSON.stringify({code: '123456'})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
request.respond({
|
||||||
|
content: 'application/json',
|
||||||
|
headers: {'Access-Control-Allow-Origin': '*'},
|
||||||
|
body: JSON.stringify({token: {
|
||||||
|
'id': 'A7al0KNofU7RFL5XPNubKsVjOAj80eoydXhm9i6rF4gj5kom6nEx4BG2bubzLocm',
|
||||||
|
'ttl': 1209600,
|
||||||
|
'created': '2024-05-30T10:43:36.014Z',
|
||||||
|
'userId': 9
|
||||||
|
}})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
request.continue();
|
||||||
|
});
|
||||||
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
|
const httpDataResponse = await page.waitForResponse(response => {
|
||||||
|
return response.status() === 200 && response.url().includes(`VnUsers/recoverPasswordSMS`);
|
||||||
|
});
|
||||||
|
const {code} = await httpDataResponse.json();
|
||||||
|
await page.write(selectors.recoverPassword.code, code);
|
||||||
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
|
await page.waitForState('reset-password');
|
||||||
|
|
||||||
|
expect(await page.getState()).toContain('reset-password');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,49 @@
|
||||||
<h5 class="vn-mb-md vn-mt-lg" translate>Recover password</h5>
|
<h5 class="vn-mb-md vn-mt-lg" translate>Recover password</h5>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
|
disabled="$ctrl.code"
|
||||||
label="User or recovery email"
|
label="User or recovery email"
|
||||||
ng-model="$ctrl.user"
|
ng-model="$ctrl.user"
|
||||||
vn-focus>
|
vn-focus
|
||||||
|
>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<div
|
<vn-textfield
|
||||||
class="text-secondary"
|
ng-if="$ctrl.code"
|
||||||
translate>
|
label="Verification code"
|
||||||
We will sent you an email to recover your password
|
ng-model="$ctrl.verificationCode"
|
||||||
|
vn-name="verificationCode"
|
||||||
|
autocomplete="false"
|
||||||
|
class="vn-mt-md">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-one>
|
||||||
|
<vn-vertical class="vn-mb-sm">
|
||||||
|
<vn-radio
|
||||||
|
disabled="$ctrl.code"
|
||||||
|
label="Teléfono móvil"
|
||||||
|
val="sms"
|
||||||
|
ng-model="$ctrl.method">
|
||||||
|
</vn-radio>
|
||||||
|
<vn-radio
|
||||||
|
disabled="$ctrl.code"
|
||||||
|
label="E-mail"
|
||||||
|
val="email"
|
||||||
|
ng-model="$ctrl.method">
|
||||||
|
</vn-radio>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-one>
|
||||||
|
<div class="text-secondary" ng-if="$ctrl.method && $ctrl.user">
|
||||||
|
<span ng-if="$ctrl.method ==='sms'" translate>
|
||||||
|
We will sent you a sms to recover your password
|
||||||
|
</span>
|
||||||
|
<span ng-if="$ctrl.method ==='email'" translate>
|
||||||
|
We will sent you an email to recover your password
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<vn-submit label="Recover password" ng-click="$ctrl.submit()"></vn-submit>
|
<vn-submit
|
||||||
|
disabled="!$ctrl.user || !$ctrl.method || ($ctrl.code&&!$ctrl.verificationCode)"
|
||||||
|
label="Recover password"
|
||||||
|
ng-click="$ctrl.submit()"
|
||||||
|
></vn-submit>
|
||||||
<div class="spinner-wrapper">
|
<div class="spinner-wrapper">
|
||||||
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
import UserError from '../../../core/lib/user-error';
|
||||||
import ngModule from '../../module';
|
import ngModule from '../../module';
|
||||||
|
|
||||||
export default class Controller {
|
export default class Controller {
|
||||||
constructor($scope, $element, $http, vnApp, $translate, $state) {
|
constructor($scope, $element, $http, vnApp, $translate, $state, $location) {
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
$scope,
|
$scope,
|
||||||
$element,
|
$element,
|
||||||
$http,
|
$http,
|
||||||
vnApp,
|
vnApp,
|
||||||
$translate,
|
$translate,
|
||||||
$state
|
$state,
|
||||||
|
$location
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,19 +18,49 @@ export default class Controller {
|
||||||
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
|
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
|
||||||
this.$state.go('login');
|
this.$state.go('login');
|
||||||
}
|
}
|
||||||
|
goToChangePassword({token}) {
|
||||||
|
if (!token)
|
||||||
|
this.vnApp.showError(this.$translate.instant('Invalid login'));
|
||||||
|
else
|
||||||
|
this.$location.path('/reset-password').search('access_token', token.id);
|
||||||
|
}
|
||||||
|
handleCode(code) {
|
||||||
|
this.code = true;
|
||||||
|
this.$state.params.verificationCode = code;
|
||||||
|
}
|
||||||
|
methodsAvailables() {
|
||||||
|
return {
|
||||||
|
'email': {
|
||||||
|
url: 'VnUsers/recoverPassword',
|
||||||
|
data: {user: this.user},
|
||||||
|
cb: data => {
|
||||||
|
data === '' && this.goToLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
'sms': {
|
||||||
|
url: 'VnUsers/recoverPasswordSMS',
|
||||||
|
data: {user: this.user, verificationCode: this.verificationCode},
|
||||||
|
cb: data => {
|
||||||
|
if (this.method && this.code) {
|
||||||
|
data.token && this.goToChangePassword(data);
|
||||||
|
if (!data.token) throw new UserError(`Credentials not valid`);
|
||||||
|
} else
|
||||||
|
this.handleCode(data.code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
const params = {
|
if (!this.user || (this.sms) || (this.code && !this.code))
|
||||||
user: this.user
|
throw new UserError(`Credentials not valid`);
|
||||||
};
|
const method = this.methodsAvailables()[this.method];
|
||||||
|
this.$http.post(method.url, method.data)
|
||||||
this.$http.post('VnUsers/recoverPassword', params)
|
.then(({data}) => method.cb(data));
|
||||||
.then(() => {
|
|
||||||
this.goToLogin();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state'];
|
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location'];
|
||||||
|
|
||||||
ngModule.vnComponent('vnRecoverPassword', {
|
ngModule.vnComponent('vnRecoverPassword', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
Recover password: Recuperar contraseña
|
Recover password: Recuperar contraseña
|
||||||
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
|
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
|
||||||
|
We will sent you a sms to recover your password: Te enviaremos un sms para restablecer tu contraseña
|
||||||
Notification sent!: ¡Notificación enviada!
|
Notification sent!: ¡Notificación enviada!
|
||||||
User or recovery email: Usuario o correo de recuperación
|
User or recovery email: Usuario
|
||||||
|
User's phone: Móvil del usuario
|
||||||
|
User's id: Id del usuario
|
||||||
|
Credentials not valid: Credenciales no válidas
|
||||||
|
E-mail: Correo electrónico
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default class Controller {
|
||||||
throw new UserError(`Passwords don't match`);
|
throw new UserError(`Passwords don't match`);
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization: this.$location.$$search.access_token
|
Authorization: this.$location.$$search.access_token ?? this.$state.params.access_token
|
||||||
};
|
};
|
||||||
|
|
||||||
const newPassword = this.newPassword;
|
const newPassword = this.newPassword;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Reset password: Restrablecer contraseña
|
Reset password: Restablecer contraseña
|
||||||
New password: Nueva contraseña
|
New password: Nueva contraseña
|
||||||
Repeat password: Repetir contraseña
|
Repeat password: Repetir contraseña
|
||||||
Password changed!: ¡Contraseña cambiada!
|
Password changed!: ¡Contraseña cambiada!
|
||||||
|
|
|
@ -229,6 +229,7 @@
|
||||||
"InvoiceIn is already booked": "InvoiceIn is already booked",
|
"InvoiceIn is already booked": "InvoiceIn is already booked",
|
||||||
"This workCenter is already assigned to this agency": "This workCenter is already assigned to this agency",
|
"This workCenter is already assigned to this agency": "This workCenter is already assigned to this agency",
|
||||||
"You can only have one PDA": "You can only have one PDA",
|
"You can only have one PDA": "You can only have one PDA",
|
||||||
|
"Credentials not valid": "Credentials not valid",
|
||||||
"Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member",
|
"Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member",
|
||||||
"The invoices have been created but the PDFs could not be generated": "The invoices have been created but the PDFs could not be generated",
|
"The invoices have been created but the PDFs could not be generated": "The invoices have been created but the PDFs could not be generated",
|
||||||
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
|
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
|
||||||
|
@ -240,10 +241,6 @@
|
||||||
"There is already a tray with the same height": "There is already a tray with the same height",
|
"There is already a tray with the same height": "There is already a tray with the same height",
|
||||||
"The height must be greater than 50cm": "The height must be greater than 50cm",
|
"The height must be greater than 50cm": "The height must be greater than 50cm",
|
||||||
"The maximum height of the wagon is 200cm": "The maximum height of the wagon is 200cm",
|
"The maximum height of the wagon is 200cm": "The maximum height of the wagon is 200cm",
|
||||||
"The quantity claimed cannot be greater than the quantity of the line": "The quantity claimed cannot be greater than the quantity of the line",
|
"The quantity claimed cannot be greater than the quantity of the line": "The quantity claimed cannot be greater than the quantity of the line"
|
||||||
"There are tickets for this area, delete them first": "There are tickets for this area, delete them first",
|
}
|
||||||
"ticketLostExpedition": "The ticket [{{ticketId}}]({{{ticketUrl}}}) has the following lost expedition:{{ expeditionId }}",
|
|
||||||
"null": "null",
|
|
||||||
"Invalid or expired verification code": "Invalid or expired verification code",
|
|
||||||
"Payment method is required": "Payment method is required"
|
|
||||||
}
|
|
||||||
|
|
|
@ -350,7 +350,6 @@
|
||||||
"Cmr file does not exist": "El archivo del cmr no existe",
|
"Cmr file does not exist": "El archivo del cmr no existe",
|
||||||
"You are not allowed to modify the alias": "No estás autorizado a modificar el alias",
|
"You are not allowed to modify the alias": "No estás autorizado a modificar el alias",
|
||||||
"The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas",
|
"The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas",
|
||||||
"No invoice series found for these parameters": "No se encontró una serie para estos parámetros",
|
|
||||||
"The line could not be marked": "La linea no puede ser marcada",
|
"The line could not be marked": "La linea no puede ser marcada",
|
||||||
"Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado",
|
"Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado",
|
||||||
"They're not your subordinate": "No es tu subordinado/a.",
|
"They're not your subordinate": "No es tu subordinado/a.",
|
||||||
|
@ -364,6 +363,7 @@
|
||||||
"You can not use the same password": "No puedes usar la misma contraseña",
|
"You can not use the same password": "No puedes usar la misma contraseña",
|
||||||
"This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario",
|
"This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario",
|
||||||
"You can only have one PDA": "Solo puedes tener un PDA",
|
"You can only have one PDA": "Solo puedes tener un PDA",
|
||||||
|
"Credentials not valid": "Credentials not valid",
|
||||||
"The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF",
|
"The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF",
|
||||||
"It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono",
|
"It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono",
|
||||||
"Payment method is required": "El método de pago es obligatorio",
|
"Payment method is required": "El método de pago es obligatorio",
|
||||||
|
@ -382,10 +382,6 @@
|
||||||
"The entry does not have stickers": "La entrada no tiene etiquetas",
|
"The entry does not have stickers": "La entrada no tiene etiquetas",
|
||||||
"This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha",
|
"This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha",
|
||||||
"No valid travel thermograph found": "No se encontró un termógrafo válido",
|
"No valid travel thermograph found": "No se encontró un termógrafo válido",
|
||||||
"The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea",
|
"The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea"
|
||||||
"type cannot be blank": "Se debe rellenar el tipo",
|
}
|
||||||
"There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero",
|
|
||||||
"There is no company associated with that warehouse": "No hay ninguna empresa asociada a ese almacén",
|
|
||||||
"ticketLostExpedition": "El ticket [{{ticketId}}]({{{ticketUrl}}}) tiene la siguiente expedición perdida:{{ expeditionId }}",
|
|
||||||
"The web user's email already exists": "El correo del usuario web ya existe"
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "Account",
|
"name": "Account",
|
||||||
"base": "VnModel",
|
"base": "VnModel",
|
||||||
|
"mixins": {
|
||||||
|
"Loggable": true
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"table": "account.account"
|
"table": "account.account"
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<mg-ajax path="VnUsers/{{patch.params.id}}/update-user" options="vnPatch"></mg-ajax>
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
data="$ctrl.user"
|
||||||
|
form="form"
|
||||||
|
save="patch">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
ng-submit="$ctrl.onSubmit()"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-textfield
|
||||||
|
label="User"
|
||||||
|
ng-model="$ctrl.user.name"
|
||||||
|
rule="VnUser"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Nickname"
|
||||||
|
ng-model="$ctrl.user.nickname"
|
||||||
|
rule="VnUser">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Personal email"
|
||||||
|
ng-model="$ctrl.user.email"
|
||||||
|
rule="VnUser">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Recovery phone"
|
||||||
|
ng-model="$ctrl.user.recoveryPhone"
|
||||||
|
disabled="$root.user.id !== $ctrl.user.id">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Language"
|
||||||
|
ng-model="$ctrl.user.lang"
|
||||||
|
url="Languages"
|
||||||
|
value-field="code"
|
||||||
|
rule="VnUser">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-submit
|
||||||
|
disabled="!watcher.dataChanged()"
|
||||||
|
label="Save">
|
||||||
|
</vn-submit>
|
||||||
|
<vn-button
|
||||||
|
class="cancel"
|
||||||
|
label="Undo changes"
|
||||||
|
disabled="!watcher.dataChanged()"
|
||||||
|
ng-click="watcher.loadOriginalData()">
|
||||||
|
</vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
</form>
|
|
@ -0,0 +1,3 @@
|
||||||
|
Email verified successfully!: Correo verificado correctamente!
|
||||||
|
Recovery phone: Teléfono de recuperación de cuenta
|
||||||
|
|
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.sendSms = async(ctx, id, destination, message) => {
|
Self.sendSms = async(ctx, id, destination, message) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const sms = await models.Sms.send(ctx, destination, message);
|
const sms = await models.Sms.send(ctx, destination, message, {insert: true});
|
||||||
|
|
||||||
await models.ClientSms.create({
|
await models.ClientSms.create({
|
||||||
clientFk: id,
|
clientFk: id,
|
||||||
|
|
|
@ -30,7 +30,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
const allSms = [];
|
const allSms = [];
|
||||||
for (let client of targetClients) {
|
for (let client of targetClients) {
|
||||||
let sms = await Self.app.models.Sms.send(ctx, client, message);
|
let sms = await Self.app.models.Sms.send(ctx, client, message, {insert: true});
|
||||||
allSms.push(sms);
|
allSms.push(sms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ module.exports = Self => {
|
||||||
CALL vn.sale_recalcComponent(null);
|
CALL vn.sale_recalcComponent(null);
|
||||||
DROP TEMPORARY TABLE tmp.recalculateSales;`;
|
DROP TEMPORARY TABLE tmp.recalculateSales;`;
|
||||||
|
|
||||||
const recalculation = await Self.rawSql(query, [salesIds], myOptions);
|
const recalculation = await Self.rawSql(query, salesIds, myOptions);
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.sendSms = async(ctx, id, destination, message) => {
|
Self.sendSms = async(ctx, id, destination, message) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const sms = await models.Sms.send(ctx, destination, message);
|
const sms = await models.Sms.send(ctx, destination, message, {insert: true});
|
||||||
const {clientFk} = await models.Ticket.findById(id);
|
const {clientFk} = await models.Ticket.findById(id);
|
||||||
await models.ClientSms.create({
|
await models.ClientSms.create({
|
||||||
clientFk,
|
clientFk,
|
||||||
|
|
Loading…
Reference in New Issue