Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4045-incoterms_authorization
gitea/salix/pipeline/head This commit is unstable Details

This commit is contained in:
Joan Sanchez 2022-06-10 13:16:27 +02:00
commit 9ab8b84290
158 changed files with 3274 additions and 710 deletions

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,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 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(); return true;
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 ctx = {req: {accessToken: {userId: 1}}};
const chatModel = models.Chat;
const departmentId = 23;
const workerId = 1107; 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,28 +19,42 @@ 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) {
try { const fileNames = updatableFiles.map(file => file.name);
const fileName = file.file;
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}...`); console.debug(`Downloading file ${fileName}...`);
@ -48,108 +62,175 @@ module.exports = Self => {
tempDir = `${tempPath}/${fileName}`; tempDir = `${tempPath}/${fileName}`;
tempFile = `${tempPath}/${fileName}.zip`; tempFile = `${tempPath}/${fileName}.zip`;
await extractFile({ try {
ftpClient: ftpClient, await fs.readFile(tempFile);
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,
zip.extractAllTo(paths.tempDir, false); procotol: 'ftp'
if (fs.existsSync(paths.tempFile))
await fs.unlink(paths.tempFile);
await dumpData({file, entries, paths});
await fs.rmdir(paths.tempDir, {recursive: true});
}); });
} }
async function dumpData({file, entries, paths}) { return ftpClient;
const toTable = file.toTable;
const baseName = file.fileName;
for (const zipEntry of entries) {
const entryName = zipEntry.entryName;
console.log(`Reading file ${entryName}...`);
const startIndex = (entryName.length - 10);
const endIndex = (entryName.length - 4);
const dateString = entryName.substring(startIndex, endIndex);
const lastUpdated = new Date();
// Format string date to a date object
let updated = null;
if (file.updated) {
updated = new Date(file.updated);
updated.setHours(0, 0, 0, 0);
} }
lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`); async function getChecksum(file) {
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1); const ftpClient = await getFtpClient();
lastUpdated.setDate(dateString.substring(0, 2)); console.debug(`Checking checksum for file ${file.name}...`);
lastUpdated.setHours(0, 0, 0, 0);
if (updated && lastUpdated <= updated) { ftpClient.cat(`codes/${file.name}.txt`);
console.debug(`Table ${toTable} already updated, skipping...`);
continue; const response = await new Promise((resolve, reject) => {
ftpClient.exec((err, response) => {
if (response.error) {
console.debug(`Error downloading checksum file... ${response.error}`);
reject(err);
} }
console.log('Dumping data...'); resolve(response);
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`); });
const sqlTemplate = fs.readFileSync(templatePath, 'utf8'); });
const rawPath = path.join(paths.tempDir, entryName); 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 extractFile(tempFile, tempDir) {
const JSZip = require('jszip');
try { try {
await fs.mkdir(tempDir);
} catch (error) {
if (error.code !== 'EEXIST')
throw e;
}
const fileStream = await fs.readFile(tempFile);
if (fileStream) {
const zip = new JSZip();
const zipContents = await zip.loadAsync(fileStream);
if (!zipContents) return;
const fileNames = Object.keys(zipContents.files);
for (const fileName of fileNames) {
const fileContent = await zip.file(fileName).async('nodebuffer');
const dest = path.join(tempDir, fileName);
await fs.writeFile(dest, fileContent);
}
}
}
async function dumpData(tempDir, table) {
const toTable = table.toTable;
const baseName = table.fileName;
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx}; const options = {transaction: tx};
await Self.rawSql(`DELETE FROM edi.${toTable}`, null, options); const tableName = `edi.${toTable}`;
await Self.rawSql(sqlTemplate, [rawPath], options); 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(` await Self.rawSql(`
UPDATE edi.fileConfig UPDATE edi.tableConfig
SET updated = ? SET updated = ?
WHERE fileName = ? WHERE fileName = ?
`, [lastUpdated, baseName], options); `, [new Date(), baseName], options);
}
tx.commit(); tx.commit();
} catch (error) { } catch (error) {
tx.rollback(); tx.rollback();
throw error; throw error;
} }
console.log(`Updated table ${toTable}\n`); 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

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

@ -99,13 +99,19 @@ INSERT INTO `account`.`mailForward`(`account`, `forwardTo`)
VALUES VALUES
(1, 'employee@domain.local'); (1, 'employee@domain.local');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`)
VALUES VALUES
(1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106), (1, 'printer1', 'path1', 0),
(1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107), (2, 'printer2', 'path2', 1);
(1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108),
(1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109),
(1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110); INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
VALUES
(1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106, NULL, NULL),
(1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107, NULL, 1),
(1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108, 1, NULL),
(1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109, 1, 2),
(1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110, 2, 1);
INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`) INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
VALUES VALUES
@ -156,22 +162,23 @@ INSERT INTO `vn`.`shelving` (`code`, `parkingFk`, `isPrinted`, `priority`, `park
('HEJ', 2, 0, 1, 0, 1106), ('HEJ', 2, 0, 1, 0, 1106),
('UXN', 1, 0, 1, 0, 1106); ('UXN', 1, 0, 1, 0, 1106);
INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`code`, `maxAmount`) INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`code`, `maxAmount`, `daysInFuture`)
VALUES VALUES
(1, 'CC y Polizas de crédito', NULL, NULL, NULL), (1, 'CC and credit policies', 'Transfers', 'wireTransfer', NULL, 1),
(2, 'Cash', 'Cash', 'cash', 1000), (2, 'Cash', 'Cash', 'cash', 1000, 0),
(3, 'Credit card', 'Credit Card', 'creditCard', NULL), (3, 'Credit card', 'Credit Card', 'creditCard', NULL, 0),
(4, 'Finalcial lines', NULL, NULL, NULL), (4, 'Finalcial lines', NULL, NULL, NULL, 0),
(5, 'Other products', NULL, NULL, NULL), (5, 'Other products', NULL, NULL, NULL, 0),
(6, 'Loans', NULL, NULL, NULL), (6, 'Loans', NULL, NULL, NULL, 0),
(7, 'Leasing', NULL, NULL, NULL), (7, 'Leasing', NULL, NULL, NULL, 0),
(8, 'Compensations', 'Compensations', 'compensation', NULL); (8, 'Compensations', 'Compensations', 'compensation', NULL, 0);
INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`) INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`)
VALUES VALUES
(1, 'Pay on receipt', '5720000001', 3, 0, 1, 1), (1, 'Pay on receipt', '5720000001', 3, 0, 1, 1),
(2, 'Cash', '5700000001', 2, 0, 1, 1), (2, 'Cash', '5700000001', 2, 0, 1, 1),
(3, 'Compensation', '4000000000', 8, 0, 1, 1), (3, 'Compensation', '4000000000', 8, 0, 1, 1),
(4, 'Transfers', '4000000001', 1, 0, 1, 1),
(3117, 'Caixa Rural d''Algemesi', '5720000000', 8, 3117, 1, 1); (3117, 'Caixa Rural d''Algemesi', '5720000000', 8, 3117, 1, 1);
@ -451,7 +458,7 @@ INSERT INTO `vn`.`creditClassification`(`id`, `client`, `dateStart`, `dateEnd`)
(4, 1104, CURDATE(), CURDATE()), (4, 1104, CURDATE(), CURDATE()),
(5, 1105, CURDATE(), CURDATE()); (5, 1105, CURDATE(), CURDATE());
INSERT INTO `vn`.`creditInsurance`(`id`, `creditClassification`, `credit`, `creationDate`, `grade`) INSERT INTO `vn`.`creditInsurance`(`id`, `creditClassificationFk`, `credit`, `creationDate`, `grade`)
VALUES VALUES
(1, 1, 3000, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL), (1, 1, 3000, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL),
(2, 2, 6000, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), NULL), (2, 2, 6000, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), NULL),
@ -744,14 +751,19 @@ INSERT INTO `vn`.`itemCategory`(`id`, `name`, `display`, `color`, `icon`, `code`
(7, 'Accessories', 1, NULL, 'icon-accessory', 'accessory'), (7, 'Accessories', 1, NULL, 'icon-accessory', 'accessory'),
(8, 'Fruit', 1, NULL, 'icon-fruit', 'fruit'); (8, 'Fruit', 1, NULL, 'icon-fruit', 'fruit');
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `life`,`workerFk`, `isPackaging`) INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
VALUES VALUES
(1, 'CRI', 'Crisantemo', 2, 1, 31, 35, 0), ('warm', 'Warm', 'Warm'),
(2, 'ITG', 'Anthurium', 1, 1, 31, 35, 0), ('cool', 'Cool', 'Cool');
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1), INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `life`,`workerFk`, `isPackaging`, `temperatureFk`)
(5, 'CON', 'Container', 3, 1, NULL, 35, 1), VALUES
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0); (1, 'CRI', 'Crisantemo', 2, 1, 31, 35, 0, 'cool'),
(2, 'ITG', 'Anthurium', 1, 1, 31, 35, 0, 'cool'),
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0, 'cool'),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1, 'warm'),
(5, 'CON', 'Container', 3, 1, NULL, 35, 1, 'warm'),
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0, 'warm');
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`) INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES VALUES
@ -809,25 +821,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('VT', 'Sales'); ('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`) `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`)
VALUES VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0), (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0), (5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0), (6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0), (7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0), (8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0), (9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0), (10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0), (11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0), (12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1), (13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0), (14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0), (15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0), (16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0); (71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL);
-- Update the taxClass after insert of the items -- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2 UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -1629,7 +1641,9 @@ INSERT INTO `hedera`.`visit`(`id`, `firstAgentFk`)
(6, NULL), (6, NULL),
(7, NULL), (7, NULL),
(8, NULL), (8, NULL),
(9, NULL); (9, NULL),
(10, NULL),
(11, NULL);
INSERT INTO `hedera`.`visitAgent`(`id`, `visitFk`) INSERT INTO `hedera`.`visitAgent`(`id`, `visitFk`)
VALUES VALUES
@ -1641,7 +1655,9 @@ INSERT INTO `hedera`.`visitAgent`(`id`, `visitFk`)
(6, 6), (6, 6),
(7, 7), (7, 7),
(8, 8), (8, 8),
(9, 9); (9, 9),
(10, 10),
(11, 11);
INSERT INTO `hedera`.`visitAccess`(`id`, `agentFk`, `stamp`) INSERT INTO `hedera`.`visitAccess`(`id`, `agentFk`, `stamp`)
VALUES VALUES
@ -1653,7 +1669,9 @@ INSERT INTO `hedera`.`visitAccess`(`id`, `agentFk`, `stamp`)
(6, 6, CURDATE()), (6, 6, CURDATE()),
(7, 7, CURDATE()), (7, 7, CURDATE()),
(8, 8, CURDATE()), (8, 8, CURDATE()),
(9, 9, CURDATE()); (9, 9, CURDATE()),
(10, 10, CURDATE()),
(11, 11, CURDATE());
INSERT INTO `hedera`.`visitUser`(`id`, `accessFk`, `userFk`, `stamp`) INSERT INTO `hedera`.`visitUser`(`id`, `accessFk`, `userFk`, `stamp`)
VALUES VALUES
@ -1665,7 +1683,9 @@ INSERT INTO `hedera`.`visitUser`(`id`, `accessFk`, `userFk`, `stamp`)
(6, 6, 1102, CURDATE()), (6, 6, 1102, CURDATE()),
(7, 7, 1103, CURDATE()), (7, 7, 1103, CURDATE()),
(8, 8, 1103, CURDATE()), (8, 8, 1103, CURDATE()),
(9, 9, 1103, CURDATE()); (9, 9, 1103, CURDATE()),
(10, 10, 1102, DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
(11, 11, 1103, DATE_SUB(CURDATE(), INTERVAL 1 DAY));
INSERT INTO `hedera`.`userSession`(`created`, `lastUpdate`, `ssid`, `data`, `userVisitFk`) INSERT INTO `hedera`.`userSession`(`created`, `lastUpdate`, `ssid`, `data`, `userVisitFk`)
VALUES VALUES
@ -2291,11 +2311,6 @@ INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `week
INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11'); INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11');
INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
VALUES
('warm', 'Warm', 'Warm'),
('cool', 'Cool', 'Cool');
INSERT INTO `vn`.`thermograph`(`id`, `model`) INSERT INTO `vn`.`thermograph`(`id`, `model`)
VALUES VALUES
('TMM190901395', 'TEMPMATE'), ('TMM190901395', 'TEMPMATE'),
@ -2544,3 +2559,36 @@ INSERT INTO `vn`.`supplierAgencyTerm` (`agencyFk`, `supplierFk`, `minimumPackage
(3, 2, 0, 15.00, 0.00, NULL, 0, 0.00, 0), (3, 2, 0, 15.00, 0.00, NULL, 0, 0.00, 0),
(4, 2, 0, 20.00, 0.00, NULL, 0, 0.00, 0), (4, 2, 0, 20.00, 0.00, NULL, 0, 0.00, 0),
(5, 442, 0, 0.00, 3.05, NULL, 0, 0.00, 0); (5, 442, 0, 0.00, 3.05, NULL, 0, 0.00, 0);
INSERT INTO `vn`.`chat` (`senderFk`, `recipient`, `dated`, `checkUserStatus`, `message`, `status`, `attempts`)
VALUES
(1101, '@PetterParker', CURDATE(), 1, 'First test message', 0, 0),
(1101, '@PetterParker', CURDATE(), 0, 'Second test message', 0, 0);
INSERT INTO `vn`.`mobileAppVersionControl` (`appName`, `version`, `isVersionCritical`)
VALUES
('delivery', '9.2', 0),
('warehouse', '8.1', 0);
INSERT INTO `vn`.`machine` (`plate`, `maker`, `model`, `warehouseFk`, `departmentFk`, `type`, `use`, `productionYear`, `workerFk`, `companyFk`)
VALUES
('RE-001', 'STILL', 'LTX-20', 60, 23, 'ELECTRIC TOW', 'Drag cars', 2020, 103, 442),
('RE-002', 'STILL', 'LTX-20', 60, 23, 'ELECTRIC TOW', 'Drag cars', 2020, 103, 442);
INSERT INTO `vn`.`machineWorker` (`workerFk`, `machineFk`, `inTimed`, `outTimed`)
VALUES
(1106, 1, CURDATE(), CURDATE()),
(1106, 1, DATE_ADD(CURDATE(), INTERVAL + 1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY)),
(1106, 2, CURDATE(), NULL),
(1106, 2, DATE_ADD(CURDATE(), INTERVAL + 1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY));
INSERT INTO `vn`.`mdbBranch` (`name`)
VALUES
('test'),
('master');
INSERT INTO `vn`.`mdbVersion` (`app`, `branchFk`, `version`)
VALUES
('tpv', 'test', '1'),
('lab', 'master', '1');

View File

@ -37460,6 +37460,31 @@ SET character_set_client = utf8;
) ENGINE=MyISAM */; ) ENGINE=MyISAM */;
SET character_set_client = @saved_cs_client; SET character_set_client = @saved_cs_client;
--
-- Temporary table structure for view `printer`
--
DROP TABLE IF EXISTS `printer`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `printer` (
`id` tinyint(3) unsigned NOT NULL,
`name` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`path` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`modelFk` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`macWifi` varchar(20) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`ipAddress` varchar(15) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`reference` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`isLabeler` tinyint(1) DEFAULT 0 COMMENT 'Indica si es impresora de etiquetas',
PRIMARY KEY (`id`),
UNIQUE KEY `printer_UN` (`reference`),
UNIQUE KEY `printer_UN1` (`macWifi`),
UNIQUE KEY `printer_UN2` (`name`),
KEY `printer_FK` (`modelFk`),
CONSTRAINT `printer_FK` FOREIGN KEY (`modelFk`) REFERENCES `printerModel` (`code`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
-- --
-- Table structure for table `printingQueueCheck` -- Table structure for table `printingQueueCheck`
-- --

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

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

@ -101,6 +101,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

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

@ -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>
<div ng-transclude="pagination">
<vn-pagination <vn-pagination
ng-if="$ctrl.model" ng-if="$ctrl.model"
model="$ctrl.model" model="$ctrl.model"
class="vn-pt-md"> class="vn-pt-md">
</vn-pagination> </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

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

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

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

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

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

@ -129,10 +129,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

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

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

@ -31,6 +31,7 @@ module.exports = Self => {
require('../methods/client/createReceipt')(Self); require('../methods/client/createReceipt')(Self);
require('../methods/client/updatePortfolio')(Self); require('../methods/client/updatePortfolio')(Self);
require('../methods/client/checkDuplicated')(Self); require('../methods/client/checkDuplicated')(Self);
require('../methods/client/extendedListFilter')(Self);
// Validations // Validations
@ -232,7 +233,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 +245,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)

View File

@ -139,6 +139,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'
}; };

