Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2808-e2e_account_descriptor

This commit is contained in:
Carlos Jimenez Ruiz 2022-06-16 16:46:33 +02:00
commit 1e504b6b9f
202 changed files with 6594 additions and 4538 deletions

5
back/helpers.spec.js Normal file
View File

@ -0,0 +1,5 @@
const baseTime = null; // new Date(2022, 0, 19, 8, 0, 0, 0);
if (baseTime) {
jasmine.clock().install();
jasmine.clock().mockDate(baseTime);
}

View File

@ -5,17 +5,17 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The user id', description: 'The user id',
http: {source: 'path'} http: {source: 'path'}
}, { }, {
arg: 'oldPassword', arg: 'oldPassword',
type: 'String', type: 'string',
description: 'The old password', description: 'The old password',
required: true required: true
}, { }, {
arg: 'newPassword', arg: 'newPassword',
type: 'String', type: 'string',
description: 'The new password', description: 'The new password',
required: true required: true
} }

View File

@ -1,16 +1,15 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('setPassword', { Self.remoteMethod('setPassword', {
description: 'Sets the user password', description: 'Sets the user password',
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The user id', description: 'The user id',
http: {source: 'path'} http: {source: 'path'}
}, { }, {
arg: 'newPassword', arg: 'newPassword',
type: 'String', type: 'string',
description: 'The new password', description: 'The new password',
required: true required: true
} }

View File

@ -1,7 +1,6 @@
const axios = require('axios');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('send', { Self.remoteMethodCtx('send', {
description: 'Send a RocketChat message', description: 'Creates a direct message in the chat model for a user or a channel',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'to', arg: 'to',
@ -31,39 +30,19 @@ module.exports = Self => {
const recipient = to.replace('@', ''); const recipient = to.replace('@', '');
if (sender.name != recipient) { if (sender.name != recipient) {
await sendMessage(sender, to, message); await models.Chat.create({
senderFk: sender.id,
recipient: to,
dated: new Date(),
checkUserStatus: 0,
message: message,
status: 0,
attempts: 0
});
return true; return true;
} }
return false; return false;
}; };
async function sendMessage(sender, channel, message) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({
statusCode: 200,
message: 'Fake notification sent'
});
});
}
const login = await Self.getServiceAuth();
const avatar = `${login.host}/avatar/${sender.name}`;
const options = {
headers: {
'X-Auth-Token': login.auth.token,
'X-User-Id': login.auth.userId
},
};
return axios.post(`${login.api}/chat.postMessage`, {
'channel': channel,
'avatar': avatar,
'alias': sender.nickname,
'text': message
}, options);
}
}; };

View File

@ -1,8 +1,6 @@
const axios = require('axios');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('sendCheckingPresence', { Self.remoteMethodCtx('sendCheckingPresence', {
description: 'Sends a RocketChat message to a connected user or department channel', description: 'Creates a message in the chat model checking the user status',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'workerId', arg: 'workerId',
@ -36,6 +34,7 @@ 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);
const recipient = await models.Account.findById(recipientId, null, myOptions); const recipient = await models.Account.findById(recipientId, null, myOptions);
// Prevent sending messages to yourself // Prevent sending messages to yourself
@ -44,54 +43,16 @@ module.exports = Self => {
if (!recipient) if (!recipient)
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`); throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
const {data} = await Self.getUserStatus(recipient.name); await models.Chat.create({
if (data) { senderFk: sender.id,
if (data.status === 'offline' || data.status === 'busy') { recipient: `@${recipient.name}`,
// Send message to department room dated: new Date(),
const workerDepartment = await models.WorkerDepartment.findById(recipientId, { checkUserStatus: 1,
include: { message: message,
relation: 'department' status: 0,
} attempts: 0
}, myOptions); });
const department = workerDepartment && workerDepartment.department();
const channelName = department && department.chatName;
if (channelName) return true;
return Self.send(ctx, `#${channelName}`, `@${recipient.name}${message}`);
else
return Self.send(ctx, `@${recipient.name}`, message);
} else
return Self.send(ctx, `@${recipient.name}`, message);
}
};
/**
* Returns the current user status on Rocketchat
*
* @param {string} username - The recipient user name
* @return {Promise} - The request promise
*/
Self.getUserStatus = async function getUserStatus(username) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({
data: {
status: 'online'
}
});
});
}
const login = await Self.getServiceAuth();
const options = {
params: {username},
headers: {
'X-Auth-Token': login.auth.token,
'X-User-Id': login.auth.userId
},
};
return axios.get(`${login.api}/users.getStatus`, options);
}; };
}; };

View File

@ -0,0 +1,168 @@
const axios = require('axios');
module.exports = Self => {
Self.remoteMethodCtx('sendQueued', {
description: 'Send a RocketChat message',
accessType: 'WRITE',
accepts: [],
returns: {
type: 'object',
root: true
},
http: {
path: `/sendQueued`,
verb: 'POST'
}
});
Self.sendQueued = async() => {
const models = Self.app.models;
const maxAttempts = 3;
const sentStatus = 1;
const errorStatus = 2;
const chats = await models.Chat.find({
where: {
status: {neq: sentStatus},
attempts: {lt: maxAttempts}
}
});
for (let chat of chats) {
if (chat.checkUserStatus) {
try {
await Self.sendCheckingUserStatus(chat);
await updateChat(chat, sentStatus);
} catch (error) {
await updateChat(chat, errorStatus);
}
} else {
try {
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
await updateChat(chat, sentStatus);
} catch (error) {
await updateChat(chat, errorStatus);
}
}
}
};
/**
* Check user status in Rocket
*
* @param {object} chat - The sender id
* @return {Promise} - The request promise
*/
Self.sendCheckingUserStatus = async function sendCheckingUserStatus(chat) {
const models = Self.app.models;
const recipientName = chat.recipient.slice(1);
const recipient = await models.Account.findOne({
where: {
name: recipientName
}
});
const {data} = await Self.getUserStatus(recipient.name);
if (data) {
if (data.status === 'offline' || data.status === 'busy') {
// Send message to department room
const workerDepartment = await models.WorkerDepartment.findById(recipient.id, {
include: {
relation: 'department'
}
});
const department = workerDepartment && workerDepartment.department();
const channelName = department && department.chatName;
if (channelName)
return Self.sendMessage(chat.senderFk, `#${channelName}`, `@${recipient.name}${message}`);
else
return Self.sendMessage(chat.senderFk, `@${recipient.name}`, chat.message);
} else
return Self.sendMessage(chat.senderFk, `@${recipient.name}`, chat.message);
}
};
/**
* Send a rocket message
*
* @param {object} senderFk - The sender id
* @param {string} recipient - The user (@) or channel (#) to send the message
* @param {string} message - The message to send
* @return {Promise} - The request promise
*/
Self.sendMessage = async function sendMessage(senderFk, recipient, message) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({
statusCode: 200,
message: 'Fake notification sent'
});
});
}
const models = Self.app.models;
const sender = await models.Account.findById(senderFk);
const login = await Self.getServiceAuth();
const avatar = `${login.host}/avatar/${sender.name}`;
const options = {
headers: {
'X-Auth-Token': login.auth.token,
'X-User-Id': login.auth.userId
},
};
return axios.post(`${login.api}/chat.postMessage`, {
'channel': recipient,
'avatar': avatar,
'alias': sender.nickname,
'text': message
}, options);
};
/**
* Update status and attempts of a chat
*
* @param {object} chat - The chat
* @param {string} status - The new status
* @return {Promise} - The request promise
*/
async function updateChat(chat, status) {
return chat.updateAttributes({
status: status,
attempts: ++chat.attempts
});
}
/**
* Returns the current user status on Rocketchat
*
* @param {string} username - The recipient user name
* @return {Promise} - The request promise
*/
Self.getUserStatus = async function getUserStatus(username) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({
data: {
status: 'online'
}
});
});
}
const login = await Self.getServiceAuth();
const options = {
params: {username},
headers: {
'X-Auth-Token': login.auth.token,
'X-User-Id': login.auth.userId
},
};
return axios.get(`${login.api}/users.getStatus`, options);
};
};

View File

