Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1982-travel_add_thermograph
gitea/salix/1982-travel_add_thermograph There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2020-01-20 13:05:06 +01:00
commit 41e69a2e9d
45 changed files with 385 additions and 618 deletions

96
back/methods/chat/send.js Normal file
View File

@ -0,0 +1,96 @@
const request = require('request-promise-native');
module.exports = Self => {
Self.remoteMethodCtx('send', {
description: 'Send a RocketChat message',
accessType: 'WRITE',
accepts: [{
arg: 'to',
type: 'String',
required: true,
description: 'user (@) or channel (#) to send the message'
}, {
arg: 'message',
type: 'String',
required: true,
description: 'The message'
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/send`,
verb: 'POST'
}
});
Self.send = async(ctx, to, message) => {
const models = Self.app.models;
const accessToken = ctx.req.accessToken;
const sender = await models.Account.findById(accessToken.userId);
const recipient = to.replace('@', '');
if (sender.name != recipient)
return sendMessage(to, `@${sender.name}: ${message}`);
};
async function sendMessage(name, message) {
const models = Self.app.models;
const chatConfig = await models.ChatConfig.findOne();
if (!Self.token)
Self.token = await login();
const uri = `${chatConfig.uri}/chat.postMessage`;
return makeRequest(uri, {
'channel': name,
'text': message
}).catch(async error => {
if (error.statusCode === 401 && !Self.loginAttempted) {
Self.token = await login();
Self.loginAttempted = true;
return sendMessage(name, message);
}
throw new Error(error.message);
});
}
/**
* Returns a rocketchat token
* @return {Object} userId and authToken
*/
async function login() {
const models = Self.app.models;
const chatConfig = await models.ChatConfig.findOne();
const uri = `${chatConfig.uri}/login`;
return makeRequest(uri, {
user: chatConfig.user,
password: chatConfig.password
}).then(res => res.data);
}
function makeRequest(uri, body) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({statusCode: 200, message: 'Fake notification sent'});
});
}
const options = {
method: 'POST',
uri: uri,
body: body,
headers: {'content-type': 'application/json'},
json: true
};
if (Self.token) {
options.headers['X-Auth-Token'] = Self.token.authToken;
options.headers['X-User-Id'] = Self.token.userId;
}
return request(options);
}
};

View File

@ -0,0 +1,53 @@
module.exports = Self => {
Self.remoteMethodCtx('sendCheckingPresence', {
description: 'Sends a RocketChat message to a working worker or department channel',
accessType: 'WRITE',
accepts: [{
arg: 'workerId',
type: 'Number',
required: true,
description: 'The worker id of the destinatary'
}, {
arg: 'message',
type: 'String',
required: true,
description: 'The message'
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/sendCheckingPresence`,
verb: 'POST'
}
});
Self.sendCheckingPresence = async(ctx, workerId, message) => {
const models = Self.app.models;
const account = await models.Account.findById(workerId);
const query = `SELECT worker_isWorking(?) isWorking`;
const [result] = await Self.rawSql(query, [workerId]);
let room;
if (result.isWorking)
room = `@${account.name}`;
else {
const workerDepartment = await models.WorkerDepartment.findById(workerId, {
include: {
relation: 'department'
}
});
const department = workerDepartment.department();
const channelName = department.chatName;
room = `#${channelName}`;
if (channelName)
room = `#${channelName}`;
else room = `@${account.name}`;
}
return Self.send(ctx, room, message);
};
};

View File

@ -1,4 +1,3 @@
const request = require('request-promise-native');
module.exports = Self => {
Self.remoteMethodCtx('sendMessage', {
description: 'Send a RocketChat message',
@ -24,115 +23,8 @@ module.exports = Self => {
}
});
// FIXME: Deprecate this method #2019
Self.sendMessage = async(ctx, to, message) => {
const models = Self.app.models;
const accessToken = ctx.req.accessToken;
const sender = await models.Account.findById(accessToken.userId);
const recipient = to.replace('@', '');
if (sender.name != recipient)
return sendMessage(sender, to, `@${sender.name}: ${message} `);
return Self.send(ctx, to, message);
};
async function sendMessage(sender, channel, message) {
const config = await getConfig();
const avatar = `${config.host}/avatar/${sender.name}`;
const uri = `${config.api}/chat.postMessage`;
return sendAuth(uri, {
'channel': channel,
'avatar': avatar,
'text': message
}).catch(async error => {
if (error.statusCode === 401 && !this.resendAttempted) {
this.resendAttempted = true;
return sendMessage(sender, channel, message);
}
throw new Error(error.message);
});
}
/**
* Returns a rocketchat token
* @return {Object} userId and authToken
*/
async function getAuthToken() {
if (!this.auth || this.auth && !this.auth.authToken) {
const config = await getConfig();
const uri = `${config.api}/login`;
const res = await send(uri, {
user: config.user,
password: config.password
});
this.auth = res.data;
}
return this.auth;
}
/**
* Returns a rocketchat config
* @return {Object} Auth config
*/
async function getConfig() {
if (!this.chatConfig) {
const models = Self.app.models;
this.chatConfig = await models.ChatConfig.findOne();
}
return this.chatConfig;
}
/**
* Send unauthenticated request
* @param {*} uri - Request uri
* @param {*} body - Request params
* @param {*} options - Request options
*
* @return {Object} Request response
*/
async function send(uri, body, options) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({statusCode: 200, message: 'Fake notification sent'});
});
}
const defaultOptions = {
method: 'POST',
uri: uri,
body: body,
headers: {'content-type': 'application/json'},
json: true
};
if (options) Object.assign(defaultOptions, options);
return request(defaultOptions);
}
/**
* Send authenticated request
* @param {*} uri - Request uri
* @param {*} body - Request params
*
* @return {Object} Request response
*/
async function sendAuth(uri, body) {
const login = await getAuthToken();
const options = {
headers: {'content-type': 'application/json'}
};
if (login) {
options.headers['X-Auth-Token'] = login.authToken;
options.headers['X-User-Id'] = login.userId;
}
return send(uri, body, options);
}
};