View File

@ -30,7 +30,7 @@
"classification": { "classification": {
"type": "belongsTo", "type": "belongsTo",
"model": "CreditClassification", "model": "CreditClassification",
"foreignKey": "creditClassification" "foreignKey": "creditClassificationFk"
} }
}, },
"scope": { "scope": {

View File

@ -6,12 +6,7 @@ class Controller extends Dialog {
super($element, $, $transclude); super($element, $, $transclude);
this.vnReport = vnReport; this.vnReport = vnReport;
this.receipt = {};
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
this.receipt = {
payed: tomorrow
};
} }
set payed(value) { set payed(value) {
@ -72,6 +67,10 @@ class Controller extends Dialog {
`${accountingType && accountingType.receiptDescription}`; `${accountingType && accountingType.receiptDescription}`;
} }
this.maxAmount = accountingType && accountingType.maxAmount; this.maxAmount = accountingType && accountingType.maxAmount;
this.receipt.payed = new Date();
if (accountingType.daysInFuture)
this.receipt.payed.setDate(this.receipt.payed.getDate() + accountingType.daysInFuture);
} }
} }

View File

@ -54,14 +54,7 @@
show-field="bic" show-field="bic"
vn-acl="salesAssistant" vn-acl="salesAssistant"
disabled="$ctrl.ibanCountry == 'ES'"> disabled="$ctrl.ibanCountry == 'ES'">
<tpl-item> <tpl-item>{{bic}} {{name}}</tpl-item>
<vn-horizontal>
<vn-one>{{bic}}</vn-one>
<vn-one>
<div class="ellipsize" style="max-width: 10em">{{name}}</div>
</vn-one>
</vn-horizontal>
</tpl-item>
<append> <append>
<vn-icon-button <vn-icon-button
vn-auto vn-auto