@ -1,14 +1,14 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Chat send()', () => { describe('Chat send()', () => {
it('should return a "Fake notification sent" 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 app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(true); expect(response).toEqual(true);
}); });
it('should retrun 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 app.models.Chat.send(ctx, '@salesPerson', 'I changed something');

View File

@ -1,58 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('Chat sendCheckingPresence()', () => { describe('Chat sendCheckingPresence()', () => {
const today = new Date(); it('should return true as response', async() => {
today.setHours(6, 0); const workerId = 1107;
const ctx = {req: {accessToken: {userId: 1}}};
const chatModel = models.Chat;
const departmentId = 23;
const workerId = 1107;
it(`should call to send() method with "@HankPym" as recipient argument`, async() => { let ctx = {req: {accessToken: {userId: 1}}};
spyOn(chatModel, 'send').and.callThrough(); let response = await models.Chat.sendCheckingPresence(ctx, workerId, 'I changed something');
spyOn(chatModel, 'getUserStatus').and.returnValue(
new Promise(resolve => {
return resolve({
data: {
status: 'online'
}
});
})
);
await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something'); expect(response).toEqual(true);
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', 'I changed something');
}); });
it(`should call to send() method with "#cooler" as recipient argument`, async() => { it('should return false as response', async() => {
spyOn(chatModel, 'send').and.callThrough(); const salesPersonId = 18;
spyOn(chatModel, 'getUserStatus').and.returnValue(
new Promise(resolve => {
return resolve({
data: {
status: 'offline'
}
});
})
);
const tx = await models.Claim.beginTransaction({}); let ctx = {req: {accessToken: {userId: 18}}};
let response = await models.Chat.sendCheckingPresence(ctx, salesPersonId, 'I changed something');
try { expect(response).toEqual(false);
const options = {transaction: tx};
const department = await models.Department.findById(departmentId, null, options);
await department.updateAttribute('chatName', 'cooler');
await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something');
expect(chatModel.send).toHaveBeenCalledWith(ctx, '#cooler', '@HankPym ➔ I changed something');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -0,0 +1,41 @@
const models = require('vn-loopback/server/server').models;
describe('Chat sendCheckingPresence()', () => {
const today = new Date();
today.setHours(6, 0);
const chatModel = models.Chat;
it(`should call to sendCheckingUserStatus()`, async() => {
spyOn(chatModel, 'sendCheckingUserStatus').and.callThrough();
const chat = {
checkUserStatus: 1,
status: 0,
attempts: 0
};
await chatModel.destroyAll();
await chatModel.create(chat);
await chatModel.sendQueued();
expect(chatModel.sendCheckingUserStatus).toHaveBeenCalled();
});
it(`should call to sendMessage() method`, async() => {
spyOn(chatModel, 'sendMessage').and.callThrough();
const chat = {
checkUserStatus: 0,
status: 0,
attempts: 0
};
await chatModel.destroyAll();
await chatModel.create(chat);
await chatModel.sendQueued();
expect(chatModel.sendMessage).toHaveBeenCalled();
});
});

View File

@ -1,5 +1,5 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('setSaleQuantity', { Self.remoteMethod('setSaleQuantity', {
description: 'Update sale quantity', description: 'Update sale quantity',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
@ -24,11 +24,13 @@ module.exports = Self => {
} }
}); });
Self.setSaleQuantity = async ctx => { Self.setSaleQuantity = async(saleId, quantity) => {
const args = ctx.args;
const models = Self.app.models; const models = Self.app.models;
const sale = await models.Sale.findById(args.saleId); const sale = await models.Sale.findById(saleId);
return await sale.updateAttribute('quantity', args.quantity); return await sale.updateAttributes({
originalQuantity: sale.quantity,
quantity: quantity
});
}; };
}; };

View File

@ -5,19 +5,12 @@ describe('setSaleQuantity()', () => {
const saleId = 30; const saleId = 30;
const newQuantity = 10; const newQuantity = 10;
const ctx = {
args: {
saleId: saleId,
quantity: newQuantity
}
};
const originalSale = await models.Sale.findById(saleId); const originalSale = await models.Sale.findById(saleId);
await models.Collection.setSaleQuantity(ctx); await models.Collection.setSaleQuantity(saleId, newQuantity);
const updateSale = await models.Sale.findById(saleId); const updateSale = await models.Sale.findById(saleId);
expect(updateSale.quantity).toBeLessThan(originalSale.quantity); expect(updateSale.originalQuantity).toEqual(originalSale.quantity);
expect(updateSale.quantity).toEqual(newQuantity); expect(updateSale.quantity).toEqual(newQuantity);
}); });
}); });

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE bucket INTO TABLE `edi`.`bucket`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE bucket_type INTO TABLE `edi`.`bucket_type`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE `feature` INTO TABLE `edi`.`feature`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE genus INTO TABLE `edi`.`genus`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE item INTO TABLE `edi`.`item`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE `item_feature` INTO TABLE `edi`.`item_feature`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE item_group INTO TABLE `edi`.`item_group`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE plant INTO TABLE `edi`.`plant`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE specie INTO TABLE `edi`.`specie`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE edi.supplier INTO TABLE `edi`.`supplier`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12, @col13, @col14, @col15, @col16, @col17, @col18, @col19, @col20) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12, @col13, @col14, @col15, @col16, @col17, @col18, @col19, @col20)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE `type` INTO TABLE `edi`.`type`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
SET SET

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE `value` INTO TABLE `edi`.`value`
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
SET SET

View File

