Merge branch 'dev' into 4732-previa-label
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
commit
5235ed5499
|
@ -0,0 +1,30 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('recoverPassword', {
|
||||||
|
description: 'Send email to the user',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'email',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The email of user',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/recoverPassword`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.recoverPassword = async function(email) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await models.user.resetPassword({email, emailTemplate: 'recover-password'});
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'EMAIL_NOT_FOUND')
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('account changePassword()', () => {
|
describe('account setPassword()', () => {
|
||||||
it('should throw an error when password does not meet requirements', async() => {
|
it('should throw an error when password does not meet requirements', async() => {
|
||||||
let req = app.models.Account.setPassword(1, 'insecurePass');
|
let req = app.models.Account.setPassword(1, 'insecurePass');
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
||||||
let message = $t(`There's a new urgent ticket:`);
|
let message = $t(`There's a new urgent ticket:`);
|
||||||
const ostUri = 'https://cau.verdnatura.es/scp/tickets.php?id=';
|
const ostUri = 'https://cau.verdnatura.es/scp/tickets.php?id=';
|
||||||
tickets.forEach(ticket => {
|
tickets.forEach(ticket => {
|
||||||
message += `\r\n[ID: *${ticket.number}* - ${ticket.subject} (@${ticket.username})](${ostUri + ticket.id})`;
|
message += `\r\n[ID: ${ticket.number} - ${ticket.subject} @${ticket.username}](${ostUri + ticket.id})`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const department = await models.Department.findOne({
|
const department = await models.Department.findOne({
|
||||||
|
@ -42,7 +42,5 @@ module.exports = Self => {
|
||||||
|
|
||||||
if (channelName)
|
if (channelName)
|
||||||
return Self.send(ctx, `#${channelName}`, `@all ➔ ${message}`);
|
return Self.send(ctx, `#${channelName}`, `@all ➔ ${message}`);
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe('Chat notifyIssue()', () => {
|
||||||
subject: 'Issue title'}
|
subject: 'Issue title'}
|
||||||
]);
|
]);
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
const expectedMessage = `@all ➔ There's a new urgent ticket:\r\n[ID: *00001* - Issue title (@batman)](https://cau.verdnatura.es/scp/tickets.php?id=1)`;
|
const expectedMessage = `@all ➔ There's a new urgent ticket:\r\n[ID: 00001 - Issue title @batman](https://cau.verdnatura.es/scp/tickets.php?id=1)`;
|
||||||
|
|
||||||
const department = await app.models.Department.findById(departmentId);
|
const department = await app.models.Department.findById(departmentId);
|
||||||
let orgChatName = department.chatName;
|
let orgChatName = department.chatName;
|
||||||
|
|
|
@ -51,7 +51,7 @@ module.exports = Self => {
|
||||||
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
|
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
|
||||||
await fs.unlink(dstFile);
|
await fs.unlink(dstFile);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code != 'ENOENT')
|
if (err.code != 'ENOENT' && dms.file)
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
/* eslint max-len: ["error", { "code": 150 }]*/
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
const {Email} = require('vn-print');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/account/login')(Self);
|
require('../methods/account/login')(Self);
|
||||||
|
@ -6,6 +9,7 @@ module.exports = Self => {
|
||||||
require('../methods/account/acl')(Self);
|
require('../methods/account/acl')(Self);
|
||||||
require('../methods/account/change-password')(Self);
|
require('../methods/account/change-password')(Self);
|
||||||
require('../methods/account/set-password')(Self);
|
require('../methods/account/set-password')(Self);
|
||||||
|
require('../methods/account/recover-password')(Self);
|
||||||
require('../methods/account/validate-token')(Self);
|
require('../methods/account/validate-token')(Self);
|
||||||
require('../methods/account/privileges')(Self);
|
require('../methods/account/privileges')(Self);
|
||||||
|
|
||||||
|
@ -27,17 +31,62 @@ module.exports = Self => {
|
||||||
ctx.data.password = md5(ctx.data.password);
|
ctx.data.password = md5(ctx.data.password);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
||||||
|
if (!ctx.args || !ctx.args.data.email) return;
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const httpCtx = {req: loopBackContext.active};
|
||||||
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
const url = origin.split(':');
|
||||||
|
|
||||||
|
const userId = ctx.instance.id;
|
||||||
|
const user = await models.user.findById(userId);
|
||||||
|
|
||||||
|
class Mailer {
|
||||||
|
async send(verifyOptions, cb) {
|
||||||
|
const params = {
|
||||||
|
url: verifyOptions.verifyHref,
|
||||||
|
recipient: verifyOptions.to,
|
||||||
|
lang: ctx.req.getLocale()
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = new Email('email-verify', params);
|
||||||
|
email.send();
|
||||||
|
|
||||||
|
cb(null, verifyOptions.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
type: 'email',
|
||||||
|
to: instance.email,
|
||||||
|
from: {},
|
||||||
|
redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`,
|
||||||
|
template: false,
|
||||||
|
mailer: new Mailer,
|
||||||
|
host: url[1].split('/')[2],
|
||||||
|
port: url[2],
|
||||||
|
protocol: url[0],
|
||||||
|
user: Self
|
||||||
|
};
|
||||||
|
|
||||||
|
await user.verify(options);
|
||||||
|
});
|
||||||
|
|
||||||
Self.remoteMethod('getCurrentUserData', {
|
Self.remoteMethod('getCurrentUserData', {
|
||||||
description: 'Gets the current user data',
|
description: 'Gets the current user data',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
arg: 'ctx',
|
arg: 'ctx',
|
||||||
type: 'Object',
|
type: 'object',
|
||||||
http: {source: 'context'}
|
http: {source: 'context'}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'Object',
|
type: 'object',
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -58,7 +107,7 @@ module.exports = Self => {
|
||||||
*
|
*
|
||||||
* @param {Integer} userId The user id
|
* @param {Integer} userId The user id
|
||||||
* @param {String} name The role name
|
* @param {String} name The role name
|
||||||
* @param {Object} options Options
|
* @param {object} options Options
|
||||||
* @return {Boolean} %true if user has the role, %false otherwise
|
* @return {Boolean} %true if user has the role, %false otherwise
|
||||||
*/
|
*/
|
||||||
Self.hasRole = async function(userId, name, options) {
|
Self.hasRole = async function(userId, name, options) {
|
||||||
|
@ -70,8 +119,8 @@ module.exports = Self => {
|
||||||
* Get all user roles.
|
* Get all user roles.
|
||||||
*
|
*
|
||||||
* @param {Integer} userId The user id
|
* @param {Integer} userId The user id
|
||||||
* @param {Object} options Options
|
* @param {object} options Options
|
||||||
* @return {Object} User role list
|
* @return {object} User role list
|
||||||
*/
|
*/
|
||||||
Self.getRoles = async(userId, options) => {
|
Self.getRoles = async(userId, options) => {
|
||||||
let result = await Self.rawSql(
|
let result = await Self.rawSql(
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
@ -88,16 +91,23 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"property": "recoverPassword",
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"property": "logout",
|
"property": "logout",
|
||||||
"accessType": "EXECUTE",
|
"accessType": "EXECUTE",
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$authenticated",
|
"principalId": "$authenticated",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "validateToken",
|
"property": "validateToken",
|
||||||
"accessType": "EXECUTE",
|
"accessType": "EXECUTE",
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$authenticated",
|
"principalId": "$authenticated",
|
||||||
|
|
|
@ -4,4 +4,23 @@ module.exports = Self => {
|
||||||
require('../methods/chat/sendCheckingPresence')(Self);
|
require('../methods/chat/sendCheckingPresence')(Self);
|
||||||
require('../methods/chat/notifyIssues')(Self);
|
require('../methods/chat/notifyIssues')(Self);
|
||||||
require('../methods/chat/sendQueued')(Self);
|
require('../methods/chat/sendQueued')(Self);
|
||||||
|
|
||||||
|
Self.observe('before save', async function(ctx) {
|
||||||
|
if (!ctx.isNewInstance) return;
|
||||||
|
|
||||||
|
let {message} = ctx.instance;
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
|
||||||
|
if (!parts) return;
|
||||||
|
|
||||||
|
const replacedParts = parts.map(part => {
|
||||||
|
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [index, part] of parts.entries())
|
||||||
|
message = message.replace(part, replacedParts[index]);
|
||||||
|
|
||||||
|
ctx.instance.message = message;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
describe('loopback model Account', () => {
|
describe('loopback model Account', () => {
|
||||||
it('should return true if the user has the given role', async() => {
|
it('should return true if the user has the given role', async() => {
|
||||||
let result = await app.models.Account.hasRole(1, 'employee');
|
let result = await models.Account.hasRole(1, 'employee');
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if the user doesnt have the given role', async() => {
|
it('should return false if the user doesnt have the given role', async() => {
|
||||||
let result = await app.models.Account.hasRole(1, 'administrator');
|
let result = await models.Account.hasRole(1, 'administrator');
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('account recoverPassword()', () => {
|
||||||
|
const userId = 1107;
|
||||||
|
|
||||||
|
const activeCtx = {
|
||||||
|
accessToken: {userId: userId},
|
||||||
|
http: {
|
||||||
|
req: {
|
||||||
|
headers: {origin: 'http://localhost'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: activeCtx
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send email with token', async() => {
|
||||||
|
const userId = 1107;
|
||||||
|
const user = await models.Account.findById(userId);
|
||||||
|
|
||||||
|
await models.Account.recoverPassword(user.email);
|
||||||
|
|
||||||
|
const result = await models.AccessToken.findOne({where: {userId: userId}});
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
const {Email} = require('vn-print');
|
||||||
|
|
||||||
|
module.exports = function(Self) {
|
||||||
|
Self.on('resetPasswordRequest', async function(info) {
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const httpCtx = {req: loopBackContext.active};
|
||||||
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
|
||||||
|
const user = await Self.app.models.Account.findById(info.user.id);
|
||||||
|
const params = {
|
||||||
|
recipient: info.email,
|
||||||
|
lang: user.lang,
|
||||||
|
url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = Object.assign({}, info.options);
|
||||||
|
for (const param in options)
|
||||||
|
params[param] = options[param];
|
||||||
|
|
||||||
|
const email = new Email(options.emailTemplate, params);
|
||||||
|
|
||||||
|
return email.send();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
DELETE FROM `salix`.`ACL`
|
||||||
|
WHERE model = 'UserPassword';
|
|
@ -2564,10 +2564,6 @@ UPDATE `vn`.`route`
|
||||||
UPDATE `vn`.`route`
|
UPDATE `vn`.`route`
|
||||||
SET `invoiceInFk`=2
|
SET `invoiceInFk`=2
|
||||||
WHERE `id`=2;
|
WHERE `id`=2;
|
||||||
INSERT INTO `bs`.`salesPerson` (`workerFk`, `year`, `month`, `portfolioWeight`)
|
|
||||||
VALUES
|
|
||||||
(18, YEAR(util.VN_CURDATE()), MONTH(util.VN_CURDATE()), 807.23),
|
|
||||||
(19, YEAR(util.VN_CURDATE()), MONTH(util.VN_CURDATE()), 34.40);
|
|
||||||
|
|
||||||
INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`)
|
INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -81044,4 +81044,3 @@ USE `vncontrol`;
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
-- Dump completed on 2022-09-16 10:44:31
|
-- Dump completed on 2022-09-16 10:44:31
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,11 @@ export default {
|
||||||
firstModulePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="push_pin"]',
|
firstModulePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="push_pin"]',
|
||||||
firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]'
|
firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]'
|
||||||
},
|
},
|
||||||
|
recoverPassword: {
|
||||||
|
recoverPasswordButton: 'vn-login a[ui-sref="recoverPassword"]',
|
||||||
|
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.email"]',
|
||||||
|
sendEmailButton: 'vn-recover-password vn-submit',
|
||||||
|
},
|
||||||
accountIndex: {
|
accountIndex: {
|
||||||
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
addAccount: 'vn-user-index button vn-icon[icon="add"]',
|
||||||
newName: 'vn-user-create vn-textfield[ng-model="$ctrl.user.name"]',
|
newName: 'vn-user-create vn-textfield[ng-model="$ctrl.user.name"]',
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import selectors from '../../helpers/selectors';
|
||||||
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('Login path', async() => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||||
|
await page.waitForState('recoverPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw error if not exist user', async() => {
|
||||||
|
await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
|
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toContain('Notification sent!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send email', async() => {
|
||||||
|
await page.waitForState('login');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
|
||||||
|
|
||||||
|
await page.write(selectors.recoverPassword.email, 'BruceWayne@mydomain.com');
|
||||||
|
await page.waitToClick(selectors.recoverPassword.sendEmailButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
await page.waitForState('login');
|
||||||
|
|
||||||
|
expect(message.text).toContain('Notification sent!');
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,80 +5,78 @@ describe('Ticket Future path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async() => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
await page.loginAndModule('employee', 'ticket');
|
await page.loginAndModule('employee', 'ticket');
|
||||||
await page.accessToSection('ticket.future');
|
await page.accessToSection('ticket.future');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async() => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const tomorrow = new Date(now.getDate() + 1);
|
const tomorrow = new Date(now.getDate() + 1);
|
||||||
const ticket = {
|
|
||||||
originDated: now,
|
|
||||||
futureDated: now,
|
|
||||||
linesMax: '9999',
|
|
||||||
litersMax: '9999',
|
|
||||||
warehouseFk: 'Warehouse One'
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should show errors snackbar because of the required data', async () => {
|
it('should show errors snackbar because of the required data', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.warehouseFk);
|
await page.clearInput(selectors.ticketFuture.warehouseFk);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
let message = await page.waitForSnackbar();
|
let message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('warehouseFk is a required argument');
|
expect(message.text).toContain('warehouseFk is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.litersMax);
|
await page.clearInput(selectors.ticketFuture.litersMax);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('litersMax is a required argument');
|
expect(message.text).toContain('litersMax is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.linesMax);
|
await page.clearInput(selectors.ticketFuture.linesMax);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('linesMax is a required argument');
|
expect(message.text).toContain('linesMax is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.futureDated);
|
await page.clearInput(selectors.ticketFuture.futureDated);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('futureDated is a required argument');
|
expect(message.text).toContain('futureDated is a required argument');
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.originDated);
|
await page.clearInput(selectors.ticketFuture.originDated);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
message = await page.waitForSnackbar();
|
message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('originDated is a required argument');
|
expect(message.text).toContain('originDated is a required argument');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the required data', async () => {
|
it('should search with the required data', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin shipped today', async () => {
|
it('should search with the origin shipped today', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.pickDate(selectors.ticketFuture.shipped, now);
|
await page.pickDate(selectors.ticketFuture.shipped, now);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin shipped tomorrow', async () => {
|
it('should search with the origin shipped tomorrow', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.pickDate(selectors.ticketFuture.shipped, tomorrow);
|
await page.pickDate(selectors.ticketFuture.shipped, tomorrow);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination shipped today', async () => {
|
it('should search with the destination shipped today', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
await page.pickDate(selectors.ticketFuture.tfShipped, now);
|
await page.pickDate(selectors.ticketFuture.tfShipped, now);
|
||||||
|
@ -86,14 +84,14 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination shipped tomorrow', async () => {
|
it('should search with the destination shipped tomorrow', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow);
|
await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow);
|
||||||
await page.waitToClick(selectors.ticketFuture.submit);
|
await page.waitToClick(selectors.ticketFuture.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin IPT', async () => {
|
it('should search with the origin IPT', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -108,7 +106,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination IPT', async () => {
|
it('should search with the destination IPT', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -123,7 +121,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin grouped state', async () => {
|
it('should search with the origin grouped state', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -138,7 +136,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 3);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the destination grouped state', async () => {
|
it('should search with the destination grouped state', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
|
||||||
|
|
||||||
await page.clearInput(selectors.ticketFuture.shipped);
|
await page.clearInput(selectors.ticketFuture.shipped);
|
||||||
|
@ -164,10 +162,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an ID Origin', async () => {
|
it('should search in smart-table with an ID Origin', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableId, "13");
|
await page.write(selectors.ticketFuture.tableId, '13');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 2);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 2);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -176,10 +174,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an ID Destination', async () => {
|
it('should search in smart-table with an ID Destination', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableTfId, "12");
|
await page.write(selectors.ticketFuture.tableTfId, '12');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -188,7 +186,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an IPT Origin', async () => {
|
it('should search in smart-table with an IPT Origin', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical');
|
await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
@ -199,7 +197,7 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an IPT Destination', async () => {
|
it('should search in smart-table with an IPT Destination', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical');
|
await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
@ -210,10 +208,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with especified Lines', async () => {
|
it('should search in smart-table with especified Lines', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLines, "0");
|
await page.write(selectors.ticketFuture.tableLines, '0');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -222,8 +220,8 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLines, "1");
|
await page.write(selectors.ticketFuture.tableLines, '1');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -232,10 +230,10 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with especified Liters', async () => {
|
it('should search in smart-table with especified Liters', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLiters, "0");
|
await page.write(selectors.ticketFuture.tableLiters, '0');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -244,8 +242,8 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
await page.write(selectors.ticketFuture.tableLiters, "28");
|
await page.write(selectors.ticketFuture.tableLiters, '28');
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
|
||||||
|
@ -254,13 +252,13 @@ describe('Ticket Future path', () => {
|
||||||
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check the three last tickets and move to the future', async () => {
|
it('should check the three last tickets and move to the future', async() => {
|
||||||
await page.waitToClick(selectors.ticketFuture.multiCheck);
|
await page.waitToClick(selectors.ticketFuture.multiCheck);
|
||||||
await page.waitToClick(selectors.ticketFuture.firstCheck);
|
await page.waitToClick(selectors.ticketFuture.firstCheck);
|
||||||
await page.waitToClick(selectors.ticketFuture.moveButton);
|
await page.waitToClick(selectors.ticketFuture.moveButton);
|
||||||
await page.waitToClick(selectors.ticketFuture.acceptButton);
|
await page.waitToClick(selectors.ticketFuture.acceptButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('Tickets moved successfully!');
|
expect(message.text).toContain('Tickets moved successfully!');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,9 +12,10 @@ export default class Component extends EventEmitter {
|
||||||
* @param {HTMLElement} $element The main component element
|
* @param {HTMLElement} $element The main component element
|
||||||
* @param {$rootScope.Scope} $scope The element scope
|
* @param {$rootScope.Scope} $scope The element scope
|
||||||
* @param {Function} $transclude The transclusion function
|
* @param {Function} $transclude The transclusion function
|
||||||
|
* @param {Function} $location The location function
|
||||||
*/
|
*/
|
||||||
constructor($element, $scope, $transclude) {
|
constructor($element, $scope, $transclude, $location) {
|
||||||
super();
|
super($element, $scope, $transclude, $location);
|
||||||
this.$ = $scope;
|
this.$ = $scope;
|
||||||
|
|
||||||
if (!$element) return;
|
if (!$element) return;
|
||||||
|
@ -164,7 +165,7 @@ export default class Component extends EventEmitter {
|
||||||
$transclude.$$boundTransclude.$$slots[slot];
|
$transclude.$$boundTransclude.$$slots[slot];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component.$inject = ['$element', '$scope'];
|
Component.$inject = ['$element', '$scope', '$location', '$state'];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Automatically adds the most used services to the prototype, so they are
|
* Automatically adds the most used services to the prototype, so they are
|
||||||
|
|
|
@ -23,7 +23,10 @@ export default class Auth {
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
let criteria = {
|
let criteria = {
|
||||||
to: state => state.name != 'login'
|
to: state => {
|
||||||
|
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
|
||||||
|
return !outLayout.some(ol => ol == state.name);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
this.$transitions.onStart(criteria, transition => {
|
this.$transitions.onStart(criteria, transition => {
|
||||||
if (this.loggedIn)
|
if (this.loggedIn)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<vn-layout
|
<vn-layout
|
||||||
ng-if="$ctrl.showLayout">
|
ng-if="$ctrl.showLayout">
|
||||||
</vn-layout>
|
</vn-layout>
|
||||||
<ui-view
|
<vn-out-layout
|
||||||
name="login"
|
|
||||||
ng-if="!$ctrl.showLayout">
|
ng-if="!$ctrl.showLayout">
|
||||||
</ui-view>
|
</vn-out-layout>
|
||||||
<vn-snackbar vn-id="snackbar"></vn-snackbar>
|
<vn-snackbar vn-id="snackbar"></vn-snackbar>
|
||||||
<vn-debug-info></vn-debug-info>
|
<vn-debug-info></vn-debug-info>
|
||||||
|
|
|
@ -9,13 +9,20 @@ import Component from 'core/lib/component';
|
||||||
* @property {SideMenu} rightMenu The left menu, if it's present
|
* @property {SideMenu} rightMenu The left menu, if it's present
|
||||||
*/
|
*/
|
||||||
export default class App extends Component {
|
export default class App extends Component {
|
||||||
|
constructor($element, $, $location, $state) {
|
||||||
|
super($element, $, $location, $state);
|
||||||
|
this.$location = $location;
|
||||||
|
this.$state = $state;
|
||||||
|
}
|
||||||
|
|
||||||
$postLink() {
|
$postLink() {
|
||||||
this.vnApp.logger = this;
|
this.vnApp.logger = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showLayout() {
|
get showLayout() {
|
||||||
let state = this.$state.current.name;
|
const state = this.$state.current.name || this.$location.$$path.substring(1).replace('/', '.');
|
||||||
return state && state != 'login';
|
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
|
||||||
|
return state && !outLayout.some(ol => ol == state);
|
||||||
}
|
}
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
|
|
|
@ -5,7 +5,10 @@ import './descriptor-popover';
|
||||||
import './home/home';
|
import './home/home';
|
||||||
import './layout';
|
import './layout';
|
||||||
import './left-menu/left-menu';
|
import './left-menu/left-menu';
|
||||||
|
import './login/index';
|
||||||
import './login/login';
|
import './login/login';
|
||||||
|
import './login/recover-password';
|
||||||
|
import './login/reset-password';
|
||||||
import './module-card';
|
import './module-card';
|
||||||
import './module-main';
|
import './module-main';
|
||||||
import './side-menu/side-menu';
|
import './side-menu/side-menu';
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
const validations = window.validations;
|
const validations = window.validations;
|
||||||
value.forEach(log => {
|
value.forEach(log => {
|
||||||
const locale = validations[log.changedModel].locale ? validations[log.changedModel].locale : {};
|
const locale = validations[log.changedModel] && validations[log.changedModel].locale ? validations[log.changedModel].locale : {};
|
||||||
|
|
||||||
log.oldProperties = this.getInstance(log.oldInstance, locale);
|
log.oldProperties = this.getInstance(log.oldInstance, locale);
|
||||||
log.newProperties = this.getInstance(log.newInstance, locale);
|
log.newProperties = this.getInstance(log.newInstance, locale);
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="box">
|
||||||
|
<img src="./logo.svg"/>
|
||||||
|
<form name="form">
|
||||||
|
<ui-view></ui-view>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -0,0 +1,16 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Component from 'core/lib/component';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default class OutLayout extends Component {
|
||||||
|
constructor($element, $scope) {
|
||||||
|
super($element, $scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutLayout.$inject = ['$element', '$scope'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnOutLayout', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: OutLayout
|
||||||
|
});
|
|
@ -1,4 +1,8 @@
|
||||||
User: User
|
User: User
|
||||||
Password: Password
|
Password: Password
|
||||||
Do not close session: Do not close session
|
Do not close session: Do not close session
|
||||||
Enter: Enter
|
Enter: Enter
|
||||||
|
Password requirements: >
|
||||||
|
The password must have at least {{ length }} length characters,
|
||||||
|
{{nAlpha}} alphabetic characters, {{nUpper}} capital letters, {{nDigits}}
|
||||||
|
digits and {{nPunct}} symbols (Ex: $%&.)
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
User: Usuario
|
User: Usuario
|
||||||
Password: Contraseña
|
Password: Contraseña
|
||||||
|
Email: Correo electrónico
|
||||||
Do not close session: No cerrar sesión
|
Do not close session: No cerrar sesión
|
||||||
Enter: Entrar
|
Enter: Entrar
|
||||||
|
I do not remember my password: No recuerdo mi contraseña
|
||||||
|
Recover password: Recuperar contraseña
|
||||||
|
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
|
||||||
|
Notification sent!: ¡Notificación enviada!
|
||||||
|
Reset password: Restrablecer contraseña
|
||||||
|
New password: Nueva contraseña
|
||||||
|
Repeat password: Repetir contraseña
|
||||||
|
Password requirements: >
|
||||||
|
La contraseña debe tener al menos {{ length }} caracteres de longitud,
|
||||||
|
{{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}}
|
||||||
|
dígitos y {{nPunct}} símbolos (Ej: $%&.)
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<div class="box">
|
<vn-textfield
|
||||||
<img src="./logo.svg"/>
|
label="User"
|
||||||
<form name="form" ng-submit="$ctrl.submit()">
|
ng-model="$ctrl.user"
|
||||||
<vn-textfield
|
vn-id="userField"
|
||||||
label="User"
|
vn-focus>
|
||||||
ng-model="$ctrl.user"
|
</vn-textfield>
|
||||||
vn-id="userField"
|
<vn-textfield
|
||||||
vn-focus>
|
label="Password"
|
||||||
</vn-textfield>
|
ng-model="$ctrl.password"
|
||||||
<vn-textfield
|
type="password">
|
||||||
label="Password"
|
</vn-textfield>
|
||||||
ng-model="$ctrl.password"
|
<vn-check
|
||||||
type="password">
|
label="Do not close session"
|
||||||
</vn-textfield>
|
ng-model="$ctrl.remember"
|
||||||
<vn-check
|
name="remember">
|
||||||
label="Do not close session"
|
</vn-check>
|
||||||
ng-model="$ctrl.remember"
|
<div class="footer">
|
||||||
name="remember">
|
<vn-submit label="Enter" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
</vn-check>
|
<div class="spinner-wrapper">
|
||||||
<div class="footer">
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
<vn-submit label="Enter"></vn-submit>
|
</div>
|
||||||
<div class="spinner-wrapper">
|
<div class="vn-pt-lg">
|
||||||
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
<a ui-sref="recoverPassword" translate>
|
||||||
</div>
|
I do not remember my password
|
||||||
</div>
|
</a>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<h5 class="vn-mb-md vn-mt-lg" translate>Recover password</h5>
|
||||||
|
<vn-textfield
|
||||||
|
label="Email"
|
||||||
|
ng-model="$ctrl.email"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<div
|
||||||
|
class="text-secondary"
|
||||||
|
translate>
|
||||||
|
We will sent you an email to recover your password
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<vn-submit label="Recover password" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,37 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default class Controller {
|
||||||
|
constructor($scope, $element, $http, vnApp, $translate, $state) {
|
||||||
|
Object.assign(this, {
|
||||||
|
$scope,
|
||||||
|
$element,
|
||||||
|
$http,
|
||||||
|
vnApp,
|
||||||
|
$translate,
|
||||||
|
$state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
goToLogin() {
|
||||||
|
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
|
||||||
|
this.$state.go('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
const params = {
|
||||||
|
email: this.email
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$http.post('Accounts/recoverPassword', params)
|
||||||
|
.then(() => {
|
||||||
|
this.goToLogin();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnRecoverPassword', {
|
||||||
|
template: require('./recover-password.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
<h5 class="vn-mb-md vn-mt-lg" translate>Reset password</h5>
|
||||||
|
<vn-textfield
|
||||||
|
label="New password"
|
||||||
|
ng-model="$ctrl.newPassword"
|
||||||
|
type="password"
|
||||||
|
info="{{'Password requirements' | translate:$ctrl.passRequirements}}"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Repeat password"
|
||||||
|
ng-model="$ctrl.repeatPassword"
|
||||||
|
type="password">
|
||||||
|
</vn-textfield>
|
||||||
|
<div class="footer">
|
||||||
|
<vn-submit label="Reset password" ng-click="$ctrl.submit()"></vn-submit>
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,48 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default class Controller {
|
||||||
|
constructor($scope, $element, $http, vnApp, $translate, $state, $location) {
|
||||||
|
Object.assign(this, {
|
||||||
|
$scope,
|
||||||
|
$element,
|
||||||
|
$http,
|
||||||
|
vnApp,
|
||||||
|
$translate,
|
||||||
|
$state,
|
||||||
|
$location
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
this.$http.get('UserPasswords/findOne')
|
||||||
|
.then(res => {
|
||||||
|
this.passRequirements = res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (!this.newPassword)
|
||||||
|
throw new UserError(`You must enter a new password`);
|
||||||
|
if (this.newPassword != this.repeatPassword)
|
||||||
|
throw new UserError(`Passwords don't match`);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: this.$location.$$search.access_token
|
||||||
|
};
|
||||||
|
|
||||||
|
const newPassword = this.newPassword;
|
||||||
|
|
||||||
|
this.$http.post('users/reset-password', {newPassword}, {headers})
|
||||||
|
.then(() => {
|
||||||
|
this.vnApp.showSuccess(this.$translate.instant('Password changed!'));
|
||||||
|
this.$state.go('login');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnResetPassword', {
|
||||||
|
template: require('./reset-password.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -1,6 +1,31 @@
|
||||||
@import "variables";
|
@import "variables";
|
||||||
|
|
||||||
vn-login {
|
vn-login,
|
||||||
|
vn-reset-password,
|
||||||
|
vn-recover-password{
|
||||||
|
.footer {
|
||||||
|
margin-top: 32px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
& > .vn-submit {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .spinner-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
top: 3px;
|
||||||
|
right: -8px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vn-out-layout{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -39,28 +64,17 @@ vn-login {
|
||||||
white-space: inherit;
|
white-space: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > .footer {
|
|
||||||
margin-top: 32px;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
& > vn-submit {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
& > input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& > .spinner-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
top: 3px;
|
|
||||||
right: -8px;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h5{
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-secondary{
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
|
@ -71,4 +85,8 @@ vn-login {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a{
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ function $exceptionHandler(vnApp, $window, $state, $injector) {
|
||||||
|
|
||||||
switch (exception.status) {
|
switch (exception.status) {
|
||||||
case 401:
|
case 401:
|
||||||
if ($state.current.name != 'login') {
|
if (!$state.current.name.includes('login')) {
|
||||||
messageT = 'Session has expired';
|
messageT = 'Session has expired';
|
||||||
let params = {continue: $window.location.hash};
|
let params = {continue: $window.location.hash};
|
||||||
$state.go('login', params);
|
$state.go('login', params);
|
||||||
|
|
|
@ -9,9 +9,17 @@ function config($stateProvider, $urlRouterProvider) {
|
||||||
.state('login', {
|
.state('login', {
|
||||||
url: '/login?continue',
|
url: '/login?continue',
|
||||||
description: 'Login',
|
description: 'Login',
|
||||||
views: {
|
template: '<vn-login></vn-login>'
|
||||||
login: {template: '<vn-login></vn-login>'}
|
})
|
||||||
}
|
.state('recoverPassword', {
|
||||||
|
url: '/recover-password',
|
||||||
|
description: 'Recover-password',
|
||||||
|
template: '<vn-recover-password>asd</vn-recover-password>'
|
||||||
|
})
|
||||||
|
.state('resetPassword', {
|
||||||
|
url: '/reset-password',
|
||||||
|
description: 'Reset-password',
|
||||||
|
template: '<vn-reset-password></vn-reset-password>'
|
||||||
})
|
})
|
||||||
.state('home', {
|
.state('home', {
|
||||||
url: '/',
|
url: '/',
|
||||||
|
|
|
@ -131,12 +131,15 @@
|
||||||
"Fichadas impares": "Odd signs",
|
"Fichadas impares": "Odd signs",
|
||||||
"Descanso diario 9h.": "Daily rest 9h.",
|
"Descanso diario 9h.": "Daily rest 9h.",
|
||||||
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
|
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
|
||||||
|
"Verify email": "Verify email",
|
||||||
|
"Click on the following link to verify this email. If you haven't requested this email, just ignore it": "Click on the following link to verify this email. If you haven't requested this email, just ignore it",
|
||||||
"Password does not meet requirements": "Password does not meet requirements",
|
"Password does not meet requirements": "Password does not meet requirements",
|
||||||
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
|
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
|
||||||
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
|
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
|
||||||
"Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*",
|
"Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*",
|
||||||
"You don't have grant privilege": "You don't have grant privilege",
|
"You don't have grant privilege": "You don't have grant privilege",
|
||||||
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
|
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
|
||||||
|
"Email verify": "Email verify",
|
||||||
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
|
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
|
||||||
"Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production",
|
"Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production",
|
||||||
"Receipt's bank was not found": "Receipt's bank was not found",
|
"Receipt's bank was not found": "Receipt's bank was not found",
|
||||||
|
|
|
@ -245,6 +245,8 @@
|
||||||
"Already has this status": "Ya tiene este estado",
|
"Already has this status": "Ya tiene este estado",
|
||||||
"There aren't records for this week": "No existen registros para esta semana",
|
"There aren't records for this week": "No existen registros para esta semana",
|
||||||
"Empty data source": "Origen de datos vacio",
|
"Empty data source": "Origen de datos vacio",
|
||||||
|
"Email verify": "Correo de verificación",
|
||||||
|
"Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment",
|
||||||
"Receipt's bank was not found": "No se encontró el banco del recibo",
|
"Receipt's bank was not found": "No se encontró el banco del recibo",
|
||||||
"This receipt was not compensated": "Este recibo no ha sido compensado",
|
"This receipt was not compensated": "Este recibo no ha sido compensado",
|
||||||
"Client's email was not found": "No se encontró el email del cliente"
|
"Client's email was not found": "No se encontró el email del cliente"
|
||||||
|
|
|
@ -113,4 +113,4 @@
|
||||||
"application/x-7z-compressed"
|
"application/x-7z-compressed"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,5 +30,13 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,11 @@ import ngModule from '../module';
|
||||||
import Section from 'salix/components/section';
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
export default class Controller extends Section {
|
export default class Controller extends Section {
|
||||||
|
$onInit() {
|
||||||
|
if (this.$params.emailConfirmed)
|
||||||
|
this.vnApp.showSuccess(this.$t('Email verified successfully!'));
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.$.watcher.submit()
|
this.$.watcher.submit()
|
||||||
.then(() => this.card.reload());
|
.then(() => this.card.reload());
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Email verified successfully!: Correo verificado correctamente!
|
|
@ -74,7 +74,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/basic-data",
|
"url": "/basic-data?emailConfirmed",
|
||||||
"state": "account.card.basicData",
|
"state": "account.card.basicData",
|
||||||
"component": "vn-user-basic-data",
|
"component": "vn-user-basic-data",
|
||||||
"description": "Basic data",
|
"description": "Basic data",
|
||||||
|
|
|
@ -91,7 +91,18 @@ module.exports = Self => {
|
||||||
case 'search':
|
case 'search':
|
||||||
return /^\d+$/.test(value)
|
return /^\d+$/.test(value)
|
||||||
? {'c.id': {inq: value}}
|
? {'c.id': {inq: value}}
|
||||||
: {'c.name': {like: `%${value}%`}};
|
: {or: [
|
||||||
|
{'c.name': {like: `%${value}%`}},
|
||||||
|
{'c.socialName': {like: `%${value}%`}},
|
||||||
|
]};
|
||||||
|
case 'phone':
|
||||||
|
return {or: [
|
||||||
|
{'c.phone': {like: `%${value}%`}},
|
||||||
|
{'c.mobile': {like: `%${value}%`}},
|
||||||
|
]};
|
||||||
|
case 'zoneFk':
|
||||||
|
param = 'a.postalCode';
|
||||||
|
return {[param]: {inq: postalCode}};
|
||||||
case 'name':
|
case 'name':
|
||||||
case 'salesPersonFk':
|
case 'salesPersonFk':
|
||||||
case 'fi':
|
case 'fi':
|
||||||
|
@ -100,12 +111,8 @@ module.exports = Self => {
|
||||||
case 'postcode':
|
case 'postcode':
|
||||||
case 'provinceFk':
|
case 'provinceFk':
|
||||||
case 'email':
|
case 'email':
|
||||||
case 'phone':
|
|
||||||
param = `c.${param}`;
|
param = `c.${param}`;
|
||||||
return {[param]: value};
|
return {[param]: {like: `%${value}%`}};
|
||||||
case 'zoneFk':
|
|
||||||
param = 'a.postalCode';
|
|
||||||
return {[param]: {inq: postalCode}};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -119,6 +126,7 @@ module.exports = Self => {
|
||||||
c.fi,
|
c.fi,
|
||||||
c.socialName,
|
c.socialName,
|
||||||
c.phone,
|
c.phone,
|
||||||
|
c.mobile,
|
||||||
c.city,
|
c.city,
|
||||||
c.postcode,
|
c.postcode,
|
||||||
c.email,
|
c.email,
|
||||||
|
@ -132,7 +140,7 @@ module.exports = Self => {
|
||||||
LEFT JOIN account.user u ON u.id = c.salesPersonFk
|
LEFT JOIN account.user u ON u.id = c.salesPersonFk
|
||||||
LEFT JOIN province p ON p.id = c.provinceFk
|
LEFT JOIN province p ON p.id = c.provinceFk
|
||||||
JOIN vn.address a ON a.clientFk = c.id
|
JOIN vn.address a ON a.clientFk = c.id
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
stmt.merge(conn.makeWhere(filter.where));
|
stmt.merge(conn.makeWhere(filter.where));
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
|
||||||
const LoopBackContext = require('loopback-context');
|
|
||||||
|
|
||||||
describe('Client updatePortfolio', () => {
|
|
||||||
const activeCtx = {
|
|
||||||
accessToken: {userId: 9},
|
|
||||||
http: {
|
|
||||||
req: {
|
|
||||||
headers: {origin: 'http://localhost'},
|
|
||||||
[`__`]: value => {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
|
||||||
active: activeCtx
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update the portfolioWeight when the salesPerson of a client changes', async() => {
|
|
||||||
const clientId = 1108;
|
|
||||||
const salesPersonId = 18;
|
|
||||||
|
|
||||||
const tx = await models.Client.beginTransaction({});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
const expectedResult = 841.63;
|
|
||||||
|
|
||||||
const client = await models.Client.findById(clientId, null, options);
|
|
||||||
await client.updateAttribute('salesPersonFk', salesPersonId, options);
|
|
||||||
|
|
||||||
await models.Client.updatePortfolio(options);
|
|
||||||
|
|
||||||
const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `;
|
|
||||||
const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options);
|
|
||||||
|
|
||||||
expect(salesPerson.portfolioWeight).toEqual(expectedResult);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => {
|
|
||||||
const clientId = 1107;
|
|
||||||
const salesPersonId = 19;
|
|
||||||
const tx = await models.Client.beginTransaction({});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
const expectedResult = 34.40;
|
|
||||||
|
|
||||||
const client = await models.Client.findById(clientId, null, options);
|
|
||||||
await client.updateAttribute('salesPersonFk', null, options);
|
|
||||||
|
|
||||||
await models.Client.updatePortfolio();
|
|
||||||
|
|
||||||
const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `;
|
|
||||||
const [salesPerson] = await models.Client.rawSql(portfolioQuery);
|
|
||||||
|
|
||||||
expect(salesPerson.portfolioWeight).toEqual(expectedResult);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,25 +0,0 @@
|
||||||
module.exports = function(Self) {
|
|
||||||
Self.remoteMethodCtx('updatePortfolio', {
|
|
||||||
description: 'Update salesPeson potfolio weight',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [],
|
|
||||||
returns: {
|
|
||||||
type: 'Object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/updatePortfolio`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.updatePortfolio = async options => {
|
|
||||||
const myOptions = {};
|
|
||||||
|
|
||||||
if (typeof options == 'object')
|
|
||||||
Object.assign(myOptions, options);
|
|
||||||
|
|
||||||
query = `CALL bs.salesPerson_updatePortfolio()`;
|
|
||||||
return Self.rawSql(query, null, myOptions);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -57,14 +57,16 @@ module.exports = Self => {
|
||||||
r.clientFk,
|
r.clientFk,
|
||||||
FALSE hasPdf,
|
FALSE hasPdf,
|
||||||
FALSE isInvoice,
|
FALSE isInvoice,
|
||||||
CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation
|
at2.id IS NOT NULL as isCompensation
|
||||||
FROM vn.receipt r
|
FROM vn.receipt r
|
||||||
LEFT JOIN vn.worker w ON w.id = r.workerFk
|
LEFT JOIN vn.worker w ON w.id = r.workerFk
|
||||||
LEFT JOIN account.user u ON u.id = w.userFk
|
LEFT JOIN account.user u ON u.id = w.userFk
|
||||||
JOIN vn.company c ON c.id = r.companyFk
|
JOIN vn.company c ON c.id = r.companyFk
|
||||||
JOIN vn.accounting a ON a.id = r.bankFk
|
LEFT JOIN vn.accounting a ON a.id = r.bankFk
|
||||||
JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk
|
LEFT JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk AND at2.code = 'compensation'
|
||||||
WHERE r.clientFk = ? AND r.companyFk = ?
|
WHERE
|
||||||
|
r.clientFk = ?
|
||||||
|
AND r.companyFk = ?
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
i.id,
|
i.id,
|
||||||
|
@ -81,13 +83,10 @@ module.exports = Self => {
|
||||||
i.clientFk,
|
i.clientFk,
|
||||||
i.hasPdf,
|
i.hasPdf,
|
||||||
TRUE isInvoice,
|
TRUE isInvoice,
|
||||||
CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation
|
NULL
|
||||||
FROM vn.invoiceOut i
|
FROM vn.invoiceOut i
|
||||||
JOIN vn.company c ON c.id = i.companyFk
|
JOIN vn.company c ON c.id = i.companyFk
|
||||||
JOIN vn.accounting a ON a.id = i.bankFk
|
|
||||||
JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk
|
|
||||||
WHERE i.clientFk = ? AND i.companyFk = ?
|
WHERE i.clientFk = ? AND i.companyFk = ?
|
||||||
ORDER BY payed DESC, created DESC
|
|
||||||
) t ORDER BY payed DESC, created DESC`,
|
) t ORDER BY payed DESC, created DESC`,
|
||||||
[
|
[
|
||||||
clientId,
|
clientId,
|
||||||
|
|
|
@ -22,7 +22,6 @@ module.exports = Self => {
|
||||||
require('../methods/client/summary')(Self);
|
require('../methods/client/summary')(Self);
|
||||||
require('../methods/client/updateAddress')(Self);
|
require('../methods/client/updateAddress')(Self);
|
||||||
require('../methods/client/updateFiscalData')(Self);
|
require('../methods/client/updateFiscalData')(Self);
|
||||||
require('../methods/client/updatePortfolio')(Self);
|
|
||||||
require('../methods/client/updateUser')(Self);
|
require('../methods/client/updateUser')(Self);
|
||||||
require('../methods/client/uploadFile')(Self);
|
require('../methods/client/uploadFile')(Self);
|
||||||
require('../methods/client/campaignMetricsPdf')(Self);
|
require('../methods/client/campaignMetricsPdf')(Self);
|
||||||
|
|
|
@ -10,8 +10,6 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
return this.$.watcher.submit().then(() => {
|
return this.$.watcher.submit().then(() => {
|
||||||
const query = `Clients/updatePortfolio`;
|
|
||||||
this.$http.get(query);
|
|
||||||
this.$http.get(`Clients/${this.$params.id}/checkDuplicatedData`);
|
this.$http.get(`Clients/${this.$params.id}/checkDuplicatedData`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ describe('InvoiceOut createPdf()', () => {
|
||||||
const ctx = {req: activeCtx};
|
const ctx = {req: activeCtx};
|
||||||
|
|
||||||
it('should create a new PDF file and set true the hasPdf property', async() => {
|
it('should create a new PDF file and set true the hasPdf property', async() => {
|
||||||
|
pending('https://redmine.verdnatura.es/issues/4875');
|
||||||
const invoiceId = 1;
|
const invoiceId = 1;
|
||||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
active: activeCtx
|
active: activeCtx
|
||||||
|
|
|
@ -30,6 +30,7 @@ describe('InvoiceOut downloadZip()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an error if the size of the files is too large', async() => {
|
it('should return an error if the size of the files is too large', async() => {
|
||||||
|
pending('https://redmine.verdnatura.es/issues/4875');
|
||||||
const tx = await models.InvoiceOut.beginTransaction({});
|
const tx = await models.InvoiceOut.beginTransaction({});
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
|
|
|
@ -51,6 +51,7 @@ describe('InvoiceOut filter()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the invoice out matching hasPdf', async() => {
|
it('should return the invoice out matching hasPdf', async() => {
|
||||||
|
pending('https://redmine.verdnatura.es/issues/4875');
|
||||||
const tx = await models.InvoiceOut.beginTransaction({});
|
const tx = await models.InvoiceOut.beginTransaction({});
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
|
|
@ -137,9 +137,11 @@ module.exports = Self => {
|
||||||
const params = [args.id, args.shipped, args.warehouseFk];
|
const params = [args.id, args.shipped, args.warehouseFk];
|
||||||
const [salesMovable] = await Self.rawSql(query, params, myOptions);
|
const [salesMovable] = await Self.rawSql(query, params, myOptions);
|
||||||
|
|
||||||
|
const sales = await models.Sale.find({ticketFk: args.id}, myOptions);
|
||||||
const salesNewTicket = salesMovable.filter(sale => (sale.movable ? sale.movable : 0) >= sale.quantity);
|
const salesNewTicket = salesMovable.filter(sale => (sale.movable ? sale.movable : 0) >= sale.quantity);
|
||||||
|
|
||||||
if (salesNewTicket.length) {
|
const salesNewTicketLength = salesNewTicket.length;
|
||||||
|
if (salesNewTicketLength && sales.length != salesNewTicketLength) {
|
||||||
const newTicket = await models.Ticket.transferSales(ctx, args.id, null, salesNewTicket, myOptions);
|
const newTicket = await models.Ticket.transferSales(ctx, args.id, null, salesNewTicket, myOptions);
|
||||||
args.id = newTicket.id;
|
args.id = newTicket.id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ class Controller extends Component {
|
||||||
ticketHaveNegatives() {
|
ticketHaveNegatives() {
|
||||||
let haveNegatives = false;
|
let haveNegatives = false;
|
||||||
let haveNotNegatives = false;
|
let haveNotNegatives = false;
|
||||||
|
this.ticket.withoutNegatives = false;
|
||||||
const haveDifferences = this.ticket.sale.haveDifferences;
|
const haveDifferences = this.ticket.sale.haveDifferences;
|
||||||
|
|
||||||
this.ticket.sale.items.forEach(item => {
|
this.ticket.sale.items.forEach(item => {
|
||||||
|
@ -76,8 +77,9 @@ class Controller extends Component {
|
||||||
haveNotNegatives = true;
|
haveNotNegatives = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ticket.withoutNegatives = true;
|
|
||||||
this.haveNegatives = (haveNegatives && haveNotNegatives && haveDifferences);
|
this.haveNegatives = (haveNegatives && haveNotNegatives && haveDifferences);
|
||||||
|
if (this.haveNegatives)
|
||||||
|
this.ticket.withoutNegatives = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
const Imap = require('imap');
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('checkInbox', {
|
||||||
|
description: 'Check an email inbox and process it',
|
||||||
|
accessType: 'READ',
|
||||||
|
returns:
|
||||||
|
{
|
||||||
|
arg: 'body',
|
||||||
|
type: 'file',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/checkInbox`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.checkInbox = async() => {
|
||||||
|
let imapConfig = await Self.app.models.WorkerTimeControlParams.findOne();
|
||||||
|
let imap = new Imap({
|
||||||
|
user: imapConfig.mailUser,
|
||||||
|
password: imapConfig.mailPass,
|
||||||
|
host: imapConfig.mailHost,
|
||||||
|
port: 993,
|
||||||
|
tls: true
|
||||||
|
});
|
||||||
|
let isEmailOk;
|
||||||
|
let uid;
|
||||||
|
let emailBody;
|
||||||
|
|
||||||
|
function openInbox(cb) {
|
||||||
|
imap.openBox('INBOX', true, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
imap.once('ready', function() {
|
||||||
|
openInbox(function(err, box) {
|
||||||
|
if (err) throw err;
|
||||||
|
const totalMessages = box.messages.total;
|
||||||
|
if (totalMessages == 0)
|
||||||
|
imap.end();
|
||||||
|
|
||||||
|
let f = imap.seq.fetch('1:*', {
|
||||||
|
bodies: ['HEADER.FIELDS (FROM SUBJECT)', '1'],
|
||||||
|
struct: true
|
||||||
|
});
|
||||||
|
f.on('message', function(msg, seqno) {
|
||||||
|
isEmailOk = false;
|
||||||
|
msg.on('body', function(stream, info) {
|
||||||
|
let buffer = '';
|
||||||
|
let bufferCopy = '';
|
||||||
|
stream.on('data', function(chunk) {
|
||||||
|
buffer = chunk.toString('utf8');
|
||||||
|
if (info.which === '1' && bufferCopy.length == 0)
|
||||||
|
bufferCopy = buffer.replace(/\s/g, ' ');
|
||||||
|
});
|
||||||
|
stream.on('end', function() {
|
||||||
|
if (bufferCopy.length > 0) {
|
||||||
|
emailBody = bufferCopy.toUpperCase().trim();
|
||||||
|
|
||||||
|
const bodyPositionOK = emailBody.match(/\bOK\b/i);
|
||||||
|
if (bodyPositionOK != null && (bodyPositionOK.index == 0 || bodyPositionOK.index == 122))
|
||||||
|
isEmailOk = true;
|
||||||
|
else
|
||||||
|
isEmailOk = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
msg.once('attributes', function(attrs) {
|
||||||
|
uid = attrs.uid;
|
||||||
|
});
|
||||||
|
msg.once('end', function() {
|
||||||
|
if (info.which === 'HEADER.FIELDS (FROM SUBJECT)') {
|
||||||
|
if (isEmailOk) {
|
||||||
|
imap.move(uid, 'exito', function(err) {
|
||||||
|
});
|
||||||
|
emailConfirm(buffer);
|
||||||
|
} else {
|
||||||
|
imap.move(uid, 'error', function(err) {
|
||||||
|
});
|
||||||
|
emailReply(buffer, emailBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
f.once('end', function() {
|
||||||
|
imap.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
imap.connect();
|
||||||
|
return 'Leer emails de gestion horaria';
|
||||||
|
};
|
||||||
|
|
||||||
|
async function emailConfirm(buffer) {
|
||||||
|
const now = new Date();
|
||||||
|
const from = JSON.stringify(Imap.parseHeader(buffer).from);
|
||||||
|
const subject = JSON.stringify(Imap.parseHeader(buffer).subject);
|
||||||
|
|
||||||
|
const timeControlDate = await getEmailDate(subject);
|
||||||
|
const week = timeControlDate[0];
|
||||||
|
const year = timeControlDate[1];
|
||||||
|
const user = await getUser(from);
|
||||||
|
let workerMail;
|
||||||
|
|
||||||
|
if (user.id != null) {
|
||||||
|
workerMail = await Self.app.models.WorkerTimeControlMail.findOne({
|
||||||
|
where: {
|
||||||
|
week: week,
|
||||||
|
year: year,
|
||||||
|
workerFk: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (workerMail != null) {
|
||||||
|
await workerMail.updateAttributes({
|
||||||
|
updated: now,
|
||||||
|
state: 'CONFIRMED'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function emailReply(buffer, emailBody) {
|
||||||
|
const now = new Date();
|
||||||
|
const from = JSON.stringify(Imap.parseHeader(buffer).from);
|
||||||
|
const subject = JSON.stringify(Imap.parseHeader(buffer).subject);
|
||||||
|
|
||||||
|
const timeControlDate = await getEmailDate(subject);
|
||||||
|
const week = timeControlDate[0];
|
||||||
|
const year = timeControlDate[1];
|
||||||
|
const user = await getUser(from);
|
||||||
|
let workerMail;
|
||||||
|
|
||||||
|
if (user.id != null) {
|
||||||
|
workerMail = await Self.app.models.WorkerTimeControlMail.findOne({
|
||||||
|
where: {
|
||||||
|
week: week,
|
||||||
|
year: year,
|
||||||
|
workerFk: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (workerMail != null) {
|
||||||
|
await workerMail.updateAttributes({
|
||||||
|
updated: now,
|
||||||
|
state: 'REVISE',
|
||||||
|
reason: emailBody
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
await sendMail(user, subject, emailBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUser(workerEmail) {
|
||||||
|
const userEmail = workerEmail.match(/(?<=<)(.*?)(?=>)/);
|
||||||
|
|
||||||
|
let [user] = await Self.rawSql(`SELECT u.id,u.name FROM account.user u
|
||||||
|
LEFT JOIN account.mailForward m on m.account = u.id
|
||||||
|
WHERE forwardTo =? OR
|
||||||
|
CONCAT(u.name,'@verdnatura.es') = ?`,
|
||||||
|
[userEmail[0], userEmail[0]]);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEmailDate(subject) {
|
||||||
|
const date = subject.match(/\d+/g);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMail(user, subject, emailBody) {
|
||||||
|
const sendTo = 'rrhh@verdnatura.es';
|
||||||
|
const emailSubject = subject + ' ' + user.name;
|
||||||
|
|
||||||
|
await Self.app.models.Mail.create({
|
||||||
|
receiver: sendTo,
|
||||||
|
subject: emailSubject,
|
||||||
|
body: emailBody
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -133,7 +133,7 @@ module.exports = Self => {
|
||||||
tb.permissionRate,
|
tb.permissionRate,
|
||||||
d.isTeleworking
|
d.isTeleworking
|
||||||
FROM tmp.timeBusinessCalculate tb
|
FROM tmp.timeBusinessCalculate tb
|
||||||
JOIN user u ON u.id = tb.userFk
|
JOIN account.user u ON u.id = tb.userFk
|
||||||
JOIN department d ON d.id = tb.departmentFk
|
JOIN department d ON d.id = tb.departmentFk
|
||||||
JOIN business b ON b.id = tb.businessFk
|
JOIN business b ON b.id = tb.businessFk
|
||||||
LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk AND tc.dated = tb.dated
|
LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk AND tc.dated = tb.dated
|
||||||
|
@ -143,7 +143,7 @@ module.exports = Self => {
|
||||||
IF(tc.timeWorkDecimal > 0, FALSE, IF(tb.timeWorkDecimal > 0, TRUE, FALSE)),
|
IF(tc.timeWorkDecimal > 0, FALSE, IF(tb.timeWorkDecimal > 0, TRUE, FALSE)),
|
||||||
TRUE))isTeleworkingWeek
|
TRUE))isTeleworkingWeek
|
||||||
FROM tmp.timeBusinessCalculate tb
|
FROM tmp.timeBusinessCalculate tb
|
||||||
LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk
|
LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk
|
||||||
AND tc.dated = tb.dated
|
AND tc.dated = tb.dated
|
||||||
GROUP BY tb.userFk
|
GROUP BY tb.userFk
|
||||||
HAVING isTeleworkingWeek > 0
|
HAVING isTeleworkingWeek > 0
|
||||||
|
@ -332,18 +332,9 @@ module.exports = Self => {
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
const timestamp = started.getTime() / 1000;
|
const timestamp = started.getTime() / 1000;
|
||||||
await models.Mail.create({
|
const url = `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`;
|
||||||
receiver: previousReceiver,
|
|
||||||
subject: $t('Record of hours week', {
|
|
||||||
week: args.week,
|
|
||||||
year: args.year
|
|
||||||
}),
|
|
||||||
body: `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`
|
|
||||||
}, myOptions);
|
|
||||||
|
|
||||||
query = `INSERT IGNORE INTO workerTimeControlMail (workerFk, year, week)
|
await models.WorkerTimeControl.weeklyHourRecordEmail(ctx, previousReceiver, args.week, args.year, url);
|
||||||
VALUES (?, ?, ?);`;
|
|
||||||
await Self.rawSql(query, [previousWorkerFk, args.year, args.week], myOptions);
|
|
||||||
|
|
||||||
previousWorkerFk = day.workerFk;
|
previousWorkerFk = day.workerFk;
|
||||||
previousReceiver = day.receiver;
|
previousReceiver = day.receiver;
|
||||||
|
|
|
@ -2,15 +2,12 @@ const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
describe('workerTimeControl sendMail()', () => {
|
describe('workerTimeControl sendMail()', () => {
|
||||||
const workerId = 18;
|
const workerId = 18;
|
||||||
const ctx = {
|
const activeCtx = {
|
||||||
req: {
|
getLocale: () => {
|
||||||
__: value => {
|
return 'en';
|
||||||
return value;
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
args: {}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
const ctx = {req: activeCtx, args: {}};
|
||||||
|
|
||||||
it('should fill time control of a worker without records in Journey and with rest', async() => {
|
it('should fill time control of a worker without records in Journey and with rest', async() => {
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
const {Email} = require('vn-print');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('weeklyHourRecordEmail', {
|
||||||
|
description: 'Sends the buyer waste email',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'recipient',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The recipient email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'week',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'year',
|
||||||
|
type: 'number',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'url',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: '/weekly-hour-hecord-email',
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.weeklyHourRecordEmail = async(ctx, recipient, week, year, url) => {
|
||||||
|
const params = {
|
||||||
|
recipient: recipient,
|
||||||
|
lang: ctx.req.getLocale(),
|
||||||
|
week: week,
|
||||||
|
year: year,
|
||||||
|
url: url
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = new Email('weekly-hour-record', params);
|
||||||
|
|
||||||
|
return email.send();
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/worker-time-control-mail/checkInbox')(Self);
|
||||||
|
};
|
|
@ -7,6 +7,7 @@ module.exports = Self => {
|
||||||
require('../methods/worker-time-control/updateTimeEntry')(Self);
|
require('../methods/worker-time-control/updateTimeEntry')(Self);
|
||||||
require('../methods/worker-time-control/sendMail')(Self);
|
require('../methods/worker-time-control/sendMail')(Self);
|
||||||
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
|
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
|
||||||
|
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
||||||
|
|
||||||
Self.rewriteDbError(function(err) {
|
Self.rewriteDbError(function(err) {
|
||||||
if (err.code === 'ER_DUP_ENTRY')
|
if (err.code === 'ER_DUP_ENTRY')
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"jsdom": "^16.7.0",
|
"jsdom": "^16.7.0",
|
||||||
"jszip": "^3.10.0",
|
"jszip": "^3.10.0",
|
||||||
"ldapjs": "^2.2.0",
|
"ldapjs": "^2.2.0",
|
||||||
"loopback": "^3.26.0",
|
"loopback": "^3.28.0",
|
||||||
"loopback-boot": "3.3.1",
|
"loopback-boot": "3.3.1",
|
||||||
"loopback-component-explorer": "^6.5.0",
|
"loopback-component-explorer": "^6.5.0",
|
||||||
"loopback-component-storage": "3.6.1",
|
"loopback-component-storage": "3.6.1",
|
||||||
|
@ -4878,13 +4878,20 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001299",
|
"version": "1.0.30001434",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||||
|
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "CC-BY-4.0",
|
"funding": [
|
||||||
"funding": {
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/browserslist"
|
"url": "https://opencollective.com/browserslist"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"node_modules/canonical-json": {
|
"node_modules/canonical-json": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
|
@ -12881,6 +12888,66 @@
|
||||||
"xmlcreate": "^1.0.1"
|
"xmlcreate": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsbarcode": {
|
||||||
|
"version": "3.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz",
|
||||||
|
"integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA==",
|
||||||
|
"bin": {
|
||||||
|
"auto.js": "bin/barcodes/CODE128/auto.js",
|
||||||
|
"Barcode.js": "bin/barcodes/Barcode.js",
|
||||||
|
"barcodes": "bin/barcodes",
|
||||||
|
"canvas.js": "bin/renderers/canvas.js",
|
||||||
|
"checksums.js": "bin/barcodes/MSI/checksums.js",
|
||||||
|
"codabar": "bin/barcodes/codabar",
|
||||||
|
"CODE128": "bin/barcodes/CODE128",
|
||||||
|
"CODE128_AUTO.js": "bin/barcodes/CODE128/CODE128_AUTO.js",
|
||||||
|
"CODE128.js": "bin/barcodes/CODE128/CODE128.js",
|
||||||
|
"CODE128A.js": "bin/barcodes/CODE128/CODE128A.js",
|
||||||
|
"CODE128B.js": "bin/barcodes/CODE128/CODE128B.js",
|
||||||
|
"CODE128C.js": "bin/barcodes/CODE128/CODE128C.js",
|
||||||
|
"CODE39": "bin/barcodes/CODE39",
|
||||||
|
"constants.js": "bin/barcodes/ITF/constants.js",
|
||||||
|
"defaults.js": "bin/options/defaults.js",
|
||||||
|
"EAN_UPC": "bin/barcodes/EAN_UPC",
|
||||||
|
"EAN.js": "bin/barcodes/EAN_UPC/EAN.js",
|
||||||
|
"EAN13.js": "bin/barcodes/EAN_UPC/EAN13.js",
|
||||||
|
"EAN2.js": "bin/barcodes/EAN_UPC/EAN2.js",
|
||||||
|
"EAN5.js": "bin/barcodes/EAN_UPC/EAN5.js",
|
||||||
|
"EAN8.js": "bin/barcodes/EAN_UPC/EAN8.js",
|
||||||
|
"encoder.js": "bin/barcodes/EAN_UPC/encoder.js",
|
||||||
|
"ErrorHandler.js": "bin/exceptions/ErrorHandler.js",
|
||||||
|
"exceptions": "bin/exceptions",
|
||||||
|
"exceptions.js": "bin/exceptions/exceptions.js",
|
||||||
|
"fixOptions.js": "bin/help/fixOptions.js",
|
||||||
|
"GenericBarcode": "bin/barcodes/GenericBarcode",
|
||||||
|
"getOptionsFromElement.js": "bin/help/getOptionsFromElement.js",
|
||||||
|
"getRenderProperties.js": "bin/help/getRenderProperties.js",
|
||||||
|
"help": "bin/help",
|
||||||
|
"index.js": "bin/renderers/index.js",
|
||||||
|
"index.tmp.js": "bin/barcodes/index.tmp.js",
|
||||||
|
"ITF": "bin/barcodes/ITF",
|
||||||
|
"ITF.js": "bin/barcodes/ITF/ITF.js",
|
||||||
|
"ITF14.js": "bin/barcodes/ITF/ITF14.js",
|
||||||
|
"JsBarcode.js": "bin/JsBarcode.js",
|
||||||
|
"linearizeEncodings.js": "bin/help/linearizeEncodings.js",
|
||||||
|
"merge.js": "bin/help/merge.js",
|
||||||
|
"MSI": "bin/barcodes/MSI",
|
||||||
|
"MSI.js": "bin/barcodes/MSI/MSI.js",
|
||||||
|
"MSI10.js": "bin/barcodes/MSI/MSI10.js",
|
||||||
|
"MSI1010.js": "bin/barcodes/MSI/MSI1010.js",
|
||||||
|
"MSI11.js": "bin/barcodes/MSI/MSI11.js",
|
||||||
|
"MSI1110.js": "bin/barcodes/MSI/MSI1110.js",
|
||||||
|
"object.js": "bin/renderers/object.js",
|
||||||
|
"options": "bin/options",
|
||||||
|
"optionsFromStrings.js": "bin/help/optionsFromStrings.js",
|
||||||
|
"pharmacode": "bin/barcodes/pharmacode",
|
||||||
|
"renderers": "bin/renderers",
|
||||||
|
"shared.js": "bin/renderers/shared.js",
|
||||||
|
"svg.js": "bin/renderers/svg.js",
|
||||||
|
"UPC.js": "bin/barcodes/EAN_UPC/UPC.js",
|
||||||
|
"UPCE.js": "bin/barcodes/EAN_UPC/UPCE.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsbn": {
|
"node_modules/jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
@ -23759,6 +23826,14 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/xmldom": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -23938,6 +24013,7 @@
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
|
"jsbarcode": "^3.11.5",
|
||||||
"jsonexport": "^3.2.0",
|
"jsonexport": "^3.2.0",
|
||||||
"juice": "^5.2.0",
|
"juice": "^5.2.0",
|
||||||
"log4js": "^6.7.0",
|
"log4js": "^6.7.0",
|
||||||
|
@ -23948,7 +24024,8 @@
|
||||||
"strftime": "^0.10.0",
|
"strftime": "^0.10.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-i18n": "^8.15.0",
|
"vue-i18n": "^8.15.0",
|
||||||
"vue-server-renderer": "^2.6.10"
|
"vue-server-renderer": "^2.6.10",
|
||||||
|
"xmldom": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"print/node_modules/fs-extra": {
|
"print/node_modules/fs-extra": {
|
||||||
|
@ -27170,7 +27247,9 @@
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001299",
|
"version": "1.0.30001434",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||||
|
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"canonical-json": {
|
"canonical-json": {
|
||||||
|
@ -32604,6 +32683,11 @@
|
||||||
"xmlcreate": "^1.0.1"
|
"xmlcreate": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jsbarcode": {
|
||||||
|
"version": "3.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz",
|
||||||
|
"integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA=="
|
||||||
|
},
|
||||||
"jsbn": {
|
"jsbn": {
|
||||||
"version": "0.1.1"
|
"version": "0.1.1"
|
||||||
},
|
},
|
||||||
|
@ -39035,6 +39119,7 @@
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
|
"jsbarcode": "^3.11.5",
|
||||||
"jsonexport": "^3.2.0",
|
"jsonexport": "^3.2.0",
|
||||||
"juice": "^5.2.0",
|
"juice": "^5.2.0",
|
||||||
"log4js": "^6.7.0",
|
"log4js": "^6.7.0",
|
||||||
|
@ -39045,7 +39130,8 @@
|
||||||
"strftime": "^0.10.0",
|
"strftime": "^0.10.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-i18n": "^8.15.0",
|
"vue-i18n": "^8.15.0",
|
||||||
"vue-server-renderer": "^2.6.10"
|
"vue-server-renderer": "^2.6.10",
|
||||||
|
"xmldom": "^0.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs-extra": {
|
"fs-extra": {
|
||||||
|
@ -40382,6 +40468,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz",
|
||||||
"integrity": "sha512-Mbe56Dvj00onbnSo9J0qj/XlY5bfN9KidsOnpd5tRCsR3ekB3hyyNU9fGrTdqNT5ZNvv4BsA2TcQlignsZyVcw=="
|
"integrity": "sha512-Mbe56Dvj00onbnSo9J0qj/XlY5bfN9KidsOnpd5tRCsR3ekB3hyyNU9fGrTdqNT5ZNvv4BsA2TcQlignsZyVcw=="
|
||||||
},
|
},
|
||||||
|
"xmldom": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg=="
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2"
|
"version": "4.0.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -49,4 +49,10 @@
|
||||||
|
|
||||||
.page-break-after {
|
.page-break-after {
|
||||||
page-break-after: always;
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsize {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
|
@ -8,10 +8,12 @@ module.exports = {
|
||||||
this.transporter = nodemailer.createTransport(config.smtp);
|
this.transporter = nodemailer.createTransport(config.smtp);
|
||||||
},
|
},
|
||||||
|
|
||||||
send(options) {
|
async send(options) {
|
||||||
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const notProductionError = {message: 'This not production, this email not sended'};
|
||||||
|
await this.mailLog(options, notProductionError);
|
||||||
if (!config.smtp.auth.user)
|
if (!config.smtp.auth.user)
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
|
|
||||||
|
@ -24,29 +26,35 @@ module.exports = {
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}).finally(async() => {
|
}).finally(async() => {
|
||||||
const attachments = [];
|
await this.mailLog(options, error);
|
||||||
if (options.attachments) {
|
|
||||||
for (let attachment of options.attachments) {
|
|
||||||
const fileName = attachment.filename;
|
|
||||||
const filePath = attachment.path;
|
|
||||||
if (fileName.includes('.png')) continue;
|
|
||||||
|
|
||||||
if (fileName || filePath)
|
|
||||||
attachments.push(filePath ? filePath : fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileNames = attachments.join(',\n');
|
|
||||||
await db.rawSql(`
|
|
||||||
INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status)
|
|
||||||
VALUES (?, ?, 1, ?, ?, ?, ?)`, [
|
|
||||||
options.to,
|
|
||||||
options.replyTo,
|
|
||||||
options.subject,
|
|
||||||
options.text || options.html,
|
|
||||||
fileNames,
|
|
||||||
error && error.message || 'Sent'
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async mailLog(options, error) {
|
||||||
|
const attachments = [];
|
||||||
|
if (options.attachments) {
|
||||||
|
for (let attachment of options.attachments) {
|
||||||
|
const fileName = attachment.filename;
|
||||||
|
const filePath = attachment.path;
|
||||||
|
if (fileName.includes('.png')) continue;
|
||||||
|
|
||||||
|
if (fileName || filePath)
|
||||||
|
attachments.push(filePath ? filePath : fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileNames = attachments.join(',\n');
|
||||||
|
|
||||||
|
await db.rawSql(`
|
||||||
|
INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status)
|
||||||
|
VALUES (?, ?, 1, ?, ?, ?, ?)`, [
|
||||||
|
options.to,
|
||||||
|
options.replyTo,
|
||||||
|
options.subject,
|
||||||
|
options.text || options.html,
|
||||||
|
fileNames,
|
||||||
|
error && error.message || 'Sent'
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
const Stylesheet = require(`vn-print/core/stylesheet`);
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const vnPrintPath = path.resolve('print');
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${vnPrintPath}/common/css/spacing.css`,
|
||||||
|
`${vnPrintPath}/common/css/misc.css`,
|
||||||
|
`${vnPrintPath}/common/css/layout.css`,
|
||||||
|
`${vnPrintPath}/common/css/email.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.external-link {
|
||||||
|
border: 2px dashed #8dba25;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<p>
|
||||||
|
{{ $t(`click`) }}
|
||||||
|
<a :href="url">{{ $t('subject') }}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'email-verify',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: [String],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
subject: Email Verify
|
||||||
|
title: Email Verify
|
||||||
|
click: Click on the following link to verify this email. If you haven't requested this email, just ignore it
|
|
@ -0,0 +1,3 @@
|
||||||
|
subject: Verificar correo
|
||||||
|
title: Verificar correo
|
||||||
|
click: Pulsa en el siguiente link para verificar este correo. Si no has pedido este correo, simplemente ignóralo
|
|
@ -0,0 +1,13 @@
|
||||||
|
const Stylesheet = require(`vn-print/core/stylesheet`);
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const vnPrintPath = path.resolve('print');
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${vnPrintPath}/common/css/spacing.css`,
|
||||||
|
`${vnPrintPath}/common/css/misc.css`,
|
||||||
|
`${vnPrintPath}/common/css/layout.css`,
|
||||||
|
`${vnPrintPath}/common/css/email.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.external-link {
|
||||||
|
border: 2px dashed #8dba25;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
subject: Recuperar contraseña
|
||||||
|
title: Recuperar contraseña
|
||||||
|
Click on the following link to change your password.: Pulsa en el siguiente link para cambiar tu contraseña.
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<p>
|
||||||
|
{{ $t('Click on the following link to change your password.') }}
|
||||||
|
<a :href="url">{{ $t('subject') }}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'recover-password',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: [String],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,12 @@
|
||||||
|
const Stylesheet = require(`vn-print/core/stylesheet`);
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const vnPrintPath = path.resolve('print');
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${vnPrintPath}/common/css/spacing.css`,
|
||||||
|
`${vnPrintPath}/common/css/misc.css`,
|
||||||
|
`${vnPrintPath}/common/css/layout.css`,
|
||||||
|
`${vnPrintPath}/common/css/email.css`])
|
||||||
|
.mergeStyles();
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
subject: Weekly time log
|
||||||
|
title: Record of hours week {0} year {1}
|
||||||
|
dear: Dear worker
|
||||||
|
description: Access the following link:<br/><br/>
|
||||||
|
{0} <br/><br/>
|
||||||
|
Click 'SATISFIED' if you agree with the hours worked. Otherwise, press 'NOT SATISFIED', detailing the cause of the disagreement.
|
|
@ -0,0 +1,6 @@
|
||||||
|
subject: Registro de horas semanal
|
||||||
|
title: Registro de horas semana {0} año {1}
|
||||||
|
dear: Estimado trabajador
|
||||||
|
description: Acceda al siguiente enlace:<br/><br/>
|
||||||
|
{0} <br/><br/>
|
||||||
|
Pulse 'CONFORME' si esta de acuerdo con las horas trabajadas. En caso contrario pulse 'NO CONFORME', detallando la causa de la disconformidad.
|
|
@ -0,0 +1,9 @@
|
||||||
|
<email-body v-bind="$props">
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<h1>{{ $t('title', [week, year]) }}</h1>
|
||||||
|
<p>{{$t('dear')}},</p>
|
||||||
|
<p v-html="$t('description', [url])"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</email-body>
|
|
@ -0,0 +1,23 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const emailBody = new Component('email-body');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'weekly-hour-record',
|
||||||
|
components: {
|
||||||
|
'email-body': emailBody.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
week: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
year: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
html {
|
html {
|
||||||
font-family: "Roboto";
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
margin-top: -7px;
|
margin-top: -6px;
|
||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -9,29 +9,36 @@ html {
|
||||||
}
|
}
|
||||||
#vertical {
|
#vertical {
|
||||||
writing-mode: vertical-rl;
|
writing-mode: vertical-rl;
|
||||||
height: 226px;
|
height: 230px;
|
||||||
|
font-size: 29px;
|
||||||
margin-left: -13px;
|
margin-left: -13px;
|
||||||
}
|
}
|
||||||
.outline {
|
.outline {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
height: 37px;
|
||||||
|
width: 100px;
|
||||||
}
|
}
|
||||||
#nickname {
|
#nickname {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
|
max-width: 50px;
|
||||||
}
|
}
|
||||||
#agencyDescripton {
|
#agencyDescripton {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
|
width: 375px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
#bold {
|
#bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
#barcode{
|
#barcode{
|
||||||
width: 390px;
|
width: 370px;
|
||||||
}
|
}
|
||||||
#shipped {
|
#shipped {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
width: 50px;
|
||||||
|
max-width: 100px;
|
||||||
}
|
}
|
||||||
#ticketFk, #vertical {
|
#ticketFk {
|
||||||
font-size: 34px;
|
font-size: 32px;
|
||||||
}
|
}
|
|
@ -1,35 +1,34 @@
|
||||||
<report-body v-bind="$props">
|
<!DOCTYPE html>
|
||||||
<template v-slot:header>
|
<html>
|
||||||
<span></span>
|
<body>
|
||||||
</template>
|
<table v-for="labelData in labelsData" style="break-before: page">
|
||||||
<table v-for="labelData in labelsData">
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td rowspan="6"><span id="vertical" class="ellipsize">{{getVertical(labelData)}}</span></td>
|
||||||
<td rowspan="6"><span id="vertical">{{labelData.levelV}}</span></td>
|
<td id="ticketFk">
|
||||||
<td id="ticketFk">{{labelData.ticketFk}} ⬸ {{labelData.clientFk}}</td>
|
{{labelData.clientFk ? `${labelData.ticketFk} « ${labelData.clientFk}` : labelData.ticketFk}}
|
||||||
<td colspan="2" id="shipped">{{labelData.shipped}}</td>
|
</td>
|
||||||
</tr>
|
<td colspan="2" id="shipped">{{labelData.shipped || '---'}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td rowspan="3"><div v-html="getBarcode(labelData.ticketFk)" id="barcode"></div></td>
|
<tr>
|
||||||
<td class="outline">{{labelData.workerCode}}</td>
|
<td rowspan="3"><div v-html="getBarcode(labelData.ticketFk)" id="barcode"></div></td>
|
||||||
</tr>
|
<td class="outline">{{labelData.workerCode || '---'}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td class="outline">{{labelData.labelCount}}</td>
|
<tr>
|
||||||
</tr>
|
<td class="outline">{{labelData.labelCount || 0}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td class="outline">{{labelData.size}}</td>
|
<tr>
|
||||||
</tr>
|
<td class="outline">{{labelData.code == 'V' ? (labelData.size || 0) + 'cm' : (labelData.volume || 0) + 'm³'}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td><div id="agencyDescripton">{{labelData.agencyDescription}}</div></td>
|
<tr>
|
||||||
<td id="bold">{{labelData.lineCount}}</td>
|
<td><div id="agencyDescripton" class="ellipsize">{{labelData.agencyDescription ? labelData.agencyDescription.toUpperCase() : '---'}}</div></td>
|
||||||
</tr>
|
<td id="bold">{{labelData.lineCount || 0}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td id="nickname">{{labelData.nickName}}</td>
|
<tr>
|
||||||
<td id="bold">{{labelData.agencyHour}}</td>
|
<td id="nickname" class="ellipsize">{{labelData.nickName ? labelData.nickName.toUpperCase() : '---'}}</td>
|
||||||
</tr>
|
<td id="bold">{{labelData.shippedHour || labelData.zoneHour}}</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
</tbody>
|
||||||
<template v-slot:footer>
|
</table>
|
||||||
<span></span>
|
</body>
|
||||||
</template>
|
</html>
|
||||||
</report-body>
|
|
|
@ -25,7 +25,6 @@ module.exports = {
|
||||||
ticketIds = [this.id];
|
ticketIds = [this.id];
|
||||||
|
|
||||||
this.labelsData = await this.rawSqlFromDef('labelsData', [ticketIds]);
|
this.labelsData = await this.rawSqlFromDef('labelsData', [ticketIds]);
|
||||||
|
|
||||||
if (!this.labelsData.length)
|
if (!this.labelsData.length)
|
||||||
throw new UserError('Empty data source');
|
throw new UserError('Empty data source');
|
||||||
},
|
},
|
||||||
|
@ -40,10 +39,23 @@ module.exports = {
|
||||||
format: 'code128',
|
format: 'code128',
|
||||||
displayValue: false,
|
displayValue: false,
|
||||||
width: 3.8,
|
width: 3.8,
|
||||||
height: 110,
|
height: 115,
|
||||||
});
|
});
|
||||||
return xmlSerializer.serializeToString(svgNode);
|
return xmlSerializer.serializeToString(svgNode);
|
||||||
},
|
},
|
||||||
|
getVertical(labelData) {
|
||||||
|
let value;
|
||||||
|
if (labelData.collectionFk) {
|
||||||
|
value = `${labelData.collectionFk} ~ `;
|
||||||
|
if (labelData.code == 'V')
|
||||||
|
value = value + `${labelData.wagon}-${labelData.level}`;
|
||||||
|
else
|
||||||
|
value = value + `${labelData.color.substring(0, 4)}`;
|
||||||
|
} else
|
||||||
|
value = '-'.repeat(19);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
'report-body': reportBody.build()
|
'report-body': reportBody.build()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"width": "10.4cm",
|
"width": "10.4cm",
|
||||||
"height": "4.8cm",
|
"height": "4.9cm",
|
||||||
"margin": {
|
"margin": {
|
||||||
"top": "0cm",
|
"top": "0.3cm",
|
||||||
"right": "0.5cm",
|
"right": "0.6cm",
|
||||||
"bottom": "0cm",
|
"bottom": "0cm",
|
||||||
"left": "0cm"
|
"left": "0cm"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
SELECT c.itemPackingTypeFk,
|
SELECT c.itemPackingTypeFk code,
|
||||||
CONCAT(tc.collectionFk, ' ', LEFT(cc.code, 4)) color,
|
tc.collectionFk,
|
||||||
CONCAT(tc.collectionFk, ' ', SUBSTRING('ABCDEFGH',tc.wagon, 1), '-', tc.`level`) levelV,
|
SUBSTRING('ABCDEFGH', tc.wagon, 1) wagon,
|
||||||
tc.ticketFk,
|
tc.`level`,
|
||||||
LEFT(COALESCE(et.description, zo.name, am.name),12) agencyDescription,
|
t.id ticketFk,
|
||||||
am.name,
|
COALESCE(et.description, zo.name, am.name) agencyDescription,
|
||||||
|
cc.code color,
|
||||||
t.clientFk,
|
t.clientFk,
|
||||||
CONCAT(CAST(SUM(sv.volume) AS DECIMAL(5, 2)), 'm³') m3 ,
|
CAST(SUM(sv.volume) AS DECIMAL(5, 2)) volume,
|
||||||
CAST(IF(ic.code = 'plant', CONCAT(MAX(i.`size`),' cm'), COUNT(*)) AS CHAR) size,
|
MAX(i.`size`) `size`,
|
||||||
w.code workerCode,
|
w.code workerCode,
|
||||||
tt.labelCount,
|
TIME_FORMAT(t.shipped, '%H:%i') shippedHour,
|
||||||
IF(HOUR(t.shipped), TIME_FORMAT(t.shipped, '%H:%i'), TIME_FORMAT(zo.`hour`, '%H:%i')) agencyHour,
|
TIME_FORMAT(zo.`hour`, '%H:%i') zoneHour,
|
||||||
DATE_FORMAT(t.shipped, '%d/%m/%y') shipped,
|
DATE_FORMAT(t.shipped, '%d/%m/%y') shipped,
|
||||||
COUNT(*) lineCount,
|
t.nickName,
|
||||||
t.nickName
|
tt.labelCount,
|
||||||
|
COUNT(*) lineCount
|
||||||
FROM vn.ticket t
|
FROM vn.ticket t
|
||||||
JOIN vn.ticketCollection tc ON tc.ticketFk = t.id
|
JOIN vn.ticketCollection tc ON tc.ticketFk = t.id
|
||||||
JOIN vn.collection c ON c.id = tc.collectionFk
|
JOIN vn.collection c ON c.id = tc.collectionFk
|
||||||
|
@ -30,6 +32,6 @@ SELECT c.itemPackingTypeFk,
|
||||||
LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id
|
LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id
|
||||||
LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk
|
LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk
|
||||||
LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk
|
LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk
|
||||||
WHERE tc.ticketFk IN (?)
|
WHERE t.id IN (?)
|
||||||
GROUP BY t.id
|
GROUP BY t.id
|
||||||
ORDER BY cc.`code`;
|
ORDER BY cc.`code`;
|
Loading…
Reference in New Issue