View File

@ -1,7 +1,7 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="CreditInsurances" url="CreditInsurances"
link="{creditClassification: $ctrl.$params.classificationId}" link="{creditClassificationFk: $ctrl.$params.classificationId}"
limit="20" limit="20"
data="insurances" data="insurances"
auto-load="true"> auto-load="true">

View File

@ -0,0 +1,319 @@
<vn-crud-model
vn-id="model"
url="Clients/extendedListFilter"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-client-search-panel"
placeholder="Search client"
info="Search client by id or name"
auto-state="false"
model="model">
</vn-searchbar>
</vn-portal>
<vn-card>
<smart-table
model="model"
view-config-id="clientsDetail"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-table>
<table>
<thead>
<tr>
<th></th>
<th field="id">
<span translate>Identifier</span>
</th>
<th field="name">
<span translate>Name</span>
</th>
<th field="socialName">
<span translate>Social name</span>
</th>
<th field="fi">
<span translate>Tax number</span>
</th>
<th field="salesPersonFk">
<span translate>Salesperson</span>
</th>
<th field="credit">
<span translate>Credit</span>
</th>
<th field="creditInsurance">
<span translate>Credit insurance</span>
</th>
<th field="phone">
<span translate>Phone</span>
</th>
<th field="mobile">
<span translate>Mobile</span>
</th>
<th field="street">
<span translate>Street</span>
</th>
<th field="countryFk">
<span translate>Country</span>
</th>
<th field="provinceFk">
<span translate>Province</span>
</th>
<th field="city">
<span translate>City</span>
</th>
<th field="postcode">
<span translate>Postcode</span>
</th>
<th field="email">
<span translate>Email</span>
</th>
<th field="created">
<span translate>Created</span>
</th>
<th field="businessTypeFk">
<span translate>Business type</span>
</th>
<th field="payMethodFk">
<span translate>Billing data</span>
</th>
<th field="sageTaxTypeFk">
<span translate>Sage tax type</span>
</th>
<th field="sageTransactionTypeFk">
<span translate>Sage tr. type</span>
</th>
<th field="isActive" centered>
<span translate>Active</span>
</th>
<th field="isVies" centered>
<span translate>Vies</span>
</th>
<th field="isTaxDataChecked" centered>
<span translate>Verified data</span>
</th>
<th field="isEqualizated" centered>
<span translate>Is equalizated</span>
</th>
<th field="isFreezed" centered>
<span translate>Freezed</span>
</th>
<th field="hasToInvoice" centered>
<span translate>Invoice</span>
</th>
<th field="hasToInvoiceByAddress" centered>
<span translate>Invoice by address</span>
</th>
<th field="isToBeMailed" centered>
<span translate>Mailing</span>
</th>
<th field="hasLcr" centered>
<span translate>Received LCR</span>
</th>
<th field="hasCoreVnl" centered>
<span translate>Received core VNL</span>
</th>
<th field="hasSepaVnl" centered>
<span translate>Received B2B VNL</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="client in model.data"
vn-anchor="::{
state: 'client.card.summary',
params: {id: client.id}
}">
<td>
<vn-icon-button ng-show="::client.isActive == false"
vn-tooltip="Client inactive"
icon="icon-disabled">
</vn-icon-button>
<vn-icon-button ng-show="::client.isActive && client.isFreezed == true"
vn-tooltip="Client frozen"
icon="icon-frozen">
</vn-icon-button>
</td>
<td>
<span
vn-click-stop="clientDescriptor.show($event, client.id)"
class="link">
{{::client.id}}
</span>
</td>
<td>{{::client.name}}</td>
<td>{{::client.socialName}}</td>
<td>{{::client.fi}}</td>
<td>
<span
vn-click-stop="workerDescriptor.show($event, client.salesPersonFk)"
ng-class="{'link': client.salesPersonFk}">
{{::client.salesPerson | dashIfEmpty}}
</span>
</td>
<td>{{::client.credit}}</td>
<td>{{::client.creditInsurance | dashIfEmpty}}</td>
<td>{{::client.phone | dashIfEmpty}}</td>
<td>{{::client.mobile | dashIfEmpty}}</td>
<td>{{::client.street | dashIfEmpty}}</td>
<td>{{::client.country | dashIfEmpty}}</td>
<td>{{::client.province | dashIfEmpty}}</td>
<td>{{::client.city | dashIfEmpty}}</td>
<td>{{::client.postcode | dashIfEmpty}}</td>
<td>{{::client.email | dashIfEmpty}}</td>
<td>{{::client.created | date:'dd/MM/yyyy'}}</td>
<td>{{::client.businessType | dashIfEmpty}}</td>
<td>{{::client.payMethod | dashIfEmpty}}</td>
<td>{{::client.sageTaxType | dashIfEmpty}}</td>
<td>{{::client.sageTransactionType | dashIfEmpty}}</td>
<td centered>
<vn-chip ng-class="::{
'success': client.isActive,
'alert': !client.isActive,
}">
{{ ::client.isActive ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.isVies,
'alert': !client.isVies,
}">
{{ ::client.isVies ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.isTaxDataChecked,
'alert': !client.isTaxDataChecked,
}">
{{ ::client.isTaxDataChecked ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.isEqualizated,
'alert': !client.isEqualizated,
}">
{{ ::client.isEqualizated ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.isFreezed,
'alert': !client.isFreezed,
}">
{{ ::client.isFreezed ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.hasToInvoice,
'alert': !client.hasToInvoice,
}">
{{ ::client.hasToInvoice ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.hasToInvoiceByAddress,
'alert': !client.hasToInvoiceByAddress,
}">
{{ ::client.hasToInvoiceByAddress ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.isToBeMailed,
'alert': !client.isToBeMailed,
}">
{{ ::client.isToBeMailed ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.hasLcr,
'alert': !client.hasLcr,
}">
{{ ::client.hasLcr ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.hasCoreVnl,
'alert': !client.hasCoreVnl,
}">
{{ ::client.hasCoreVnl ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td centered>
<vn-chip ng-class="::{
'success': client.hasSepaVnl,
'alert': !client.hasSepaVnl,
}">
{{ ::client.hasSepaVnl ? 'Yes' : 'No' | translate}}
</vn-chip>
</td>
<td shrink>
<vn-horizontal class="buttons">
<vn-icon-button vn-anchor="{state: 'ticket.index', params: {q: {clientFk: client.id} } }"
vn-tooltip="Client tickets"
icon="icon-ticket">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(client)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<a ui-sref="client.create" vn-tooltip="New client" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-client-descriptor-popover
vn-id="client-descriptor">
</vn-client-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
<vn-popup vn-id="preview">
<vn-client-summary
client="$ctrl.clientSelected">
</vn-client-summary>
</vn-popup>
<vn-contextmenu
vn-id="contextmenu"
targets="['smart-table']"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
</slot-menu>
</vn-contextmenu>

View File

@ -0,0 +1,184 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.smartTableOptions = {
activeButtons: {
search: true,
shownColumns: true,
},
columns: [
{
field: 'socialName',
autocomplete: {
url: 'Clients',
showField: 'socialName',
valueField: 'socialName',
}
},
{
field: 'created',
datepicker: true
},
{
field: 'countryFk',
autocomplete: {
url: 'Countries',
showField: 'country',
}
},
{
field: 'provinceFk',
autocomplete: {
url: 'Provinces'
}
},
{
field: 'salesPersonFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'businessTypeFk',
autocomplete: {
url: 'BusinessTypes',
valueField: 'code',
showField: 'description',
}
},
{
field: 'payMethodFk',
autocomplete: {
url: 'PayMethods',
}
},
{
field: 'sageTaxTypeFk',
autocomplete: {
url: 'SageTaxTypes',
showField: 'vat',
}
},
{
field: 'sageTransactionTypeFk',
autocomplete: {
url: 'SageTransactionTypes',
showField: 'transaction',
}
},
{
field: 'isActive',
checkbox: true
},
{
field: 'isVies',
checkbox: true
},
{
field: 'isTaxDataChecked',
checkbox: true
},
{
field: 'isEqualizated',
checkbox: true
},
{
field: 'isFreezed',
checkbox: true
},
{
field: 'hasToInvoice',
checkbox: true
},
{
field: 'hasToInvoiceByAddress',
checkbox: true
},
{
field: 'isToBeMailed',
checkbox: true
},
{
field: 'hasSepaVnl',
checkbox: true
},
{
field: 'hasLcr',
checkbox: true
},
{
field: 'hasCoreVnl',
checkbox: true
}
]
};
}
exprBuilder(param, value) {
switch (param) {
case 'created':
return {'c.created': {
between: this.dateRange(value)}
};
case 'id':
case 'name':
case 'socialName':
case 'fi':
case 'credit':
case 'creditInsurance':
case 'phone':
case 'mobile':
case 'street':
case 'city':
case 'postcode':
case 'email':
case 'isActive':
case 'isVies':
case 'isTaxDataChecked':
case 'isEqualizated':
case 'isFreezed':
case 'hasToInvoice':
case 'hasToInvoiceByAddress':
case 'isToBeMailed':
case 'hasSepaVnl':
case 'hasLcr':
case 'hasCoreVnl':
case 'countryFk':
case 'provinceFk':
case 'salesPersonFk':
case 'businessTypeFk':
case 'payMethodFk':
case 'sageTaxTypeFk':
case 'sageTransactionTypeFk':
return {[`c.${param}`]: value};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
preview(client) {
this.clientSelected = client;
this.$.preview.show();
}
}
ngModule.vnComponent('vnClientExtendedList', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,3 @@
Mailing: Env. emails
Sage tr. type: Tipo tr. sage
Yes:

View File

@ -0,0 +1,6 @@
@import "variables";
vn-chip.success,
vn-chip.alert {
color: $color-font-bg
}

View File

@ -182,7 +182,7 @@
vn-one vn-one
label="Verified data" label="Verified data"
ng-model="$ctrl.client.isTaxDataChecked" ng-model="$ctrl.client.isTaxDataChecked"
vn-acl="administrative"> vn-acl="salesAssistant">
</vn-check> </vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -47,3 +47,4 @@ import './consumption-search-panel';
import './defaulter'; import './defaulter';
import './notification'; import './notification';
import './unpaid'; import './unpaid';
import './extended-list';

View File

@ -33,6 +33,7 @@ Search client by id or name: Buscar clientes por identificador o nombre
# Sections # Sections
Clients: Clientes Clients: Clientes
Extended list: Listado extendido
Defaulter: Morosos Defaulter: Morosos
New client: Nuevo cliente New client: Nuevo cliente
Fiscal data: Datos fiscales Fiscal data: Datos fiscales

View File

@ -7,6 +7,7 @@
"menus": { "menus": {
"main": [ "main": [
{"state": "client.index", "icon": "person"}, {"state": "client.index", "icon": "person"},
{"state": "client.extendedList", "icon": "person"},
{"state": "client.notification", "icon": "campaign"}, {"state": "client.notification", "icon": "campaign"},
{"state": "client.defaulter", "icon": "icon-defaulter"} {"state": "client.defaulter", "icon": "icon-defaulter"}
], ],
@ -381,6 +382,12 @@
"component": "vn-client-unpaid", "component": "vn-client-unpaid",
"acl": ["administrative"], "acl": ["administrative"],
"description": "Unpaid" "description": "Unpaid"
},
{
"url": "/extended-list",
"state": "client.extendedList",
"component": "vn-client-extended-list",
"description": "Extended list"
} }
] ]
} }

View File

@ -1,7 +1,7 @@
Client id: Id cliente Client id: Id cliente
Tax number: NIF/CIF Tax number: NIF/CIF
Name: Nombre Name: Nombre
Social name: Razon social Social name: Razón social
Town/City: Ciudad Town/City: Ciudad
Postcode: Código postal Postcode: Código postal
Email: E-mail Email: E-mail

View File

@ -98,9 +98,6 @@ module.exports = Self => {
Self.latestBuysFilter = async(ctx, filter, options) => { Self.latestBuysFilter = async(ctx, filter, options) => {
const myOptions = {}; const myOptions = {};
if (filter && filter.modelParams)
ctx.args = filter.modelParams;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);

View File

@ -148,12 +148,12 @@
</td> </td>
<td number> <td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 2}"> <vn-chip class="transparent" translate-attr="buy.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 2}">
<span translate>{{::buy.packing | dashIfEmpty}}</span> <span>{{::buy.packing | dashIfEmpty}}</span>
</vn-chip> </vn-chip>
</td> </td>
<td number> <td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 1 ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 1}"> <vn-chip class="transparent" translate-attr="buy.groupingMode == 1 ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 1}">
<span translate>{{::buy.grouping | dashIfEmpty}}</span> <span>{{::buy.grouping | dashIfEmpty}}</span>
</vn-chip> </vn-chip>
</td> </td>
<td number>{{::buy.quantity}}</td> <td number>{{::buy.quantity}}</td>

View File

@ -159,8 +159,22 @@ export default class Controller extends Section {
lines: rowsToEdit lines: rowsToEdit
}; };
if (this.checkedDummyCount && this.checkedDummyCount > 0) if (this.checkedDummyCount && this.checkedDummyCount > 0) {
data.filter = this.$.model.userParams; const params = {};
if (this.$.model.userParams) {
const userParams = this.$.model.userParams;
for (let param in userParams) {
let newParam = this.exprBuilder(param, userParams[param]);
if (!newParam)
newParam = {[param]: userParams[param]};
Object.assign(params, newParam);
}
}
if (this.$.model.userFilter)
Object.assign(params, this.$.model.userFilter.where);
data.filter = params;
}
return this.$http.post('Buys/editLatestBuys', data) return this.$http.post('Buys/editLatestBuys', data)
.then(() => { .then(() => {

View File

@ -76,6 +76,13 @@
translate> translate>
Show CITES letter Show CITES letter
</vn-item> </vn-item>
<vn-item
ng-click="refundConfirmation.show()"
name="refundInvoice"
vn-tooltip="Create a single ticket with all the content of the current invoice"
translate>
Refund
</vn-item>
</vn-list> </vn-list>
</vn-menu> </vn-menu>
<vn-confirm <vn-confirm
@ -88,6 +95,11 @@
on-accept="$ctrl.bookInvoiceOut()" on-accept="$ctrl.bookInvoiceOut()"
question="Are you sure you want to book this invoice?"> question="Are you sure you want to book this invoice?">
</vn-confirm> </vn-confirm>
<vn-confirm
vn-id="refundConfirmation"
on-accept="$ctrl.refundInvoiceOut()"
question="Are you sure you want to refund this invoice?">
</vn-confirm>
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="clientDescriptor">
</vn-client-descriptor-popover> </vn-client-descriptor-popover>

View File

@ -116,6 +116,35 @@ class Controller extends Section {
invoiceId: this.id invoiceId: this.id
}); });
} }
async refundInvoiceOut() {
let filter = {
where: {refFk: this.invoiceOut.ref}
};
const tickets = await this.$http.get('Tickets', {filter});
this.tickets = tickets.data;
this.ticketsIds = [];
for (let ticket of this.tickets)
this.ticketsIds.push(ticket.id);
filter = {
where: {ticketFk: {inq: this.ticketsIds}}
};
const sales = await this.$http.get('Sales', {filter});
this.sales = sales.data;
const ticketServices = await this.$http.get('TicketServices', {filter});
this.services = ticketServices.data;
const params = {
sales: this.sales,
services: this.services
};
const query = `Sales/refund`;
return this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});
});
}
} }
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];