View File

@ -1,9 +1,9 @@
const app = require('vn-loopback/server/server');
describe('chat sendMessage()', () => {
describe('chat send()', () => {
it('should return a "Fake notification sent" as response', async() => {
let ctx = {req: {accessToken: {userId: 1}}};
let response = await app.models.Chat.sendMessage(ctx, '@salesPerson', 'I changed something');
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response.statusCode).toEqual(200);
expect(response.message).toEqual('Fake notification sent');
@ -11,7 +11,7 @@ describe('chat sendMessage()', () => {
it('should not return a response', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let response = await app.models.Chat.sendMessage(ctx, '@salesPerson', 'I changed something');
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toBeUndefined();
});

View File

@ -0,0 +1,65 @@
const app = require('vn-loopback/server/server');
describe('chat sendCheckingPresence()', () => {
const departmentId = 23;
const workerId = 107;
let timeEntry;
afterAll(async done => {
const department = await app.models.Department.findById(departmentId);
await department.updateAttribute('chatName', null);
await app.models.WorkerTimeControl.destroyById(timeEntry.id);
done();
});
it(`should call to send() method with the worker username when no department channel is specified
and then return a "Fake notification sent" as response`, async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const chatModel = app.models.Chat;
spyOn(chatModel, 'send').and.callThrough();
const response = await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something');
expect(response.statusCode).toEqual(200);
expect(response.message).toEqual('Fake notification sent');
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', 'I changed something');
});
it(`should call to send() method with the worker department channel if is specified
and then return a "Fake notification sent" as response`, async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const chatModel = app.models.Chat;
spyOn(chatModel, 'send').and.callThrough();
const department = await app.models.Department.findById(departmentId);
await department.updateAttribute('chatName', 'cooler');
const response = await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something');
expect(response.statusCode).toEqual(200);
expect(response.message).toEqual('Fake notification sent');
expect(chatModel.send).toHaveBeenCalledWith(ctx, '#cooler', 'I changed something');
});
it(`should call to send() method with the worker username when the worker is working`, async() => {
const ctx = {req: {accessToken: {userId: 1}}};
const chatModel = app.models.Chat;
spyOn(chatModel, 'send').and.callThrough();
const today = new Date();
today.setHours(6, 0);
timeEntry = await app.models.WorkerTimeControl.create({
userFk: workerId,
timed: today,
manual: false,
direction: 'in'
});
const response = await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something');
expect(response.statusCode).toEqual(200);
expect(response.message).toEqual('Fake notification sent');
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', 'I changed something');
});
});

View File

@ -59,12 +59,6 @@
"Postcode": {
"dataSource": "vn"
},
"UserPhoneType": {
"dataSource": "vn"
},
"UserPhone": {
"dataSource": "vn"
},
"UserLog": {
"dataSource": "vn"
}

View File

@ -1,3 +1,5 @@
module.exports = Self => {
require('../methods/chat/send')(Self);
require('../methods/chat/sendMessage')(Self);
require('../methods/chat/sendCheckingPresence')(Self);
};

View File

@ -1,26 +0,0 @@
{
"name": "UserPhoneType",
"base": "VnModel",
"options": {
"mysql": {
"table": "userPhoneType"
}
},
"properties": {
"code": {
"id": true,
"type": "String"
},
"description": {
"type": "String"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,9 +0,0 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`This phone already exists`);
return err;
});
};

View File

@ -1,39 +0,0 @@
{
"name": "UserPhone",
"base": "Loggable",
"log": {
"model":"UserLog",
"relation": "user"
},
"options": {
"mysql": {
"table": "userPhone"
}
},
"properties": {
"id": {
"id": true,
"type": "Number"
},
"phone": {
"type": "Number",
"required": true
},
"typeFk": {
"type": "String",
"required": true
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
},
"type": {
"type": "belongsTo",
"model": "UserPhoneType",
"foreignKey": "typeFk"
}
}
}

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`department`
ADD COLUMN `chatName` VARCHAR(45) NULL AFTER `path`;

View File

@ -0,0 +1,32 @@
USE `vn`;
DROP function IF EXISTS `worker_isWorking`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` FUNCTION `worker_isWorking`(vWorkerFk INT) RETURNS tinyint(1)
READS SQL DATA
BEGIN
/**
* Comprueba si el trabajador está trabajando en el momento de la consulta
* @return Devuelve TRUE en caso de que este trabajando. Si se encuentra en un descanso devolverá FALSE
*/
DECLARE vLastIn DATETIME ;
SELECT MAX(timed) INTO vLastIn
FROM vn.workerTimeControl
WHERE userFk = vWorkerFk AND
direction = 'in';
IF (SELECT MOD(COUNT(*),2)
FROM vn.workerTimeControl
WHERE userFk = vWorkerFk AND
timed >= vLastIn
) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END$$
DELIMITER ;

View File

@ -68,13 +68,13 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,
(111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es'),
(112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'es');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`)
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
VALUES
(106, 'LGN', 'David Charles', 'Haller', 106, 19),
(107, 'ANT', 'Hank' , 'Pym' , 107, 19),
(108, 'DCX', 'Charles' , 'Xavier', 108, 19),
(109, 'HLK', 'Bruce' , 'Banner', 109, 19),
(110, 'JJJ', 'Jessica' , 'Jones' , 110, 19);
(106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106),
(107, 'ANT', 'Hank' , 'Pym' , 107, 19, 432978107),
(108, 'DCX', 'Charles' , 'Xavier', 108, 19, 432978108),
(109, 'HLK', 'Bruce' , 'Banner', 109, 19, 432978109),
(110, 'JJJ', 'Jessica' , 'Jones' , 110, 19, 432978110);
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`)
VALUES
@ -216,21 +216,20 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
(3, 'Daily Bugle'),
(4, 'GCN Channel'),
(5, 'The Newspaper');
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`fax`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`,`mailAddress`,`cplusTerIdNifFk`,`hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`)
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`fax`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`,`mailAddress`,`cplusTerIdNifFk`,`hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`)
VALUES
(101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Silla', 46460, 333333333, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street', 'Silla', 46460, 333333333, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street', 'Silla', 46460, 333333333, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 0, 19, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point', 'Silla', 46460, 333333333, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Silla', 46460, 333333333, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 8, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1),
(106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'Evil hideout', 'Silla', 46460, 333333333, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1),
(107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill', 'Silla', 46460, 333333333, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1),
(108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 333333333, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1),
(109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Silla', 46460, 333333333, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1),
(110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Silla', 46460, 333333333, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1),
(111, 'Missing', NULL, 'Missing man', 'Anton', 'The space', 'Silla', 46460, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1),
(112, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city', 'Silla', 46460, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1);
(101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 0, 19, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
(105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 8, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1),
(106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'Evil hideout', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1),
(107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1),
(108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1),
(109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1),
(110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1),
(111, 'Missing', NULL, 'Missing man', 'Anton', 'The space', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1),
(112, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1);
INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`)
SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'SILLA', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, CURDATE(), 1
@ -1941,73 +1940,6 @@ INSERT INTO `vn`.`queuePriority`(`id`, `priority`)
(2, 'Normal'),
(3, 'Baja');
INSERT INTO `vn`.`userPhoneType` (`code`, `description`)
VALUES
('businessPhone', 'Telefono de empresa del usuario'),
('personalPhone', 'Telefono personal del usuario');
INSERT INTO `vn`.`userPhone`(`id`, `userFk`, `typeFk`, `phone`)
VALUES
(1, 101, 'personalPhone', 1111111111),
(2, 102, 'personalPhone', 1111111111),
(3, 103, 'personalPhone', 1111111111),
(4, 104, 'personalPhone', 1111111111),
(5, 105, 'personalPhone', 1111111111),
(6, 106, 'personalPhone', 1111111111),
(7, 107, 'personalPhone', 1111111111),
(8, 108, 'personalPhone', 1111111111),
(9, 109, 'personalPhone', 1111111111),
(10, 110, 'personalPhone', 1111111111),
(11, 111, 'personalPhone', 1111111111),
(12, 112, 'personalPhone', 1111111111),
(13, 1, 'personalPhone', 623111111),
(14, 2, 'personalPhone', 623111111),
(15, 3, 'personalPhone', 623111111),
(16, 5, 'personalPhone', 623111111),
(17, 6, 'personalPhone', 623111111),
(18, 9, 'personalPhone', 623111111),
(19, 13, 'personalPhone', 623111111),
(20, 15, 'personalPhone', 623111111),
(21, 16, 'personalPhone', 623111111),
(22, 17, 'personalPhone', 623111111),
(23, 18, 'personalPhone', 623111111),
(24, 19, 'personalPhone', 623111111),
(26, 21, 'personalPhone', 623111111),
(27, 22, 'personalPhone', 623111111),
(28, 30, 'personalPhone', 623111111),
(29, 31, 'personalPhone', 623111111),
(30, 32, 'personalPhone', 623111111),
(31, 34, 'personalPhone', 623111111),
(32, 35, 'personalPhone', 623111111),
(33, 36, 'personalPhone', 623111111),
(34, 37, 'personalPhone', 623111111),
(35, 38, 'personalPhone', 623111111),
(36, 39, 'personalPhone', 623111111),
(37, 40, 'personalPhone', 623111111),
(38, 41, 'personalPhone', 623111111),
(39, 42, 'personalPhone', 623111111),
(40, 43, 'personalPhone', 623111111),
(41, 44, 'personalPhone', 623111111),
(42, 45, 'personalPhone', 623111111),
(43, 47, 'personalPhone', 623111111),
(44, 48, 'personalPhone', 623111111),
(45, 50, 'personalPhone', 623111111),
(46, 51, 'personalPhone', 623111111),
(47, 52, 'personalPhone', 623111111),
(48, 54, 'personalPhone', 623111111),
(49, 55, 'personalPhone', 623111111),
(50, 56, 'personalPhone', 623111111),
(51, 57, 'personalPhone', 623111111),
(52, 58, 'personalPhone', 623111111),
(53, 59, 'personalPhone', 623111111),
(54, 60, 'personalPhone', 623111111),
(55, 61, 'personalPhone', 623111111),
(56, 65, 'personalPhone', 623111111),
(57, 66, 'personalPhone', 623111111),
(65, 107, 'businessPhone', 700987987),
(67, 106, 'businessPhone', 1111111112),
(68, 106, 'personalPhone', 1111111113);
INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`)
VALUES
(1, 43200, 129600, 734400, 43200, 50400);

View File

@ -155,10 +155,10 @@ module.exports = function(Self) {
const result = await realMethod.call(this, data, options);
if (cb) cb(null, result);
else return result;
} catch (err) {
let myErr = replaceErr(err, replaceErrFunc);
if (cb)
cb(myErr);
if (cb) cb(myErr);
else
throw myErr;
}

View File

@ -60,8 +60,6 @@ module.exports = function(Self) {
scope: {
fields: ['id', 'name', 'active']
}
}, {
relation: 'phones'
}
]
});

View File

@ -1,10 +1,10 @@
const app = require('vn-loopback/server/server');
describe('client sendSms()', () => {
let clientLog;
let createdLog;
afterAll(async done => {
await app.models.ClientLog.destroyById(clientLog.id);
await app.models.ClientLog.destroyById(createdLog.id);
done();
});
@ -19,7 +19,7 @@ describe('client sendSms()', () => {
logId = sms.logId;
let createdLog = await app.models.ClientLog.findById(logId);
createdLog = await app.models.ClientLog.findById(logId);
let json = JSON.parse(JSON.stringify(createdLog.newInstance));
expect(json.message).toEqual(message);

View File

@ -53,9 +53,6 @@ module.exports = Self => {
}
}
},
{
relation: 'phones'
},
{
relation: 'country',
scope: {

View File

@ -200,11 +200,6 @@
"type": "hasOne",
"model": "ClaimRatio",
"foreignKey": "clientFk"
},
"phones": {
"type": "hasMany",
"model": "UserPhone",
"foreignKey": "userFk"
}
}
}
}

View File

@ -31,6 +31,20 @@
info="You can save multiple emails">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.client.phone"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Mobile"
ng-model="$ctrl.client.mobile"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one

View File

@ -1,48 +1,45 @@
<vn-crud-model
url="UserPhoneTypes"
data="phoneTypes"
auto-load="true">
</vn-crud-model>
<vn-crud-model
vn-id="model"
url="UserPhones"
data="$ctrl.phones">
url="ClientContacts"
fields="['id', 'name', 'phone', 'clientFk']"
link="{clientFk: $ctrl.$stateParams.id}"
data="contacts"
auto-load="true">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.phones">
data="contacts"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="workerPhone in $ctrl.phones">
<vn-autocomplete
vn-one
ng-model="workerPhone.typeFk"
initial-data="workerPhone.typeFk"
data ="phoneTypes"
show-field="code"
value-field="code"
label="Type"
vn-focus>
</vn-autocomplete>
<vn-horizontal ng-repeat="contact in contacts">
<vn-textfield
vn-one
label="Name"
ng-model="contact.name"
rule="ClientContact">
</vn-textfield>
<vn-textfield
vn-one
label="Phone"
ng-model="workerPhone.phone">
ng-model="contact.phone"
rule="ClientContact"
vn-focus>
</vn-textfield>
<vn-none>
<vn-icon-button
vn-tooltip="Remove phone"
vn-tooltip="Remove contact"
icon="delete"
ng-click="model.remove($index)"
tabindex="-1">
tabindex="-1"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-none>
</vn-horizontal>
<vn-one>
<vn-icon-button
vn-bind="+"
vn-tooltip="Add phone"
vn-tooltip="Add contact"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
@ -51,4 +48,4 @@
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>
</form>

View File

@ -0,0 +1,35 @@
import ngModule from '../module';
class Controller {
constructor($scope, $stateParams, $translate) {
this.$scope = $scope;
this.$stateParams = $stateParams;
this.$translate = $translate;
}
add() {
this.$scope.model.insert({
clientFk: this.client.id,
name: this.$translate.instant('Phone'),
phone: null
});
}
onSubmit() {
this.$scope.watcher.check();
this.$scope.model.save().then(() => {
this.$scope.watcher.notifySaved();
this.$scope.model.refresh();
});
}
}
Controller.$inject = ['$scope', '$stateParams', '$translate'];
ngModule.component('vnClientContact', {
template: require('./index.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -30,7 +30,7 @@ import './credit-insurance/index';
import './credit-insurance/create';
import './credit-insurance/insurance/index';
import './credit-insurance/insurance/create';
import './phones';
import './contact';
import './sample/index';
import './sample/create';
import './web-payment';

View File

@ -25,6 +25,10 @@
label="Id"
value="{{::client.id}}">
</vn-label-value>
<vn-label-value
label="Phone"
value="{{::client.phone | phone}}">
</vn-label-value>
<vn-label-value
label="Town/City"
value="{{::client.city}}">

View File

@ -1,54 +0,0 @@
<vn-crud-model
url="UserPhoneTypes"
data="phoneTypes"
auto-load="true">
</vn-crud-model>
<vn-crud-model
vn-id="model"
url="UserPhones"
data="$ctrl.phones">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.phones">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="clientPhone in $ctrl.phones">
<vn-autocomplete
vn-one
ng-model="clientPhone.typeFk"
initial-data="clientPhone.typeFk"
data ="phoneTypes"
show-field="code"
value-field="code"
label="Type"
vn-focus>
</vn-autocomplete>
<vn-textfield
vn-one
label="Phone"
ng-model="clientPhone.phone">
</vn-textfield>
<vn-none>
<vn-icon-button
vn-tooltip="Remove phone"
icon="delete"
ng-click="model.remove($index)"
tabindex="-1">
</vn-icon-button>
</vn-none>
</vn-horizontal>
<vn-one>
<vn-icon-button
vn-bind="+"
vn-tooltip="Add phone"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
</vn-one>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,44 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
get client() {
return this._client;
}
set client(value) {
this._client = value;
if (value)
this.setLink(value);
}
setLink(value) {
this.$.$applyAsync(()=> {
this.$.model.link = {userFk: value.id};
this.$.model.refresh();
});
}
onSubmit() {
this.$.watcher.check();
return this.$.model.save().then(() => {
this.$.watcher.updateOriginalData();
this.$.watcher.notifySaved();
this.card.reload();
});
}
add() {
this.$.model.insert();
}
}
ngModule.component('vnClientPhones', {
template: require('./index.html'),
controller: Controller,
require: {card: '^vnClientCard'},
bindings: {
client: '<'
}
});

View File

@ -1,50 +0,0 @@
import './index';
import watcher from 'core/mocks/watcher';
describe('Component vnClientPhones', () => {
let controller;
let $element;
let $scope;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<div></div>');
$scope.watcher = watcher;
$scope.model = {
link: 1,
save: () => {}
};
controller = $componentController('vnClientPhones', {$element, $scope});
controller.card = {reload: () => {}};
}));
describe('setLink()', () => {
it('set the link in the model and refreshes it', () => {
spyOn(controller.$, '$applyAsync');
let value = {id: 106};
controller.setLink(value);
expect(controller.$.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
});
});
describe('onSubmit()', () => {
it('should call watcher functions, reload the card and save the model', done => {
spyOn(controller.$.watcher, 'check');
spyOn(controller.$.model, 'save').and.returnValue(Promise.resolve());
spyOn(controller.$.watcher, 'updateOriginalData');
spyOn(controller.$.watcher, 'notifySaved');
spyOn(controller.card, 'reload');
controller.onSubmit();
controller.onSubmit().then(() => {
expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith();
expect(controller.$.watcher.notifySaved).toHaveBeenCalledWith();
expect(controller.card.reload).toHaveBeenCalledWith();
done();
}).catch(done.fail);
});
});
});

View File

@ -26,7 +26,7 @@
{"state": "client.card.webAccess", "icon": "cloud"},
{"state": "client.card.mandate", "icon": "pan_tool"},
{"state": "client.card.creditInsurance.index", "icon": "icon-solunion"},
{"state": "client.card.phones", "icon": "contact_phone"},
{"state": "client.card.contact", "icon": "contact_phone"},
{"state": "client.card.sample.index", "icon": "mail"},
{"state": "client.card.webPayment", "icon": "icon-onlinepayment"},
{"state": "client.card.dms.index", "icon": "cloud_upload"}
@ -282,10 +282,10 @@
"client": "$ctrl.client"
}
}, {
"url": "/phones",
"state": "client.card.phones",
"component": "vn-client-phones",
"description": "Client phones",
"url": "/contact",
"state": "client.card.contact",
"component": "vn-client-contact",
"description": "Contacts",
"params": {
"client": "$ctrl.client"
}

View File

@ -12,10 +12,11 @@
<vn-label-value label="Contact"
value="{{$ctrl.summary.contact}}">
</vn-label-value>
<vn-label-value ng-repeat = "phone in $ctrl.summary.phones"
label="Phone"
value="{{phone.phone}}">
<vn-label-value label="Phone"
value="{{$ctrl.summary.phone}}">
</vn-label-value>
<vn-label-value label="Mobile"
value="{{$ctrl.summary.mobile}}">
<vn-label-value label="Email" ellipsize="false"
value="{{$ctrl.summary.email}}">
</vn-label-value>

View File

@ -94,7 +94,7 @@ module.exports = Self => {
id: id,
url: `${origin}/#!/ticket/${id}/summary`
});
await models.Chat.sendMessage(ctx, `@${salesPersonUser}`, message);
await models.Chat.send(ctx, `@${salesPersonUser}`, message);
}
return ticket.updateAttribute('isDeleted', true);

