This commit is contained in:
Carlos Jimenez Ruiz 2020-01-24 10:17:08 +01:00
commit f74792dbe9
180 changed files with 8050 additions and 1488 deletions

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

@ -0,0 +1,138 @@
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(sender, to, `@${sender.name}: ${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

@ -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

@ -22,8 +22,10 @@ module.exports = Self => {
Self.removeFile = async(ctx, id) => {
const models = Self.app.models;
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}});
const dms = await models.Dms.findById(id);
const trashDmsType = await models.DmsType.findOne({
where: {code: 'trash'}
});
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dms.dmsTypeFk);
if (!hasWriteRole)

View File

@ -1,48 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('send', {
description: 'Send message to user',
accessType: 'WRITE',
accepts: [{
arg: 'data',
type: 'object',
required: true,
description: 'recipientFk, message',
http: {source: 'body'}
}, {
arg: 'context',
type: 'object',
http: function(ctx) {
return ctx;
}
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:recipient/send`,
verb: 'post'
}
});
Self.send = async(ctx, data, options) => {
const accessToken = ctx.options && ctx.options.accessToken || ctx.req && ctx.req.accessToken;
const userId = accessToken.userId;
const models = Self.app.models;
const sender = await models.Account.findById(userId, null, options);
const recipient = await models.Account.findById(data.recipientFk, null, options);
await Self.create({
sender: sender.name,
recipient: recipient.name,
message: data.message
}, options);
return await models.MessageInbox.create({
sender: sender.name,
recipient: recipient.name,
finalRecipient: recipient.name,
message: data.message
}, options);
};
};

View File

@ -1,14 +0,0 @@
const app = require('vn-loopback/server/server');
describe('message send()', () => {
it('should return a response containing the same message in params', async() => {
let ctx = {req: {accessToken: {userId: 1}}};
let params = {
recipientFk: 1,
message: 'I changed something'
};
let response = await app.models.Message.send(ctx, params, {transaction: 'You'});
expect(response.message).toEqual(params.message);
});
});

View File

@ -23,12 +23,6 @@
"Delivery": {
"dataSource": "vn"
},
"Message": {
"dataSource": "vn"
},
"MessageInbox": {
"dataSource": "vn"
},
"Province": {
"dataSource": "vn"
},
@ -59,12 +53,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