View File

@ -122,4 +122,34 @@ describe('vnInvoiceOutDescriptorMenu', () => {
expect(controller.vnApp.showMessage).toHaveBeenCalled(); expect(controller.vnApp.showMessage).toHaveBeenCalled();
}); });
}); });
// #4084 review with Juan
xdescribe('refundInvoiceOut()', () => {
it('should make a query and go to ticket.card.sale', () => {
controller.$state.go = jest.fn();
const invoiceOut = {
id: 1,
ref: 'T1111111'
};
controller.invoiceOut = invoiceOut;
const tickets = [{id: 1}];
const sales = [{id: 1}];
const services = [{id: 2}];
$httpBackend.expectGET(`Tickets`).respond(tickets);
$httpBackend.expectGET(`Sales`).respond(sales);
$httpBackend.expectGET(`TicketServices`).respond(services);
const expectedParams = {
sales: sales,
services: services
};
$httpBackend.expectPOST(`Sales/refund`, expectedParams).respond();
controller.refundInvoiceOut();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined});
});
});
}); });

View File

@ -12,6 +12,8 @@ Are you sure you want to delete this invoice?: Estas seguro de eliminar esta fac
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura? Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?
InvoiceOut booked: Factura asentada InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura? Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Are you sure you want to refund this invoice?: Estas seguro de querer abonar esta factura?
Create a single ticket with all the content of the current invoice: Crear un ticket unico con todo el contenido de la factura actual
Regenerate PDF invoice: Regenerar PDF factura Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
The email can't be empty: El correo no puede estar vacío The email can't be empty: El correo no puede estar vacío

View File

@ -21,8 +21,11 @@
"life": { "life": {
"type": "number" "type": "number"
}, },
"isPackaging": { "promo": {
"type": "boolean" "type": "number"
},
"isUnconventionalSize": {
"type": "number"
} }
}, },
"relations": { "relations": {
@ -40,6 +43,16 @@
"type": "belongsTo", "type": "belongsTo",
"model": "ItemCategory", "model": "ItemCategory",
"foreignKey": "categoryFk" "foreignKey": "categoryFk"
},
"itemPackingType": {
"type": "belongsTo",
"model": "ItemPackingType",
"foreignKey": "itemPackingTypeFk"
},
"temperature": {
"type": "belongsTo",
"model": "Temperature",
"foreignKey": "temperatureFk"
} }
}, },
"acls": [ "acls": [

View File

@ -140,6 +140,9 @@
}, },
"isFloramondo": { "isFloramondo": {
"type": "boolean" "type": "boolean"
},
"packingShelve": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -23,4 +23,4 @@ import './waste/index/';
import './waste/detail'; import './waste/detail';
import './fixed-price'; import './fixed-price';
import './fixed-price-search-panel'; import './fixed-price-search-panel';
import './item-type';

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