View File

@ -25,6 +25,9 @@
},
"sons": {
"type": "Number"
},
"chatName": {
"type": "String"
}
}
}

View File

@ -54,12 +54,6 @@
"type": "hasMany",
"model": "WorkerTeamCollegues",
"foreignKey": "workerFk"
},
"phones": {
"type": "hasMany",
"model": "UserPhone",
"foreignKey": "userFk",
"primaryKey": "userFk"
}
}
}

View File

@ -23,6 +23,14 @@
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.worker.phone"
rule>
</vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>

View File

@ -28,12 +28,6 @@ class Controller extends ModuleCard {
relation: 'department'
}
}
}, {
relation: 'phones',
scope: {
fields: ['phone'],
order: 'typeFk ASC'
}
}
]
};

View File

@ -26,9 +26,8 @@
<vn-label-value label="Department"
value="{{$ctrl.worker.department.department.name}}">
</vn-label-value>
<vn-label-value ng-repeat ="phone in $ctrl.worker.phones"
label="Phone"
value="{{phone.phone}}">
<vn-label-value label="Phone"
value="{{$ctrl.worker.phone}}">
</vn-label-value>
<vn-label-value label="Extension"
value="{{$ctrl.worker.sip.extension}}">

View File

@ -13,7 +13,6 @@ import './department';
import './calendar';
import './time-control';
import './log';
import './phones';
import './dms/index';
import './dms/create';
import './dms/edit';

