231801_test_to_master #1519

Merged
alexm merged 490 commits from 231801_test_to_master into master 2023-05-12 06:29:59 +00:00
427 changed files with 12330 additions and 2626 deletions
Showing only changes of commit 4a7902ae4d - Show all commits

View File

@ -8,14 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2316.01] - 2023-05-04 ## [2316.01] - 2023-05-04
### Added ### Added
- - (Usuarios -> Histórico) Nueva sección
- (Roles -> Histórico) Nueva sección
### Changed ### Changed
- - (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral
### Fixed ### Fixed
- -
## [2314.01] - 2023-04-20 ## [2314.01] - 2023-04-20
### Added ### Added

1
Jenkinsfile vendored
View File

@ -52,6 +52,7 @@ pipeline {
}}} }}}
environment { environment {
NODE_ENV = "" NODE_ENV = ""
TZ = 'Europe/Madrid'
} }
parallel { parallel {
stage('Frontend') { stage('Frontend') {

View File

@ -0,0 +1,17 @@
name: account
columns:
id: id
name: name
roleFk: role
nickname: nickname
lang: lang
password: password
bcryptPassword: bcrypt password
active: active
email: email
emailVerified: email verified
created: created
updated: updated
image: image
hasGrant: has grant
userFk: user

View File

@ -0,0 +1,17 @@
name: cuenta
columns:
id: id
name: nombre
roleFk: rol
nickname: apodo
lang: idioma
password: contraseña
bcryptPassword: contraseña bcrypt
active: activo
email: email
emailVerified: email verificado
created: creado
updated: actualizado
image: imagen
hasGrant: tiene permiso
userFk: usuario

View File

@ -1,9 +1,9 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('campaign latest()', () => { describe('campaign latest()', () => {
it('should return the campaigns from the last year', async() => { it('should return the campaigns from the last year', async() => {
const now = Date.vnNew(); const now = Date.vnNew();
const result = await app.models.Campaign.latest(); const result = await models.Campaign.latest();
const randomIndex = Math.floor(Math.random() * result.length); const randomIndex = Math.floor(Math.random() * result.length);
const campaignDated = result[randomIndex].dated; const campaignDated = result[randomIndex].dated;
@ -14,7 +14,7 @@ describe('campaign latest()', () => {
it('should return the campaigns from the current year', async() => { it('should return the campaigns from the current year', async() => {
const now = Date.vnNew(); const now = Date.vnNew();
const currentYear = now.getFullYear(); const currentYear = now.getFullYear();
const result = await app.models.Campaign.latest({ const result = await models.Campaign.latest({
where: {dated: {like: `%${currentYear}%`}} where: {dated: {like: `%${currentYear}%`}}
}); });

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('campaign upcoming()', () => { describe('campaign upcoming()', () => {
it('should return the upcoming campaign but from the last year', async() => { it('should return the upcoming campaign but from the last year', async() => {
const response = await app.models.Campaign.upcoming(); const response = await models.Campaign.upcoming();
const campaignDated = response.dated; const campaignDated = response.dated;
const now = Date.vnNew(); const now = Date.vnNew();

View File

@ -26,7 +26,7 @@ module.exports = Self => {
Self.send = async(ctx, to, message) => { Self.send = async(ctx, to, message) => {
const models = Self.app.models; const models = Self.app.models;
const accessToken = ctx.req.accessToken; const accessToken = ctx.req.accessToken;
const sender = await models.Account.findById(accessToken.userId); const sender = await models.VnUser.findById(accessToken.userId);
const recipient = to.replace('@', ''); const recipient = to.replace('@', '');
if (sender.name != recipient) { if (sender.name != recipient) {

View File

@ -29,8 +29,8 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const sender = await models.Account.findById(userId, {fields: ['id']}); const sender = await models.VnUser.findById(userId, {fields: ['id']});
const recipient = await models.Account.findById(recipientId, null); const recipient = await models.VnUser.findById(recipientId, null);
// Prevent sending messages to yourself // Prevent sending messages to yourself
if (recipientId == userId) return false; if (recipientId == userId) return false;

View File

@ -58,7 +58,7 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const recipientName = chat.recipient.slice(1); const recipientName = chat.recipient.slice(1);
const recipient = await models.Account.findOne({ const recipient = await models.VnUser.findOne({
where: { where: {
name: recipientName name: recipientName
} }
@ -104,7 +104,7 @@ module.exports = Self => {
} }
const models = Self.app.models; const models = Self.app.models;
const sender = await models.Account.findById(senderFk); const sender = await models.VnUser.findById(senderFk);
const login = await Self.getServiceAuth(); const login = await Self.getServiceAuth();
const avatar = `${login.host}/avatar/${sender.name}`; const avatar = `${login.host}/avatar/${sender.name}`;

View File

@ -1,12 +1,12 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('Chat notifyIssue()', () => { describe('Chat notifyIssue()', () => {
const ctx = {req: {accessToken: {userId: 1}}}; const ctx = {req: {accessToken: {userId: 1}}};
ctx.req.__ = value => { ctx.req.__ = value => {
return value; return value;
}; };
const chatModel = app.models.Chat; const chatModel = models.Chat;
const osTicketModel = app.models.OsTicket; const osTicketModel = models.OsTicket;
const departmentId = 31; const departmentId = 31;
it(`should not call to the send() method and neither return a response`, async() => { it(`should not call to the send() method and neither return a response`, async() => {
@ -29,7 +29,7 @@ describe('Chat notifyIssue()', () => {
// 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 models.Department.findById(departmentId);
let orgChatName = department.chatName; let orgChatName = department.chatName;
await department.updateAttribute('chatName', 'IT'); await department.updateAttribute('chatName', 'IT');

View File

@ -1,16 +1,16 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('Chat send()', () => { describe('Chat send()', () => {
it('should return true as response', async() => { it('should return true as response', async() => {
let ctx = {req: {accessToken: {userId: 1}}}; let ctx = {req: {accessToken: {userId: 1}}};
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something'); let response = await models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(true); expect(response).toEqual(true);
}); });
it('should return false as response', async() => { it('should return false as response', async() => {
let ctx = {req: {accessToken: {userId: 18}}}; let ctx = {req: {accessToken: {userId: 18}}};
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something'); let response = await models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(false); expect(response).toEqual(false);
}); });

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('getSectors()', () => { describe('getSectors()', () => {
it('return list of sectors', async() => { it('return list of sectors', async() => {
let response = await app.models.Collection.getSectors(); let response = await models.Collection.getSectors();
expect(response.length).toBeGreaterThan(0); expect(response.length).toBeGreaterThan(0);
expect(response[0].id).toEqual(1); expect(response[0].id).toEqual(1);

View File

@ -1,10 +1,10 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('newCollection()', () => { describe('newCollection()', () => {
it('should return a new collection', async() => { it('should return a new collection', async() => {
pending('#3400 analizar que hacer con rutas de back collection'); pending('#3400 analizar que hacer con rutas de back collection');
let ctx = {req: {accessToken: {userId: 1106}}}; let ctx = {req: {accessToken: {userId: 1106}}};
let response = await app.models.Collection.newCollection(ctx, 1, 1, 1); let response = await models.Collection.newCollection(ctx, 1, 1, 1);
expect(response.length).toBeGreaterThan(0); expect(response.length).toBeGreaterThan(0);
expect(response[0].ticketFk).toEqual(2); expect(response[0].ticketFk).toEqual(2);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms downloadFile()', () => { describe('dms downloadFile()', () => {
let dmsId = 1; let dmsId = 1;
@ -6,7 +6,7 @@ describe('dms downloadFile()', () => {
it('should return a response for an employee with text content-type', async() => { it('should return a response for an employee with text content-type', async() => {
let workerId = 1107; let workerId = 1107;
let ctx = {req: {accessToken: {userId: workerId}}}; let ctx = {req: {accessToken: {userId: workerId}}};
const result = await app.models.Dms.downloadFile(ctx, dmsId); const result = await models.Dms.downloadFile(ctx, dmsId);
expect(result[1]).toEqual('text/plain'); expect(result[1]).toEqual('text/plain');
}); });
@ -16,7 +16,7 @@ describe('dms downloadFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}}; let ctx = {req: {accessToken: {userId: clientId}}};
let error; let error;
await app.models.Dms.downloadFile(ctx, dmsId).catch(e => { await models.Dms.downloadFile(ctx, dmsId).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms removeFile()', () => { describe('dms removeFile()', () => {
let dmsId = 1; let dmsId = 1;
@ -8,7 +8,7 @@ describe('dms removeFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}}; let ctx = {req: {accessToken: {userId: clientId}}};
let error; let error;
await app.models.Dms.removeFile(ctx, dmsId).catch(e => { await models.Dms.removeFile(ctx, dmsId).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms updateFile()', () => { describe('dms updateFile()', () => {
it(`should return an error for a user without enough privileges`, async() => { it(`should return an error for a user without enough privileges`, async() => {
@ -11,7 +11,7 @@ describe('dms updateFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: dmsTypeId}}; let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: dmsTypeId}};
let error; let error;
await app.models.Dms.updateFile(ctx, dmsId, warehouseId, companyId, dmsTypeId).catch(e => { await models.Dms.updateFile(ctx, dmsId, warehouseId, companyId, dmsTypeId).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms uploadFile()', () => { describe('dms uploadFile()', () => {
it(`should return an error for a user without enough privileges`, async() => { it(`should return an error for a user without enough privileges`, async() => {
@ -7,7 +7,7 @@ describe('dms uploadFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}}; let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}};
let error; let error;
await app.models.Dms.uploadFile(ctx).catch(e => { await models.Dms.uploadFile(ctx).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

130
back/methods/image/scrub.js Normal file
View File

@ -0,0 +1,130 @@
const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('scrub', {
description: 'Deletes images without database reference',
accessType: 'WRITE',
accepts: [
{
arg: 'collection',
type: 'string',
description: 'The collection name',
required: true
}, {
arg: 'remove',
type: 'boolean',
description: 'Delete instead of move images to trash'
}, {
arg: 'limit',
type: 'integer',
description: 'Maximum number of images to clean'
}, {
arg: 'dryRun',
type: 'boolean',
description: 'Simulate actions'
}, {
arg: 'skipLock',
type: 'boolean',
description: 'Wether to skip exclusive lock'
}
],
returns: {
type: 'integer',
root: true
},
http: {
path: `/scrub`,
verb: 'POST'
}
});
Self.scrub = async function(collection, remove, limit, dryRun, skipLock) {
const $ = Self.app.models;
const env = process.env.NODE_ENV;
dryRun = dryRun || (env && env !== 'production');
const instance = await $.ImageCollection.findOne({
fields: ['id'],
where: {name: collection}
});
if (!instance)
throw new UserError('Collection does not exist');
const container = await $.ImageContainer.container(collection);
const rootPath = container.client.root;
let tx;
let opts;
const lockName = 'salix.Image.scrub';
if (!skipLock) {
tx = await Self.beginTransaction({timeout: null});
opts = {transaction: tx};
const [row] = await Self.rawSql(
`SELECT GET_LOCK(?, 10) hasLock`, [lockName], opts);
if (!row.hasLock)
throw new UserError('Cannot obtain exclusive lock');
}
try {
const now = Date.vnNew().toJSON();
const scrubDir = path.join(rootPath, '.scrub', now);
const collectionDir = path.join(rootPath, collection);
const sizes = await fs.readdir(collectionDir);
let cleanCount = 0;
mainLoop: for (const size of sizes) {
const sizeDir = path.join(collectionDir, size);
const scrubSizeDir = path.join(scrubDir, collection, size);
const images = await fs.readdir(sizeDir);
for (const image of images) {
const imageName = path.parse(image).name;
const count = await Self.count({
collectionFk: collection,
name: imageName
}, opts);
const exists = count > 0;
let scrubDirCreated = false;
if (!exists) {
const srcFile = path.join(sizeDir, image);
if (remove !== true) {
if (!scrubDirCreated) {
if (!dryRun)
await fs.mkdir(scrubSizeDir, {recursive: true});
scrubDirCreated = true;
}
const dstFile = path.join(scrubSizeDir, image);
if (!dryRun) await fs.rename(srcFile, dstFile);
} else {
try {
if (!dryRun) await fs.unlink(srcFile);
} catch (err) {
console.error(err.message);
}
}
cleanCount++;
if (limit && cleanCount == limit)
break mainLoop;
}
}
}
return cleanCount;
} finally {
if (!skipLock) {
try {
await Self.rawSql(`DO RELEASE_LOCK(?)`, [lockName], opts);
await tx.rollback();
} catch (err) {
console.error(err.message);
}
}
}
};
};

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('image download()', () => { describe('image download()', () => {
const collection = 'user'; const collection = 'user';
@ -8,7 +8,7 @@ describe('image download()', () => {
it('should return the image content-type of the user', async() => { it('should return the image content-type of the user', async() => {
const userId = 9; const userId = 9;
const image = await app.models.Image.download(ctx, collection, size, userId); const image = await models.Image.download(ctx, collection, size, userId);
const contentType = image[1]; const contentType = image[1];
expect(contentType).toEqual('image/png'); expect(contentType).toEqual('image/png');
@ -16,7 +16,7 @@ describe('image download()', () => {
it(`should return false if the user doesn't have image`, async() => { it(`should return false if the user doesn't have image`, async() => {
const userId = 1110; const userId = 1110;
const image = await app.models.Image.download(ctx, collection, size, userId); const image = await models.Image.download(ctx, collection, size, userId);
expect(image).toBeFalse(); expect(image).toBeFalse();
}); });

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('image upload()', () => { describe('image upload()', () => {
describe('as buyer', () => { describe('as buyer', () => {
@ -16,7 +16,7 @@ describe('image upload()', () => {
let error; let error;
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } catch (err) {
error = err; error = err;
} }
@ -25,7 +25,7 @@ describe('image upload()', () => {
}); });
it('should call to the TempContainer upload method for the collection "catalog"', async() => { it('should call to the TempContainer upload method for the collection "catalog"', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: buyerId}}, const ctx = {req: {accessToken: {userId: buyerId}},
@ -36,7 +36,7 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
@ -49,7 +49,7 @@ describe('image upload()', () => {
const itemId = 4; const itemId = 4;
it('should be able to call to the TempContainer upload method for the collection "user"', async() => { it('should be able to call to the TempContainer upload method for the collection "user"', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: marketingId}}, const ctx = {req: {accessToken: {userId: marketingId}},
@ -60,14 +60,14 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
}); });
it('should be able to call to the TempContainer upload method for the collection "catalog"', async() => { it('should be able to call to the TempContainer upload method for the collection "catalog"', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: marketingId}}, const ctx = {req: {accessToken: {userId: marketingId}},
@ -78,7 +78,7 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
@ -91,7 +91,7 @@ describe('image upload()', () => {
const itemId = 4; const itemId = 4;
it('should upload a file for the collection "user" and call to the TempContainer upload method', async() => { it('should upload a file for the collection "user" and call to the TempContainer upload method', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: hhrrId}}, const ctx = {req: {accessToken: {userId: hhrrId}},
@ -102,7 +102,7 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
@ -118,7 +118,7 @@ describe('image upload()', () => {
let error; let error;
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } catch (err) {
error = err; error = err;
} }

View File

@ -12,13 +12,13 @@ module.exports = Self => {
type: 'Number', type: 'Number',
description: 'The entity id', description: 'The entity id',
required: true required: true
}, }, {
{
arg: 'collection', arg: 'collection',
type: 'string', type: 'string',
description: 'The collection name', description: 'The collection name',
required: true required: true
}], }
],
returns: { returns: {
type: 'Object', type: 'Object',
root: true root: true

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('getStarredModules()', () => { describe('getStarredModules()', () => {
@ -19,13 +19,13 @@ describe('getStarredModules()', () => {
}); });
it(`should return the starred modules for a given user`, async() => { it(`should return the starred modules for a given user`, async() => {
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'customer', position: 1}); const newStarred = await models.StarredModule.create({workerFk: 9, moduleFk: 'customer', position: 1});
const starredModules = await app.models.StarredModule.getStarredModules(ctx); const starredModules = await models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(1); expect(starredModules.length).toEqual(1);
expect(starredModules[0].moduleFk).toEqual('customer'); expect(starredModules[0].moduleFk).toEqual('customer');
// restores // restores
await app.models.StarredModule.destroyById(newStarred.id); await models.StarredModule.destroyById(newStarred.id);
}); });
}); });

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('setPosition()', () => { describe('setPosition()', () => {
@ -21,7 +21,7 @@ describe('setPosition()', () => {
}); });
it('should increase the orders module position by replacing it with clients and vice versa', async() => { it('should increase the orders module position by replacing it with clients and vice versa', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -32,24 +32,24 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(1); expect(orders.position).toEqual(1);
expect(clients.position).toEqual(2); expect(clients.position).toEqual(2);
await app.models.StarredModule.setPosition(ctx, 'customer', 'left', options); await models.StarredModule.setPosition(ctx, 'customer', 'left', options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
expect(clients.position).toEqual(1); expect(clients.position).toEqual(1);
expect(orders.position).toEqual(2); expect(orders.position).toEqual(2);
@ -62,7 +62,7 @@ describe('setPosition()', () => {
}); });
it('should decrease the orders module position by replacing it with clients and vice versa', async() => { it('should decrease the orders module position by replacing it with clients and vice versa', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -73,24 +73,24 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(1); expect(orders.position).toEqual(1);
expect(clients.position).toEqual(2); expect(clients.position).toEqual(2);
await app.models.StarredModule.setPosition(ctx, 'order', 'right', options); await models.StarredModule.setPosition(ctx, 'order', 'right', options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(2); expect(orders.position).toEqual(2);
expect(clients.position).toEqual(1); expect(clients.position).toEqual(1);
@ -103,7 +103,7 @@ describe('setPosition()', () => {
}); });
it('should switch two modules after adding and deleting several modules', async() => { it('should switch two modules after adding and deleting several modules', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -115,29 +115,29 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'item', options); await models.StarredModule.toggleStarredModule(ctx, 'item', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options); await models.StarredModule.toggleStarredModule(ctx, 'claim', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'zone', options); await models.StarredModule.toggleStarredModule(ctx, 'zone', options);
const items = await app.models.StarredModule.findOne(filter, options); const items = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'claim'; filter.where.moduleFk = 'claim';
const claims = await app.models.StarredModule.findOne(filter, options); const claims = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'zone'; filter.where.moduleFk = 'zone';
const zones = await app.models.StarredModule.findOne(filter, options); const zones = await models.StarredModule.findOne(filter, options);
expect(items.position).toEqual(1); expect(items.position).toEqual(1);
expect(claims.position).toEqual(2); expect(claims.position).toEqual(2);
@ -145,13 +145,13 @@ describe('setPosition()', () => {
expect(orders.position).toEqual(4); expect(orders.position).toEqual(4);
expect(zones.position).toEqual(5); expect(zones.position).toEqual(5);
await app.models.StarredModule.setPosition(ctx, 'customer', 'right', options); await models.StarredModule.setPosition(ctx, 'customer', 'right', options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(3); expect(orders.position).toEqual(3);
expect(clients.position).toEqual(4); expect(clients.position).toEqual(4);
@ -164,7 +164,7 @@ describe('setPosition()', () => {
}); });
it('should switch two modules after adding and deleting a module between them', async() => { it('should switch two modules after adding and deleting a module between them', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -176,25 +176,25 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'item', options); await models.StarredModule.toggleStarredModule(ctx, 'item', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options); await models.StarredModule.toggleStarredModule(ctx, 'claim', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'zone', options); await models.StarredModule.toggleStarredModule(ctx, 'zone', options);
const items = await app.models.StarredModule.findOne(filter, options); const items = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'claim'; filter.where.moduleFk = 'claim';
const claims = await app.models.StarredModule.findOne(filter, options); const claims = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'zone'; filter.where.moduleFk = 'zone';
const zones = await app.models.StarredModule.findOne(filter, options); const zones = await models.StarredModule.findOne(filter, options);
expect(items.position).toEqual(1); expect(items.position).toEqual(1);
expect(clients.position).toEqual(2); expect(clients.position).toEqual(2);
@ -202,14 +202,14 @@ describe('setPosition()', () => {
expect(orders.position).toEqual(4); expect(orders.position).toEqual(4);
expect(zones.position).toEqual(5); expect(zones.position).toEqual(5);
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options); await models.StarredModule.toggleStarredModule(ctx, 'claim', options);
await app.models.StarredModule.setPosition(ctx, 'customer', 'right', options); await models.StarredModule.setPosition(ctx, 'customer', 'right', options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(2); expect(orders.position).toEqual(2);
expect(clients.position).toEqual(4); expect(clients.position).toEqual(4);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('toggleStarredModule()', () => { describe('toggleStarredModule()', () => {
@ -21,16 +21,16 @@ describe('toggleStarredModule()', () => {
}); });
it('should create a new starred module and then remove it by calling the method again with same args', async() => { it('should create a new starred module and then remove it by calling the method again with same args', async() => {
const starredModule = await app.models.StarredModule.toggleStarredModule(ctx, 'order'); const starredModule = await models.StarredModule.toggleStarredModule(ctx, 'order');
let starredModules = await app.models.StarredModule.getStarredModules(ctx); let starredModules = await models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(1); expect(starredModules.length).toEqual(1);
expect(starredModule.moduleFk).toEqual('order'); expect(starredModule.moduleFk).toEqual('order');
expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId); expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId);
expect(starredModule.position).toEqual(starredModules.length); expect(starredModule.position).toEqual(starredModules.length);
await app.models.StarredModule.toggleStarredModule(ctx, 'order'); await models.StarredModule.toggleStarredModule(ctx, 'order');
starredModules = await app.models.StarredModule.getStarredModules(ctx); starredModules = await models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(0); expect(starredModules.length).toEqual(0);
}); });

View File

@ -22,7 +22,7 @@ module.exports = Self => {
let userId = ctx.req.accessToken.userId; let userId = ctx.req.accessToken.userId;
let models = Self.app.models; let models = Self.app.models;
let user = await models.Account.findById(userId, { let user = await Self.findById(userId, {
fields: ['id', 'name', 'nickname', 'email', 'lang'], fields: ['id', 'name', 'nickname', 'email', 'lang'],
include: { include: {
relation: 'userConfig', relation: 'userConfig',

View File

@ -1,9 +1,14 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('privileges', { Self.remoteMethod('privileges', {
description: 'Change role and hasGrant if user has privileges', description: 'Change role and hasGrant if user has privileges',
accepts: [ accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
},
{ {
arg: 'id', arg: 'id',
type: 'number', type: 'number',
@ -39,9 +44,9 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const user = await models.Account.findById(userId, {fields: ['hasGrant']}, myOptions); const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
const userToUpdate = await models.Account.findById(id, { const userToUpdate = await Self.findById(id, {
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'], fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'],
include: { include: {
relation: 'role', relation: 'role',
@ -54,7 +59,7 @@ module.exports = Self => {
if (!user.hasGrant) if (!user.hasGrant)
throw new UserError(`You don't have grant privilege`); throw new UserError(`You don't have grant privilege`);
const hasRoleFromUser = await models.Account.hasRole(userId, userToUpdate.role().name, myOptions); const hasRoleFromUser = await Self.hasRole(userId, userToUpdate.role().name, myOptions);
if (!hasRoleFromUser) if (!hasRoleFromUser)
throw new UserError(`You don't own the role and you can't assign it to another user`); throw new UserError(`You don't own the role and you can't assign it to another user`);
@ -64,7 +69,7 @@ module.exports = Self => {
if (roleFk) { if (roleFk) {
const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions); const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions);
const hasRole = await models.Account.hasRole(userId, role.name, myOptions); const hasRole = await Self.hasRole(userId, role.name, myOptions);
if (!hasRole) if (!hasRole)
throw new UserError(`You don't own the role and you can't assign it to another user`); throw new UserError(`You don't own the role and you can't assign it to another user`);
@ -73,6 +78,6 @@ module.exports = Self => {
} }
await userToUpdate.save(userToUpdate); await userToUpdate.save(userToUpdate);
await models.UserAccount.sync(userToUpdate.name); await models.Account.sync(userToUpdate.name);
}; };
}; };

View File

@ -20,7 +20,7 @@ module.exports = Self => {
const usesEmail = user.indexOf('@') !== -1; const usesEmail = user.indexOf('@') !== -1;
if (!usesEmail) { if (!usesEmail) {
const account = await models.Account.findOne({ const account = await models.VnUser.findOne({
fields: ['email'], fields: ['email'],
where: {name: user} where: {name: user}
}); });
@ -28,7 +28,7 @@ module.exports = Self => {
} }
try { try {
await models.user.resetPassword({email: user, emailTemplate: 'recover-password'}); await Self.resetPassword({email: user, emailTemplate: 'recover-password'});
} catch (err) { } catch (err) {
if (err.code === 'EMAIL_NOT_FOUND') if (err.code === 'EMAIL_NOT_FOUND')
return; return;

View File

@ -1,14 +1,14 @@
const md5 = require('md5');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('login', { Self.remoteMethod('signIn', {
description: 'Login a user with username/email and password', description: 'Login a user with username/email and password',
accepts: [ accepts: [
{ {
arg: 'user', arg: 'user',
type: 'String', type: 'String',
description: 'The user name or email', description: 'The user name or email',
http: {source: 'form'},
required: true required: true
}, { }, {
arg: 'password', arg: 'password',
@ -21,20 +21,20 @@ module.exports = Self => {
root: true root: true
}, },
http: { http: {
path: `/login`, path: `/signIn`,
verb: 'POST' verb: 'POST'
} }
}); });
Self.login = async function(user, password) { Self.signIn = async function(user, password) {
let $ = Self.app.models; let models = Self.app.models;
let token; let token;
let usesEmail = user.indexOf('@') !== -1; let usesEmail = user.indexOf('@') !== -1;
let userInfo = usesEmail let userInfo = usesEmail
? {email: user} ? {email: user}
: {username: user}; : {username: user};
let instance = await $.User.findOne({ let instance = await Self.findOne({
fields: ['username', 'password'], fields: ['username', 'password'],
where: userInfo where: userInfo
}); });
@ -42,29 +42,27 @@ module.exports = Self => {
let where = usesEmail let where = usesEmail
? {email: user} ? {email: user}
: {name: user}; : {name: user};
let account = await Self.findOne({ let vnUser = await Self.findOne({
fields: ['active', 'password'], fields: ['active'],
where where
}); });
let validCredentials = instance && ( let validCredentials = instance
await instance.hasPassword(password) || && await instance.hasPassword(password);
account.password == md5(password || '')
);
if (validCredentials) { if (validCredentials) {
if (!account.active) if (!vnUser.active)
throw new UserError('User disabled'); throw new UserError('User disabled');
try { try {
await $.UserAccount.sync(instance.username, password); await models.Account.sync(instance.username, password);
} catch (err) { } catch (err) {
console.warn(err); console.warn(err);
} }
} }
let loginInfo = Object.assign({password}, userInfo); let loginInfo = Object.assign({password}, userInfo);
token = await $.User.login(loginInfo, 'user'); token = await Self.login(loginInfo, 'user');
return {token: token.id}; return {token: token.id};
}; };
}; };

View File

@ -1,6 +1,6 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('account privileges()', () => { describe('VnUser privileges()', () => {
const employeeId = 1; const employeeId = 1;
const developerId = 9; const developerId = 9;
const sysadminId = 66; const sysadminId = 66;
@ -10,13 +10,13 @@ describe('account privileges()', () => {
it('should throw an error when user not has privileges', async() => { it('should throw an error when user not has privileges', async() => {
const ctx = {req: {accessToken: {userId: developerId}}}; const ctx = {req: {accessToken: {userId: developerId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, employeeId, null, true, options); await models.VnUser.privileges(ctx, employeeId, null, true, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -29,13 +29,13 @@ describe('account privileges()', () => {
it('should throw an error when user has privileges but not has the role', async() => { it('should throw an error when user has privileges but not has the role', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, employeeId, rootId, null, options); await models.VnUser.privileges(ctx, employeeId, rootId, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -48,13 +48,13 @@ describe('account privileges()', () => {
it('should throw an error when user has privileges but not has the role from user', async() => { it('should throw an error when user has privileges but not has the role from user', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, itBossId, developerId, null, options); await models.VnUser.privileges(ctx, itBossId, developerId, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -67,7 +67,7 @@ describe('account privileges()', () => {
it('should change role', async() => { it('should change role', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};
const agency = await models.Role.findOne({ const agency = await models.Role.findOne({
@ -79,8 +79,8 @@ describe('account privileges()', () => {
let error; let error;
let result; let result;
try { try {
await models.Account.privileges(ctx, clarkKent, agency.id, null, options); await models.VnUser.privileges(ctx, clarkKent, agency.id, null, options);
result = await models.Account.findById(clarkKent, null, options); result = await models.VnUser.findById(clarkKent, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -94,14 +94,14 @@ describe('account privileges()', () => {
it('should change hasGrant', async() => { it('should change hasGrant', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
let result; let result;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, clarkKent, null, true, options); await models.VnUser.privileges(ctx, clarkKent, null, true, options);
result = await models.Account.findById(clarkKent, null, options); result = await models.VnUser.findById(clarkKent, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,25 +1,25 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('account login()', () => { describe('VnUser signIn()', () => {
describe('when credentials are correct', () => { describe('when credentials are correct', () => {
it('should return the token', async() => { it('should return the token', async() => {
let login = await app.models.Account.login('salesAssistant', 'nightmare'); let login = await models.VnUser.signIn('salesAssistant', 'nightmare');
let accessToken = await app.models.AccessToken.findById(login.token); let accessToken = await models.AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await app.models.Account.logout(ctx); await models.VnUser.signOut(ctx);
}); });
it('should return the token if the user doesnt exist but the client does', async() => { it('should return the token if the user doesnt exist but the client does', async() => {
let login = await app.models.Account.login('PetterParker', 'nightmare'); let login = await models.VnUser.signIn('PetterParker', 'nightmare');
let accessToken = await app.models.AccessToken.findById(login.token); let accessToken = await models.AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await app.models.Account.logout(ctx); await models.VnUser.signOut(ctx);
}); });
}); });
@ -28,7 +28,7 @@ describe('account login()', () => {
let error; let error;
try { try {
await app.models.Account.login('IDontExist', 'TotallyWrongPassword'); await models.VnUser.signIn('IDontExist', 'TotallyWrongPassword');
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -1,13 +1,13 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('account logout()', () => { describe('VnUser signOut()', () => {
it('should logout and remove token after valid login', async() => { it('should logout and remove token after valid login', async() => {
let loginResponse = await app.models.Account.login('buyer', 'nightmare'); let loginResponse = await models.VnUser.signOut('buyer', 'nightmare');
let accessToken = await app.models.AccessToken.findById(loginResponse.token); let accessToken = await models.AccessToken.findById(loginResponse.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
let logoutResponse = await app.models.Account.logout(ctx); let logoutResponse = await models.VnUser.signOut(ctx);
let tokenAfterLogout = await app.models.AccessToken.findById(loginResponse.token); let tokenAfterLogout = await models.AccessToken.findById(loginResponse.token);
expect(logoutResponse).toBeTrue(); expect(logoutResponse).toBeTrue();
expect(tokenAfterLogout).toBeNull(); expect(tokenAfterLogout).toBeNull();
@ -18,7 +18,7 @@ describe('account logout()', () => {
let ctx = {req: {accessToken: {id: 'invalidToken'}}}; let ctx = {req: {accessToken: {id: 'invalidToken'}}};
try { try {
response = await app.models.Account.logout(ctx); response = await models.VnUser.signOut(ctx);
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -32,7 +32,7 @@ describe('account logout()', () => {
let ctx = {req: {accessToken: null}}; let ctx = {req: {accessToken: null}};
try { try {
response = await app.models.Account.logout(ctx); response = await models.VnUser.signOut(ctx);
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -1,7 +1,4 @@
{ {
"Account": {
"dataSource": "vn"
},
"AccountingType": { "AccountingType": {
"dataSource": "vn" "dataSource": "vn"
}, },
@ -125,10 +122,10 @@
"UserConfigView": { "UserConfigView": {
"dataSource": "vn" "dataSource": "vn"
}, },
"UserLog": { "Warehouse": {
"dataSource": "vn" "dataSource": "vn"
}, },
"Warehouse": { "VnUser": {
"dataSource": "vn" "dataSource": "vn"
}, },
"OsTicket": { "OsTicket": {

View File

@ -1,139 +0,0 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const md5 = require('md5');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
module.exports = Self => {
require('../methods/account/login')(Self);
require('../methods/account/logout')(Self);
require('../methods/account/acl')(Self);
require('../methods/account/change-password')(Self);
require('../methods/account/set-password')(Self);
require('../methods/account/recover-password')(Self);
require('../methods/account/validate-token')(Self);
require('../methods/account/privileges')(Self);
// Validations
Self.validatesFormatOf('email', {
message: 'Invalid email',
allowNull: true,
allowBlank: true,
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
});
Self.validatesUniquenessOf('name', {
message: `A client with that Web User name already exists`
});
Self.observe('before save', async function(ctx) {
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && 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', {
description: 'Gets the current user data',
accepts: [
{
arg: 'ctx',
type: 'object',
http: {source: 'context'}
}
],
returns: {
type: 'object',
root: true
},
http: {
verb: 'GET',
path: '/getCurrentUserData'
}
});
Self.getCurrentUserData = async function(ctx) {
let userId = ctx.req.accessToken.userId;
return await Self.findById(userId, {
fields: ['id', 'name', 'nickname']
});
};
/**
* Checks if user has a role.
*
* @param {Integer} userId The user id
* @param {String} name The role name
* @param {object} options Options
* @return {Boolean} %true if user has the role, %false otherwise
*/
Self.hasRole = async function(userId, name, options) {
let roles = await Self.getRoles(userId, options);
return roles.some(role => role == name);
};
/**
* Get all user roles.
*
* @param {Integer} userId The user id
* @param {object} options Options
* @return {object} User role list
*/
Self.getRoles = async(userId, options) => {
let result = await Self.rawSql(
`SELECT r.name
FROM account.user u
JOIN account.roleRole rr ON rr.role = u.role
JOIN account.role r ON r.id = rr.inheritsFrom
WHERE u.id = ?`, [userId], options);
let roles = [];
for (const role of result)
roles.push(role.name);
return roles;
};
};

View File

@ -54,8 +54,8 @@ module.exports = Self => {
const writeRole = dmsType.writeRole() && dmsType.writeRole().name; const writeRole = dmsType.writeRole() && dmsType.writeRole().name;
const requiredRole = readRole || writeRole; const requiredRole = readRole || writeRole;
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options); const hasRequiredRole = await models.VnUser.hasRole(myUserId, requiredRole, options);
const isRoot = await models.Account.hasRole(myUserId, 'root', options); const isRoot = await models.VnUser.hasRole(myUserId, 'root', options);
if (isRoot || hasRequiredRole) if (isRoot || hasRequiredRole)
return true; return true;

View File

@ -20,7 +20,7 @@
"relations": { "relations": {
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
}, },

View File

@ -53,8 +53,8 @@ module.exports = Self => {
const writeRole = collection.writeRole() && collection.writeRole().name; const writeRole = collection.writeRole() && collection.writeRole().name;
const requiredRole = readRole || writeRole; const requiredRole = readRole || writeRole;
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options); const hasRequiredRole = await models.VnUser.hasRole(myUserId, requiredRole, options);
const isRoot = await models.Account.hasRole(myUserId, 'root', options); const isRoot = await models.VnUser.hasRole(myUserId, 'root', options);
if (isRoot || hasRequiredRole) if (isRoot || hasRequiredRole)
return true; return true;

View File

@ -5,6 +5,7 @@ const gm = require('gm');
module.exports = Self => { module.exports = Self => {
require('../methods/image/download')(Self); require('../methods/image/download')(Self);
require('../methods/image/upload')(Self); require('../methods/image/upload')(Self);
require('../methods/image/scrub')(Self);
Self.resize = async function({collectionName, srcFile, fileName, entityId}) { Self.resize = async function({collectionName, srcFile, fileName, entityId}) {
const models = Self.app.models; const models = Self.app.models;
@ -29,13 +30,14 @@ module.exports = Self => {
); );
// Insert image row // Insert image row
const imageName = path.parse(fileName).name;
await models.Image.upsertWithWhere( await models.Image.upsertWithWhere(
{ {
name: fileName, name: imageName,
collectionFk: collectionName collectionFk: collectionName
}, },
{ {
name: fileName, name: imageName,
collectionFk: collectionName, collectionFk: collectionName,
updated: Date.vnNow() / 1000, updated: Date.vnNow() / 1000,
} }
@ -49,7 +51,7 @@ module.exports = Self => {
if (entity) { if (entity) {
await entity.updateAttribute( await entity.updateAttribute(
collection.property, collection.property,
fileName imageName
); );
} }

View File

@ -31,7 +31,7 @@
}, },
"author": { "author": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "authorFk" "foreignKey": "authorFk"
} }
} }

View File

@ -29,7 +29,7 @@
}, },
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('loopback model Company', () => { describe('loopback model Company', () => {
it('should check that the company FTH doesnt exists', async() => { it('should check that the company FTH doesnt exists', async() => {
let result = await app.models.Company.findOne({where: {code: 'FTH'}}); let result = await models.Company.findOne({where: {code: 'FTH'}});
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });

View File

@ -1,6 +1,6 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('Dms', () => { describe('Dms', () => {
const Dms = app.models.Dms; const Dms = models.Dms;
describe('getFile()', () => { describe('getFile()', () => {
it('should return a response with text content-type', async() => { it('should return a response with text content-type', async() => {
@ -23,7 +23,7 @@ describe('Dms', () => {
it('should return an error for a record does not exists', async() => { it('should return an error for a record does not exists', async() => {
let error = {}; let error = {};
try { try {
await app.models.Dms.getFile('NotExistentId'); await models.Dms.getFile('NotExistentId');
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -1,7 +1,7 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('account recoverPassword()', () => { describe('VnUser recoverPassword()', () => {
const userId = 1107; const userId = 1107;
const activeCtx = { const activeCtx = {
@ -21,9 +21,9 @@ describe('account recoverPassword()', () => {
it('should send email with token', async() => { it('should send email with token', async() => {
const userId = 1107; const userId = 1107;
const user = await models.Account.findById(userId); const user = await models.VnUser.findById(userId);
await models.Account.recoverPassword(user.email); await models.VnUser.recoverPassword(user.email);
const result = await models.AccessToken.findOne({where: {userId: userId}}); const result = await models.AccessToken.findOne({where: {userId: userId}});

View File

@ -1,14 +1,14 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('loopback model Account', () => { describe('loopback model VnUser', () => {
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 models.Account.hasRole(1, 'employee'); let result = await models.VnUser.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 models.Account.hasRole(1, 'administrator'); let result = await models.VnUser.hasRole(1, 'administrator');
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });

View File

@ -26,7 +26,7 @@
"relations": { "relations": {
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }

View File

@ -39,9 +39,9 @@
"model": "Company", "model": "Company",
"foreignKey": "companyFk" "foreignKey": "companyFk"
}, },
"account": { "VnUser": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }

View File

@ -1,27 +0,0 @@
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();
});
};

View File

@ -1,20 +0,0 @@
{
"name": "user",
"base": "User",
"options": {
"mysql": {
"table": "salix.User"
},
"resetPasswordTokenTTL": "604800"
},
"properties": {
"id": {
"id": true,
"type": "number",
"forceId": false
},
"username":{
"type": "string"
}
}
}

110
back/models/vn-user.js Normal file
View File

@ -0,0 +1,110 @@
const vnModel = require('vn-loopback/common/models/vn-model');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
module.exports = function(Self) {
vnModel(Self);
require('../methods/vn-user/signIn')(Self);
require('../methods/vn-user/acl')(Self);
require('../methods/vn-user/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self);
// Validations
Self.validatesFormatOf('email', {
message: 'Invalid email',
allowNull: true,
allowBlank: true,
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
});
Self.validatesUniquenessOf('name', {
message: `A client with that Web User name already exists`
});
Self.remoteMethod('getCurrentUserData', {
description: 'Gets the current user data',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}
],
returns: {
type: 'Object',
root: true
},
http: {
verb: 'GET',
path: '/getCurrentUserData'
}
});
Self.getCurrentUserData = async function(ctx) {
let userId = ctx.req.accessToken.userId;
return await Self.findById(userId, {
fields: ['id', 'name', 'nickname']
});
};
/**
* Checks if user has a role.
*
* @param {Integer} userId The user id
* @param {String} name The role name
* @param {Object} options Options
* @return {Boolean} %true if user has the role, %false otherwise
*/
Self.hasRole = async function(userId, name, options) {
const roles = await Self.getRoles(userId, options);
return roles.some(role => role == name);
};
/**
* Get all user roles.
*
* @param {Integer} userId The user id
* @param {Object} options Options
* @return {Object} User role list
*/
Self.getRoles = async(userId, options) => {
const result = await Self.rawSql(
`SELECT r.name
FROM account.user u
JOIN account.roleRole rr ON rr.role = u.role
JOIN account.role r ON r.id = rr.inheritsFrom
WHERE u.id = ?`, [userId], options);
const roles = [];
for (const role of result)
roles.push(role.name);
return roles;
};
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.VnUser.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();
});
};

View File

@ -1,11 +1,13 @@
{ {
"name": "Account", "name": "VnUser",
"base": "VnModel", "base": "User",
"validateUpsert": true,
"options": { "options": {
"mysql": { "mysql": {
"table": "account.user" "table": "account.user"
} }
}, },
"resetPasswordTokenTTL": "604800",
"properties": { "properties": {
"id": { "id": {
"type": "number", "type": "number",
@ -15,6 +17,19 @@
"type": "string", "type": "string",
"required": true "required": true
}, },
"username": {
"type": "string",
"mysql": {
"columnName": "name"
}
},
"password": {
"type": "string",
"required": true,
"mysql": {
"columnName": "bcryptPassword"
}
},
"roleFk": { "roleFk": {
"type": "number", "type": "number",
"mysql": { "mysql": {
@ -27,10 +42,6 @@
"lang": { "lang": {
"type": "string" "type": "string"
}, },
"password": {
"type": "string",
"required": true
},
"bcryptPassword": { "bcryptPassword": {
"type": "string" "type": "string"
}, },
@ -40,9 +51,6 @@
"email": { "email": {
"type": "string" "type": "string"
}, },
"emailVerified": {
"type": "boolean"
},
"created": { "created": {
"type": "date" "type": "date"
}, },
@ -86,7 +94,7 @@
}, },
"acls": [ "acls": [
{ {
"property": "login", "property": "signIn",
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$everyone", "principalId": "$everyone",
@ -100,13 +108,6 @@
"permission": "ALLOW" "permission": "ALLOW"
}, },
{ {
"property": "logout",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"property": "validateToken", "property": "validateToken",
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",

View File

@ -53,7 +53,7 @@ async function test() {
const JunitReporter = require('jasmine-reporters'); const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter()); jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
jasmine.exitOnCompletion = true; jasmine.exitOnCompletion = true;
} }

View File

@ -0,0 +1,18 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES ('VnUser', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (id, model, property, accessType, permission, principalType, principalId)
VALUES ('VnUser', 'acl', 'READ', 'ALLOW', 'ROLE', 'account');
INSERT INTO `salix`.`ACL` (id, model, property, accessType, permission, principalType, principalId)
VALUES ('VnUser', 'getCurrentUserData', 'READ', 'ALLOW', 'ROLE', 'account');
INSERT INTO `salix`.`ACL` (id, model, property, accessType, permission, principalType, principalId)
VALUES ('VnUser', 'changePassword', '*', 'ALLOW', 'ROLE', 'account');
UPDATE `hedera`.`imageCollection` t
SET t.model = 'VnUser'
WHERE t.id = 6;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Receipt', 'receiptEmail', '*', 'ALLOW', 'ROLE', 'salesAssistant');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`printQueueArgs` MODIFY COLUMN value varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL;

View File

@ -0,0 +1,5 @@
DROP TRIGGER `vn`.`deviceProduction_afterInsert`;
DROP TRIGGER `vn`.`deviceProduction_afterUpdate`;
DROP TRIGGER `vn`.`deviceProductionUser_afterDelete`;

View File

@ -1,12 +1,5 @@
DROP TABLE IF EXISTS `vn`.`dmsRecover`; DROP TABLE IF EXISTS `vn`.`dmsRecover`;
DROP PROCEDURE IF EXISTS `vn`.`route_getTickets`;
ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk;
ALTER TABLE `vn`.`delivery` DROP CONSTRAINT delivery_ticketFk_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN ticketFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT DEFAULT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DELIMITER $$ DELIMITER $$
$$ $$
@ -17,54 +10,68 @@ BEGIN
* de sus tickets. * de sus tickets.
* *
* @param vRouteFk * @param vRouteFk
*
* @select Información de los tickets * @select Información de los tickets
*/ */
SELECT *
SELECT FROM (
t.id Id, SELECT t.id Id,
t.clientFk Client, t.clientFk Client,
a.id Address, a.id Address,
t.packages Packages, a.nickname ClientName,
a.street AddressName, t.packages Packages,
a.postalCode PostalCode, a.street AddressName,
a.city City, a.postalCode PostalCode,
sub2.itemPackingTypeFk PackingType, a.city City,
c.phone ClientPhone, sub2.itemPackingTypeFk PackingType,
c.mobile ClientMobile, c.phone ClientPhone,
a.phone AddressPhone, c.mobile ClientMobile,
a.mobile AddressMobile, a.phone AddressPhone,
d.longitude Longitude, a.mobile AddressMobile,
d.latitude Latitude, d.longitude Longitude,
wm.mediaValue SalePersonPhone, d.latitude Latitude,
tob.Note Note, wm.mediaValue SalePersonPhone,
t.isSigned Signed tob.description Note,
FROM ticket t t.isSigned Signed,
JOIN client c ON t.clientFk = c.id t.priority
JOIN address a ON t.addressFk = a.id FROM ticket t
LEFT JOIN delivery d ON t.id = d.ticketFk JOIN client c ON t.clientFk = c.id
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk JOIN address a ON t.addressFk = a.id
LEFT JOIN LEFT JOIN delivery d ON d.ticketFk = t.id
(SELECT tob.description Note, t.id LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
FROM ticketObservation tob LEFT JOIN(
JOIN ticket t ON tob.ticketFk = t.id SELECT tob.description, t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk FROM ticketObservation tob
WHERE t.routeFk = vRouteFk JOIN ticket t ON tob.ticketFk = t.id
AND ot.code = 'delivery' JOIN observationType ot ON ot.id = tob.observationTypeFk
)tob ON tob.id = t.id WHERE t.routeFk = vRouteFk
LEFT JOIN AND ot.code = 'delivery'
(SELECT sub.ticketFk, )tob ON tob.id = t.id
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk LEFT JOIN(
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items SELECT sub.ticketFk,
FROM ticket t CONCAT('(',
JOIN sale s ON s.ticketFk = t.id GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk
JOIN item i ON i.id = s.itemFk ORDER BY sub.items DESC SEPARATOR ','),
WHERE t.routeFk = vRouteFk ') ') itemPackingTypeFk
GROUP BY t.id,i.itemPackingTypeFk)sub FROM (
GROUP BY sub.ticketFk SELECT s.ticketFk, i.itemPackingTypeFk, COUNT(*) items
) sub2 ON sub2.ticketFk = t.id FROM ticket t
WHERE t.routeFk = vRouteFk JOIN sale s ON s.ticketFk = t.id
GROUP BY t.id JOIN item i ON i.id = s.itemFk
ORDER BY t.priority; WHERE t.routeFk = vRouteFk
GROUP BY t.id, i.itemPackingTypeFk
)sub
GROUP BY sub.ticketFk
)sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
ORDER BY d.id DESC
LIMIT 10000000000000000000
)sub3
GROUP BY sub3.id
ORDER BY sub3.priority;
END$$ END$$
DELIMITER ; DELIMITER ;
ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_ticketFk_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN ticketFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT DEFAULT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);

View File

@ -0,0 +1,3 @@
UPDATE `salix`.`ACL`
SET model = 'InvoiceOut'
WHERE property IN ('negativeBases', 'negativeBasesCsv');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES ('ClientInforma', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('ClientInforma', '*', 'WRITE', 'ALLOW', 'ROLE', 'financial');

View File

@ -0,0 +1,16 @@
ALTER TABLE `vn`.`client` ADD rating INT UNSIGNED DEFAULT NULL NULL COMMENT 'información proporcionada por Informa';
ALTER TABLE `vn`.`client` ADD recommendedCredit INT UNSIGNED DEFAULT NULL NULL COMMENT 'información proporcionada por Informa';
CREATE TABLE `vn`.`clientInforma` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`clientFk` int(11) NOT NULL,
`rating` int(10) unsigned DEFAULT NULL,
`recommendedCredit` int(10) unsigned DEFAULT NULL,
`workerFk` int(10) unsigned NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `informaWorkers_fk_idx` (`workerFk`),
KEY `informaClientFk` (`clientFk`),
CONSTRAINT `informa_ClienteFk` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `informa_workers_fk` FOREIGN KEY (`workerFk`) REFERENCES `worker` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='información proporcionada por Informa, se actualiza desde el hook de client (salix)';

View File

@ -0,0 +1,64 @@
DELETE FROM `salix`.`ACL` WHERE id=7;
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Client', 'setRating', 'WRITE', 'ALLOW', 'ROLE', 'financial');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Client', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'addressesPropagateRe', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'canBeInvoiced', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'canCreateTicket', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'consumption', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'createAddress', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'createWithUser', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'extendedListFilter', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getAverageInvoiced', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getCard', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getDebt', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getMana', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'transactions', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'hasCustomerRole', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'isValidClient', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'lastActiveTickets', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'sendSms', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'setPassword', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientWelcomeHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientWelcomeEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'printerSetupHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'printerSetupEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'sepaCoreEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorStHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorStEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorNdHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorNdEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationHtml', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationEmail', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'consumptionSendQueued', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'filter', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'getClientOrSupplierReference', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'upsert', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'create', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'replaceById', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAttributes', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAttributes', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'deleteById', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'replaceOrCreate', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAll', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'upsertWithWhere', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,2 @@
DROP PROCEDURE `vn`.`refund`;
DROP PROCEDURE `vn`.`ticket_doRefund`;

View File

@ -0,0 +1,9 @@
-- vn.companyI18n definition
CREATE TABLE `vn`.`companyI18n` (
`companyFk` smallint(5) unsigned NOT NULL,
`lang` char(2) CHARACTER SET utf8mb3 NOT NULL,
`footnotes` longtext COLLATE utf8mb3_unicode_ci DEFAULT NULL,
PRIMARY KEY (`companyFk`,`lang`),
CONSTRAINT `companyI18n_FK` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`company` ADD `web` varchar(100) NULL;

View File

@ -0,0 +1,21 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('VnUser', '*', '*', 'ALLOW', 'ROLE', 'employee'),
('VnUser','acl','READ','ALLOW','ROLE','account'),
('VnUser','getCurrentUserData','READ','ALLOW','ROLE','account'),
('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account'),
('Account','exists','READ','ALLOW','ROLE','account');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Account','exists','READ','ALLOW','ROLE','account');
DELETE FROM `salix`.`ACL` WHERE (model, property) = ('Account', 'acl');
DELETE FROM `salix`.`ACL` WHERE (model, property) = ('Account', 'getCurrentUserData');
DELETE FROM `salix`.`ACL` WHERE (model, property) = ('Account', 'changePassword');
DELETE FROM `salix`.`ACL` WHERE model = 'UserAccount';
UPDATE `hedera`.`imageCollection` t
SET t.model = 'VnUser'
WHERE t.id = 6;

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('UserLog', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('RoleLog', '*', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -98,20 +98,20 @@ INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType`
VALUES VALUES
(1, 978, 1, 0, 2000, 9, 0); (1, 978, 1, 0, 2000, 9, 0);
INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`, `image`) INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `bcryptPassword`, `password`,`role`,`active`,`email`,`lang`, `image`)
VALUES VALUES
(1101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), (1101, 'BruceWayne', 'Bruce Wayne', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), (1102, 'PetterParker', 'Petter Parker', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr', 'e7723f0b24ff05b32ed09d95196f2f29'), (1103, 'ClarkKent', 'Clark Kent', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), (1104, 'TonyStark', 'Tony Stark', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt', 'e7723f0b24ff05b32ed09d95196f2f29'), (1105, 'MaxEisenhardt', 'Max Eisenhardt', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), (1106, 'DavidCharlesHaller', 'David Charles Haller', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), (1107, 'HankPym', 'Hank Pym', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), (1108, 'CharlesXavier', 'Charles Xavier', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), (1109, 'BruceBanner', 'Bruce Banner', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'),
(1110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'en', NULL), (1110, 'JessicaJones', 'Jessica Jones', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'en', NULL),
(1111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL), (1111, 'Missing', 'Missing', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL),
(1112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL); (1112, 'Trash', 'Trash', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL);
INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`)
VALUES VALUES
@ -173,10 +173,6 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
(1, 'First sector', 1, 1, 'FIRST'), (1, 'First sector', 1, 1, 'FIRST'),
(2, 'Second sector', 2, 0, 'SECOND'); (2, 'Second sector', 2, 0, 'SECOND');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES ('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '2', '1');
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`) INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`)
VALUES VALUES
(1, 'printer1', 'path1', 0, 1 , NULL), (1, 'printer1', 'path1', 0, 1 , NULL),
@ -550,7 +546,8 @@ INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`
VALUES VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'), (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'), (2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'); (442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
(1381, 'Ornamentales', 'Ornamentales', 7185000440, 1, '03815934E', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`) INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES VALUES
@ -1200,6 +1197,11 @@ INSERT INTO `vn`.`train`(`id`, `name`)
(1, 'Train1'), (1, 'Train1'),
(2, 'Train2'); (2, 'Train2');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES
('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '1', '1');
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`) INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES VALUES
(1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1), (1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1),
@ -2788,7 +2790,7 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES VALUES
('lilium', 'dev', 'http://localhost:8080/#/'), ('lilium', 'dev', 'http://localhost:9000/#/'),
('salix', 'dev', 'http://localhost:5000/#!/'); ('salix', 'dev', 'http://localhost:5000/#!/');
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`) INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)

View File

@ -68,6 +68,7 @@ TABLES=(
time time
volumeConfig volumeConfig
workCenter workCenter
companyI18n
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}

View File

@ -156,14 +156,14 @@ let actions = {
await this.waitForSpinnerLoad(); await this.waitForSpinnerLoad();
}, },
accessToSection: async function(state) { accessToSection: async function(state, name = 'Others') {
await this.waitForSelector('vn-left-menu'); await this.waitForSelector('vn-left-menu');
let nested = await this.evaluate(state => { let nested = await this.evaluate(state => {
return document.querySelector(`vn-left-menu li li > a[ui-sref="${state}"]`) != null; return document.querySelector(`vn-left-menu li li > a[ui-sref="${state}"]`) != null;
}, state); }, state);
if (nested) { if (nested) {
let selector = 'vn-left-menu vn-item-section > vn-icon[icon=keyboard_arrow_down]'; let selector = `vn-left-menu li[name="${name}"]`;
await this.evaluate(selector => { await this.evaluate(selector => {
document.querySelector(selector).scrollIntoViewIfNeeded(); document.querySelector(selector).scrollIntoViewIfNeeded();
}, selector); }, selector);
@ -218,6 +218,23 @@ let actions = {
return handle.jsonValue(); return handle.jsonValue();
}, },
getValue: async function(selector) {
return await this.waitToGetProperty(selector, 'value');
},
getValues: async function(selectorMap) {
const values = {};
for (const key in selectorMap)
values[key] = await this.waitToGetProperty(selectorMap[key], 'value');
return values;
},
innerText: async function(selector) {
const element = await this.$(selector);
const handle = await element.getProperty('innerText');
return handle.jsonValue();
},
waitPropertyLength: async function(selector, property, minLength) { waitPropertyLength: async function(selector, property, minLength) {
await this.waitForFunction((selector, property, minLength) => { await this.waitForFunction((selector, property, minLength) => {
const element = document.querySelector(selector); const element = document.querySelector(selector);

View File

@ -283,12 +283,6 @@ export default {
cancelEditAddressButton: 'vn-client-address-edit > form > vn-button-bar > vn-button > button', cancelEditAddressButton: 'vn-client-address-edit > form > vn-button-bar > vn-button > button',
watcher: 'vn-client-address-edit vn-watcher' watcher: 'vn-client-address-edit vn-watcher'
}, },
clientWebAccess: {
enableWebAccessCheckbox: 'vn-check[label="Enable web access"]',
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'button[type=submit]'
},
clientNotes: { clientNotes: {
addNoteFloatButton: 'vn-float-button', addNoteFloatButton: 'vn-float-button',
note: 'vn-textarea[ng-model="$ctrl.note.text"]', note: 'vn-textarea[ng-model="$ctrl.note.text"]',
@ -312,15 +306,6 @@ export default {
clientMandate: { clientMandate: {
firstMandateText: 'vn-client-mandate vn-card vn-table vn-tbody > vn-tr' firstMandateText: 'vn-client-mandate vn-card vn-table vn-tbody > vn-tr'
}, },
clientLog: {
lastModificationPreviousValue: 'vn-client-log vn-tr table tr td.before',
lastModificationCurrentValue: 'vn-client-log vn-tr table tr td.after',
namePreviousValue: 'vn-client-log vn-tr table tr:nth-child(1) td.before',
nameCurrentValue: 'vn-client-log vn-tr table tr:nth-child(1) td.after',
activePreviousValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.before',
activeCurrentValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.after'
},
clientBalance: { clientBalance: {
company: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyId"]', company: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyId"]',
newPaymentButton: `vn-float-button`, newPaymentButton: `vn-float-button`,
@ -414,7 +399,7 @@ export default {
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button' saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
}, },
itemFixedPrice: { itemFixedPrice: {
add: 'vn-fixed-price vn-icon-button[icon="add_circle"]', add: 'vn-fixed-price vn-icon-button[vn-tooltip="Add fixed price"]',
firstItemID: 'vn-fixed-price tr:nth-child(2) vn-autocomplete[ng-model="price.itemFk"]', firstItemID: 'vn-fixed-price tr:nth-child(2) vn-autocomplete[ng-model="price.itemFk"]',
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)', fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]', fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
@ -427,7 +412,18 @@ export default {
fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]', fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]',
fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]', fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]',
orderColumnId: 'vn-fixed-price th[field="itemFk"]', orderColumnId: 'vn-fixed-price th[field="itemFk"]',
removeWarehouseFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(1) > vn-icon > i' removeWarehouseFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(1) > vn-icon > i',
generalSearchFilter: 'vn-fixed-price-search-panel vn-textfield[ng-model="$ctrl.filter.search"]',
reignFilter: 'vn-fixed-price-search-panel vn-horizontal.item-category vn-one',
typeFilter: 'vn-fixed-price-search-panel vn-autocomplete[ng-model="$ctrl.filter.typeFk"]',
buyerFilter: 'vn-fixed-price-search-panel vn-autocomplete[ng-model="$ctrl.filter.buyerFk"]',
warehouseFilter: 'vn-fixed-price-search-panel vn-autocomplete[ng-model="$ctrl.filter.warehouseFk"]',
mineFilter: 'vn-fixed-price-search-panel vn-check[ng-model="$ctrl.filter.mine"]',
hasMinPriceFilter: 'vn-fixed-price-search-panel vn-check[ng-model="$ctrl.filter.hasMinPrice"]',
addTag: 'vn-fixed-price-search-panel vn-icon-button[icon="add_circle"]',
tagFilter: 'vn-fixed-price-search-panel vn-autocomplete[ng-model="itemTag.tagFk"]',
tagValueFilter: 'vn-fixed-price-search-panel vn-autocomplete[ng-model="itemTag.value"]',
chip: 'vn-fixed-price-search-panel vn-chip > vn-icon'
}, },
itemCreateView: { itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
@ -744,6 +740,7 @@ export default {
anyDocument: 'vn-ticket-dms-index > vn-data-viewer vn-tbody vn-tr' anyDocument: 'vn-ticket-dms-index > vn-data-viewer vn-tbody vn-tr'
}, },
ticketFuture: { ticketFuture: {
searchResult: 'vn-ticket-future tbody tr',
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]', openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
originDated: 'vn-date-picker[label="Origin date"]', originDated: 'vn-date-picker[label="Origin date"]',
futureDated: 'vn-date-picker[label="Destination date"]', futureDated: 'vn-date-picker[label="Destination date"]',
@ -759,7 +756,6 @@ export default {
problems: 'vn-check[label="With problems"]', problems: 'vn-check[label="With problems"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Future tickets"]', moveButton: 'vn-button[vn-tooltip="Future tickets"]',
acceptButton: '.vn-confirm.shown button[response="accept"]',
firstCheck: 'tbody > tr:nth-child(1) > td > vn-check', firstCheck: 'tbody > tr:nth-child(1) > td > vn-check',
multiCheck: 'vn-multi-check', multiCheck: 'vn-multi-check',
tableId: 'vn-textfield[name="id"]', tableId: 'vn-textfield[name="id"]',
@ -989,9 +985,9 @@ export default {
saveButton: 'vn-worker-basic-data button[type=submit]' saveButton: 'vn-worker-basic-data button[type=submit]'
}, },
workerNotes: { workerNotes: {
addNoteFloatButton: 'vn-worker-note vn-float-button', addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]',
note: 'vn-textarea[ng-model="$ctrl.note.text"]', note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]',
saveButton: 'button[type=submit]', saveButton: 'vn-note-worker-create button[type=submit]',
firstNoteText: 'vn-worker-note .text' firstNoteText: 'vn-worker-note .text'
}, },
workerPbx: { workerPbx: {
@ -1350,18 +1346,6 @@ export default {
notes: 'vn-supplier-basic-data vn-textarea[ng-model="$ctrl.supplier.note"]', notes: 'vn-supplier-basic-data vn-textarea[ng-model="$ctrl.supplier.note"]',
saveButton: 'vn-supplier-basic-data button[type="submit"]', saveButton: 'vn-supplier-basic-data button[type="submit"]',
}, },
supplierFiscalData: {
socialName: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.name"]',
taxNumber: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.nif"]',
account: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.account"]',
sageTaxType: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.sageTaxTypeFk"]',
sageWihholding: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.sageWithholdingFk"]',
postCode: 'vn-supplier-fiscal-data vn-datalist[ng-model="$ctrl.supplier.postCode"]',
city: 'vn-supplier-fiscal-data vn-datalist[ng-model="$ctrl.supplier.city"]',
province: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.provinceFk"]',
country: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.countryFk"]',
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
},
supplierBillingData: { supplierBillingData: {
payMethod: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payMethodFk"]', payMethod: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payMethodFk"]',
payDem: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payDemFk"]', payDem: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payDemFk"]',

View File

@ -79,27 +79,17 @@ describe('SmartTable SearchBar integration', () => {
it('should order by first id', async() => { it('should order by first id', async() => {
await page.loginAndModule('developer', 'item'); await page.loginAndModule('developer', 'item');
await page.accessToSection('item.fixedPrice'); await page.accessToSection('item.fixedPrice');
await page.doSearch(); await page.keyboard.press('Enter');
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '1');
expect(result).toEqual('1');
}); });
it('should order by last id', async() => { it('should order by last id, reload page and have same order', async() => {
await page.waitToClick(selectors.itemFixedPrice.orderColumnId); await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('3');
});
it('should reload page and have same order', async() => {
await page.reload({ await page.reload({
waitUntil: 'networkidle2' waitUntil: 'networkidle2'
}); });
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '13');
expect(result).toEqual('3');
}); });
}); });
}); });

View File

@ -1,88 +1,56 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Client Edit web access path', () => { const $ = {
enableWebAccess: 'vn-client-web-access vn-check[label="Enable web access"]',
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'vn-client-web-access button[type=submit]',
nameValue: 'vn-client-log .change:nth-child(1) .basic-json:nth-child(1) vn-json-value',
activeValue: 'vn-client-log .change:nth-child(2) .basic-json:nth-child(2) vn-json-value'
};
describe('Client web access 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('salesPerson', 'client'); await page.loginAndModule('salesPerson', 'client');
await page.accessToSearchResult('max'); await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess');
}); });
afterAll(async() => { afterAll(async() => {
await browser.close(); await browser.close();
}); });
it('should uncheck the Enable web access checkbox', async() => { it('should modify and save web access attributes', async() => {
await page.waitToClick(selectors.clientWebAccess.enableWebAccessCheckbox); await page.accessToSection('client.card.webAccess');
await page.waitToClick(selectors.clientWebAccess.saveButton); await page.click($.enableWebAccess);
const message = await page.waitForSnackbar(); await page.click($.saveButton);
const enableMessage = await page.waitForSnackbar();
await page.overwrite($.userName, 'Legion');
await page.overwrite($.email, 'legion@marvel.com');
await page.click($.saveButton);
const modifyMessage = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should update the name`, async() => {
await page.clearInput(selectors.clientWebAccess.userName);
await page.write(selectors.clientWebAccess.userName, 'Legion');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should update the email`, async() => {
await page.clearInput(selectors.clientWebAccess.email);
await page.write(selectors.clientWebAccess.email, 'legion@marvel.com');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section and confirm web access is now unchecked', async() => {
await page.reloadSection('client.card.webAccess'); await page.reloadSection('client.card.webAccess');
const result = await page.checkboxState(selectors.clientWebAccess.enableWebAccessCheckbox); const hasAccess = await page.checkboxState($.enableWebAccess);
const userName = await page.getValue($.userName);
const email = await page.getValue($.email);
expect(result).toBe('unchecked');
});
it('should confirm web access name have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.userName, 'value');
expect(result).toEqual('Legion');
});
it('should confirm web access email have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.email, 'value');
expect(result).toEqual('legion@marvel.com');
});
it(`should navigate to the log section`, async() => {
await page.accessToSection('client.card.log'); await page.accessToSection('client.card.log');
}); const logName = await page.innerText($.nameValue);
const logActive = await page.innerText($.activeValue);
it(`should confirm the last log shows the updated client name and no modifications on active checkbox`, async() => { expect(enableMessage.type).toBe('success');
let namePreviousValue = await page expect(modifyMessage.type).toBe('success');
.waitToGetProperty(selectors.clientLog.namePreviousValue, 'innerText');
let nameCurrentValue = await page
.waitToGetProperty(selectors.clientLog.nameCurrentValue, 'innerText');
expect(namePreviousValue).toEqual('MaxEisenhardt'); expect(hasAccess).toBe('unchecked');
expect(nameCurrentValue).toEqual('Legion'); expect(userName).toEqual('Legion');
}); expect(email).toEqual('legion@marvel.com');
it(`should confirm the penultimate log shows the updated active and no modifications on client name`, async() => { expect(logName).toEqual('Legion');
let activePreviousValue = await page expect(logActive).toEqual('✗');
.waitToGetProperty(selectors.clientLog.activePreviousValue, 'innerText');
let activeCurrentValue = await page
.waitToGetProperty(selectors.clientLog.activeCurrentValue, 'innerText');
expect(activePreviousValue).toEqual('✓');
expect(activeCurrentValue).toEqual('✗');
}); });
}); });

View File

@ -4,20 +4,69 @@ import getBrowser from '../../helpers/puppeteer';
describe('Item fixed prices path', () => { describe('Item fixed prices path', () => {
let browser; let browser;
let page; let page;
let httpRequest;
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('buyer', 'item'); await page.loginAndModule('buyer', 'item');
await page.accessToSection('item.fixedPrice'); await page.accessToSection('item.fixedPrice');
page.on('request', req => {
if (req.url().includes(`FixedPrices/filter`))
httpRequest = req.url();
});
}); });
afterAll(async() => { afterAll(async() => {
await browser.close(); await browser.close();
}); });
it('should filter using all the fields', async() => {
await page.write(selectors.itemFixedPrice.generalSearchFilter, 'item');
await page.keyboard.press('Enter');
expect(httpRequest).toContain('search=item');
await page.click(selectors.itemFixedPrice.chip);
await page.click(selectors.itemFixedPrice.reignFilter);
expect(httpRequest).toContain('categoryFk');
await page.autocompleteSearch(selectors.itemFixedPrice.typeFilter, 'Alstroemeria');
expect(httpRequest).toContain('typeFk');
await page.click(selectors.itemFixedPrice.chip);
await page.autocompleteSearch(selectors.itemFixedPrice.buyerFilter, 'buyerNick');
expect(httpRequest).toContain('buyerFk');
await page.click(selectors.itemFixedPrice.chip);
await page.autocompleteSearch(selectors.itemFixedPrice.warehouseFilter, 'Algemesi');
expect(httpRequest).toContain('warehouseFk');
await page.click(selectors.itemFixedPrice.chip);
await page.click(selectors.itemFixedPrice.mineFilter);
expect(httpRequest).toContain('mine=true');
await page.click(selectors.itemFixedPrice.chip);
await page.click(selectors.itemFixedPrice.hasMinPriceFilter);
expect(httpRequest).toContain('hasMinPrice=true');
await page.click(selectors.itemFixedPrice.chip);
await page.click(selectors.itemFixedPrice.addTag);
await page.autocompleteSearch(selectors.itemFixedPrice.tagFilter, 'Color');
await page.autocompleteSearch(selectors.itemFixedPrice.tagValueFilter, 'Brown');
expect(httpRequest).toContain('tags');
await page.click(selectors.itemFixedPrice.chip);
});
it('should click on the add new fixed price button', async() => { it('should click on the add new fixed price button', async() => {
await page.waitToClick(selectors.itemFixedPrice.removeWarehouseFilter);
await page.waitForSpinnerLoad();
await page.waitToClick(selectors.itemFixedPrice.add); await page.waitToClick(selectors.itemFixedPrice.add);
await page.waitForSelector(selectors.itemFixedPrice.fourthFixedPrice); await page.waitForSelector(selectors.itemFixedPrice.fourthFixedPrice);
}); });
@ -36,10 +85,7 @@ describe('Item fixed prices path', () => {
}); });
it('should reload the section and check the created price has the expected ID', async() => { it('should reload the section and check the created price has the expected ID', async() => {
await page.accessToSection('item.index'); await page.goto(`http://localhost:5000/#!/item/fixed-price`);
await page.accessToSection('item.fixedPrice');
await page.waitToClick(selectors.itemFixedPrice.removeWarehouseFilter);
await page.waitForSpinnerLoad();
const result = await page.waitToGetProperty(selectors.itemFixedPrice.fourthItemID, 'value'); const result = await page.waitToGetProperty(selectors.itemFixedPrice.fourthItemID, 'value');

View File

@ -246,6 +246,7 @@ describe('Ticket Edit sale path', () => {
it('should select the third sale and create a claim of it', async() => { it('should select the third sale and create a claim of it', async() => {
await page.accessToSearchResult('16'); await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale'); await page.accessToSection('ticket.card.sale');
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);

View File

@ -126,10 +126,11 @@ describe('Ticket Future path', () => {
}); });
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.waitForNumberOfElements(selectors.ticketFuture.searchResult, 4);
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.globalItems.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!');

View File

@ -35,7 +35,7 @@ describe('InvoiceIn serial path', () => {
}); });
it('should go to index and check if the search-panel has the correct params', async() => { it('should go to index and check if the search-panel has the correct params', async() => {
await page.click(selectors.invoiceInSerial.goToIndex); await page.waitToClick(selectors.invoiceInSerial.goToIndex);
const params = await page.$$(selectors.invoiceInIndex.topbarSearchParams); const params = await page.$$(selectors.invoiceInIndex.topbarSearchParams);
const serial = await params[0].getProperty('title'); const serial = await params[0].getProperty('title');
const isBooked = await params[1].getProperty('title'); const isBooked = await params[1].getProperty('title');

View File

@ -1,6 +1,6 @@
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('InvoiceIn negative bases path', () => { describe('InvoiceOut negative bases path', () => {
let browser; let browser;
let page; let page;
const httpRequests = []; const httpRequests = [];
@ -9,11 +9,11 @@ describe('InvoiceIn negative bases path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
page.on('request', req => { page.on('request', req => {
if (req.url().includes(`InvoiceIns/negativeBases`)) if (req.url().includes(`InvoiceOuts/negativeBases`))
httpRequests.push(req.url()); httpRequests.push(req.url());
}); });
await page.loginAndModule('administrative', 'invoiceIn'); await page.loginAndModule('administrative', 'invoiceOut');
await page.accessToSection('invoiceIn.negative-bases'); await page.accessToSection('invoiceOut.negative-bases');
}); });
afterAll(async() => { afterAll(async() => {

View File

@ -1,7 +1,21 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Supplier fiscal data path', () => { const $ = {
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
};
const $inputs = {
province: 'vn-supplier-fiscal-data [name="province"]',
country: 'vn-supplier-fiscal-data [name="country"]',
postcode: 'vn-supplier-fiscal-data [name="postcode"]',
city: 'vn-supplier-fiscal-data [name="city"]',
socialName: 'vn-supplier-fiscal-data [name="socialName"]',
taxNumber: 'vn-supplier-fiscal-data [name="taxNumber"]',
account: 'vn-supplier-fiscal-data [name="account"]',
sageWithholding: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageWithholdingFk"]',
sageTaxType: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageTaxTypeFk"]'
};
fdescribe('Supplier fiscal data path', () => {
let browser; let browser;
let page; let page;
@ -10,102 +24,44 @@ describe('Supplier fiscal data path', () => {
page = browser.page; page = browser.page;
await page.loginAndModule('administrative', 'supplier'); await page.loginAndModule('administrative', 'supplier');
await page.accessToSearchResult('2'); await page.accessToSearchResult('2');
await page.accessToSection('supplier.card.fiscalData');
}); });
afterAll(async() => { afterAll(async() => {
await browser.close(); await browser.close();
}); });
it('should attempt to edit the fiscal data but fail as the tax number is invalid', async() => { it('should attempt to edit the fiscal data and check data is saved', async() => {
await page.clearInput(selectors.supplierFiscalData.city); await page.accessToSection('supplier.card.fiscalData');
await page.clearInput(selectors.supplierFiscalData.province); await page.clearInput($inputs.province);
await page.clearInput(selectors.supplierFiscalData.country); await page.clearInput($inputs.country);
await page.clearInput(selectors.supplierFiscalData.postCode); await page.clearInput($inputs.postcode);
await page.write(selectors.supplierFiscalData.city, 'Valencia'); await page.overwrite($inputs.city, 'Valencia');
await page.waitForTimeout(1000); // must repeat this action twice or fails. also #2699 may be a cool solution to this. await page.overwrite($inputs.socialName, 'Farmer King SL');
await page.clearInput(selectors.supplierFiscalData.city); await page.overwrite($inputs.taxNumber, 'Wrong tax number');
await page.write(selectors.supplierFiscalData.city, 'Valencia'); await page.overwrite($inputs.account, '0123456789');
await page.clearInput(selectors.supplierFiscalData.socialName); await page.autocompleteSearch($inputs.sageWithholding, 'retencion estimacion objetiva');
await page.write(selectors.supplierFiscalData.socialName, 'Farmer King SL'); await page.autocompleteSearch($inputs.sageTaxType, 'operaciones no sujetas');
await page.clearInput(selectors.supplierFiscalData.taxNumber); await page.click($.saveButton);
await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number'); const errorMessage = await page.waitForSnackbar();
await page.clearInput(selectors.supplierFiscalData.account); await page.overwrite($inputs.taxNumber, '12345678Z');
await page.write(selectors.supplierFiscalData.account, '0123456789'); await page.click($.saveButton);
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva'); const successMessage = await page.waitForSnackbar();
await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas');
await page.waitToClick(selectors.supplierFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Invalid Tax number');
});
it('should save the changes as the tax number is valid this time', async() => {
await page.clearInput(selectors.supplierFiscalData.taxNumber);
await page.write(selectors.supplierFiscalData.taxNumber, '12345678Z');
await page.waitToClick(selectors.supplierFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section', async() => {
await page.reloadSection('supplier.card.fiscalData'); await page.reloadSection('supplier.card.fiscalData');
}); const values = await page.getValues($inputs);
it('should check the socialName was edited', async() => { expect(errorMessage.text).toContain('Invalid Tax number');
const result = await page.waitToGetProperty(selectors.supplierFiscalData.socialName, 'value'); expect(successMessage.type).toBe('success');
expect(values).toEqual({
expect(result).toEqual('Farmer King SL'); province: 'Province one (España)',
}); country: 'España',
postcode: '46000',
it('should check the taxNumber was edited', async() => { city: 'Valencia',
const result = await page.waitToGetProperty(selectors.supplierFiscalData.taxNumber, 'value'); socialName: 'Farmer King SL',
taxNumber: '12345678Z',
expect(result).toEqual('12345678Z'); account: '0123456789',
}); sageWithholding: 'RETENCION ESTIMACION OBJETIVA',
sageTaxType: 'Operaciones no sujetas'
it('should check the account was edited', async() => { });
const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value');
expect(result).toEqual('0123456789');
});
it('should check the sageWihholding was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.sageWihholding, 'value');
expect(result).toEqual('RETENCION ESTIMACION OBJETIVA');
});
it('should check the sageTaxType was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.sageTaxType, 'value');
expect(result).toEqual('Operaciones no sujetas');
});
it('should check the postCode was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.postCode, 'value');
expect(result).toEqual('46000');
});
it('should check the city was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.city, 'value');
expect(result).toEqual('Valencia');
});
it('should check the province was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.province, 'value');
expect(result).toEqual('Province one (España)');
});
it('should check the country was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.country, 'value');
expect(result).toEqual('España');
}); });
}); });

View File

@ -23,7 +23,7 @@ describe('Account ACL path', () => {
it('should create new acl', async() => { it('should create new acl', async() => {
await page.autocompleteSearch(selectors.accountAcl.role, 'sysadmin'); await page.autocompleteSearch(selectors.accountAcl.role, 'sysadmin');
await page.autocompleteSearch(selectors.accountAcl.model, 'UserAccount'); await page.autocompleteSearch(selectors.accountAcl.model, 'Account');
await page.autocompleteSearch(selectors.accountAcl.accessType, '*'); await page.autocompleteSearch(selectors.accountAcl.accessType, '*');
await page.autocompleteSearch(selectors.accountAcl.permission, 'ALLOW'); await page.autocompleteSearch(selectors.accountAcl.permission, 'ALLOW');
await page.waitToClick(selectors.accountAcl.save); await page.waitToClick(selectors.accountAcl.save);

View File

@ -166,7 +166,7 @@ export default class Field extends FormInput {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
this.field = null; this.field = null;
this.input.dispatchEvent(new Event('change')); this.element.dispatchEvent(new Event('change'));
} }
buildInput(type) { buildInput(type) {

View File

@ -32,6 +32,7 @@ import './float-button';
import './icon-menu'; import './icon-menu';
import './icon-button'; import './icon-button';
import './input-number'; import './input-number';
import './json-value';
import './label-value'; import './label-value';
import './range'; import './range';
import './input-time'; import './input-time';

View File

@ -0,0 +1,73 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
const maxStrLen = 50;
/**
* Displays pretty JSON value.
*
* @property {*} value The value
*/
export default class Controller extends Component {
get value() {
return this._value;
}
set value(value) {
const wasEmpty = this._value === undefined;
this._value = value;
let text;
let cssClass;
const type = typeof value;
if (value == null) {
text = '∅';
cssClass = 'null';
} else {
cssClass = type;
switch (type) {
case 'boolean':
text = value ? '✓' : '✗';
cssClass = value ? 'true' : 'false';
break;
case 'string':
text = value.length <= maxStrLen
? value
: value.substring(0, maxStrLen) + '...';
break;
case 'object':
if (value instanceof Date) {
const hasZeroTime =
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0;
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
text = this.$filter('date')(value, format);
} else
text = value;
break;
default:
text = value;
}
}
const el = this.element;
el.textContent = text;
el.title = type == 'string' && value.length > maxStrLen ? value : '';
cssClass = `json-${cssClass}`;
if (wasEmpty)
el.classList.add(cssClass);
else
el.classList.replace(this.className, cssClass);
}
}
ngModule.vnComponent('vnJsonValue', {
controller: Controller,
bindings: {
value: '<?'
}
});

View File

@ -0,0 +1,79 @@
import './index';
describe('Salix Component vnLog', () => {
let controller;
let $scope;
let $element;
let el;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<json-value></json-value>');
controller = $componentController('vnJsonValue', {$element, $scope});
el = controller.element;
}));
describe('set value()', () => {
it('should display null symbol when value is null equivalent', () => {
controller.value = null;
expect(el.textContent).toEqual('∅');
expect(el.className).toContain('json-null');
});
it('should display ballot when value is false', () => {
controller.value = false;
expect(el.textContent).toEqual('✗');
expect(el.className).toContain('json-false');
});
it('should display check when value is true', () => {
controller.value = true;
expect(el.textContent).toEqual('✓');
expect(el.className).toContain('json-true');
});
it('should display string when value is an string', () => {
controller.value = 'Foo';
expect(el.textContent).toEqual('Foo');
expect(el.className).toContain('json-string');
});
it('should display only date when value is date with time set to zero', () => {
const date = Date.vnNew();
date.setHours(0, 0, 0, 0);
controller.value = date;
expect(el.textContent).toEqual('01/01/2001');
expect(el.className).toContain('json-object');
});
it('should display full date without time when value is date with time', () => {
const date = Date.vnNew();
date.setHours(15, 45);
controller.value = date;
expect(el.textContent).toEqual('01/01/2001 15:45:00');
expect(el.className).toContain('json-object');
});
it('should display object when value is an object', () => {
controller.value = {foo: 'bar'};
expect(el.textContent).toEqual('[object Object]');
expect(el.className).toContain('json-object');
});
it('should display number when value is a number', () => {
controller.value = 2050;
expect(el.textContent).toEqual('2050');
expect(el.className).toContain('json-number');
});
});
});

View File

@ -0,0 +1,23 @@
vn-json-value {
display: inline;
&.json-string {
color: #d172cc;
}
&.json-object {
color: #d1a572;
}
&.json-number {
color: #85d0ff;
}
&.json-true {
color: #7dc489;
}
&.json-false {
color: #c74949;
}
&.json-null {
color: #cd7c7c;
font-style: italic;
}
}

View File

@ -41,10 +41,15 @@ vn-table {
display: table-row; display: table-row;
height: 48px; height: 48px;
} }
vn-thead, .vn-thead, & > thead,
vn-tbody, .vn-tbody, & > tbody,
vn-tfoot, .vn-tfoot, & > tfoot,
thead, tbody, tfoot { & > vn-thead,
& > vn-tbody,
& > vn-tfoot,
& > .vn-thead,
& > .vn-tbody,
& > .vn-tfoot {
& > * { & > * {
display: table-row; display: table-row;
@ -111,14 +116,14 @@ vn-table {
color: inherit; color: inherit;
} }
} }
a.vn-tbody { & > a.vn-tbody {
&.clickable { &.clickable {
@extend %clickable; @extend %clickable;
} }
} }
vn-tbody > *, & > vn-tbody > *,
.vn-tbody > *, & > .vn-tbody > *,
tbody > * { & > tbody > * {
border-bottom: $border-thin; border-bottom: $border-thin;
&:last-child { &:last-child {

View File

@ -7,7 +7,7 @@ describe('Directive acl', () => {
beforeEach(ngModule('vnCore')); beforeEach(ngModule('vnCore'));
beforeEach(inject(($httpBackend, aclService) => { beforeEach(inject(($httpBackend, aclService) => {
$httpBackend.whenGET('Accounts/acl') $httpBackend.whenGET('VnUsers/acl')
.respond({ .respond({
user: {id: 1, name: 'myUser'}, user: {id: 1, name: 'myUser'},
roles: [ roles: [

View File

@ -4,7 +4,7 @@ describe('Service acl', () => {
beforeEach(ngModule('vnCore')); beforeEach(ngModule('vnCore'));
beforeEach(inject((_aclService_, $httpBackend) => { beforeEach(inject((_aclService_, $httpBackend) => {
$httpBackend.when('GET', `Accounts/acl`).respond({ $httpBackend.when('GET', `VnUsers/acl`).respond({
roles: [ roles: [
{role: {name: 'foo'}}, {role: {name: 'foo'}},
{role: {name: 'bar'}}, {role: {name: 'bar'}},

View File

@ -11,7 +11,7 @@ class AclService {
} }
load() { load() {
return this.$http.get('Accounts/acl').then(res => { return this.$http.get('VnUsers/acl').then(res => {
this.user = res.data.user; this.user = res.data.user;
this.roles = {}; this.roles = {};

View File

@ -59,7 +59,7 @@ export default class Auth {
password: password || undefined password: password || undefined
}; };
return this.$http.post('Accounts/login', params).then( return this.$http.post('VnUsers/signIn', params).then(
json => this.onLoginOk(json, remember)); json => this.onLoginOk(json, remember));
} }
@ -76,7 +76,7 @@ export default class Auth {
} }
logout() { logout() {
let promise = this.$http.post('Accounts/logout', null, { let promise = this.$http.post('VnUsers/logout', null, {
headers: {Authorization: this.vnToken.token} headers: {Authorization: this.vnToken.token}
}).catch(() => {}); }).catch(() => {});

View File

@ -2,7 +2,6 @@
$font-size: 11pt; $font-size: 11pt;
$menu-width: 256px; $menu-width: 256px;
$right-menu-width: 318px;
$topbar-height: 56px; $topbar-height: 56px;
$mobile-width: 800px; $mobile-width: 800px;
$float-spacing: 20px; $float-spacing: 20px;

View File

@ -13,7 +13,7 @@ export class Layout extends Component {
} }
getUserData() { getUserData() {
this.$http.get('Accounts/getCurrentUserData').then(json => { this.$http.get('VnUsers/getCurrentUserData').then(json => {
this.$.$root.user = json.data; this.$.$root.user = json.data;
window.localStorage.currentUserWorkerId = json.data.id; window.localStorage.currentUserWorkerId = json.data.id;
}); });

View File

@ -15,7 +15,7 @@ describe('Component vnLayout', () => {
describe('getUserData()', () => { describe('getUserData()', () => {
it(`should set the user name property in the controller`, () => { it(`should set the user name property in the controller`, () => {
$httpBackend.expect('GET', `Accounts/getCurrentUserData`).respond({name: 'batman'}); $httpBackend.expect('GET', `VnUsers/getCurrentUserData`).respond({name: 'batman'});
controller.getUserData(); controller.getUserData();
$httpBackend.flush(); $httpBackend.flush();

View File

@ -88,13 +88,13 @@ vn-layout {
} }
&.right-menu { &.right-menu {
& > vn-topbar > .end { & > vn-topbar > .end {
width: 80px + $right-menu-width; width: 80px + $menu-width;
} }
& > .main-view { & > .main-view {
padding-right: $right-menu-width; padding-right: $menu-width;
} }
[fixed-bottom-right] { [fixed-bottom-right] {
right: $right-menu-width; right: $menu-width;
} }
} }
& > .main-view { & > .main-view {

View File

@ -3,71 +3,211 @@
url="{{$ctrl.url}}" url="{{$ctrl.url}}"
filter="$ctrl.filter" filter="$ctrl.filter"
link="{originFk: $ctrl.originId}" link="{originFk: $ctrl.originId}"
where="{changedModel: $ctrl.changedModel, where="{changedModel: $ctrl.changedModel, changedModelId: $ctrl.changedModelId}"
changedModelId: $ctrl.changedModelId}"
data="$ctrl.logs" data="$ctrl.logs"
limit="20" order="creationDate DESC, id DESC"
limit="20">
</vn-crud-model>
<vn-crud-model
url="{{$ctrl.url}}/{{$ctrl.originId}}/models"
data="models"
order="changedModel"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="model" class="vn-w-xl"> <vn-data-viewer model="model" class="vn-w-md vn-px-sm">
<vn-card> <div class="change vn-mb-sm" ng-repeat="log in $ctrl.logs">
<vn-table model="model"> <div class="user-wrapper">
<vn-thead> <div
<vn-tr> class="user vn-mt-xs"
<vn-th field="creationDate">Date</vn-th> title="{{::log.user.nickname || 'System'}}">
<vn-th field="userFk" shrink>User</vn-th> <img
<vn-th field="changedModel" ng-if="$ctrl.showModelName" shrink>Model</vn-th> ng-if="::log.user.worker"
<vn-th field="action" shrink>Action</vn-th> src="/api/Images/user/160x160/{{::log.user.worker.id}}/download?access_token={{::$ctrl.vnToken.token}}"
<vn-th field="changedModelValue" ng-if="$ctrl.showModelName">Name</vn-th> ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)">
<vn-th expand>Changes</vn-th> </img>
</vn-tr> <div
</vn-thead> ng-if="::!log.user.worker"
<vn-tbody> ng-class="::{system: !log.user}"
<vn-tr ng-repeat="log in $ctrl.logs"> ng-style="::$ctrl.userBgColor(log.user)"
<vn-td shrink-datetime> class="user-icon">
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}} {{::log.user ? log.user.name.charAt(0).toUpperCase() : 'S'}}
</vn-td> </div>
<vn-td> </div>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}" <div class="arrow bg-panel"></div>
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)" <div class="line"></div>
translate>{{::log.user.name || 'System' | translate}} </div>
<vn-card class="detail vn-pa-sm">
<div class="header vn-mb-sm">
<div
class="date text-secondary text-caption"
title="{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}">
{{::$ctrl.relativeDate(log.creationDate)}}
</div>
<span class="chip {{::$ctrl.actionsClass[log.action]}}" translate>
{{::$ctrl.actionsText[log.action]}}
</span>
</div>
<div
class="model vn-mb-sm"
title="{{::log.changedModelValue}}"
ng-if="::log.changedModel || log.changedModelValue">
<span class="model-name"
ng-if="::$ctrl.showModelName"
title="{{::log.changedModel}}">
{{::log.changedModelI18n}}
</span>
<span class="model-id"
ng-if="::log.changedModelId">
#{{::log.changedModelId}}
</span>
<span class="model-value">
{{::log.changedModelValue}}
</span>
</div>
<div class="changes {{::log.props.length ? 'props' : 'no-props'}}" vn-id="changes">
<vn-icon icon="visibility"
class="expand-button"
ng-click="$ctrl.toggleAttributes(log, changes, true)">
</vn-icon>
<vn-icon icon="visibility_off"
class="shrink-button"
ng-click="$ctrl.toggleAttributes(log, changes, false)">
</vn-icon>
<div class="changes-wrapper">
<span ng-if="::log.props.length"
class="attributes">
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
class="basic-json">
<span class="json-field"
title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value><span ng-if="::!$last">,</span>
</span> </span>
</vn-td> <div ng-if="log.expand"
<vn-td ng-if="$ctrl.showModelName"> class="expanded-json">
{{::log.changedModel}} <div ng-repeat="prop in ::log.props">
</vn-td> <span class="json-field"
<vn-td shrink translate> title="{{::prop.name}}">
{{::$ctrl.actionsText[log.action]}} {{::prop.nameI18n}}:
</vn-td> </span>
<vn-td ng-if="$ctrl.showModelName"> <vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value>
{{::log.changedModelValue}} <span ng-if="::log.action == 'update'">
</vn-td> <vn-json-value value="::prop.old"></vn-json-value>
<vn-td expand> </span>
<table class="attributes"> </div>
<thead>
<tr>
<th translate class="field">Field</th>
<th translate>Before</th>
<th translate>After</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="prop in ::log.props">
<td class="field">{{prop.name}}</td>
<td class="before">{{prop.old}}</td>
<td class="after">{{prop.new}}</td>
</tr>
</tbody>
</table>
<div ng-if="log.description != null">
{{::log.description}}
</div> </div>
</vn-td> </span>
</vn-tr> <span ng-if="::!log.props.length"
</vn-tbody> class="description">
</vn-table> {{::log.description}}
<vn-pagination model="model"></vn-pagination> </span>
</vn-card> <span ng-if="::!log.description && !log.props.length"
class="no-changes"
translate>
No changes
</span>
</div>
</vn-card>
</div>
</div>
</vn-data-viewer> </vn-data-viewer>
<vn-side-menu side="right">
<form vn-vertical
ng-model-options="{updateOn: 'change blur'}"
class="vn-pa-md filter">
<h6
class="text-secondary vn-mb-md"
style="font-weight: normal;"
translate>
Filter
</h6>
<vn-textfield
label="Name"
ng-model="filter.changedModelValue">
</vn-textfield>
<vn-vertical>
<vn-radio
label="All"
val="all"
ng-model="filter.who">
</vn-radio>
<vn-radio
label="User"
val="user"
ng-model="filter.who">
</vn-radio>
<vn-radio
label="System"
val="system"
ng-model="filter.who">
</vn-radio>
</div>
<vn-autocomplete
ng-show="filter.who != 'system'"
label="User"
ng-model="filter.userFk"
value-field="id"
show-field="nickname"
fields="['id', 'name', 'nickname']"
search-function="$ctrl.searchUser($search)"
url="{{$ctrl.url}}/{{$ctrl.originId}}/editors"
order="nickname">
<tpl-item>
<div>{{nickname}}</div>
<div class="text-secondary text-caption">{{name}}</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
label="Model"
ng-model="filter.changedModel"
value-field="changedModel"
show-field="changedModel"
data="models">
</vn-autocomplete>
<vn-textfield
label="Id"
ng-model="filter.changedModelId">
</vn-textfield>
<vn-vertical>
<vn-check
label="Creates"
ng-model="filter.actions.insert">
</vn-check>
<vn-check
label="Updates"
ng-model="filter.actions.update">
</vn-check>
<vn-check
label="Deletes"
ng-model="filter.actions.delete">
</vn-check>
<vn-check
label="Views"
ng-model="filter.actions.select">
</vn-check>
</div>
<vn-date-picker
label="Date"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="filter.to">
</vn-date-picker>
<vn-button-bar vn-vertical class="vn-mt-sm">
<vn-button
label="Filter"
ng-click="$ctrl.applyFilter(filter)">
</vn-button>
<vn-button
label="Reset"
class="flat"
ng-click="$ctrl.resetFilter()"
ng-if="model.userFilter">
</vn-button>
</vn-button-bar>
</form>
</vn-side-menu>
<vn-worker-descriptor-popover vn-id="workerDescriptor"> <vn-worker-descriptor-popover vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>

View File

@ -13,11 +13,17 @@ export default class Controller extends Section {
delete: 'Deletes', delete: 'Deletes',
select: 'Views' select: 'Views'
}; };
this.actionsClass = {
insert: 'success',
update: 'warning',
delete: 'alert',
select: 'notice'
};
this.filter = { this.filter = {
include: [{ include: [{
relation: 'user', relation: 'user',
scope: { scope: {
fields: ['name'], fields: ['nickname'],
include: { include: {
relation: 'worker', relation: 'worker',
scope: { scope: {
@ -27,6 +33,20 @@ export default class Controller extends Section {
}, },
}], }],
}; };
this.dateFilter = this.$filter('date');
this.lang = this.$translate.use();
this.today = Date.vnNew();
this.today.setHours(0, 0, 0, 0);
}
$postLink() {
this.resetFilter();
this.$.$watch(
() => this.$.filter,
() => this.applyFilter(),
true
);
} }
get logs() { get logs() {
@ -42,6 +62,7 @@ export default class Controller extends Section {
const oldValues = log.oldInstance || empty; const oldValues = log.oldInstance || empty;
const newValues = log.newInstance || empty; const newValues = log.newInstance || empty;
const locale = validations[log.changedModel]?.locale || empty; const locale = validations[log.changedModel]?.locale || empty;
log.changedModelI18n = locale.name || log.changedModel;
let props = Object.keys(oldValues).concat(Object.keys(newValues)); let props = Object.keys(oldValues).concat(Object.keys(newValues));
props = [...new Set(props)]; props = [...new Set(props)];
@ -49,9 +70,10 @@ export default class Controller extends Section {
log.props = []; log.props = [];
for (const prop of props) { for (const prop of props) {
log.props.push({ log.props.push({
name: locale[prop] || prop, name: prop,
old: this.formatValue(oldValues[prop]), nameI18n: locale.columns?.[prop] || prop,
new: this.formatValue(newValues[prop]) old: this.castJsonValue(oldValues[prop]),
new: this.castJsonValue(newValues[prop])
}); });
} }
} }
@ -61,37 +83,124 @@ export default class Controller extends Section {
return !(this.changedModel && this.changedModelId); return !(this.changedModel && this.changedModelId);
} }
formatValue(value) { castJsonValue(value) {
let type = typeof value; return typeof value === 'string' && validDate.test(value)
? new Date(value)
: value;
}
if (type === 'string' && validDate.test(value)) { mainVal(prop, action) {
value = new Date(value); return action == 'delete' ? prop.old : prop.new;
type = typeof value; }
}
switch (type) { toggleAttributes(log, changesEl, force) {
case 'boolean': log.expand = force;
return value ? '✓' : '✗'; changesEl.classList.toggle('expanded', force);
case 'object': }
if (value instanceof Date) {
const hasZeroTime = relativeDate(dateVal) {
value.getHours() === 0 && if (dateVal == null) return '';
value.getMinutes() === 0 && const date = new Date(dateVal);
value.getSeconds() === 0; const dateZeroTime = new Date(dateVal);
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss'; dateZeroTime.setHours(0, 0, 0, 0);
return this.$filter('date')(value, format); const diff = Math.trunc((this.today.getTime() - dateZeroTime.getTime()) / (1000 * 3600 * 24));
}
else let format;
return value; if (diff == 0)
default: format = `'${this.$t('today')}'`;
return value; else if (diff == 1)
} format = `'${this.$t('yesterday')}'`;
else if (diff > 1 && diff < 7)
format = `'${date.toLocaleDateString(this.lang, {weekday: 'short'})}'`;
else if (this.today.getFullYear() == date.getFullYear())
format = `d '${date.toLocaleDateString(this.lang, {month: 'short'})}'`;
else
format = `dd/MM/yyyy`;
return this.dateFilter(date, `${format} HH:mm`);
} }
showWorkerDescriptor(event, workerId) { showWorkerDescriptor(event, workerId) {
if (!workerId) return; if (!workerId) return;
this.$.workerDescriptor.show(event.target, workerId); this.$.workerDescriptor.show(event.target, workerId);
} }
resetFilter() {
this.$.filter = {who: 'all'};
}
applyFilter() {
const filter = this.$.filter;
function getParam(prop, value) {
if (value == null || value == '') return null;
switch (prop) {
case 'changedModelValue':
return {[prop]: {like: `%${value}%`}};
case 'who':
switch (value) {
case 'all':
return null;
case 'user':
return {userFk: {neq: null}};
case 'system':
return {userFk: null};
}
case 'actions':
const inq = [];
for (const action in value) {
if (value[action])
inq.push(action);
}
return inq.length ? {action: {inq}} : null;
case 'from':
if (filter.to) {
return {creationDate: {gte: value}};
} else {
const to = new Date(value);
to.setHours(23, 59, 59, 999);
return {creationDate: {between: [value, to]}};
}
case 'to':
const to = new Date(value);
to.setHours(23, 59, 59, 999);
return {creationDate: {lte: to}};
default:
return {[prop]: value};
}
}
const and = [];
for (const prop in filter) {
const param = getParam(prop, filter[prop]);
if (param) and.push(param);
}
const lbFilter = and.length ? {where: {and}} : null;
return this.$.model.applyFilter(lbFilter);
}
searchUser(search) {
if (/^[0-9]+$/.test(search)) {
return {id: search};
} else {
return {or: [
{name: search},
{nickname: {like: `%${search}%`}}
]}
}
}
userBgColor(user) {
if (!user) return;
const name = user.name || '';
let hash = 0;
for (let i = 0; i < name.length; i++)
hash += name.charCodeAt(i);
return {
backgroundColor: '#'+ colors[hash % colors.length]
};
}
} }
ngModule.vnComponent('vnLog', { ngModule.vnComponent('vnLog', {
@ -105,3 +214,29 @@ ngModule.vnComponent('vnLog', {
url: '@' url: '@'
} }
}); });
const colors = [
'e2553d', // Coral
'FFA07A', // Salmon
'FFDAB9', // Peach
'a17077', // Pink
'bf0e99', // Pink light
'52a500', // Green chartreuse
'00aeae', // Cian
'b754cf', // Purple middle
'8a69cd', // Blue lavender
'1fa8a1', // Green ocean
'DC143C', // Red crimson
'5681cf', // Blue steel
'FF1493', // Ping intense
'00d700', // Green lime
'1E90FF', // Blue sky
'8B008B', // Purple dark
'cc7000', // Orange bright
'00b5b8', // Turquoise
'8B0000', // Red dark
'008080', // Green bluish
'2F4F4F', // Gray board
'7e7e7e', // Gray
'5d5d5d', // Gray dark
];

View File

@ -0,0 +1,97 @@
import './index';
describe('Salix Component vnLog', () => {
let controller;
let $scope;
let $element;
beforeEach(ngModule('salix'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<vn-log></vn-log>');
controller = $componentController('vnLog', {$element, $scope});
}));
describe('relativeDate()', () => {
let date;
beforeEach(() => {
date = Date.vnNew();
});
it('should return empty string when date is null', () => {
const ret = controller.relativeDate(null);
expect(ret).toEqual('');
});
it('should return empty string when date is undefined', () => {
const ret = controller.relativeDate(undefined);
expect(ret).toEqual('');
});
it('should return today and time when date is today', () => {
const ret = controller.relativeDate(date);
expect(ret).toEqual('today 12:00');
});
it('should return yesterday and time when date is yesterday', () => {
date.setDate(date.getDate() - 1);
const ret = controller.relativeDate(date);
expect(ret).toEqual('yesterday 12:00');
});
it('should return abreviated weekday name and time when date is on past week', () => {
date.setDate(date.getDate() - 3);
const ret = controller.relativeDate(date);
expect(ret).toEqual('Fri 12:00');
});
it('should return abreviated month name, day number and time when date is on this year', () => {
date.setDate(date.getDate() + 20);
const ret = controller.relativeDate(date);
expect(ret).toEqual('21 Jan 12:00');
});
it('should return abreviated month name, day number, year and time when date is on different year', () => {
date.setDate(date.getDate() - 20);
const ret = controller.relativeDate(date);
expect(ret).toEqual('12/12/2000 12:00');
});
it('should convert to date and return string when date is not a Date class instance', () => {
const ret = controller.relativeDate(date.toJSON());
expect(ret).toEqual('today 12:00');
});
});
describe('castJsonValue()', () => {
it('should return date when string has valid JSON date format', () => {
const now = Date.vnNew();
const ret = controller.castJsonValue(now.toJSON());
expect(ret).toBeInstanceOf(Date);
});
it('should return same value when is string with invalid JSON date format', () => {
const ret = controller.castJsonValue('Foo');
expect(ret).toEqual('Foo');
});
it('should return same value when is not an string', () => {
const ret = controller.castJsonValue(1001);
expect(ret).toEqual(1001);
});
});
});

View File

@ -13,3 +13,6 @@ Views: Visualiza
System: Sistema System: Sistema
note: nota note: nota
Changes: Cambios Changes: Cambios
No changes: No hay cambios
today: hoy
yesterday: ayer

View File

@ -1,66 +1,173 @@
@import "variables"; @import "variables";
vn-log { vn-log {
vn-td { .change {
vertical-align: initial !important; display: flex;
}
.changes {
display: none;
}
.label {
color: $color-font-secondary;
}
.value {
color: $color-font;
}
@media screen and (max-width: 1570px) { & > .user-wrapper {
vn-table .expendable { position: relative;
padding-right: 10px;
& > .user {
border-radius: 50%;
height: 36px;
width: 36px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
& > * {
width: 100%;
height: 100%;
}
img {
display: block;
cursor: pointer;
}
.user-icon {
font-size: 22px;
display: flex;
align-items: center;
justify-content: center;
&.system {
background-color: $color-main;
}
}
}
& > .arrow {
height: 8px;
width: 8px;
position: absolute;
transform: rotateY(0deg) rotate(45deg);
top: 18px;
right: -4px;
z-index: 1;
}
& > .line {
position: absolute;
background-color: $color-main;
width: 2px;
left: 17px;
z-index: -1;
top: 44px;
bottom: -8px;
}
}
&:last-child > .user-wrapper > .line {
display: none; display: none;
} }
.changes { .detail {
padding-top: 10px; position: relative;
display: block; flex-grow: 1;
width: 100%;
border-radius: 2px;
overflow: hidden;
& > .header {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& > .chip {
padding: 2px 4px;
border-radius: 4px;
display: inline-block;
color: $color-font-bg;
&.notice {
background-color: $color-notice-medium;
}
&.success {
background-color: $color-success-medium;
}
&.warning {
background-color: $color-main-medium;
}
&.alert {
background-color: lighten($color-alert, 5%);
}
}
.date {
float: right;
}
}
& > .model {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& > .model-name {
text-transform: capitalize;
}
& > .model-value {
font-style: italic;
color: #c7bd2b;
}
& > .model-id {
color: $color-font-secondary;
font-size: .9em;
}
}
} }
} }
.attributes { .changes {
width: 100%; overflow: hidden;
background-color: rgba(255, 255, 255, .05);
border-radius: 4px;
color: $color-font-secondary;
transition: max-height 150ms ease-in-out;
max-height: 28px;
position: relative;
tr { & > .expand-button,
height: 10px; & > .shrink-button {
display: none;
}
&.props {
padding-right: 24px;
& > td { & > .expand-button,
padding: 2px; & > .shrink-button {
position: absolute;
top: 6px;
right: 8px;
font-size: inherit;
float: right;
cursor: pointer;
} }
& > td.field, & > .expand-button {
& > th.field { display: block;
width: 20%;
color: gray;
} }
& > td.before, &.expanded {
& > th.before, max-height: 500px;
& > td.after, padding-right: 0;
& > th.after {
width: 40%; & > .changes-wrapper {
white-space: pre-line; text-overflow: initial;
white-space: initial;
}
& > .shrink-button {
display: block;
}
& > .expand-button {
display: none;
}
}
}
& > .changes-wrapper {
padding: 4px 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& > .no-changes {
font-style: italic;
}
.json-field {
text-transform: capitalize;
} }
} }
} }
} }
.ellipsis {
white-space: nowrap;
overflow: hidden;
max-width: 400px;
text-overflow: ellipsis;
display: inline-block;
}
.no-ellipsize,
[no-ellipsize] {
text-overflow: '';
white-space: normal;
overflow: auto;
}
.alignSpan {
overflow: hidden;
display: inline-block;
}

View File

@ -23,7 +23,7 @@ export default class Controller {
user: this.user user: this.user
}; };
this.$http.post('Accounts/recoverPassword', params) this.$http.post('VnUsers/recoverPassword', params)
.then(() => { .then(() => {
this.goToLogin(); this.goToLogin();
}); });

View File

@ -0,0 +1,40 @@
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('editors', {
description: 'Get the list of entity editors',
accepts: [
{
arg: 'id',
type: 'integer',
description: 'The model id',
required: true
}, {
arg: 'filter',
type: 'Object',
description: 'The user filter object'
}
],
returns: {
type: [Self],
root: true
},
http: {
path: `/:id/editors`,
verb: 'GET'
}
});
Self.editors = async(id, filter) => {
const res = await Self.find({
fields: ['userFk'],
where: {originFk: id}
});
const userIds = new Set(res.map(x => x.userFk));
filter = mergeFilters(filter, {
where: {id: {inq: [...userIds]}}
});
return await Self.app.models.VnUser.find(filter);
};
};

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