@ -3,6 +3,9 @@
"name": "Dms",
"description": "Documental Managment system",
"base": "VnModel",
"log": {
"showField": "reference"
},
"options": {
"mysql": {
"table": "dms"

View File

@ -1,43 +0,0 @@
{
"name": "MessageInbox",
"base": "VnModel",
"options": {
"mysql": {
"table": "messageInbox"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"sender": {
"type": "String",
"required": true
},
"recipient": {
"type": "String",
"required": true
},
"finalRecipient": {
"type": "String",
"required": true
},
"message": {
"type": "String"
}
},
"relations": {
"remitter": {
"type": "belongsTo",
"model": "User",
"foreignKey": "sender"
},
"receptor": {
"type": "belongsTo",
"model": "User",
"foreignKey": "recipient"
}
}
}

View File

@ -1,3 +0,0 @@
module.exports = Self => {
require('../methods/message/send')(Self);
};

View File

@ -1,39 +0,0 @@
{
"name": "Message",
"base": "VnModel",
"options": {
"mysql": {
"table": "message"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"sender": {
"type": "String",
"required": true
},
"recipient": {
"type": "String",
"required": true
},
"message": {
"type": "String"
}
},
"relations": {
"remitter": {
"type": "belongsTo",
"model": "User",
"foreignKey": "sender"
},
"receptor": {
"type": "belongsTo",
"model": "User",
"foreignKey": "recipient"
}
}
}

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

@ -1,3 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Thermograph', '*', '*', 'ALLOW', 'ROLE', 'buyer');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('TravelThermograph', '*', '*', 'ALLOW', 'ROLE', 'buyer');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Entry', '*', '*', 'ALLOW', 'ROLE', 'buyer');

View File

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

View File

@ -0,0 +1,7 @@
ALTER TABLE `vn`.`travelThermograph`
ADD COLUMN `id` INT NOT NULL AUTO_INCREMENT FIRST,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`);
ALTER TABLE `vn`.`travelThermograph`
ADD UNIQUE INDEX `thermograph_created` (`thermographFk` ASC, `created` ASC) VISIBLE;

View File

@ -0,0 +1,29 @@
USE `bs`;
DROP procedure IF EXISTS `weekWaste_getDetail`;
DELIMITER $$
USE `bs`$$
CREATE DEFINER=`root`@`%` PROCEDURE `weekWaste_getDetail`()
BEGIN
DECLARE vLastWeek DATE;
DECLARE vWeek INT;
DECLARE vYear INT;
SET vLastWeek = TIMESTAMPADD(WEEK,-1,CURDATE());
SET vYear = YEAR(vLastWeek);
SET vWeek = WEEK(vLastWeek, 1);
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
ws.family,
sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle
FROM bs.waste ws
WHERE year = vYear AND week = vWeek
GROUP BY buyer, family
) sub
ORDER BY percentage DESC;
END$$
DELIMITER ;

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 ;

File diff suppressed because one or more lines are too long

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
@ -1128,13 +1127,26 @@ INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`,
(6, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 6, 442, 'Movement 6', 'this is the note six', 'observation six'),
(7, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 7, 442, 'Movement 7', 'this is the note seven', 'observation seven');
INSERT INTO `bi`.`claims_ratio`(`id_Cliente`, `Consumo`, `Reclamaciones`, `Ratio`, `recobro`, `inflacion`)
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(101, 500, NULL, 0.00, 0.00, 1.00),
(102, 1000, 2.00, 0.01, 0.05, 1.00),
(103, 2000, 0.00, 0.00, 0.02, 1.00),
(104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWaste`, `rate`)
VALUES
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', '1062', '51', '4.8'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Colombia', '35074', '687', '2.0'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Mini', '1777', '13', '0.7'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Short', '9182', '59', '0.6'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Containers', '-74', '0', '0.0'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Packagings', '-7', '0', '0.0'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Freight', '1100', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Funeral Accessories', '848', '-187', '-22.1'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`, `created`)
VALUES
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
@ -1928,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);
@ -2005,11 +1950,14 @@ INSERT INTO `vn`.`thermograph`(`id`, `model`)
VALUES
('TMM190901395', 'TEMPMATE'),
('TL.BBA85422', 'TL30'),
('TZ1905012010', 'DISPOSABLE');
('TZ1905012010', 'DISPOSABLE'),
('138350-0', 'DISPOSABLE');
INSERT INTO `vn`.`travelThermograph`(`thermographFk`, `created`, `warehouseFk`, `travelFk`, `temperature`, `result`, `dmsFk`)
VALUES
('TMM190901395', CURDATE(), 1, 1, 'WARM', 'Ok', NULL),
('TL.BBA85422', DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 2, 'COOL', 'Ok', NULL),
('TL.BBA85422', CURDATE(), 2, 1, 'COOL', 'can not read the temperature', NULL),
('TZ1905012010', CURDATE(), 1, 1, 'WARM', 'Temperature in range', 5);
('TMM190901395', CURDATE(), 1, 1, 'WARM', 'Ok', NULL),
('TL.BBA85422', DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 2, 'COOL', 'Ok', NULL),
('TL.BBA85422', CURDATE(), 2, 1, 'COOL', 'can not read the temperature', NULL),
('TZ1905012010', CURDATE(), 1, 1, 'WARM', 'Temperature in range', 5),
('138350-0', DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 'WARM', NULL, 5),
('138350-0', CURDATE(), 1, NULL, 'COOL', NULL, NULL);

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
SCHEMAS=(
account
bi
bs
cache
edi
hedera

View File

@ -35,7 +35,7 @@ describe('Ticket List sale path', () => {
const value = await page
.waitToGetProperty(selectors.ticketSales.firstSaleDiscount, 'innerText');
expect(value).toContain('0 %');
expect(value).toContain('0.00%');
});
it('should confirm the first sale contains the total import', async() => {

View File

@ -177,10 +177,10 @@ xdescribe('Ticket Edit sale path', () => {
it('should confirm the discount have been updated', async() => {
const result = await nightmare
.waitForTextInElement(`${selectors.ticketSales.firstSaleDiscount} > span`, '50 %')
.waitForTextInElement(`${selectors.ticketSales.firstSaleDiscount} > span`, '50.00%')
.waitToGetProperty(`${selectors.ticketSales.firstSaleDiscount} > span`, 'innerText');
expect(result).toContain('50 %');
expect(result).toContain('50.00%');
});
it('should confirm the total import for that item have been updated', async() => {

View File

@ -16,11 +16,21 @@ export default class Textarea extends Field {
get rows() {
return this.input.rows;
}
set maxlength(value) {
let length = typeof value == 'number' && value > 1 ? value : 50;
this.input.setAttribute('maxlength', length);
}
get maxlength() {
return this.input.getAttribute('maxlength', length);
}
}
ngModule.vnComponent('vnTextarea', {
controller: Textarea,
bindings: {
rows: '<?'
rows: '<?',
maxlength: '<?'
}
});

View File

@ -34,4 +34,24 @@ describe('Component vnTextarea', () => {
expect($ctrl.rows).toEqual(3);
});
});
describe('maxlength() setter', () => {
it(`should set maxlength property of the element to the given value if it's a valid number`, () => {
$ctrl.maxlength = 100;
expect($ctrl.maxlength).toEqual('100');
});
it(`should set maxlength property of the element to 3 if the given value if it's null`, () => {
$ctrl.maxlength = null;
expect($ctrl.maxlength).toEqual('50');
});
it(`should set maxlength property of the element to 3 if the given value if it's not a valid number`, () => {
$ctrl.maxlength = 'a';
expect($ctrl.maxlength).toEqual('50');
});
});
});

View File

@ -1,15 +1,22 @@
import ngModule from '../module';
/**
* Formats a number multiplying by 100 and adding character %.
*
* @return {String} The formated number
*/
export default function percentage() {
return function(input) {
export default function percentage($translate) {
function percentage(input, fractionSize = 2) {
if (input == null || input === '')
return null;
return `${input} %`;
};
return new Intl.NumberFormat($translate.use(), {
style: 'percent',
minimumFractionDigits: fractionSize,
maximumFractionDigits: fractionSize
}).format(parseFloat(input));
}
percentage.$stateful = true;
return percentage;
}
percentage.$inject = ['$translate'];
ngModule.filter('percentage', percentage);

View File

@ -23,6 +23,18 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-buyrequest:before {
content: "\e914";
}
.icon-entry:before {
content: "\e959";
}
.icon-thermometer:before {
content: "\e95a";
}
.icon-deletedTicket:before {
content: "\e958";
}
.icon-fruit:before {
content: "\e957";
}
@ -134,9 +146,6 @@
.icon-doc:before {
content: "\e913";
}
.icon-entry:before {
content: "\e914";
}
.icon-exit:before {
content: "\e947";
}
@ -287,6 +296,3 @@
.icon-worker:before {
content: "\e943";
}
.icon-deletedTicket:before {
content: "\e958";
}

View File

@ -27,7 +27,7 @@
<glyph unicode="&#xe911;" glyph-name="details" d="M908.823 844.777v-797.867h-793.6v797.867h793.6zM972.823 959.977h-921.6c-29.867 0-51.2-21.333-51.2-51.2v-921.6c0-21.333 21.333-51.2 51.2-51.2h921.6c21.333 0 51.2 29.867 51.2 51.2v921.6c0 29.867-29.867 51.2-51.2 51.2zM456.556 733.844h341.333v-115.2h-341.333v115.2zM456.556 503.444h341.333v-115.2h-341.333v115.2zM456.556 277.31h341.333v-115.2h-341.333v115.2zM226.156 733.844h115.2v-115.2h-115.2v115.2zM226.156 503.444h115.2v-115.2h-115.2v115.2zM226.156 277.31h115.2v-115.2h-115.2v115.2z" />
<glyph unicode="&#xe912;" glyph-name="fiscal" d="M140.8 520.533v-341.333h149.333v341.333h-149.333zM439.467 520.533v-341.333h149.333v341.333h-149.333zM38.4-64h942.933v145.067h-942.933v-145.067zM733.867 520.533v-341.333h149.333v341.333h-149.333zM512 960l-473.6-243.2v-98.133h942.933v98.133l-469.333 243.2z" />
<glyph unicode="&#xe913;" glyph-name="doc" d="M614.4 960h-409.6c-55.467 0-102.4-46.933-102.4-102.4v-819.2c0-55.467 46.933-102.4 102.4-102.4h614.4c55.467 0 102.4 46.933 102.4 102.4v614.4l-307.2 307.2zM716.8 140.8h-409.6v102.4h409.6v-102.4zM716.8 345.6h-409.6v102.4h409.6v-102.4zM563.2 601.6v281.6l281.6-281.6h-281.6z" />
<glyph unicode="&#xe914;" glyph-name="entry" d="M0-64h1024v430.933h-264.533v-166.4l-247.467 247.467 247.467 247.467v-166.4h264.533v430.933h-1024v-1024z" />
<glyph unicode="&#xe914;" glyph-name="buyrequest" d="M520.533 878.933c-34.133 0-55.467-25.6-55.467-55.467s25.6-55.467 55.467-55.467 55.467 25.6 55.467 55.467-21.333 55.467-55.467 55.467zM520.533 806.4c-12.8 0-17.067 8.533-17.067 17.067s8.533 17.067 17.067 17.067 17.067-8.533 17.067-17.067-4.267-17.067-17.067-17.067zM588.8 247.467l-4.267 12.8v4.267l-17.067 59.733h-273.067v-89.6h298.667zM294.4 686.933h452.267v-89.6h-452.267v89.6zM571.733 430.933c17.067 12.8 38.4 17.067 59.733 8.533l64-25.6h55.467v89.6h-456.533v-89.6h264.533c4.267 8.533 8.533 12.8 12.8 17.067zM643.985 256.128l95.27 85.478 202.299-225.473-95.27-85.478-202.299 225.473zM855.196 15.592l95.283 85.466 22.791-25.409-95.283-85.466-22.791 25.409zM1015.467 29.867l-34.133 38.4-93.867-85.333 34.133-38.4c4.267-4.267 12.8-8.533 17.067-8.533 8.533 0 12.8 0 17.067 8.533l59.733 51.2c8.533 8.533 12.8 25.6 0 34.133zM614.4 401.067c-4.267 0-8.533 0-12.8-4.267-4.267 0-4.267-8.533-4.267-12.8l34.133-119.467 93.867 85.333-110.933 51.2zM729.6 93.867h-524.8c-29.867 0-51.2 25.6-51.2 51.2v635.733c0 25.6 21.333 51.2 51.2 51.2h213.333l8.533 25.6c12.8 38.4 51.2 64 93.867 64s76.8-25.6 93.867-64l8.533-25.6h213.333c29.867 0 51.2-25.6 51.2-51.2v-541.867l38.4-42.667v584.533c0 46.933-42.667 89.6-89.6 89.6h-187.733c-17.067 51.2-68.267 89.6-128 89.6s-110.933-38.4-128-89.6h-187.733c-46.933 0-89.6-42.667-89.6-89.6v-635.733c0-46.933 42.667-89.6 89.6-89.6h554.667l-29.867 38.4z" />
<glyph unicode="&#xe915;" glyph-name="eye" d="M512 797.867c-234.667 0-430.933-145.067-512-349.867 81.067-204.8 277.333-349.867 512-349.867s430.933 145.067 512 349.867c-81.067 204.8-277.333 349.867-512 349.867zM512 213.333c-128 0-234.667 102.4-234.667 234.667s106.667 234.667 234.667 234.667 234.667-106.667 234.667-234.667-106.667-234.667-234.667-234.667zM512 588.8c-76.8 0-140.8-64-140.8-140.8s64-140.8 140.8-140.8 140.8 64 140.8 140.8-64 140.8-140.8 140.8z" />
<glyph unicode="&#xe916;" glyph-name="flower" d="M905.6 636.8c-9.6 3.2-19.2 19.2-16 25.6 6.4 89.6-25.6 163.2-99.2 214.4-76.8 51.2-156.8 54.4-236.8 9.6-19.2-9.6-28.8-9.6-48 0-76.8 48-153.6 54.4-233.6 12.8-80-44.8-118.4-112-121.6-201.6 0-19.2-6.4-28.8-25.6-38.4-83.2-41.6-124.8-108.8-128-201.6 0-80 32-150.4 102.4-192 22.4-16 38.4-28.8 28.8-57.6 3.2-89.6 38.4-160 115.2-201.6 76.8-44.8 156.8-38.4 233.6 3.2 6.4 3.2 22.4 0 32-3.2 38.4-12.8 73.6-38.4 112-38.4 140.8-9.6 243.2 89.6 246.4 233.6 0 9.6 9.6 19.2 16 25.6 28.8 25.6 70.4 48 92.8 80 86.4 112 48 259.2-70.4 329.6zM806.4 300.8c-16-3.2-28.8-3.2-48-6.4 6.4-16 9.6-32 12.8-44.8 25.6-70.4 0-140.8-64-176-64-32-134.4-12.8-179.2 51.2-6.4 12.8-16 22.4-22.4 32-22.4-22.4-41.6-44.8-64-60.8-51.2-38.4-118.4-35.2-166.4 3.2s-64 105.6-41.6 163.2c6.4 16 12.8 32 22.4 51.2-22.4 3.2-38.4 9.6-57.6 12.8-67.2 16-115.2 76.8-108.8 140.8 6.4 67.2 60.8 121.6 131.2 128 16 0 28.8 3.2 48 6.4-6.4 19.2-12.8 38.4-19.2 57.6-22.4 60.8 6.4 128 64 160s131.2 16 172.8-38.4c9.6-12.8 22.4-28.8 35.2-44.8 12.8 12.8 25.6 25.6 35.2 41.6 38.4 48 112 64 169.6 35.2s86.4-96 67.2-156.8c-6.4-22.4-16-44.8-28.8-70.4 16-3.2 32-6.4 48-9.6 73.6-12.8 118.4-67.2 118.4-140.8s-51.2-124.8-124.8-134.4zM512 624c-102.4 0-176-76.8-176-176 0-102.4 80-179.2 179.2-179.2 96 0 176 83.2 176 179.2s-80 176-179.2 176zM512 358.4c-48 0-89.6 38.4-86.4 89.6 0 48 41.6 86.4 86.4 86.4s86.4-41.6 86.4-89.6c0-48-38.4-86.4-86.4-86.4z" />
<glyph unicode="&#xe917;" glyph-name="frozen" d="M1024 499.2h-213.333l166.4 166.4-72.533 72.533-238.933-238.933h-102.4v102.4l238.933 238.933-72.533 72.533-166.4-166.4v213.333h-102.4v-213.333l-166.4 166.4-72.533-72.533 238.933-238.933v-102.4h-102.4l-238.933 238.933-72.533-72.533 166.4-166.4h-213.333v-102.4h213.333l-166.4-166.4 72.533-72.533 238.933 238.933h102.4v-102.4l-238.933-238.933 72.533-72.533 166.4 166.4v-213.333h102.4v213.333l166.4-166.4 72.533 72.533-238.933 238.933v102.4h102.4l238.933-238.933 72.533 72.533-166.4 166.4h213.333v102.4z" />
@ -96,4 +96,6 @@
<glyph unicode="&#xe956;" glyph-name="deliveryprices" d="M789.333 264.533c-55.467 0-102.4-46.933-102.4-102.4s46.933-102.4 102.4-102.4 102.4 46.933 102.4 102.4c0 59.733-46.933 102.4-102.4 102.4zM789.333 110.933c-29.867 0-51.2 21.333-51.2 51.2s21.333 51.2 51.2 51.2 51.2-21.333 51.2-51.2c0-25.6-25.6-51.2-51.2-51.2zM251.733 264.533c-55.467 0-102.4-46.933-102.4-102.4s46.933-102.4 102.4-102.4 102.4 46.933 102.4 102.4c0 59.733-46.933 102.4-102.4 102.4zM251.733 110.933c-29.867 0-51.2 21.333-51.2 51.2s21.333 51.2 51.2 51.2 51.2-21.333 51.2-51.2c0-25.6-25.6-51.2-51.2-51.2zM1006.933 537.6l-196.267 192c-12.8 12.8-29.867 17.067-46.933 17.067h-98.133v38.4c0 25.6-21.333 51.2-51.2 51.2h-563.2c-29.867 0-51.2-21.333-51.2-51.2v-554.667c0-29.867 25.6-51.2 51.2-51.2h68.267c8.533 64 64 115.2 132.267 115.2 64 0 123.733-51.2 132.267-115.2h268.8c8.533 64 64 115.2 132.267 115.2s128-51.2 136.533-115.2h51.2c29.867 0 51.2 25.6 51.2 51.2v260.267c0 17.067-8.533 34.133-17.067 46.933zM392.533 605.867v-38.4h-170.667c0-8.533 0-12.8 0-17.067s0-12.8 0-17.067h170.667v-42.667h-157.867c12.8-25.6 25.6-42.667 51.2-59.733 21.333-12.8 46.933-21.333 76.8-21.333 42.667 0 76.8 17.067 102.4 46.933l46.933-42.667c-17.067-21.333-38.4-38.4-68.267-46.933-25.6-12.8-55.467-17.067-89.6-17.067s-64 4.267-89.6 17.067c-25.6 12.8-51.2 29.867-68.267 51.2s-29.867 42.667-38.4 72.533h-64v38.4h55.467c0 4.267 0 8.533 0 17.067s0 12.8 0 17.067h-55.467v42.667h64c8.533 29.867 21.333 51.2 38.4 76.8s42.667 38.4 68.267 51.2c29.867 8.533 59.733 12.8 93.867 12.8 29.867 0 59.733-4.267 89.6-17.067 25.6-8.533 46.933-25.6 64-46.933l-46.933-42.667c-29.867 29.867-64 46.933-102.4 46.933-29.867 0-55.467-8.533-76.8-21.333-25.6-17.067-42.667-34.133-51.2-59.733h157.867zM921.6 529.067h-187.733c-8.533 0-8.533 4.267-8.533 8.533v145.067c0 4.267 4.267 8.533 8.533 8.533h34.133c0 0 4.267 0 4.267-4.267l153.6-145.067c4.267 0 0-12.8-4.267-12.8z" />
<glyph unicode="&#xe957;" glyph-name="fruit" d="M870.4 814.933c-204.8 196.267-529.067 192-725.333-8.533-196.267-204.8-192-529.067 8.533-725.333s524.8-192 725.333 12.8c196.267 200.533 192 524.8-8.533 721.067zM840.533 128c-174.933-179.2-465.067-183.467-644.267-8.533s-179.2 465.067-4.267 644.267 465.067 183.467 644.267 8.533c179.2-174.933 179.2-460.8 4.267-644.267zM145.067 366.933c-38.4 51.2-4.267 149.333 8.533 162.133 29.867 17.067 157.867-25.6 183.467-38.4s98.133-29.867 98.133-55.467c0-29.867-64-46.933-119.467-55.467-59.733-12.8-153.6-38.4-170.667-12.8zM256 170.667c-59.733 17.067-89.6 102.4-85.333 119.467 12.8 29.867 136.533 68.267 162.133 76.8 25.6 4.267 93.867 34.133 106.667 12.8 17.067-25.6-29.867-64-64-106.667-38.4-46.933-89.6-115.2-119.467-102.4zM405.333 622.933c-25.6 51.2-72.533 128-51.2 153.6 38.4 51.2 132.267 51.2 145.067 38.4 25.6-21.333 17.067-157.867 12.8-183.467s0-102.4-25.6-106.667c-29.867-8.533-55.467 46.933-81.067 98.133zM537.6 648.533c8.533 59.733 8.533 149.333 42.667 162.133 59.733 21.333 140.8-29.867 149.333-51.2 8.533-34.133-72.533-145.067-93.867-166.4s-59.733-85.333-85.333-76.8c-29.867 12.8-17.067 76.8-12.8 132.267zM494.933 217.6c-8.533-51.2-12.8-128-38.4-136.533-51.2-17.067-119.467 25.6-123.733 42.667-8.533 29.867 68.267 123.733 85.333 140.8s51.2 72.533 72.533 64c21.333-4.267 8.533-59.733 4.267-110.933zM320 516.267c-55.467 25.6-136.533 51.2-136.533 85.333 0 64 68.267 123.733 85.333 123.733 34.133 0 110.933-110.933 128-132.267 12.8-25.6 64-76.8 46.933-98.133-17.067-29.867-72.533 0-123.733 21.333zM887.467 482.133c38.4-51.2 8.533-149.333-17.067-162.133-29.867-17.067-145.067 38.4-170.667 46.933-25.6 12.8-98.133 29.867-98.133 55.467 0 29.867 64 38.4 123.733 51.2 55.467 8.533 140.8 34.133 162.133 8.533zM785.067 686.933c55.467-17.067 89.6-98.133 85.333-115.2-8.533-29.867-132.267-64-157.867-72.533-25.6-4.267-89.6-29.867-102.4-12.8-17.067 25.6 25.6 64 64 102.4 29.867 42.667 76.8 106.667 110.933 98.133zM725.333 328.533c51.2-17.067 102.4-46.933 102.4-76.8-4.267-51.2-64-115.2-98.133-115.2-29.867 0-89.6 106.667-102.4 132.267-12.8 21.333-51.2 76.8-34.133 98.133s76.8-17.067 132.267-38.4zM610.133 238.933c21.333-46.933 59.733-110.933 38.4-132.267-34.133-42.667-115.2-42.667-128-29.867-21.333 17.067-8.533 136.533-4.267 157.867 4.267 25.6 4.267 89.6 25.6 93.867 29.867 4.267 46.933-46.933 68.267-89.6z" />
<glyph unicode="&#xe958;" glyph-name="deletedTicket" horiz-adv-x="900" d="M96.823 84.328h694.333v640.777c0 0-2.019 234.895-350.243 234.895s-344.090-234.895-344.090-234.895v-640.777zM227.684 652.223h171.244v152.494h102.496v-152.494h171.276v-102.496h-171.276v-357.519h-102.496v357.519h-171.244v102.496zM0 59.649v-123.649h900.415v123.649h-900.415z" />
<glyph unicode="&#xe959;" glyph-name="entry" d="M0 328.882l392.882-392.882 265.404 265.404-100.31 100.31-102.4-100.31v303.020h303.020l-100.31-102.4 100.31-100.31 265.404 265.404-392.882 392.882z" />
<glyph unicode="&#xe95a;" glyph-name="thermometer" d="M641.567 326.792v35.527h64.784v25.078h-64.784v119.118h64.784v25.078h-64.784v119.118h64.784v25.078h-64.784v121.208h64.784v25.078h-64.784v8.359c0 71.053-58.514 129.567-129.567 129.567s-129.567-58.514-129.567-129.567v-503.641c-54.335-39.706-87.771-104.49-87.771-173.453 0-119.118 96.131-217.339 217.339-217.339 119.118 0 217.339 96.131 217.339 217.339 0 66.873-33.437 131.657-87.771 173.453zM512-28.473c-100.31 0-179.722 81.502-179.722 179.722 0 64.784 33.437 123.298 87.771 154.645v524.539c0 50.155 41.796 91.951 91.951 91.951s91.951-41.796 91.951-91.951v-522.449c54.335-31.347 87.771-89.861 87.771-154.645 0-100.31-79.412-181.812-179.722-181.812zM652.016 435.461v25.078h35.527v-25.078h-35.527zM652.016 579.657v25.078h35.527v-25.078h-35.527zM652.016 723.853v25.078h35.527v-25.078h-35.527zM568.424 284.996v543.347c0 0 0 0 0 0s0 0 0 0v0 0c0 31.347-25.078 56.424-56.424 56.424s-56.424-25.078-56.424-56.424v0-543.347c-52.245-20.898-87.771-73.143-87.771-131.657 0-79.412 64.784-144.196 144.196-144.196s144.196 64.784 144.196 144.196c0 58.514-35.527 108.669-87.771 131.657zM470.204 824.163v4.18c0 22.988 18.808 41.796 41.796 41.796s41.796-18.808 41.796-41.796v-219.429h-85.682v215.249z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -17,5 +17,6 @@ export default function moduleImport(moduleName) {
case 'worker' : return import('worker/front');
case 'invoiceOut' : return import('invoiceOut/front');
case 'route' : return import('route/front');
case 'entry' : return import('entry/front');
}
}

View File

@ -87,8 +87,7 @@ vn-layout {
& > * {
display: block;
padding: $spacing-md;
box-sizing: border-box;
height: 100%
box-sizing: border-box
}
&.ng-enter {
vn-side-menu {

View File

@ -2,4 +2,5 @@
.vn-module-card {
padding: 0;
height: 100%
}

View File

@ -2,4 +2,5 @@
.vn-module-main {
padding: 0;
height: 100%
}

View File

@ -43,6 +43,7 @@ Workers: Trabajadores
Routes: Rutas
Locator: Localizador
Invoices out: Facturas emitidas
Entries: Entradas
# Common

View File

@ -219,7 +219,7 @@ module.exports = function(Self) {
userFk: userFk,
action: action,
changedModel: ctx.Model.definition.name,
changedModelId: changedModelId,
changedModelId: changedModelId, // Model property with an different data type will throw a NaN error
changedModelValue: where,
oldInstance: oldInstance,
newInstance: newInstance

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

@ -57,5 +57,9 @@
"The postcode doesn't exists. Ensure you put the correct format": "The postcode doesn't exists. Ensure you put the correct format",
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
"Has deleted the ticket id": "Has deleted the ticket id [#{{id}}]({{{url}}})",
"Swift / BIC can't be empty": "Swift / BIC can't be empty"
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
"MESSAGE_BOUGHT_UNITS": "Bought {{quantity}} units of {{concept}} (#{{itemId}}) for the ticket id [#{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} (#{{clientId}})]({{{url}}}) to *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} (#{{clientId}})]({{{url}}})",
"MESSAGE_CLAIM_ITEM_REGULARIZE": "I sent *{{quantity}}* units of [{{concept}} (#{{itemId}})]({{{itemUrl}}}) to {{nickname}} coming from ticket id [#{{ticketId}}]({{{ticketUrl}}})"
}

View File

@ -118,5 +118,9 @@
"You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fín",
"Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fín",
"You should mark at least one week day": "Debes marcar al menos un día de la semana",
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío"
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
"MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} (#{{itemId}}) para el ticket id [#{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} (#{{clientId}})]({{{url}}}) a *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} (#{{clientId}})]({{{url}}})",
"MESSAGE_CLAIM_ITEM_REGULARIZE": "Envio *{{quantity}}* unidades de [{{concept}} (#{{itemId}})]({{{itemUrl}}}) a {{nickname}} provenientes del ticket id [#{{ticketId}}]({{{ticketUrl}}})"
}

View File

@ -19,6 +19,7 @@ module.exports = Self => {
Self.regularizeClaim = async(ctx, params) => {
const models = Self.app.models;
const $t = ctx.req.__; // $translate
const resolvedState = 3;
let tx = await Self.beginTransaction({});
@ -38,8 +39,7 @@ module.exports = Self => {
const destination = claimEnd.claimDestination();
const addressFk = destination && destination.addressFk;
if (!addressFk)
continue;
if (!addressFk) continue;
let sale = await getSale(claimEnd.saleFk, options);
let ticketFk = await getTicketId({
@ -70,15 +70,19 @@ module.exports = Self => {
discount: 100
}, options);
if (sale.ticket().client().salesPerson()) {
await sendMessage(ctx, {
itemFk: sale.itemFk,
ticketFk: sale.ticketFk,
recipientFk: sale.ticket().client().salesPerson().userFk,
const salesPerson = sale.ticket().client().salesPerson();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('MESSAGE_CLAIM_ITEM_REGULARIZE', {
quantity: sale.quantity,
concept: sale.concept,
nickname: address.nickname
}, options);
itemId: sale.itemFk,
ticketId: sale.ticketFk,
nickname: address.nickname,
ticketUrl: `${origin}/#!/ticket/${sale.ticketFk}/summary`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
}
@ -156,14 +160,4 @@ module.exports = Self => {
return ticket.id;
}
async function sendMessage(ctx, params, options) {
const message = `Envio ${params.quantity} unidades de "${params.concept}" (#${params.itemFk}) a `
+ `"${params.nickname}" provenientes del ticket #${params.ticketFk}`;
await Self.app.models.Message.send(ctx, {
recipientFk: params.recipientFk,
message: message
}, options);
}
};

View File

@ -22,7 +22,13 @@ describe('regularizeClaim()', () => {
});
it('should change claim state to resolved', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
const ctx = {req: {
accessToken: {userId: 18},
headers: {origin: 'http://localhost'}}
};
ctx.req.__ = value => {
return value;
};
let params = {claimFk: claimFk};
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {

View File

@ -206,9 +206,9 @@ describe('claim', () => {
return resolve({id: freightPickUpPrice});
}));
controller.onUpdateGreugeResponse('accept').then(res => {
console.log('asdas');
}).catch(error => {
console.log('errorrrr!!');
});
$httpBackend.flush();

View File

@ -10,4 +10,4 @@ import './descriptor';
import './development';
import './search-panel';
import './summary';
import './dms/index';
import './photos';

View File

@ -1,4 +1,4 @@
import ngModule from '../../module';
import ngModule from '../module';
import './style.scss';
class Controller {
@ -108,7 +108,7 @@ class Controller {
Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnToken', 'vnApp', 'vnConfig'];
ngModule.component('vnClaimDmsIndex', {
ngModule.component('vnClaimPhotos', {
template: require('./index.html'),
controller: Controller,
bindings: {

View File

@ -2,7 +2,7 @@ import './index';
import crudModel from 'core/mocks/crud-model';
describe('Claim', () => {
describe('Component vnClaimDmsIndex', () => {
describe('Component vnClaimPhotos', () => {
let $componentController;
let $scope;
let $httpBackend;
@ -16,7 +16,7 @@ describe('Claim', () => {
$httpParamSerializer = _$httpParamSerializer_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnClaimDmsIndex', {$: $scope});
controller = $componentController('vnClaimPhotos', {$: $scope});
controller.$.model = crudModel;
controller.claim = {
id: 1,

View File

@ -0,0 +1,32 @@
@import "./variables";
vn-claim-photos {
height: 100%;
.drop-zone {
color: $color-font-secondary;
box-sizing: border-box;
border-radius: 0.5em;
text-align: center;
min-height: 100%;
.empty-rows {
padding: 5em $spacing-md;
font-size: 1.4em
}
vn-icon {
font-size: 3em
}
}
.photo-list {
padding: $spacing-md;
min-height: 100%;
.photo {
width: 32em;
height: 18em;
}
}
}

View File

@ -11,7 +11,7 @@
"card": [
{"state": "claim.card.basicData", "icon": "settings"},
{"state": "claim.card.detail", "icon": "icon-details"},
{"state": "claim.card.dms.index", "icon": "image"},
{"state": "claim.card.photos", "icon": "image"},
{"state": "claim.card.development", "icon": "icon-traceability"},
{"state": "claim.card.action", "icon": "icon-actions"}
]
@ -81,14 +81,9 @@
},
"acl": ["salesAssistant"]
}, {
"url": "/dms",
"state": "claim.card.dms",
"abstract": true,
"component": "ui-view"
}, {
"url" : "/index",
"state": "claim.card.dms.index",
"component": "vn-claim-dms-index",
"url": "/photos",
"state": "claim.card.photos",
"component": "vn-claim-photos",
"description": "Photos",
"params": {
"claim": "$ctrl.claim"

View File

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

View File

@ -0,0 +1,57 @@
module.exports = Self => {
Self.remoteMethodCtx('sendSms', {
description: 'Log the message in clientLog and call the send method',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'destination',
type: 'String',
required: true,
},
{
arg: 'message',
type: 'String',
required: true,
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/sendSms`,
verb: 'POST'
}
});
Self.sendSms = async(ctx, id, destination, message) => {
const userId = ctx.req.accessToken.userId;
let sms = await Self.app.models.Sms.send(ctx, id, destination, message);
let logRecord = {
originFk: id,
userFk: userId,
action: 'insert',
changedModel: 'sms',
newInstance: {
destinationFk: id,
destination: destination,
message: message,
statusCode: sms.statusCode,
status: sms.status
}
};
const clientLog = await Self.app.models.ClientLog.create(logRecord);
sms.logId = clientLog.id;
return sms;
};
};

View File

@ -7,7 +7,7 @@ describe('Client activeWorkersWithRole', () => {
let isSalesPerson = await app.models.Account.hasRole(result[0].id, 'salesPerson');
expect(result.length).toEqual(14);
expect(result.length).toEqual(15);
expect(isSalesPerson).toBeTruthy();
});

View File

@ -6,7 +6,7 @@ describe('Client listWorkers', () => {
.then(result => {
let amountOfEmployees = Object.keys(result).length;
expect(amountOfEmployees).toEqual(49);
expect(amountOfEmployees).toEqual(50);
done();
})
.catch(done.fail);

View File

@ -0,0 +1,27 @@
const app = require('vn-loopback/server/server');
describe('client sendSms()', () => {
let createdLog;
afterAll(async done => {
await app.models.ClientLog.destroyById(createdLog.id);
done();
});
it('should send a message and log it', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let id = 101;
let destination = 222222222;
let message = 'this is the message created in a test';
let sms = await app.models.Client.sendSms(ctx, id, destination, message);
logId = sms.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

@ -23,23 +23,25 @@ module.exports = Self => {
});
Self.createWithInsurance = async(data, ctx) => {
let tx = await Self.beginTransaction({});
const tx = await Self.beginTransaction({});
const models = Self.app.models;
const $t = ctx.req.__; // $translate
try {
let options = {transaction: tx};
let classificationSchema = {client: data.clientFk, started: data.started};
let newClassification = await Self.create(classificationSchema, options);
let CreditInsurance = Self.app.models.CreditInsurance;
let insuranceSchema = {
const newClassification = await Self.create({
client: data.clientFk,
started: data.started
}, options);
await models.CreditInsurance.create({
creditClassification: newClassification.id,
credit: data.credit,
grade: data.grade
};
}, options);
let newCreditInsurance = await CreditInsurance.create(insuranceSchema, options);
await tx.commit();
await CreditInsurance.messageSend(newCreditInsurance, ctx.req.accessToken);
return newClassification;
} catch (e) {

View File

@ -1,7 +1,20 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Client createWithInsurance', () => {
let classificationId;
const activeCtx = {
accessToken: {userId: 101},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
activeCtx.http.req.__ = value => {
return value;
};
afterAll(async done => {
await app.models.CreditClassification.destroyById(classificationId);
@ -20,7 +33,9 @@ describe('Client createWithInsurance', () => {
it('should not create the insurance if couldnt create the classification', async() => {
let error;
let data = {clientFk: null, started: Date.now(), credit: 999, grade: 255};
let ctx = {req: {accessToken: {userId: 101}}};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
await app.models.CreditClassification.createWithInsurance(data, ctx)
.catch(e => {
error = e;
@ -37,7 +52,9 @@ describe('Client createWithInsurance', () => {
it('should create a new client credit classification with insurance', async() => {
let data = {clientFk: 101, started: Date.now(), credit: 999, grade: 255};
let ctx = {req: {accessToken: {userId: 101}}};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
let result = await app.models.CreditClassification.createWithInsurance(data, ctx);
classificationId = result.id;

View File

@ -3,7 +3,7 @@ const xmlParser = require('xml2js').parseString;
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('send', {
Self.remoteMethod('send', {
description: 'Sends SMS to a destination phone',
accessType: 'WRITE',
accepts: [{
@ -83,7 +83,6 @@ module.exports = Self => {
};
const sms = await Self.create(newSms);
if (statusCode != 200)
throw new UserError(`We weren't able to send this SMS`);

View File

@ -24,6 +24,7 @@ module.exports = Self => {
require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/uploadFile')(Self);
require('../methods/client/lastActiveTickets')(Self);
require('../methods/client/sendSms')(Self);
// Validations
@ -224,34 +225,38 @@ module.exports = Self => {
const newInstance = hookState.newInstance;
const oldInstance = hookState.oldInstance;
const instance = ctx.instance;
const models = Self.app.models;
const payMethodChanged = oldInstance.payMethodFk != newInstance.payMethodFk;
const ibanChanged = oldInstance.iban != newInstance.iban;
const dueDayChanged = oldInstance.dueDay != newInstance.dueDay;
if (payMethodChanged || ibanChanged || dueDayChanged) {
const message = `La forma de pago del cliente con id ${instance.id} ha cambiado`;
const salesPersonFk = instance.salesPersonFk;
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__;
const origin = httpRequest.headers.origin;
if (salesPersonFk) {
const salesPerson = await Self.app.models.Worker.findById(salesPersonFk);
await Self.app.models.Message.send(ctx, {
recipientFk: salesPerson.userFk,
message: message
const salesPersonId = instance.salesPersonFk;
if (salesPersonId) {
const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`;
const message = $t('MESSAGE_CHANGED_PAYMETHOD', {
clientId: instance.id,
clientName: instance.name,
url: fullUrl
});
await models.Chat.sendCheckingPresence(httpCtx, salesPersonId, message);
}
// Send email to client
if (!instance.email) return;
const loopBackContext = LoopBackContext.getCurrentContext();
const headers = loopBackContext.active.http.req.headers;
const params = {
const serializedParams = httpParamSerializer({
clientId: instance.id,
recipient: instance.email
};
const serializedParams = httpParamSerializer(params);
const query = `${headers.origin}/api/email/payment-update?${serializedParams}`;
});
const query = `${origin}/api/email/payment-update?${serializedParams}`;
await request.get(query);
}
});

View File

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

View File

@ -1,3 +1,5 @@
const LoopBackContext = require('loopback-context');
module.exports = function(Self) {
Self.validateCredit = function(credit) {
return credit >= 0;
@ -38,54 +40,31 @@ module.exports = function(Self) {
message: 'The grade must be similar to the last one'
});
Self.messageSend = async function(data, accessToken) {
let filter = {
include: {
relation: 'classification',
scope: {
fields: ['client'],
include: {
relation: 'customer',
scope: {
fields: ['name', 'salesPersonFk'],
include: {
relation: 'salesPerson',
scope: {
fields: 'userFk',
include: {
relation: 'user',
scope: {
fields: ['name']
}
}
}
}
}
}
}
}
};
let ctx = {req: {accessToken: accessToken}};
let insurance = await Self.findById(data.id, filter);
let customer = insurance.classification().customer();
if (!customer.salesPerson()) return;
let salesPersonId = customer.salesPerson().user().id;
let grade = data.grade ? `(Grado ${data.grade})` : '(Sin grado)';
let params = {
recipientFk: salesPersonId,
message: `He cambiado el crédito asegurado del `
+ `cliente "${customer.name}" a ${data.credit}${grade}`
};
Self.app.models.Message.send(ctx, params);
};
// Update from transaction misses ctx accessToken.
// Fixed passing accessToken from method messageSend()
Self.observe('after save', async function(ctx) {
if (ctx.options.accessToken)
await Self.messageSend(ctx.instance, ctx.options.accessToken);
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const options = ctx.options ? ctx.options : null;
const models = Self.app.models;
if (!ctx.isNewInstance) return;
const data = ctx.instance;
const insurance = await Self.findById(data.id, null, options);
const client = insurance.classification().customer();
const salesPerson = client.salesPerson();
if (!salesPerson) return;
const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__;
const origin = httpRequest.headers.origin;
const fullPath = `${origin}/#!/client/${client.id}`;
const message = $t('MESSAGE_INSURANCE_CHANGE', {
clientId: client.id,
clientName: client.name,
credit: data.credit,
url: fullPath
});
await models.Chat.sendCheckingPresence(httpCtx, salesPerson.id, message);
});
};

View File

@ -32,5 +32,31 @@
"model": "CreditClassification",
"foreignKey": "creditClassification"
}
},
"scope": {
"include": {
"relation": "classification",
"scope": {
"fields": ["client"],
"include": {
"relation": "customer",
"scope": {
"fields": ["name", "salesPersonFk"],
"include": {
"relation": "salesPerson",
"scope": {
"fields": "userFk",
"include": {
"relation": "user",
"scope": {
"fields": ["name"]
}
}
}
}
}
}
}
}
}
}

View File

@ -1,11 +1,7 @@
{
"name": "Sms",
"description": "Sms sent to client",
"base": "Loggable",
"log": {
"model":"ClientLog",
"relation": "recipient"
},
"base": "VnModel",
"options": {
"mysql": {
"table": "sms"
@ -45,11 +41,6 @@
"type": "belongsTo",
"model": "Account",
"foreignKey": "senderFk"
},
"recipient": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "destinationFk"
}
}
}

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="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-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="clientPhone.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,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

@ -2,22 +2,31 @@
vn-id="SMSDialog"
on-response="$ctrl.onResponse($response)">
<tpl-body>
<h5 class="vn-py-sm" translate>Send SMS</h5>
<vn-horizontal>
<vn-textfield
vn-one
label="Destination"
ng-model="$ctrl.sms.destination">
</vn-textfield>
</vn-horizontal>
<vn-horizontal >
<vn-textarea
vn-one
label="Message"
ng-model="$ctrl.sms.message"
rule>
</vn-textarea>
</vn-horizontal>
<section class="SMSDialog">
<h5 class="vn-py-sm" translate>Send SMS</h5>
<vn-horizontal>
<vn-textfield
vn-one
label="Destination"
ng-model="$ctrl.sms.destination">
</vn-textfield>
</vn-horizontal>
<vn-horizontal >
<vn-textarea vn-one
vn-id="message"
label="Message"
ng-model="$ctrl.sms.message"
rows="5"
maxlength="160"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<span>
{{'Characters remaining' | translate}}: {{$ctrl.charactersRemaining()}}
</span>
</vn-horizontal>
</section>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>

View File

@ -16,9 +16,19 @@ class Controller extends Component {
this.$scope.SMSDialog.show();
}
charactersRemaining() {
let elementMaxLength;
let textAreaLength;
const element = this.$scope.message;
textAreaLength = element.input.textLength;
elementMaxLength = element.maxlength;
return elementMaxLength - textAreaLength;
}
onResponse(response) {
if (response === 'accept') {
this.$http.post(`Sms/send`, this.sms).then(res => {
this.$http.post(`Clients/${this.$params.id}/sendSms`, this.sms).then(res => {
this.vnApp.showMessage(this.$translate.instant('SMS sent!'));
if (res.data) this.emit('send', {response: res.data});

View File

@ -8,11 +8,13 @@ describe('Client', () => {
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $scope = $rootScope.$new();
$element = angular.element('<vn-dialog></vn-dialog>');
controller = $componentController('vnClientSms', {$element});
controller = $componentController('vnClientSms', {$element, $scope});
controller.client = {id: 101};
controller.$params = {id: 101};
}));
describe('onResponse()', () => {
@ -21,8 +23,7 @@ describe('Client', () => {
controller.sms = {destinationFk: 101, destination: 111111111, message: 'My SMS'};
spyOn(controller.vnApp, 'showMessage');
$httpBackend.when('POST', `Sms/send`, params).respond(200, params);
$httpBackend.expect('POST', `Sms/send`, params).respond(params);
$httpBackend.expect('POST', `Clients/101/sendSms`, params).respond(200, params);
controller.onResponse('accept');
$httpBackend.flush();
@ -30,5 +31,20 @@ describe('Client', () => {
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent!');
});
});
describe('charactersRemaining()', () => {
it('should return the characters remaining in a element', () => {
controller.$scope.message = {
input: {
textLength: 50
},
maxlength: 150
};
let result = controller.charactersRemaining();
expect(result).toEqual(100);
});
});
});
});

View File

@ -1,4 +1,5 @@
Send SMS: Enviar SMS
Destination: Destinatario
Message: Mensaje
SMS sent!: ¡SMS enviado!
SMS sent!: ¡SMS enviado!
Characters remaining: Carácteres restantes

View File

@ -1,7 +1,5 @@
@import "variables";
vn-client-sms {
textarea {
height: 8em
}
.SMSDialog {
min-width: 25em
}

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

@ -0,0 +1,149 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}, {
arg: 'search',
type: 'String',
description: 'Searchs the entry by id',
http: {source: 'query'}
}, {
arg: 'id',
type: 'Integer',
description: 'The entry id',
http: {source: 'query'}
}, {
arg: 'created',
type: 'Date',
description: 'The created date to filter',
http: {source: 'query'}
}, {
arg: 'travelFk',
type: 'Number',
description: 'The travel id to filter',
http: {source: 'query'}
}, {
arg: 'companyFk',
type: 'Number',
description: 'The company to filter',
http: {source: 'query'}
}, {
arg: 'isBooked',
type: 'Boolean',
description: 'The isBokked filter',
http: {source: 'query'}
}, {
arg: 'isConfirmed',
type: 'Boolean',
description: 'The isConfirmed filter',
http: {source: 'query'}
}, {
arg: 'isOrdered',
type: 'Boolean',
description: 'The isOrdered filter',
http: {source: 'query'}
}, {
arg: 'ref',
type: 'String',
description: 'The ref filter',
http: {source: 'query'}
}, {
arg: 'supplierFk',
type: 'Number',
description: 'The supplier id to filter',
http: {source: 'query'}
}, {
arg: 'currencyFk',
type: 'Number',
description: 'The currency id to filter',
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return {'e.id': value};
case 'ref':
param = `e.${param}`;
return {[param]: {like: `%${value}%`}};
case 'created':
return {'e.created': {gte: value}};
case 'id':
case 'isBooked':
case 'isConfirmed':
case 'isOrdered':
case 'companyFk':
case 'travelFk':
case 'currencyFk':
case 'supplierFk':
param = `e.${param}`;
return {[param]: value};
}
});
filter = mergeFilters(ctx.args.filter, {where});
let stmts = [];
let stmt;
stmt = new ParameterizedSQL(
`SELECT
e.id,
e.supplierFk,
e.dated,
e.ref,
e.isBooked,
e.isInventory,
e.notes,
e.isConfirmed,
e.isOrdered,
e.isRaid,
e.commission,
e.created,
e.evaNotes,
e.travelFk,
e.currencyFk,
e.companyFk,
e.gestDocFk,
e.invoiceInFk,
s.name AS supplierName,
co.code AS companyCode,
cu.code AS currencyCode
FROM vn.entry e
JOIN vn.supplier s ON s.id = e.supplierFk
JOIN vn.travel t ON t.id = e.travelFk
JOIN vn.company co ON co.id = e.companyFk
JOIN vn.currency cu ON cu.id = e.currencyFk`
);
stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
return itemsIndex === 0 ? result : result[itemsIndex];
};
};

View File

@ -0,0 +1,77 @@
const app = require('vn-loopback/server/server');
describe('Entry filter()', () => {
it('should return the entry matching "search"', async() => {
let ctx = {
args: {
search: 1
}
};
let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].id).toEqual(1);
});
it('should return the entry matching the currency', async() => {
let ctx = {
args: {
currencyFk: 1
}
};
let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(7);
});
it('should return the entry matching the supplier', async() => {
let ctx = {
args: {
supplierFk: 2
}
};
let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(5);
});
it('should return the entry matching the company', async() => {
let ctx = {
args: {
companyFk: 442
}
};
let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(6);
});
it('should return the entries matching isBooked', async() => {
let ctx = {
args: {
isBooked: true,
}
};
let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(0);
});
it('should return the routes matching the reference and travel', async() => {
let ctx = {
args: {
reference: 'movement',
travelFk: '2'
}
};
let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(2);
});
});

View File

@ -0,0 +1,5 @@
{
"Entry": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/entry/filter')(Self);
};

View File

@ -0,0 +1,5 @@
<vn-portal slot="menu">
<vn-entry-descriptor entry="$ctrl.entry"></vn-entry-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,33 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: [
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
}, {
relation: 'travel'
}, {
relation: 'supplier',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'currency'
}
]
};
this.$http.get(`Entries/${this.$params.id}`, {filter})
.then(response => this.entry = response.data);
}
}
ngModule.component('vnEntry Card', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,21 @@
<div class="vn-descriptor">
<div class="header">
<a translate-attr="{title: 'Return to module index'}" ui-sref="entry.index">
<vn-icon icon="chevron_left"></vn-icon>
</a>
<a translate-attr="{title: 'Preview'}" ui-sref="entry.card.summary({id: $ctrl.entry.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<span></span>
</div>
<div class="body">
<div class="attributes">
<vn-label-value label="Id"
value="{{$ctrl.entry.id}}">
</vn-label-value>
<vn-label-value label="Reference"
value="{{$ctrl.entry.ref}}">
</vn-label-value>
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
import ngModule from '../module';
class Controller {
constructor($scope) {
this.$ = $scope;
}
}
Controller.$inject = ['$scope'];
ngModule.component('vnEntryDescriptor', {
template: require('./index.html'),
bindings: {
entry: '<'
},
require: {
card: '^?vnEntryCard'
},
controller: Controller
});

View File

@ -0,0 +1 @@
Reference: Referencia

View File

@ -0,0 +1,11 @@
export * from './module';
import './main';
import './index/';
import './search-panel';
import './descriptor';
import './card';
// import './summary';
// import './basic-data';
// import './log';
// import './create';

View File

@ -0,0 +1,63 @@
<vn-crud-model
vn-id="model"
url="Entries/filter"
limit="20"
params="::$ctrl.params"
data="entries"
auto-load="true">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="created" center>Created</vn-th>
<vn-th field="travelFk">Travel</vn-th>
<vn-th>Notes</vn-th>
<vn-th>Reference</vn-th>
<vn-th field="isBooked" center>Booked</vn-th>
<vn-th field="isInventory" center>Is inventory</vn-th>
<vn-th field="isConfirmed" center>Confirmed</vn-th>
<vn-th field="isOrdered" center>Ordered</vn-th>
<vn-th field="isRaid" center>Is raid</vn-th>
<vn-th center>Commission</vn-th>
<vn-th field="supplierFk">Supplier</vn-th>
<vn-th field="currencyFk" center>Currency</vn-th>
<vn-th field="companyFk" center>Company</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="entry in entries"
class="clickable vn-tr search-result"
ui-sref="entry.card.summary({id: {{::entry.id}}})">
<vn-td number>{{::entry.id}}</vn-td>
<vn-td center>{{::entry.created | date:'dd/MM/yyyy'}}</vn-td>
<vn-td center expand>{{::entry.travelFk}}</vn-td>
<vn-td expand>{{::entry.notes}}</vn-td>
<vn-td expand>{{::entry.ref}}</vn-td>
<vn-td center><vn-check ng-model="entry.isBooked" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isInventory" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isConfirmed" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isOrdered" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isRaid" disabled="true"></vn-check></vn-td>
<vn-td center expand>{{::entry.commission}}</vn-td>
<vn-td expand>{{::entry.supplierName}}</vn-td>
<vn-td center expand>{{::entry.currencyCode}}</vn-td>
<vn-td center expand>{{::entry.companyCode}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-data-viewer>
</vn-card>
<vn-popup vn-id="summary">
<vn-entry-summary
entry="$ctrl.entrySelected">
</vn-entry-summary>
</vn-popup>
<vn-scroll-up></vn-scroll-up>

View File

@ -0,0 +1,21 @@
import ngModule from '../module';
export default class Controller {
constructor($scope) {
this.$ = $scope;
}
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
}
Controller.$inject = ['$scope'];
ngModule.component('vnEntryIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,15 @@
#Ordenar alfabeticamente
Reference: Referencia
Created: Creado
Booked: Facturado
Is inventory: Inventario
Notes: Notas
Travel: Envío
Supplier: Proveedor
Currency: Moneda
Company: Empresa
Confirmed: Confirmada
Ordered: Pedida
Is raid: Redada
Commission: Comisión
# Sections

View File

@ -0,0 +1,11 @@
<vn-portal slot="topbar">
<vn-searchbar
search-state="entry.index"
panel="vn-entry-search-panel"
info="Search entrys by id">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Entry extends ModuleMain {}
ngModule.vnComponent('vnEntry', {
controller: Entry,
template: require('./index.html')
});

View File

@ -0,0 +1,3 @@
import {ng} from 'core/vendor';
export default ng.module('entry', ['vnCore']);

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