View File

@ -1,48 +0,0 @@
import ngModule from '../module';
class Controller {
constructor($scope) {
this.$scope = $scope;
}
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value)
this.setLink(value);
}
setLink(value) {
this.$scope.$applyAsync(()=> {
this.$scope.model.link = {userFk: value.userFk};
this.$scope.model.refresh();
});
}
onSubmit() {
this.$scope.watcher.check();
return this.$scope.model.save().then(() => {
this.$scope.watcher.updateOriginalData();
this.$scope.watcher.notifySaved();
this.card.reload();
});
}
add() {
this.$scope.model.insert();
}
}
Controller.$inject = ['$scope'];
ngModule.component('vnWorkerPhones', {
template: require('./index.html'),
controller: Controller,
require: {card: '^vnWorkerCard'},
bindings: {
worker: '<'
}
});

View File

@ -1,47 +0,0 @@
import './index';
import watcher from 'core/mocks/watcher';
describe('Component vnWorkerPhones', () => {
let controller;
beforeEach(ngModule('worker'));
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
let $scope = $rootScope.$new();
controller = $componentController('vnWorkerPhones', $scope);
controller.$scope.watcher = watcher;
controller.$scope.model = {
link: 1,
save: () => {}
};
controller.card = {reload: () => {}};
}));
describe('setLink()', () => {
it('set the link in the model and refreshes it', () => {
spyOn(controller.$scope, '$applyAsync');
let value = {userFk: 106};
controller.setLink(value);
expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
});
});
describe('onSubmit()', () => {
it('should call watcher functions, reload the card and save the model', done => {
spyOn(controller.$scope.watcher, 'check');
spyOn(controller.$scope.model, 'save').and.returnValue(Promise.resolve());
spyOn(controller.$scope.watcher, 'updateOriginalData');
spyOn(controller.$scope.watcher, 'notifySaved');
spyOn(controller.card, 'reload');
controller.onSubmit();
controller.onSubmit().then(() => {
expect(controller.$scope.watcher.updateOriginalData).toHaveBeenCalledWith();
expect(controller.$scope.watcher.notifySaved).toHaveBeenCalledWith();
expect(controller.card.reload).toHaveBeenCalledWith();
done();
}).catch(done.fail);
});
});
});