@ -19,137 +19,218 @@ module.exports = Self => {
Self.updateData = async() => { Self.updateData = async() => {
const models = Self.app.models; const models = Self.app.models;
// Get files checksum
const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig');
const updatableFiles = [];
for (const file of files) {
const fileChecksum = await getChecksum(file);
if (file.checksum != fileChecksum) {
updatableFiles.push({
name: file.name,
checksum: fileChecksum
});
} else
console.debug(`File already updated, skipping...`);
}
if (updatableFiles.length === 0)
return false;
// Download files
const container = await models.TempContainer.container('edi'); const container = await models.TempContainer.container('edi');
const tempPath = path.join(container.client.root, container.name); const tempPath = path.join(container.client.root, container.name);
const [ftpConfig] = await Self.rawSql('SELECT host, user, password FROM edi.ftpConfig');
console.debug(`Openning FTP connection to ${ftpConfig.host}...\n`);
const FtpClient = require('ftps');
const ftpClient = new FtpClient({
host: ftpConfig.host,
username: ftpConfig.user,
password: ftpConfig.password,
procotol: 'ftp'
});
const files = await Self.rawSql('SELECT fileName, toTable, file, updated FROM edi.fileConfig');
let remoteFile; let remoteFile;
let tempDir; let tempDir;
let tempFile; let tempFile;
for (const file of files) {
const fileNames = updatableFiles.map(file => file.name);
const tables = await Self.rawSql(`
SELECT fileName, toTable, file
FROM edi.tableConfig
WHERE file IN (?)`, [fileNames]);
for (const table of tables) {
const fileName = table.file;
console.debug(`Downloading file ${fileName}...`);
remoteFile = `codes/${fileName}.ZIP`;
tempDir = `${tempPath}/${fileName}`;
tempFile = `${tempPath}/${fileName}.zip`;
try { try {
const fileName = file.file; await fs.readFile(tempFile);
console.debug(`Downloading file ${fileName}...`);
remoteFile = `codes/${fileName}.ZIP`;
tempDir = `${tempPath}/${fileName}`;
tempFile = `${tempPath}/${fileName}.zip`;
await extractFile({
ftpClient: ftpClient,
file: file,
paths: {
remoteFile: remoteFile,
tempDir: tempDir,
tempFile: tempFile
}
});
} catch (error) { } catch (error) {
if (fs.existsSync(tempFile)) if (error.code === 'ENOENT') {
await fs.unlink(tempFile); const downloadOutput = await downloadFile(remoteFile, tempFile);
if (downloadOutput.error)
await fs.rmdir(tempDir, {recursive: true}); continue;
console.error(error); }
} }
console.debug(`Extracting file ${fileName}...`);
await extractFile(tempFile, tempDir);
console.debug(`Updating table ${table.toTable}...`);
await dumpData(tempDir, table);
}
// Update files checksum
for (const file of updatableFiles) {
await Self.rawSql(`
UPDATE edi.fileConfig
SET checksum = ?
WHERE name = ?`,
[file.checksum, file.name]);
}
// Clean files
try {
await fs.remove(tempPath);
} catch (error) {
if (error.code !== 'ENOENT')
throw e;
} }
return true; return true;
}; };
async function extractFile({ftpClient, file, paths}) { let ftpClient;
// Download the zip file async function getFtpClient() {
ftpClient.get(paths.remoteFile, paths.tempFile); if (!ftpClient) {
const [ftpConfig] = await Self.rawSql('SELECT host, user, password FROM edi.ftpConfig');
console.debug(`Openning FTP connection to ${ftpConfig.host}...\n`);
// Execute download command const FtpClient = require('ftps');
ftpClient.exec(async(err, response) => {
if (response.error) {
console.debug(`Error downloading file... ${response.error}`);
return;
}
const AdmZip = require('adm-zip'); ftpClient = new FtpClient({
const zip = new AdmZip(paths.tempFile); host: ftpConfig.host,
const entries = zip.getEntries(); username: ftpConfig.user,
password: ftpConfig.password,
procotol: 'ftp'
});
}
zip.extractAllTo(paths.tempDir, false); return ftpClient;
}
if (fs.existsSync(paths.tempFile)) async function getChecksum(file) {
await fs.unlink(paths.tempFile); const ftpClient = await getFtpClient();
console.debug(`Checking checksum for file ${file.name}...`);
await dumpData({file, entries, paths}); ftpClient.cat(`codes/${file.name}.txt`);
await fs.rmdir(paths.tempDir, {recursive: true}); const response = await new Promise((resolve, reject) => {
ftpClient.exec((err, response) => {
if (response.error) {
console.debug(`Error downloading checksum file... ${response.error}`);
reject(err);
}
resolve(response);
});
});
if (response && response.data) {
const fileContents = response.data;
const rows = fileContents.split('\n');
const row = rows[4];
const columns = row.split(/\s+/);
let fileChecksum;
if (file.keyValue)
fileChecksum = columns[1];
if (!file.keyValue)
fileChecksum = columns[0];
return fileChecksum;
}
}
async function downloadFile(remoteFile, tempFile) {
const ftpClient = await getFtpClient();
ftpClient.get(remoteFile, tempFile);
return new Promise((resolve, reject) => {
ftpClient.exec((err, response) => {
if (response.error) {
console.debug(`Error downloading file... ${response.error}`);
reject(err);
}
resolve(response);
});
}); });
} }
async function dumpData({file, entries, paths}) { async function extractFile(tempFile, tempDir) {
const toTable = file.toTable; const JSZip = require('jszip');
const baseName = file.fileName;
for (const zipEntry of entries) { try {
const entryName = zipEntry.entryName; await fs.mkdir(tempDir);
console.log(`Reading file ${entryName}...`); } catch (error) {
if (error.code !== 'EEXIST')
throw e;
}
const startIndex = (entryName.length - 10); const fileStream = await fs.readFile(tempFile);
const endIndex = (entryName.length - 4); if (fileStream) {
const dateString = entryName.substring(startIndex, endIndex); const zip = new JSZip();
const lastUpdated = new Date(); const zipContents = await zip.loadAsync(fileStream);
// Format string date to a date object if (!zipContents) return;
let updated = null;
if (file.updated) { const fileNames = Object.keys(zipContents.files);
updated = new Date(file.updated);
updated.setHours(0, 0, 0, 0); for (const fileName of fileNames) {
const fileContent = await zip.file(fileName).async('nodebuffer');
const dest = path.join(tempDir, fileName);
await fs.writeFile(dest, fileContent);
} }
lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`);
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1);
lastUpdated.setDate(dateString.substring(0, 2));
lastUpdated.setHours(0, 0, 0, 0);
if (updated && lastUpdated <= updated) {
console.debug(`Table ${toTable} already updated, skipping...`);
continue;
}
console.log('Dumping data...');
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
const sqlTemplate = fs.readFileSync(templatePath, 'utf8');
const rawPath = path.join(paths.tempDir, entryName);
try {
const tx = await Self.beginTransaction({});
const options = {transaction: tx};
await Self.rawSql(`DELETE FROM edi.${toTable}`, null, options);
await Self.rawSql(sqlTemplate, [rawPath], options);
await Self.rawSql(`
UPDATE edi.fileConfig
SET updated = ?
WHERE fileName = ?
`, [lastUpdated, baseName], options);
tx.commit();
} catch (error) {
tx.rollback();
throw error;
}
console.log(`Updated table ${toTable}\n`);
} }
} }
async function dumpData(tempDir, table) {
const toTable = table.toTable;
const baseName = table.fileName;
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const tableName = `edi.${toTable}`;
await Self.rawSql(`DELETE FROM ??`, [tableName], options);
const dirFiles = await fs.readdir(tempDir);
const files = dirFiles.filter(file => file.startsWith(baseName));
for (const file of files) {
console.log(`Dumping data from file ${file}...`);
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
const sqlTemplate = await fs.readFile(templatePath, 'utf8');
const filePath = path.join(tempDir, file);
await Self.rawSql(sqlTemplate, [filePath], options);
await Self.rawSql(`
UPDATE edi.tableConfig
SET updated = ?
WHERE fileName = ?
`, [new Date(), baseName], options);
}
tx.commit();
} catch (error) {
tx.rollback();
throw error;
}
console.log(`Updated table ${toTable}\n`);
}
}; };

View File

@ -68,6 +68,12 @@
"Language": { "Language": {
"dataSource": "vn" "dataSource": "vn"
}, },
"MachineWorker": {
"dataSource": "vn"
},
"MobileAppVersionControl": {
"dataSource": "vn"
},
"Module": { "Module": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -28,6 +28,9 @@
}, },
"maxAmount": { "maxAmount": {
"type": "number" "type": "number"
},
"daysInFuture": {
"type": "number"
} }
}, },
"acls": [{ "acls": [{

View File

@ -0,0 +1,24 @@
{
"name": "MobileAppVersionControl",
"base": "VnModel",
"options": {
"mysql": {
"table": "vn.mobileAppVersionControl"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"appName": {
"type": "string"
},
"version": {
"type": "string"
},
"isVersionCritical": {
"type": "boolean"
}
}
}

View File

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

View File

@ -1,6 +1,39 @@
{ {
"name": "Chat", "name": "Chat",
"base": "VnModel", "base": "VnModel",
"options": {
"mysql": {
"table": "chat"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"senderFk": {
"type": "number"
},
"recipient": {
"type": "string"
},
"dated": {
"type": "date"
},
"checkUserStatus": {
"type": "boolean"
},
"message": {
"type": "string"
},
"status": {
"type": "string"
},
"attempts": {
"type": "number"
}
},
"acls": [{ "acls": [{
"property": "validations", "property": "validations",
"accessType": "EXECUTE", "accessType": "EXECUTE",

View File

@ -0,0 +1,33 @@
{
"name": "MachineWorker",
"base": "VnModel",
"options": {
"mysql": {
"table": "vn.machineWorker"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"workerFk": {
"type": "number"
},
"machineFk": {
"type": "number"
},
"inTime": {
"type": "date",
"mysql": {
"columnName": "inTimed"
}
},
"outTime": {
"type": "date",
"mysql": {
"columnName": "outTimed"
}
}
}
}

View File

@ -31,11 +31,13 @@ COPY \
import-changes.sh \ import-changes.sh \
config.ini \ config.ini \
dump/mysqlPlugins.sql \ dump/mysqlPlugins.sql \
dump/mockDate.sql \
dump/structure.sql \ dump/structure.sql \
dump/dumpedFixtures.sql \ dump/dumpedFixtures.sql \
./ ./
RUN gosu mysql docker-init.sh \ RUN gosu mysql docker-init.sh \
&& docker-dump.sh mysqlPlugins \ && docker-dump.sh mysqlPlugins \
&& docker-dump.sh mockDate \
&& docker-dump.sh structure \ && docker-dump.sh structure \
&& docker-dump.sh dumpedFixtures \ && docker-dump.sh dumpedFixtures \
&& gosu mysql docker-temp-stop.sh && gosu mysql docker-temp-stop.sh

View File

@ -1,14 +0,0 @@
create table `vn`.`invoiceOut_queue`
(
invoiceFk int(10) unsigned not null,
queued datetime default now() not null,
printed datetime null,
`status` VARCHAR(50) default '' null,
constraint invoiceOut_queue_pk
primary key (invoiceFk),
constraint invoiceOut_queue_invoiceOut_id_fk
foreign key (invoiceFk) references invoiceOut (id)
on update cascade on delete cascade
)
comment 'Queue for PDF invoicing';

View File

@ -1,113 +0,0 @@
DROP PROCEDURE IF EXISTS vn.ticket_doRefund;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(IN vOriginTicket INT, OUT vNewTicket INT)
BEGIN
DECLARE vDone BIT DEFAULT 0;
DECLARE vCustomer MEDIUMINT;
DECLARE vWarehouse TINYINT;
DECLARE vCompany MEDIUMINT;
DECLARE vAddress MEDIUMINT;
DECLARE vRefundAgencyMode INT;
DECLARE vItemFk INT;
DECLARE vQuantity DECIMAL (10,2);
DECLARE vConcept VARCHAR(50);
DECLARE vPrice DECIMAL (10,2);
DECLARE vDiscount TINYINT;
DECLARE vSaleNew INT;
DECLARE vSaleMain INT;
DECLARE vZoneFk INT;
DECLARE vDescription VARCHAR(50);
DECLARE vTaxClassFk INT;
DECLARE vTicketServiceTypeFk INT;
DECLARE cSales CURSOR FOR
SELECT *
FROM tmp.sale;
DECLARE cTicketServices CURSOR FOR
SELECT *
FROM tmp.ticketService;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = 1;
SELECT id INTO vRefundAgencyMode
FROM agencyMode WHERE `name` = 'ABONO';
SELECT clientFk, warehouseFk, companyFk, addressFk
INTO vCustomer, vWarehouse, vCompany, vAddress
FROM ticket
WHERE id = vOriginTicket;
SELECT id INTO vZoneFk
FROM zone WHERE agencyModeFk = vRefundAgencyMode
LIMIT 1;
INSERT INTO vn.ticket (
clientFk,
shipped,
addressFk,
agencyModeFk,
nickname,
warehouseFk,
companyFk,
landed,
zoneFk
)
SELECT
vCustomer,
CURDATE(),
vAddress,
vRefundAgencyMode,
a.nickname,
vWarehouse,
vCompany,
CURDATE(),
vZoneFk
FROM address a
WHERE a.id = vAddress;
SET vNewTicket = LAST_INSERT_ID();
SET vDone := 0;
OPEN cSales;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
WHILE NOT vDone DO
INSERT INTO vn.sale(ticketFk, itemFk, quantity, concept, price, discount)
VALUES( vNewTicket, vItemFk, vQuantity, vConcept, vPrice, vDiscount );
SET vSaleNew = LAST_INSERT_ID();
INSERT INTO vn.saleComponent(saleFk,componentFk,`value`)
SELECT vSaleNew,componentFk,`value`
FROM vn.saleComponent
WHERE saleFk = vSaleMain;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
END WHILE;
CLOSE cSales;
SET vDone := 0;
OPEN cTicketServices;
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
WHILE NOT vDone DO
INSERT INTO vn.ticketService(description, quantity, price, taxClassFk, ticketFk, ticketServiceTypeFk)
VALUES(vDescription, vQuantity, vPrice, vTaxClassFk, vNewTicket, vTicketServiceTypeFk);
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
END WHILE;
CLOSE cTicketServices;
INSERT INTO vn.ticketRefund(refundTicketFk, originalTicketFk)
VALUES(vNewTicket, vOriginTicket);
END$$
DELIMITER ;

View File

@ -3,7 +3,7 @@ CREATE TABLE `vn`.`clientUnpaid` (
`dated` date NOT NULL, `dated` date NOT NULL,
`amount` double DEFAULT 0, `amount` double DEFAULT 0,
PRIMARY KEY (`clientFk`), PRIMARY KEY (`clientFk`),
CONSTRAINT `clientUnpaid_clientFk` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON UPDATE CASCADE CONSTRAINT `clientUnpaid_clientFk` FOREIGN KEY (`clientFk`) REFERENCES `vn`.`client` (`id`) ON UPDATE CASCADE
); );
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)

View File

@ -0,0 +1,8 @@
CREATE TABLE `vn`.`invoiceOut_queue` (
`invoiceFk` int(10) unsigned not null,
`queued` datetime default now() not null,
`printed` datetime null,
`status` VARCHAR(50) DEFAULT '' NULL,
CONSTRAINT `invoiceOut_queue_pk` PRIMARY KEY (`invoiceFk`),
CONSTRAINT `invoiceOut_queue_invoiceOut_id_fk` FOREIGN KEY (`invoiceFk`) REFERENCES `vn`.`invoiceOut` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
) comment 'Queue for PDF invoicing';

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`accountingType` ADD daysInFuture INT NULL;
ALTER TABLE `vn`.`accountingType` MODIFY COLUMN daysInFuture int(11) DEFAULT 0 NULL;

View File

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

View File

@ -0,0 +1,14 @@
CREATE TABLE `vn`.`mdbBranch` (
`name` VARCHAR(255),
PRIMARY KEY(`name`)
);
CREATE TABLE `vn`.`mdbVersion` (
`app` VARCHAR(255) NOT NULL,
`branchFk` VARCHAR(255) NOT NULL,
`version` INT,
CONSTRAINT `mdbVersion_branchFk` FOREIGN KEY (`branchFk`) REFERENCES `vn`.`mdbBranch` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES('MdbVersion', '*', '*', 'ALLOW', 'ROLE', 'developer');

View File

@ -0,0 +1,13 @@
CREATE TABLE `vn`.`chat` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`senderFk` int(10) unsigned DEFAULT NULL,
`recipient` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`dated` date DEFAULT NULL,
`checkUserStatus` tinyint(1) DEFAULT NULL,
`message` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,
`status` tinyint(1) DEFAULT NULL,
`attempts` int(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `chat_FK` (`senderFk`),
CONSTRAINT `chat_FK` FOREIGN KEY (`senderFk`) REFERENCES `account`.`user` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`defaultViewConfig` (tableCode, columns)
VALUES ('clientsDetail', '{"id":true,"phone":true,"city":true,"socialName":true,"salesPersonFk":true,"email":true,"name":false,"fi":false,"credit":false,"creditInsurance":false,"mobile":false,"street":false,"countryFk":false,"provinceFk":false,"postcode":false,"created":false,"businessTypeFk":false,"payMethodFk":false,"sageTaxTypeFk":false,"sageTransactionTypeFk":false,"isActive":false,"isVies":false,"isTaxDataChecked":false,"isEqualizated":false,"isFreezed":false,"hasToInvoice":false,"hasToInvoiceByAddress":false,"isToBeMailed":false,"hasLcr":false,"hasCoreVnl":false,"hasSepaVnl":false}');

View File

@ -0,0 +1,127 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_doRefund`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(OUT vNewTicket INT)
BEGIN
/**
* Crea un ticket de abono a partir de tmp.sale y/o tmp.ticketService
*
* @return vNewTicket
*/
DECLARE vDone BIT DEFAULT 0;
DECLARE vClientFk MEDIUMINT;
DECLARE vWarehouse TINYINT;
DECLARE vCompany MEDIUMINT;
DECLARE vAddress MEDIUMINT;
DECLARE vRefundAgencyMode INT;
DECLARE vItemFk INT;
DECLARE vQuantity DECIMAL (10,2);
DECLARE vConcept VARCHAR(50);
DECLARE vPrice DECIMAL (10,2);
DECLARE vDiscount TINYINT;
DECLARE vSaleNew INT;
DECLARE vSaleMain INT;
DECLARE vZoneFk INT;
DECLARE vDescription VARCHAR(50);
DECLARE vTaxClassFk INT;
DECLARE vTicketServiceTypeFk INT;
DECLARE vOriginTicket INT;
DECLARE cSales CURSOR FOR
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
FROM tmp.sale s;
DECLARE cTicketServices CURSOR FOR
SELECT ts.description, - ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk
FROM tmp.ticketService ts;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
SELECT sub.ticketFk INTO vOriginTicket
FROM (
SELECT s.ticketFk
FROM tmp.sale s
UNION ALL
SELECT ts.ticketFk
FROM tmp.ticketService ts
) sub
LIMIT 1;
SELECT id INTO vRefundAgencyMode
FROM agencyMode WHERE `name` = 'ABONO';
SELECT clientFk, warehouseFk, companyFk, addressFk
INTO vClientFk, vWarehouse, vCompany, vAddress
FROM ticket
WHERE id = vOriginTicket;
SELECT id INTO vZoneFk
FROM zone WHERE agencyModeFk = vRefundAgencyMode
LIMIT 1;
INSERT INTO vn.ticket (
clientFk,
shipped,
addressFk,
agencyModeFk,
nickname,
warehouseFk,
companyFk,
landed,
zoneFk
)
SELECT
vClientFk,
CURDATE(),
vAddress,
vRefundAgencyMode,
a.nickname,
vWarehouse,
vCompany,
CURDATE(),
vZoneFk
FROM address a
WHERE a.id = vAddress;
SET vNewTicket = LAST_INSERT_ID();
SET vDone := FALSE;
OPEN cSales;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
WHILE NOT vDone DO
INSERT INTO vn.sale(ticketFk, itemFk, quantity, concept, price, discount)
VALUES( vNewTicket, vItemFk, vQuantity, vConcept, vPrice, vDiscount );
SET vSaleNew = LAST_INSERT_ID();
INSERT INTO vn.saleComponent(saleFk,componentFk,`value`)
SELECT vSaleNew,componentFk,`value`
FROM vn.saleComponent
WHERE saleFk = vSaleMain;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
END WHILE;
CLOSE cSales;
SET vDone := FALSE;
OPEN cTicketServices;
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
WHILE NOT vDone DO
INSERT INTO vn.ticketService(description, quantity, price, taxClassFk, ticketFk, ticketServiceTypeFk)
VALUES(vDescription, vQuantity, vPrice, vTaxClassFk, vNewTicket, vTicketServiceTypeFk);
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
END WHILE;
CLOSE cTicketServices;
INSERT INTO vn.ticketRefund(refundTicketFk, originalTicketFk)
VALUES(vNewTicket, vOriginTicket);
END$$
DELIMITER ;

View File

@ -0,0 +1 @@
RENAME TABLE `edi`.`fileConfig` to `edi`.`tableConfig`;

View File

@ -0,0 +1,22 @@
CREATE TABLE `edi`.`fileConfig`
(
name varchar(25) NOT NULL,
checksum text NULL,
keyValue tinyint(1) default true NOT NULL,
constraint fileConfig_pk
primary key (name)
);
create unique index fileConfig_name_uindex
on `edi`.`fileConfig` (name);
INSERT INTO `edi`.`fileConfig` (name, checksum, keyValue)
VALUES ('FEC010104', null, 0);
INSERT INTO `edi`.`fileConfig` (name, checksum, keyValue)
VALUES ('VBN020101', null, 1);
INSERT INTO `edi`.`fileConfig` (name, checksum, keyValue)
VALUES ('florecompc2', null, 1);

View File

@ -0,0 +1,8 @@
ALTER TABLE `vn`.`creditInsurance` ADD creditClassificationFk int(11) NULL;
UPDATE `vn`.`creditInsurance` AS `destiny`
SET `destiny`.`creditClassificationFk`= (SELECT creditClassification FROM `vn`.`creditInsurance` AS `origin` WHERE `origin`.id = `destiny`.id);
ALTER TABLE `vn`.`creditInsurance`
ADD CONSTRAINT `creditInsurance_creditClassificationFk` FOREIGN KEY (`creditClassificationFk`)
REFERENCES `vn`.`creditClassification` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,11 @@
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`creditInsurance_beforeInsert`
BEFORE INSERT ON `creditInsurance`
FOR EACH ROW
BEGIN
IF NEW.creditClassificationFk THEN
SET NEW.creditClassification = NEW.creditClassificationFk;
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES
('Client','setPassword','WRITE','ALLOW','ROLE','salesPerson'),
('Client','updateUser','WRITE','ALLOW','ROLE','salesPerson');

File diff suppressed because it is too large Load Diff

43
db/dump/mockDate.sql Normal file
View File

@ -0,0 +1,43 @@
CREATE SCHEMA IF NOT EXISTS `util`;
USE `util`;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`mockedDate`;
CREATE FUNCTION `util`.`mockedDate`()
RETURNS DATETIME
DETERMINISTIC
BEGIN
RETURN NOW();
-- '2022-01-19 08:00:00'
END ;;
DELIMITER ;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`VN_CURDATE`;
CREATE FUNCTION `util`.`VN_CURDATE`()
RETURNS DATE
DETERMINISTIC
BEGIN
RETURN DATE(mockedDate());
END ;;
DELIMITER ;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`VN_CURTIME`;
CREATE FUNCTION `util`.`VN_CURTIME`()
RETURNS TIME
DETERMINISTIC
BEGIN
RETURN TIME(mockedDate());
END ;;
DELIMITER ;
DELIMITER ;;
DROP FUNCTION IF EXISTS `util`.`VN_NOW`;
CREATE FUNCTION `util`.`VN_NOW`()
RETURNS DATETIME
DETERMINISTIC
BEGIN
RETURN mockedDate();
END ;;
DELIMITER ;

File diff suppressed because it is too large Load Diff

View File

@ -60,7 +60,6 @@ IGNORETABLES=(
--ignore-table=vn.plantpassportAuthority__ --ignore-table=vn.plantpassportAuthority__
--ignore-table=vn.preparationException --ignore-table=vn.preparationException
--ignore-table=vn.priceFixed__ --ignore-table=vn.priceFixed__
--ignore-table=vn.printer
--ignore-table=vn.printingQueue --ignore-table=vn.printingQueue
--ignore-table=vn.printServerQueue__ --ignore-table=vn.printServerQueue__
--ignore-table=vn.promissoryNote --ignore-table=vn.promissoryNote
@ -97,5 +96,12 @@ mysqldump \
--databases \ --databases \
${SCHEMAS[@]} \ ${SCHEMAS[@]} \
${IGNORETABLES[@]} \ ${IGNORETABLES[@]} \
| sed 's/\bCURDATE\b/vn.VN_CURDATE/ig'\
| sed 's/\bCURTIME\b/vn.VN_CURTIME/ig' \
| sed 's/\bNOW\b/vn.VN_NOW/ig' \
| sed 's/\bCURRENT_DATE\b/vn.VN_CURDATE/ig' \
| sed 's/\bCURRENT_TIME\b/vn.VN_CURTIME/ig' \
| sed 's/\bLOCALTIME\b/vn.VN_NOW/ig' \
| sed 's/\bLOCALTIMESTAMP\b/vn.VN_NOW/ig' \
| sed 's/ AUTO_INCREMENT=[0-9]* //g' \ | sed 's/ AUTO_INCREMENT=[0-9]* //g' \
> dump/structure.sql > dump/structure.sql

View File

@ -5,8 +5,9 @@ describe('zone zone_getLanded()', () => {
it(`should return data for a shipped in the past`, async() => { it(`should return data for a shipped in the past`, async() => {
let stmts = []; let stmts = [];
let stmt; let stmt;
stmts.push('START TRANSACTION'); stmts.push('START TRANSACTION');
const date = new Date();
date.setHours(0, 0, 0, 0);
let params = { let params = {
addressFk: 121, addressFk: 121,
@ -14,7 +15,8 @@ describe('zone zone_getLanded()', () => {
warehouseFk: 1, warehouseFk: 1,
showExpiredZones: true}; showExpiredZones: true};
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL -1 DAY), ?, ?, ?, ?)', [ stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(?, INTERVAL -1 DAY), ?, ?, ?, ?)', [
date,
params.addressFk, params.addressFk,
params.agencyModeFk, params.agencyModeFk,
params.warehouseFk, params.warehouseFk,
@ -38,6 +40,8 @@ describe('zone zone_getLanded()', () => {
it(`should return data for a shipped tomorrow`, async() => { it(`should return data for a shipped tomorrow`, async() => {
let stmts = []; let stmts = [];
let stmt; let stmt;
const date = new Date();
date.setHours(0, 0, 0, 0);
stmts.push('START TRANSACTION'); stmts.push('START TRANSACTION');
@ -47,7 +51,8 @@ describe('zone zone_getLanded()', () => {
warehouseFk: 1, warehouseFk: 1,
showExpiredZones: false}; showExpiredZones: false};
stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(CURDATE(), INTERVAL +2 DAY), ?, ?, ?, ?)', [ stmt = new ParameterizedSQL('CALL zone_getLanded(DATE_ADD(?, INTERVAL +2 DAY), ?, ?, ?, ?)', [
date,
params.addressFk, params.addressFk,
params.agencyModeFk, params.agencyModeFk,
params.warehouseFk, params.warehouseFk,

View File

@ -32,6 +32,7 @@ services:
- /mnt/appdata/pdfs:/var/lib/salix/pdfs - /mnt/appdata/pdfs:/var/lib/salix/pdfs
- /mnt/appdata/dms:/var/lib/salix/dms - /mnt/appdata/dms:/var/lib/salix/dms
- /mnt/appdata/image:/var/lib/salix/image - /mnt/appdata/image:/var/lib/salix/image
- /mnt/appdata/vn-access:/var/lib/salix/vn-access
deploy: deploy:
replicas: ${BACK_REPLICAS:?} replicas: ${BACK_REPLICAS:?}
placement: placement:

View File

@ -102,6 +102,47 @@ export default {
email: 'vn-user-mail-forwarding vn-textfield[ng-model="data.forwardTo"]', email: 'vn-user-mail-forwarding vn-textfield[ng-model="data.forwardTo"]',
save: 'vn-user-mail-forwarding vn-submit' save: 'vn-user-mail-forwarding vn-submit'
}, },
accountAcl: {
addAcl: 'vn-acl-index button vn-icon[icon="add"]',
thirdAcl: 'vn-acl-index vn-list> a:nth-child(3)',
deleteThirdAcl: 'vn-acl-index vn-list > a:nth-child(3) > vn-item-section > vn-icon-button[icon="delete"]',
role: 'vn-acl-create vn-autocomplete[ng-model="$ctrl.acl.principalId"]',
model: 'vn-acl-create vn-autocomplete[ng-model="$ctrl.acl.model"]',
property: 'vn-acl-create vn-autocomplete[ng-model="$ctrl.acl.property"]',
accessType: 'vn-acl-create vn-autocomplete[ng-model="$ctrl.acl.accessType"]',
permission: 'vn-acl-create vn-autocomplete[ng-model="$ctrl.acl.permission"]',
save: 'vn-acl-create vn-submit'
},
accountConnections: {
firstConnection: 'vn-connections vn-list > a:nth-child(1)',
deleteFirstConnection: 'vn-connections vn-list > a:nth-child(1) > vn-item-section > vn-icon-button[icon="exit_to_app"]'
},
accountAccounts: {
syncRoles: 'vn-account-accounts vn-button[label="Synchronize roles"]',
syncUser: 'vn-account-accounts vn-button[label="Synchronize user"]',
syncAll: 'vn-account-accounts vn-button[label="Synchronize all"]',
syncUserName: 'vn-textfield[ng-model="$ctrl.syncUser"]',
syncUserPassword: 'vn-textfield[ng-model="$ctrl.syncPassword"]',
buttonAccept: 'button[response="accept"]'
},
accountLdap: {
checkEnable: 'vn-account-ldap vn-check[ng-model="watcher.hasData"]',
server: 'vn-account-ldap vn-textfield[ng-model="$ctrl.config.server"]',
rdn: 'vn-account-ldap vn-textfield[ng-model="$ctrl.config.rdn"]',
password: 'vn-account-ldap vn-textfield[ng-model="$ctrl.config.password"]',
userDn: 'vn-account-ldap vn-textfield[ng-model="$ctrl.config.userDn"]',
groupDn: 'vn-account-ldap vn-textfield[ng-model="$ctrl.config.groupDn"]',
save: 'vn-account-ldap vn-submit'
},
accountSamba: {
checkEnable: 'vn-account-samba vn-check[ng-model="watcher.hasData"]',
adDomain: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adDomain"]',
adController: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adController"]',
adUser: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adUser"]',
adPassword: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adPassword"]',
verifyCert: 'vn-account-samba vn-check[ng-model="$ctrl.config.verifyCert"]',
save: 'vn-account-samba vn-submit'
},
clientsIndex: { clientsIndex: {
createClientButton: `vn-float-button` createClientButton: `vn-float-button`
}, },

View File

@ -2,6 +2,7 @@ import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Client Edit web access path', () => { describe('Client Edit web access path', () => {
pending('#4170 e2e account descriptor');
let browser; let browser;
let page; let page;
beforeAll(async() => { beforeAll(async() => {

View File

@ -123,19 +123,19 @@ describe('Client lock verified data path', () => {
await page.accessToSection('client.card.fiscalData'); await page.accessToSection('client.card.fiscalData');
}, 20000); }, 20000);
it('should confirm verified data button is disabled for salesAssistant', async() => { it('should confirm verified data button is enabled for salesAssistant', async() => {
const isDisabled = await page.isDisabled(selectors.clientFiscalData.verifiedDataCheckbox); const isDisabled = await page.isDisabled(selectors.clientFiscalData.verifiedDataCheckbox);
expect(isDisabled).toBeTrue(); expect(isDisabled).toBeFalsy();
}); });
it('should return error when edit the social name', async() => { it('should now edit the social name', async() => {
await page.clearInput(selectors.clientFiscalData.socialName); await page.clearInput(selectors.clientFiscalData.socialName);
await page.write(selectors.clientFiscalData.socialName, 'new social name edition'); await page.write(selectors.clientFiscalData.socialName, 'new social name edition');
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toContain(`Not enough privileges to edit a client with verified data`); expect(message.text).toContain(`Data saved!`);
}); });
it('should now confirm the social name have been edited once and for all', async() => { it('should now confirm the social name have been edited once and for all', async() => {

View File

@ -28,12 +28,12 @@ describe('Client defaulter path', () => {
const salesPersonName = const salesPersonName =
await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText'); await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText');
expect(clientName).toEqual('Ororo Munroe'); expect(clientName).toEqual('Bruce Banner');
expect(salesPersonName).toEqual('salesPersonNick'); expect(salesPersonName).toEqual('developer');
}); });
it('should first observation not changed', async() => { it('should first observation not changed', async() => {
const expectedObservation = 'Madness, as you know, is like gravity, all it takes is a little push'; const expectedObservation = 'Meeting with Black Widow 21st 9am';
const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value');
expect(result).toContain(expectedObservation); expect(result).toContain(expectedObservation);

View File

@ -0,0 +1,60 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Account ACL path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('developer', 'account');
await page.accessToSection('account.acl');
});
afterAll(async() => {
await browser.close();
});
it('should go to create new acl', async() => {
await page.waitToClick(selectors.accountAcl.addAcl);
await page.waitForState('account.acl.create');
});
it('should create new acl', async() => {
await page.autocompleteSearch(selectors.accountAcl.role, 'sysadmin');
await page.autocompleteSearch(selectors.accountAcl.model, 'UserAccount');
await page.autocompleteSearch(selectors.accountAcl.accessType, '*');
await page.autocompleteSearch(selectors.accountAcl.permission, 'ALLOW');
await page.waitToClick(selectors.accountAcl.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should navigate to edit', async() => {
await page.doSearch();
await page.waitToClick(selectors.accountAcl.thirdAcl);
await page.waitForState('account.acl.edit');
});
it('should edit the third acl', async() => {
await page.autocompleteSearch(selectors.accountAcl.model, 'Supplier');
await page.autocompleteSearch(selectors.accountAcl.accessType, 'READ');
await page.waitToClick(selectors.accountAcl.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should delete the third result', async() => {
const result = await page.waitToGetProperty(selectors.accountAcl.thirdAcl, 'innerText');
await page.waitToClick(selectors.accountAcl.deleteThirdAcl);
await page.waitToClick(selectors.globalItems.acceptButton);
const message = await page.waitForSnackbar();
const newResult = await page.waitToGetProperty(selectors.accountAcl.thirdAcl, 'innerText');
expect(message.text).toContain('ACL removed');
expect(result).not.toEqual(newResult);
});
});

View File

@ -0,0 +1,33 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Account Connections path', () => {
let browser;
let page;
const account = 'sysadmin';
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule(account, 'account');
await page.accessToSection('account.connections');
});
afterAll(async() => {
await browser.close();
});
it('should check this is the last connection', async() => {
const firstResult = await page.waitToGetProperty(selectors.accountConnections.firstConnection, 'innerText');
expect(firstResult).toContain(account);
});
it('should kill this connection and then get redirected to the login page', async() => {
await page.waitToClick(selectors.accountConnections.deleteFirstConnection);
await page.waitToClick(selectors.globalItems.acceptButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Your session has expired, please login again');
});
});

View File

@ -0,0 +1,49 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Account Accounts path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('sysadmin', 'account');
await page.accessToSection('account.accounts');
});
afterAll(async() => {
await browser.close();
});
it('should sync roles', async() => {
await page.waitToClick(selectors.accountAccounts.syncRoles);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Roles synchronized!');
});
it('should sync user', async() => {
await page.waitToClick(selectors.accountAccounts.syncUser);
await page.write(selectors.accountAccounts.syncUserName, 'sysadmin');
await page.write(selectors.accountAccounts.syncUserPassword, 'nightmare');
await page.waitToClick(selectors.accountAccounts.buttonAccept);
const message = await page.waitForSnackbar();
expect(message.text).toContain('User synchronized!');
});
it('should relogin', async() => {
await page.loginAndModule('sysadmin', 'account');
await page.accessToSection('account.accounts');
});
it('should sync all', async() => {
await page.waitToClick(selectors.accountAccounts.syncAll);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Synchronizing in the background');
});
});

View File

@ -0,0 +1,32 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Account LDAP path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('sysadmin', 'account');
await page.accessToSection('account.ldap');
});
afterAll(async() => {
await browser.close();
});
it('should set data and save', async() => {
await page.waitToClick(selectors.accountLdap.checkEnable);
await page.write(selectors.accountLdap.server, '1234');
await page.write(selectors.accountLdap.rdn, '1234');
await page.write(selectors.accountLdap.password, 'nightmare');
await page.write(selectors.accountLdap.userDn, 'sysadmin');
await page.write(selectors.accountLdap.groupDn, '1234');
await page.waitToClick(selectors.accountLdap.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -0,0 +1,32 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Account Samba path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('sysadmin', 'account');
await page.accessToSection('account.samba');
});
afterAll(async() => {
await browser.close();
});
it('should set data and save', async() => {
await page.waitToClick(selectors.accountSamba.checkEnable);
await page.write(selectors.accountSamba.adDomain, '1234');
await page.write(selectors.accountSamba.adController, '1234');
await page.write(selectors.accountSamba.adUser, 'nightmare');
await page.write(selectors.accountSamba.adPassword, 'sysadmin');
await page.waitToClick(selectors.accountSamba.verifyCert);
await page.waitToClick(selectors.accountSamba.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -146,16 +146,17 @@ export default class MultiCheck extends FormInput {
if (!this.model || !this.model.data) return; if (!this.model || !this.model.data) return;
const data = this.model.data; const data = this.model.data;
const modelParams = this.model.userParams;
const params = { const params = {
filter: { filter: {
modelParams: modelParams,
limit: null limit: null
} }
}; };
if (this.model.userFilter)
Object.assign(params.filter, this.model.userFilter);
if (this.model.userParams)
Object.assign(params, this.model.userParams);
this.rows = data.length; this.rows = data.length;
this.$http.get(this.model.url, {params}) this.$http.get(this.model.url, {params})
.then(res => { .then(res => {
this.allRowsCount = res.data.length; this.allRowsCount = res.data.length;

View File

@ -46,11 +46,13 @@
</div> </div>
</vn-horizontal> </vn-horizontal>
<div id="table"></div> <div id="table"></div>
<vn-pagination <div ng-transclude="pagination">
ng-if="$ctrl.model" <vn-pagination
model="$ctrl.model" ng-if="$ctrl.model"
class="vn-pt-md"> model="$ctrl.model"
</vn-pagination> class="vn-pt-md">
</vn-pagination>
</div>
</div> </div>
<vn-confirm <vn-confirm

View File

@ -318,6 +318,8 @@ export default class SmartTable extends Component {
for (let column of columns) { for (let column of columns) {
const field = column.getAttribute('field'); const field = column.getAttribute('field');
const cell = document.createElement('td'); const cell = document.createElement('td');
cell.setAttribute('centered', '');
if (field) { if (field) {
let input; let input;
let options; let options;
@ -331,6 +333,15 @@ export default class SmartTable extends Component {
continue; continue;
} }
input = this.$compile(`
<vn-textfield
class="dense"
name="${field}"
ng-model="searchProps['${field}']"
ng-keydown="$ctrl.searchWithEvent($event, '${field}')"
clear-disabled="true"
/>`)(this.$inputsScope);
if (options && options.autocomplete) { if (options && options.autocomplete) {
let props = ``; let props = ``;
@ -346,16 +357,29 @@ export default class SmartTable extends Component {
on-change="$ctrl.searchByColumn('${field}')" on-change="$ctrl.searchByColumn('${field}')"
clear-disabled="true" clear-disabled="true"
/>`)(this.$inputsScope); />`)(this.$inputsScope);
} else { }
if (options && options.checkbox) {
input = this.$compile(` input = this.$compile(`
<vn-textfield <vn-check
class="dense" class="dense"
name="${field}" name="${field}"
ng-model="searchProps['${field}']" ng-model="searchProps['${field}']"
ng-keydown="$ctrl.searchWithEvent($event, '${field}')" on-change="$ctrl.searchByColumn('${field}')"
clear-disabled="true" triple-state="true"
/>`)(this.$inputsScope); />`)(this.$inputsScope);
} }
if (options && options.datepicker) {
input = this.$compile(`
<vn-date-picker
class="dense"
name="${field}"
ng-model="searchProps['${field}']"
on-change="$ctrl.searchByColumn('${field}')"
/>`)(this.$inputsScope);
}
cell.appendChild(input[0]); cell.appendChild(input[0]);
} }
searchRow.appendChild(cell); searchRow.appendChild(cell);
@ -372,13 +396,12 @@ export default class SmartTable extends Component {
searchByColumn(field) { searchByColumn(field) {
const searchCriteria = this.$inputsScope.searchProps[field]; const searchCriteria = this.$inputsScope.searchProps[field];
const emptySearch = searchCriteria == '' || null; const emptySearch = searchCriteria === '' || searchCriteria == null;
const filters = this.filterSanitizer(field); const filters = this.filterSanitizer(field);
if (filters && filters.userFilter) if (filters && filters.userFilter)
this.model.userFilter = filters.userFilter; this.model.userFilter = filters.userFilter;
if (!emptySearch) if (!emptySearch)
this.addFilter(field, this.$inputsScope.searchProps[field]); this.addFilter(field, this.$inputsScope.searchProps[field]);
else this.model.refresh(); else this.model.refresh();
@ -497,7 +520,8 @@ ngModule.vnComponent('smartTable', {
controller: SmartTable, controller: SmartTable,
transclude: { transclude: {
table: '?slotTable', table: '?slotTable',
actions: '?slotActions' actions: '?slotActions',
pagination: '?slotPagination'
}, },
bindings: { bindings: {
model: '<?', model: '<?',

View File

@ -15,8 +15,9 @@ export default function currency($translate) {
maximumFractionDigits: fractionSize maximumFractionDigits: fractionSize
}; };
const lang = $translate.use() == 'es' ? 'de' : $translate.use();
if (typeof input == 'number') { if (typeof input == 'number') {
return new Intl.NumberFormat($translate.use(), options) return new Intl.NumberFormat(lang, options)
.format(input); .format(input);
} }

View File

@ -85,7 +85,6 @@
} }
.icon-bucket:before { .icon-bucket:before {
content: "\e97a"; content: "\e97a";
color: #000;
} }
.icon-buscaman:before { .icon-buscaman:before {
content: "\e93b"; content: "\e93b";
@ -95,32 +94,26 @@
} }
.icon-calc_volum .path1:before { .icon-calc_volum .path1:before {
content: "\e915"; content: "\e915";
color: rgb(0, 0, 0);
} }
.icon-calc_volum .path2:before { .icon-calc_volum .path2:before {
content: "\e916"; content: "\e916";
margin-left: -1em; margin-left: -1em;
color: rgb(0, 0, 0);
} }
.icon-calc_volum .path3:before { .icon-calc_volum .path3:before {
content: "\e917"; content: "\e917";
margin-left: -1em; margin-left: -1em;
color: rgb(0, 0, 0);
} }
.icon-calc_volum .path4:before { .icon-calc_volum .path4:before {
content: "\e918"; content: "\e918";
margin-left: -1em; margin-left: -1em;
color: rgb(0, 0, 0);
} }
.icon-calc_volum .path5:before { .icon-calc_volum .path5:before {
content: "\e919"; content: "\e919";
margin-left: -1em; margin-left: -1em;
color: rgb(0, 0, 0);
} }
.icon-calc_volum .path6:before { .icon-calc_volum .path6:before {
content: "\e91a"; content: "\e91a";
margin-left: -1em; margin-left: -1em;
color: rgb(255, 255, 255);
} }
.icon-calendar:before { .icon-calendar:before {
content: "\e93d"; content: "\e93d";

View File

@ -123,5 +123,6 @@
"The worker has hours recorded that day": "The worker has hours recorded that day", "The worker has hours recorded that day": "The worker has hours recorded that day",
"isWithoutNegatives": "isWithoutNegatives", "isWithoutNegatives": "isWithoutNegatives",
"routeFk": "routeFk", "routeFk": "routeFk",
"Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data" "Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data",
"Can't change the password of another worker": "Can't change the password of another worker"
} }

View File

@ -224,5 +224,8 @@
"The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo", "The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo",
"date in the future": "Fecha en el futuro", "date in the future": "Fecha en el futuro",
"reference duplicated": "Referencia duplicada", "reference duplicated": "Referencia duplicada",
"This ticket is already a refund": "Este ticket ya es un abono" "This ticket is already a refund": "Este ticket ya es un abono",
"isWithoutNegatives": "isWithoutNegatives",
"routeFk": "routeFk",
"Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador"
} }

View File

@ -98,5 +98,15 @@
"image/jpg", "image/jpg",
"video/mp4" "video/mp4"
] ]
},
"accessStorage": {
"name": "accessStorage",
"connector": "loopback-component-storage",
"provider": "filesystem",
"root": "./storage/access",
"maxFileSize": "524288000",
"allowedContentTypes": [
"application/x-7z-compressed"
]
} }
} }

View File

@ -6,7 +6,6 @@ class Controller extends Component {
this._role = value; this._role = value;
this.$.summary = null; this.$.summary = null;
if (!value) return; if (!value) return;
this.$http.get(`Roles/${value.id}`) this.$http.get(`Roles/${value.id}`)
.then(res => this.$.summary = res.data); .then(res => this.$.summary = res.data);
} }

View File

@ -86,7 +86,6 @@ module.exports = Self => {
}; };
ticketFk = await createTicket(ctx, myOptions); ticketFk = await createTicket(ctx, myOptions);
} }
await models.Sale.create({ await models.Sale.create({
ticketFk: ticketFk, ticketFk: ticketFk,
itemFk: sale.itemFk, itemFk: sale.itemFk,

View File

@ -1,5 +1,6 @@
<vn-crud-model vn-id="model" <vn-crud-model vn-id="model"
url="ClaimDms" url="ClaimDms"
filter="::$ctrl.filter"
data="photos"> data="photos">
</vn-crud-model> </vn-crud-model>
<vn-card class="summary"> <vn-card class="summary">
@ -106,8 +107,13 @@
<section class="photo" ng-repeat="photo in photos"> <section class="photo" ng-repeat="photo in photos">
<section class="image" on-error-src <section class="image" on-error-src
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}" ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"> zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"
ng-if="photo.dms.contentType != 'video/mp4'">
</section> </section>
<video id="videobcg" muted="muted" controls ng-if="photo.dms.contentType == 'video/mp4'"
class="video">
<source src="{{$ctrl.getImagePath(photo.dmsFk)}}" type="video/mp4">
</video>
</section> </section>
</vn-horizontal> </vn-horizontal>
</vn-auto> </vn-auto>

View File

@ -6,6 +6,13 @@ class Controller extends Summary {
constructor($element, $, vnFile) { constructor($element, $, vnFile) {
super($element, $); super($element, $);
this.vnFile = vnFile; this.vnFile = vnFile;
this.filter = {
include: [
{
relation: 'dms'
}
]
};
} }
$onChanges() { $onChanges() {

View File

@ -10,4 +10,19 @@ vn-claim-summary {
vn-textarea *{ vn-textarea *{
height: 80px; height: 80px;
} }
.video {
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
0 3px 1px -2px rgba(0,0,0,.2),
0 1px 5px 0 rgba(0,0,0,.12);
border: 2px solid transparent;
}
.video:hover {
border: 2px solid $color-primary
}
} }

View File

@ -51,6 +51,9 @@ module.exports = function(Self) {
Self.createReceipt = async(ctx, options) => { Self.createReceipt = async(ctx, options) => {
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
const date = new Date();
date.setHours(0, 0, 0, 0);
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -92,8 +95,9 @@ module.exports = function(Self) {
throw new UserError('Invalid account'); throw new UserError('Invalid account');
await Self.rawSql( await Self.rawSql(
`CALL vn.ledger_doCompensation(CURDATE(), ?, ?, ?, ?, ?, ?)`, `CALL vn.ledger_doCompensation(?, ?, ?, ?, ?, ?, ?)`,
[ [
date,
args.compensationAccount, args.compensationAccount,
args.bankFk, args.bankFk,
accountingType.receiptDescription + originalClient.accountingAccount, accountingType.receiptDescription + originalClient.accountingAccount,
@ -106,9 +110,10 @@ module.exports = function(Self) {
} else if (accountingType.isAutoConciliated == true) { } else if (accountingType.isAutoConciliated == true) {
const description = `${originalClient.id} : ${originalClient.socialName} - ${accountingType.receiptDescription}`; const description = `${originalClient.id} : ${originalClient.socialName} - ${accountingType.receiptDescription}`;
const [xdiarioNew] = await Self.rawSql( const [xdiarioNew] = await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`, `SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`,
[ [
null, null,
date,
bank.account, bank.account,
originalClient.accountingAccount, originalClient.accountingAccount,
description, description,
@ -126,9 +131,10 @@ module.exports = function(Self) {
); );
await Self.rawSql( await Self.rawSql(
`SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, `SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
[ [
xdiarioNew.ledger, xdiarioNew.ledger,
date,
originalClient.accountingAccount, originalClient.accountingAccount,
bank.account, bank.account,
description, description,

View File

@ -0,0 +1,159 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('extendedListFilter', {
description: 'Find all clients matched by the filter',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
},
{
arg: 'search',
type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by name`,
},
{
arg: 'name',
type: 'string',
description: 'The client name',
},
{
arg: 'salesPersonFk',
type: 'number',
},
{
arg: 'fi',
type: 'string',
description: 'The client fiscal id',
},
{
arg: 'socialName',
type: 'string',
},
{
arg: 'city',
type: 'string',
},
{
arg: 'postcode',
type: 'string',
},
{
arg: 'provinceFk',
type: 'number',
},
{
arg: 'email',
type: 'string',
},
{
arg: 'phone',
type: 'string',
},
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/extendedListFilter`,
verb: 'GET'
}
});
Self.extendedListFilter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'c.id': {inq: value}}
: {'c.name': {like: `%${value}%`}};
case 'name':
case 'salesPersonFk':
case 'fi':
case 'socialName':
case 'city':
case 'postcode':
case 'provinceFk':
case 'email':
case 'phone':
param = `c.${param}`;
return {[param]: value};
}
});
filter = mergeFilters(filter, {where});
const stmts = [];
const stmt = new ParameterizedSQL(
`SELECT
c.id,
c.name,
c.socialName,
c.fi,
c.credit,
c.creditInsurance,
c.phone,
c.mobile,
c.street,
c.city,
c.postcode,
c.email,
c.created,
c.isActive,
c.isVies,
c.isTaxDataChecked,
c.isEqualizated,
c.isFreezed,
c.hasToInvoice,
c.hasToInvoiceByAddress,
c.isToBeMailed,
c.hasSepaVnl,
c.hasLcr,
c.hasCoreVnl,
ct.id AS countryFk,
ct.country,
p.id AS provinceFk,
p.name AS province,
u.id AS salesPersonFk,
u.name AS salesPerson,
bt.code AS businessTypeFk,
bt.description AS businessType,
pm.id AS payMethodFk,
pm.name AS payMethod,
sti.CodigoIva AS sageTaxTypeFk,
sti.Iva AS sageTaxType,
stt.CodigoTransaccion AS sageTransactionTypeFk,
stt.Transaccion AS sageTransactionType
FROM client c
LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN country ct ON ct.id = c.countryFk
LEFT JOIN province p ON p.id = c.provinceFk
LEFT JOIN businessType bt ON bt.code = c.businessTypeFk
LEFT JOIN payMethod pm ON pm.id = c.payMethodFk
LEFT JOIN sage.TiposIva sti ON sti.CodigoIva = c.taxTypeSageFk
LEFT JOIN sage.TiposTransacciones stt ON stt.CodigoTransaccion = c.transactionTypeSageFk
`
);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makePagination(filter));
const clientsIndex = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return clientsIndex === 0 ? result : result[clientsIndex];
};
};

View File

@ -74,8 +74,10 @@ module.exports = function(Self) {
] ]
}, myOptions); }, myOptions);
const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`; const date = new Date();
const data = await Self.rawSql(query, [id], myOptions); date.setHours(0, 0, 0, 0);
const query = `SELECT vn.clientGetDebt(?, ?) AS debt`;
const data = await Self.rawSql(query, [id, date], myOptions);
client.debt = data[0].debt; client.debt = data[0].debt;

View File

@ -25,8 +25,10 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`; const date = new Date();
const [debt] = await Self.rawSql(query, [clientFk], myOptions); date.setHours(0, 0, 0, 0);
const query = `SELECT vn.clientGetDebt(?, ?) AS debt`;
const [debt] = await Self.rawSql(query, [clientFk, date], myOptions);
return debt; return debt;
}; };

View File

@ -32,6 +32,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const ticket = await Self.app.models.Ticket.findById(ticketId, null, myOptions); const ticket = await Self.app.models.Ticket.findById(ticketId, null, myOptions);
const query = ` const query = `
SELECT SELECT
@ -50,12 +52,12 @@ module.exports = Self => {
JOIN vn.warehouse w ON t.warehouseFk = w.id JOIN vn.warehouse w ON t.warehouseFk = w.id
JOIN vn.address ad ON t.addressFk = ad.id JOIN vn.address ad ON t.addressFk = ad.id
JOIN vn.province pr ON ad.provinceFk = pr.id JOIN vn.province pr ON ad.provinceFk = pr.id
WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 WHERE t.shipped >= ? AND t.clientFk = ? AND ts.alertLevel = 0
AND t.id <> ? AND t.warehouseFk = ? AND t.id <> ? AND t.warehouseFk = ?
ORDER BY t.shipped ORDER BY t.shipped
LIMIT 10`; LIMIT 10`;
return Self.rawSql(query, [id, ticketId, ticket.warehouseFk], myOptions); return Self.rawSql(query, [date, id, ticketId, ticket.warehouseFk], myOptions);
}; };
}; };

View File

@ -39,7 +39,7 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const sms = await models.Sms.send(ctx, id, destination, message); const sms = await models.Sms.send(ctx, destination, message);
const logRecord = { const logRecord = {
originFk: id, originFk: id,
userFk: userId, userFk: userId,

View File

@ -0,0 +1,32 @@
module.exports = Self => {
Self.remoteMethod('setPassword', {
description: 'Sets the password of a non-worker client',
accepts: [
{
arg: 'id',
type: 'number',
description: 'The user id',
http: {source: 'path'}
}, {
arg: 'newPassword',
type: 'string',
description: 'The new password',
required: true
}
],
http: {
path: `/:id/setPassword`,
verb: 'PATCH'
}
});
Self.setPassword = async function(id, newPassword) {
const models = Self.app.models;
const isWorker = await models.Worker.findById(id);
if (isWorker)
throw new Error(`Can't change the password of another worker`);
await models.Account.setPassword(id, newPassword);
};
};

View File

@ -0,0 +1,180 @@
const { models } = require('vn-loopback/server/server');
describe('client extendedListFilter()', () => {
it('should return the clients matching the filter with a limit of 20 rows', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {}};
const filter = {limit: '20'};
const result = await models.Client.extendedListFilter(ctx, filter, options);
expect(result.length).toEqual(20);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the client "Bruce Wayne" matching the search argument with his name', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {search: 'Bruce Wayne'}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const firstResult = result[0];
expect(result.length).toEqual(1);
expect(firstResult.name).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the client "Bruce Wayne" matching the search argument with his id', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {search: '1101'}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const firstResult = result[0];
expect(result.length).toEqual(1);
expect(firstResult.name).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the client "Bruce Wayne" matching the name argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {name: 'Bruce Wayne'}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const firstResult = result[0];
expect(result.length).toEqual(1);
expect(firstResult.name).toEqual('Bruce Wayne');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "salesPersonFk" argument', async() => {
const tx = await models.Client.beginTransaction({});
const salesPersonId = 18;
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {salesPersonFk: salesPersonId}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const randomResultClient = result[randomIndex];
expect(result.length).toBeGreaterThanOrEqual(5);
expect(randomResultClient.salesPersonFk).toEqual(salesPersonId);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "fi" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {fi: '251628698'}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const firstClient = result[0];
expect(result.length).toEqual(1);
expect(firstClient.name).toEqual('Max Eisenhardt');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "city" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Silla'}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const randomResultClient = result[randomIndex];
expect(result.length).toBeGreaterThanOrEqual(20);
expect(randomResultClient.city.toLowerCase()).toEqual('silla');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the clients matching the "postcode" argument', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1}}, args: {postcode: '46460'}};
const filter = {};
const result = await models.Client.extendedListFilter(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const randomResultClient = result[randomIndex];
expect(result.length).toBeGreaterThanOrEqual(20);
expect(randomResultClient.postcode).toEqual('46460');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,27 @@
const models = require('vn-loopback/server/server').models;
describe('Client setPassword', () => {
it('should throw an error the setPassword target is not just a client but a worker', async() => {
let error;
try {
await models.Client.setPassword(1106, 'newPass?');
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Can't change the password of another worker`);
});
it('should change the password of the client', async() => {
let error;
try {
await models.Client.setPassword(1101, 't0pl3v3l.p455w0rd!');
} catch (e) {
error = e;
}
expect(error).toBeUndefined();
});
});

View File

@ -120,7 +120,6 @@ describe('client summary()', () => {
const result = await models.Client.summary(clientId, options); const result = await models.Client.summary(clientId, options);
expect(result.recovery.id).toEqual(3); expect(result.recovery.id).toEqual(3);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();

View File

@ -1,21 +1,39 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Client updatePortfolio', () => { describe('Client updatePortfolio', () => {
const clientId = 1108; const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'},
[`__`]: value => {
return value;
}
}
}
};
beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should update the portfolioWeight when the salesPerson of a client changes', async() => { it('should update the portfolioWeight when the salesPerson of a client changes', async() => {
const clientId = 1108;
const salesPersonId = 18; const salesPersonId = 18;
const tx = await models.Client.beginTransaction({}); const tx = await models.Client.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const expectedResult = 841.63; const expectedResult = 841.63;
const clientQuery = `UPDATE vn.client SET salesPersonFk = ${salesPersonId} WHERE id = ${clientId}; `; const client = await models.Client.findById(clientId, null, options);
await models.Client.rawSql(clientQuery); await client.updateAttribute('salesPersonFk', salesPersonId, options);
await models.Client.updatePortfolio(); await models.Client.updatePortfolio(options);
const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `; const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `;
const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options); const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options);
@ -30,21 +48,21 @@ describe('Client updatePortfolio', () => {
}); });
it('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => { it('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => {
pending('task 3817'); const clientId = 1107;
const salesPersonId = 19; const salesPersonId = 19;
const tx = await models.Client.beginTransaction({}); const tx = await models.Client.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const expectedResult = 34.40; const expectedResult = 34.40;
await models.Client.rawSql(`UPDATE vn.client SET salesPersonFk = NULL WHERE id = ${clientId}; `); const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('salesPersonFk', null, options);
await models.Client.updatePortfolio(); await models.Client.updatePortfolio();
const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `; const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `;
const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options); const [salesPerson] = await models.Client.rawSql(portfolioQuery);
expect(salesPerson.portfolioWeight).toEqual(expectedResult); expect(salesPerson.portfolioWeight).toEqual(expectedResult);

View File

@ -0,0 +1,58 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Client updateUser', () => {
const employeeId = 1;
const activeCtx = {
accessToken: {userId: employeeId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {
req: {accessToken: {userId: employeeId}},
args: {name: 'test', active: true}
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error the target user is not just a client but a worker', async() => {
let error;
try {
const clientID = 1106;
await models.Client.updateUser(ctx, clientID);
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Can't update the user details of another worker`);
});
it('should update the user data', async() => {
let error;
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const clientID = 1105;
await models.Client.updateUser(ctx, clientID, options);
const client = await models.Account.findById(clientID, null, options);
expect(client.name).toEqual('test');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(error).toBeUndefined();
});
});

View File

@ -122,7 +122,6 @@ module.exports = Self => {
return clientModel.findOne(filter, options); return clientModel.findOne(filter, options);
} }
async function getRecoveries(recoveryModel, clientId, options) { async function getRecoveries(recoveryModel, clientId, options) {
const filter = { const filter = {
where: { where: {

View File

@ -125,10 +125,10 @@ module.exports = Self => {
} }
try { try {
const isAdministrative = await models.Account.hasRole(userId, 'administrative', myOptions); const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
const client = await models.Client.findById(clientId, null, myOptions); const client = await models.Client.findById(clientId, null, myOptions);
if (!isAdministrative && client.isTaxDataChecked) if (!isSalesAssistant && client.isTaxDataChecked)
throw new UserError(`Not enough privileges to edit a client with verified data`); throw new UserError(`Not enough privileges to edit a client with verified data`);
// Sage data validation // Sage data validation

View File

@ -13,8 +13,13 @@ module.exports = function(Self) {
} }
}); });
Self.updatePortfolio = async() => { Self.updatePortfolio = async options => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
query = `CALL bs.salesPerson_updatePortfolio()`; query = `CALL bs.salesPerson_updatePortfolio()`;
return await Self.rawSql(query); return Self.rawSql(query, null, myOptions);
}; };
}; };

View File

@ -0,0 +1,56 @@
module.exports = Self => {
Self.remoteMethodCtx('updateUser', {
description: 'Updates the user information',
accepts: [
{
arg: 'id',
type: 'number',
description: 'The user id',
http: {source: 'path'}
},
{
arg: 'name',
type: 'string',
description: 'the user name'
},
{
arg: 'active',
type: 'boolean',
description: 'whether the user is active or not'
},
],
http: {
path: '/:id/updateUser',
verb: 'PATCH'
}
});
Self.updateUser = async function(ctx, id, options) {
const models = Self.app.models;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await models.Account.beginTransaction({});
myOptions.transaction = tx;
}
try {
const isWorker = await models.Worker.findById(id, null, myOptions);
if (isWorker)
throw new Error(`Can't update the user details of another worker`);
const user = await models.Account.findById(id, null, myOptions);
await user.updateAttributes(ctx.args, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -38,7 +38,7 @@ module.exports = Self => {
}, myOptions); }, myOptions);
await models.CreditInsurance.create({ await models.CreditInsurance.create({
creditClassification: newClassification.id, creditClassificationFk: newClassification.id,
credit: data.credit, credit: data.credit,
grade: data.grade grade: data.grade
}, myOptions); }, myOptions);

View File

@ -51,6 +51,8 @@ module.exports = Self => {
const stmts = []; const stmts = [];
const date = new Date();
date.setHours(0, 0, 0, 0);
const stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT * `SELECT *
FROM ( FROM (
@ -58,12 +60,12 @@ module.exports = Self => {
DISTINCT c.id clientFk, DISTINCT c.id clientFk,
c.name clientName, c.name clientName,
c.salesPersonFk, c.salesPersonFk,
u.nickname salesPersonName, u.name salesPersonName,
d.amount, d.amount,
co.created, co.created,
co.text observation, co.text observation,
uw.id workerFk, uw.id workerFk,
uw.nickname workerName, uw.name workerName,
c.creditInsurance, c.creditInsurance,
d.defaulterSinced d.defaulterSinced
FROM vn.defaulter d FROM vn.defaulter d
@ -72,10 +74,10 @@ module.exports = Self => {
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN account.user uw ON uw.id = co.workerFk
WHERE WHERE
d.created = CURDATE() d.created = ?
AND d.amount > 0 AND d.amount > 0
ORDER BY co.created DESC) d` ORDER BY co.created DESC) d`
); , [date]);
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY d.clientFk`); stmt.merge(`GROUP BY d.clientFk`);

View File

@ -27,13 +27,15 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const date = new Date();
date.setHours(0, 0, 0, 0);
const query = ` const query = `
SELECT count(*) AS hasActiveRecovery SELECT count(*) AS hasActiveRecovery
FROM vn.recovery FROM vn.recovery
WHERE clientFk = ? WHERE clientFk = ?
AND IFNULL(finished,CURDATE()) >= CURDATE(); AND IFNULL(finished, ?) >= ?;
`; `;
const [result] = await Self.rawSql(query, [id], myOptions); const [result] = await Self.rawSql(query, [id, date, date], myOptions);
return result.hasActiveRecovery != 0; return result.hasActiveRecovery != 0;
}; };

View File

@ -6,10 +6,6 @@ module.exports = Self => {
description: 'Sends SMS to a destination phone', description: 'Sends SMS to a destination phone',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
{
arg: 'destinationFk',
type: 'integer'
},
{ {
arg: 'destination', arg: 'destination',
type: 'string', type: 'string',
@ -31,7 +27,7 @@ module.exports = Self => {
} }
}); });
Self.send = async(ctx, destinationFk, destination, message) => { Self.send = async(ctx, destination, message) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const smsConfig = await Self.app.models.SmsConfig.findOne(); const smsConfig = await Self.app.models.SmsConfig.findOne();
@ -68,7 +64,6 @@ module.exports = Self => {
const newSms = { const newSms = {
senderFk: userId, senderFk: userId,
destinationFk: destinationFk || null,
destination: destination, destination: destination,
message: message, message: message,
status: error status: error

View File

@ -3,7 +3,7 @@ const app = require('vn-loopback/server/server');
describe('sms send()', () => { describe('sms send()', () => {
it('should not return status error', async() => { it('should not return status error', async() => {
const ctx = {req: {accessToken: {userId: 1}}}; const ctx = {req: {accessToken: {userId: 1}}};
const result = await app.models.Sms.send(ctx, 1105, '123456789', 'My SMS Body'); const result = await app.models.Sms.send(ctx, '123456789', 'My SMS Body');
expect(result.status).toBeUndefined(); expect(result.status).toBeUndefined();
}); });

View File

@ -8,29 +8,32 @@ const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
// Methods // Methods
require('../methods/client/getCard')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/addressesPropagateRe')(Self); require('../methods/client/addressesPropagateRe')(Self);
require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/checkDuplicated')(Self);
require('../methods/client/confirmTransaction')(Self);
require('../methods/client/consumption')(Self);
require('../methods/client/createAddress')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/extendedListFilter')(Self);
require('../methods/client/getAverageInvoiced')(Self);
require('../methods/client/getCard')(Self);
require('../methods/client/getDebt')(Self); require('../methods/client/getDebt')(Self);
require('../methods/client/getMana')(Self); require('../methods/client/getMana')(Self);
require('../methods/client/getAverageInvoiced')(Self);
require('../methods/client/summary')(Self);
require('../methods/client/updateFiscalData')(Self);
require('../methods/client/getTransactions')(Self); require('../methods/client/getTransactions')(Self);
require('../methods/client/confirmTransaction')(Self); require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/canBeInvoiced')(Self); require('../methods/client/isValidClient')(Self);
require('../methods/client/uploadFile')(Self);
require('../methods/client/lastActiveTickets')(Self); require('../methods/client/lastActiveTickets')(Self);
require('../methods/client/sendSms')(Self); require('../methods/client/sendSms')(Self);
require('../methods/client/createAddress')(Self); require('../methods/client/setPassword')(Self);
require('../methods/client/summary')(Self);
require('../methods/client/updateAddress')(Self); require('../methods/client/updateAddress')(Self);
require('../methods/client/consumption')(Self); require('../methods/client/updateFiscalData')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/updatePortfolio')(Self); require('../methods/client/updatePortfolio')(Self);
require('../methods/client/checkDuplicated')(Self); require('../methods/client/updateUser')(Self);
require('../methods/client/uploadFile')(Self);
// Validations // Validations
@ -232,7 +235,6 @@ module.exports = Self => {
const loopBackContext = LoopBackContext.getCurrentContext(); const loopBackContext = LoopBackContext.getCurrentContext();
const userId = loopBackContext.active.accessToken.userId; const userId = loopBackContext.active.accessToken.userId;
const isAdministrative = await models.Account.hasRole(userId, 'administrative', ctx.options);
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', ctx.options); const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', ctx.options);
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
@ -245,7 +247,7 @@ module.exports = Self => {
const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk); const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk);
const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType; const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType;
const cantEditVerifiedData = isTaxDataCheckedChanged && !isAdministrative; const cantEditVerifiedData = isTaxDataCheckedChanged && !isSalesAssistant;
const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !isSalesAssistant; const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !isSalesAssistant;
if (cantEditVerifiedData || cantChangeSageData) if (cantEditVerifiedData || cantChangeSageData)
@ -460,9 +462,10 @@ module.exports = Self => {
const hasChanges = oldData.name != changes.name || oldData.active != changes.active; const hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return; if (!hasChanges) return;
const isClient = await Self.app.models.Client.findById(oldData.id); const isClient = await Self.app.models.Client.count({id: oldData.id});
if (isClient) { if (isClient) {
const userId = ctx.options.accessToken.userId; const loopBackContext = LoopBackContext.getCurrentContext();
const userId = loopBackContext.active.accessToken.userId;
const logRecord = { const logRecord = {
originFk: oldData.id, originFk: oldData.id,
userFk: userId, userFk: userId,
@ -471,7 +474,6 @@ module.exports = Self => {
oldInstance: {name: oldData.name, active: oldData.active}, oldInstance: {name: oldData.name, active: oldData.active},
newInstance: {name: changes.name, active: changes.active} newInstance: {name: changes.name, active: changes.active}
}; };
await Self.app.models.ClientLog.create(logRecord); await Self.app.models.ClientLog.create(logRecord);
} }
} }

View File

@ -136,6 +136,9 @@
"mysql": { "mysql": {
"columnName": "businessTypeFk" "columnName": "businessTypeFk"
} }
},
"salesPersonFk": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -36,7 +36,7 @@
"insurances": { "insurances": {
"type": "hasMany", "type": "hasMany",
"model": "CreditInsurance", "model": "CreditInsurance",
"foreignKey": "creditClassification" "foreignKey": "creditClassificationFk"
} }
} }
} }

View File

@ -24,7 +24,7 @@ module.exports = function(Self) {
let filter = { let filter = {
fields: ['grade'], fields: ['grade'],
where: { where: {
creditClassification: this.creditClassification creditClassificationFk: this.creditClassificationFk
}, },
order: 'created DESC' order: 'created DESC'
}; };

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