View File

@ -1,4 +0,0 @@
Phones: Teléfonos
Type: Tipo
Remove phone: Eliminar teléfono
Add phone: Añadir teléfono

View File

@ -13,7 +13,6 @@
{"state": "worker.card.pbx", "icon": "icon-pbx"},
{"state": "worker.card.calendar", "icon": "icon-calendar"},
{"state": "worker.card.timeControl", "icon": "access_time"},
{"state": "worker.card.phones", "icon": "contact_phone"},
{"state": "worker.card.dms.index", "icon": "cloud_upload"}
]
},
@ -84,16 +83,6 @@
"description": "Departments",
"acl": ["hr"]
}, {
"url": "/phones",
"state": "worker.card.phones",
"component": "vn-worker-phones",
"description": "Phones",
"params": {
"worker": "$ctrl.worker"
},
"acl": ["hr"]
},
{
"url": "/dms",
"state": "worker.card.dms",
"abstract": true,

View File

@ -12,9 +12,8 @@
<vn-label-value label="Department"
value="{{worker.department.department.name}}">
</vn-label-value>
<vn-label-value ng-repeat = "phone in worker.phones"
label="Phone"
value="{{phone.phone}}">
<vn-label-value label="Phone"
value="{{worker.phone}}">
</vn-label-value>
</vn-one>
<vn-one>

View File

@ -50,12 +50,6 @@ class Controller {
relation: 'department'
}
}
}, {
relation: 'phones',
scope: {
fields: ['phone'],
order: 'typeFk ASC'
}
}
]
};

View File

@ -1,6 +1,6 @@
const Vue = require('vue');
const strftime = require('strftime');
Vue.filter('date', function(value, specifiers) {
Vue.filter('date', function(value, specifiers = '%d-%m-%Y') {
return strftime(specifiers, value);
});

View File

@ -43,7 +43,7 @@
<tbody>
<tr v-for="waste in wastes" v-bind:key="waste.buyer">
<td class="font gray">{{waste.buyer}}</td>
<td class="number">{{(waste.percentage / 100) | percentage(4, 4, locale)}}</td>
<td class="number">{{(waste.percentage / 100) | percentage(2, 2, locale)}}</td>
<td class="number">{{waste.dwindle | currency('EUR', locale)}}</td>
<td class="number">{{waste.total | currency('EUR', locale)}}</td>
</tr>

View File

@ -4,6 +4,6 @@ dear: Hola
description: A continuación se muestra la merma semanal a fecha de <strong>{0}</strong>.
buyer: Comprador
percentage: Porcentaje
weakening: Mermas
dwindle: Merma
total: Total
wasteDetailLink: 'Para ver el desglose de mermas haz clic en el siguiente enlace:'

View File

@ -231,7 +231,7 @@
<div class="header">{{$t('digitalSignature')}}</div>
<div class="body centered">
<img v-bind:src="dmsPath"/>
<div>{{signature.created | date}}</div>
<div>{{signature.created | date('%d-%m-%Y')}}</div>
</div>
</div>
</div>