Merge branch 'dev' into 3653-clientInsurance
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
de0f7c98fa
|
@ -1,7 +1,6 @@
|
|||
const axios = require('axios');
|
||||
module.exports = Self => {
|
||||
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',
|
||||
accepts: [{
|
||||
arg: 'to',
|
||||
|
@ -31,39 +30,19 @@ module.exports = Self => {
|
|||
const recipient = to.replace('@', '');
|
||||
|
||||
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 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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
const axios = require('axios');
|
||||
|
||||
module.exports = Self => {
|
||||
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',
|
||||
accepts: [{
|
||||
arg: 'workerId',
|
||||
|
@ -36,6 +34,7 @@ module.exports = Self => {
|
|||
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const sender = await models.Account.findById(userId);
|
||||
const recipient = await models.Account.findById(recipientId, null, myOptions);
|
||||
|
||||
// Prevent sending messages to yourself
|
||||
|
@ -44,54 +43,16 @@ module.exports = Self => {
|
|||
if (!recipient)
|
||||
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
|
||||
|
||||
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(recipientId, {
|
||||
include: {
|
||||
relation: 'department'
|
||||
}
|
||||
}, myOptions);
|
||||
const department = workerDepartment && workerDepartment.department();
|
||||
const channelName = department && department.chatName;
|
||||
await models.Chat.create({
|
||||
senderFk: sender.id,
|
||||
recipient: `@${recipient.name}`,
|
||||
dated: new Date(),
|
||||
checkUserStatus: 1,
|
||||
message: message,
|
||||
status: 0,
|
||||
attempts: 0
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
};
|
|
@ -1,14 +1,14 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
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 response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
|
||||
|
||||
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 response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
|
||||
|
||||
|
|
|
@ -1,58 +1,21 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Chat sendCheckingPresence()', () => {
|
||||
const today = new Date();
|
||||
today.setHours(6, 0);
|
||||
const ctx = {req: {accessToken: {userId: 1}}};
|
||||
const chatModel = models.Chat;
|
||||
const departmentId = 23;
|
||||
const workerId = 1107;
|
||||
it('should return true as response', async() => {
|
||||
const workerId = 1107;
|
||||
|
||||
it(`should call to send() method with "@HankPym" as recipient argument`, async() => {
|
||||
spyOn(chatModel, 'send').and.callThrough();
|
||||
spyOn(chatModel, 'getUserStatus').and.returnValue(
|
||||
new Promise(resolve => {
|
||||
return resolve({
|
||||
data: {
|
||||
status: 'online'
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
let ctx = {req: {accessToken: {userId: 1}}};
|
||||
let response = await models.Chat.sendCheckingPresence(ctx, workerId, 'I changed something');
|
||||
|
||||
await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something');
|
||||
|
||||
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', 'I changed something');
|
||||
expect(response).toEqual(true);
|
||||
});
|
||||
|
||||
it(`should call to send() method with "#cooler" as recipient argument`, async() => {
|
||||
spyOn(chatModel, 'send').and.callThrough();
|
||||
spyOn(chatModel, 'getUserStatus').and.returnValue(
|
||||
new Promise(resolve => {
|
||||
return resolve({
|
||||
data: {
|
||||
status: 'offline'
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
it('should return false as response', async() => {
|
||||
const salesPersonId = 18;
|
||||
|
||||
const tx = await models.Claim.beginTransaction({});
|
||||
let ctx = {req: {accessToken: {userId: 18}}};
|
||||
let response = await models.Chat.sendCheckingPresence(ctx, salesPersonId, 'I changed something');
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
expect(response).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -19,90 +19,186 @@ module.exports = Self => {
|
|||
Self.updateData = async() => {
|
||||
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 tempPath = path.join(container.client.root, container.name);
|
||||
|
||||
// Temporary file clean
|
||||
await fs.rmdir(`${tempPath}/*`, {recursive: true});
|
||||
|
||||
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 tempDir;
|
||||
let tempFile;
|
||||
for (const file of files) {
|
||||
|
||||
const fileNames = updatableFiles.map(file => file.name);
|
||||
|
||||
const tables = await Self.rawSql(`
|
||||
SELECT fileName, toTable, file
|
||||
FROM edi.tableConfig
|
||||
WHERE file IN (?)`, [fileNames]);
|
||||
|
||||
for (const table of tables) {
|
||||
const fileName = table.file;
|
||||
|
||||
console.debug(`Downloading file ${fileName}...`);
|
||||
|
||||
remoteFile = `codes/${fileName}.ZIP`;
|
||||
tempDir = `${tempPath}/${fileName}`;
|
||||
tempFile = `${tempPath}/${fileName}.zip`;
|
||||
|
||||
try {
|
||||
const fileName = file.file;
|
||||
|
||||
console.debug(`Downloading file ${fileName}...`);
|
||||
|
||||
remoteFile = `codes/${fileName}.ZIP`;
|
||||
tempDir = `${tempPath}/${fileName}`;
|
||||
tempFile = `${tempPath}/${fileName}.zip`;
|
||||
|
||||
// if (fs.existsSync(tempFile))
|
||||
// await fs.unlink(tempFile);
|
||||
|
||||
// if (fs.existsSync(tempDir))
|
||||
// await fs.rmdir(tempDir, {recursive: true});
|
||||
|
||||
await extractFile({
|
||||
ftpClient: ftpClient,
|
||||
file: file,
|
||||
paths: {
|
||||
remoteFile: remoteFile,
|
||||
tempDir: tempDir,
|
||||
tempFile: tempFile
|
||||
}
|
||||
});
|
||||
await fs.readFile(tempFile);
|
||||
} catch (error) {
|
||||
if (fs.existsSync(tempFile))
|
||||
await fs.unlink(tempFile);
|
||||
|
||||
console.error(error);
|
||||
if (error.code === 'ENOENT') {
|
||||
const downloadOutput = await downloadFile(remoteFile, tempFile);
|
||||
if (downloadOutput.error)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
async function extractFile({ftpClient, file, paths}) {
|
||||
// Download the zip file
|
||||
ftpClient.get(paths.remoteFile, paths.tempFile);
|
||||
let ftpClient;
|
||||
async function getFtpClient() {
|
||||
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
|
||||
ftpClient.exec(async(err, response) => {
|
||||
if (response.error) {
|
||||
console.debug(`Error downloading file... ${response.error}`);
|
||||
return;
|
||||
}
|
||||
const FtpClient = require('ftps');
|
||||
|
||||
const AdmZip = require('adm-zip');
|
||||
const zip = new AdmZip(paths.tempFile);
|
||||
const entries = zip.getEntries();
|
||||
ftpClient = new FtpClient({
|
||||
host: ftpConfig.host,
|
||||
username: ftpConfig.user,
|
||||
password: ftpConfig.password,
|
||||
procotol: 'ftp'
|
||||
});
|
||||
}
|
||||
|
||||
zip.extractAllTo(paths.tempDir, false);
|
||||
return ftpClient;
|
||||
}
|
||||
|
||||
await dumpData({file, entries, paths});
|
||||
async function getChecksum(file) {
|
||||
const ftpClient = await getFtpClient();
|
||||
console.debug(`Checking checksum for file ${file.name}...`);
|
||||
|
||||
await fs.rmdir(paths.tempDir, {recursive: true});
|
||||
ftpClient.cat(`codes/${file.name}.txt`);
|
||||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
ftpClient.exec((err, response) => {
|
||||
if (response.error) {
|
||||
console.debug(`Error downloading checksum file... ${response.error}`);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
if (response && response.data) {
|
||||
const fileContents = response.data;
|
||||
const rows = fileContents.split('\n');
|
||||
const row = rows[4];
|
||||
const columns = row.split(/\s+/);
|
||||
|
||||
let fileChecksum;
|
||||
if (file.keyValue)
|
||||
fileChecksum = columns[1];
|
||||
|
||||
if (!file.keyValue)
|
||||
fileChecksum = columns[0];
|
||||
|
||||
return fileChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(remoteFile, tempFile) {
|
||||
const ftpClient = await getFtpClient();
|
||||
|
||||
ftpClient.get(remoteFile, tempFile);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ftpClient.exec((err, response) => {
|
||||
if (response.error) {
|
||||
console.debug(`Error downloading file... ${response.error}`);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function dumpData({file, entries, paths}) {
|
||||
const toTable = file.toTable;
|
||||
const baseName = file.fileName;
|
||||
async function extractFile(tempFile, tempDir) {
|
||||
const JSZip = require('jszip');
|
||||
|
||||
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({});
|
||||
|
||||
|
@ -112,44 +208,22 @@ module.exports = Self => {
|
|||
const tableName = `edi.${toTable}`;
|
||||
await Self.rawSql(`DELETE FROM ??`, [tableName], options);
|
||||
|
||||
for (const zipEntry of entries) {
|
||||
const entryName = zipEntry.entryName;
|
||||
console.log(`Reading file ${entryName}...`);
|
||||
const dirFiles = await fs.readdir(tempDir);
|
||||
const files = dirFiles.filter(file => file.startsWith(baseName));
|
||||
|
||||
const startIndex = (entryName.length - 10);
|
||||
const endIndex = (entryName.length - 4);
|
||||
const dateString = entryName.substring(startIndex, endIndex);
|
||||
const lastUpdated = new Date();
|
||||
for (const file of files) {
|
||||
console.log(`Dumping data from file ${file}...`);
|
||||
|
||||
// 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)}`);
|
||||
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1);
|
||||
lastUpdated.setDate(dateString.substring(0, 2));
|
||||
lastUpdated.setHours(0, 0, 0, 0);
|
||||
|
||||
if (updated && lastUpdated <= updated) {
|
||||
console.debug(`Table ${toTable} already updated, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('Dumping data...');
|
||||
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
|
||||
const sqlTemplate = fs.readFileSync(templatePath, 'utf8');
|
||||
const sqlTemplate = await fs.readFile(templatePath, 'utf8');
|
||||
const filePath = path.join(tempDir, file);
|
||||
|
||||
const rawPath = path.join(paths.tempDir, entryName);
|
||||
|
||||
await Self.rawSql(sqlTemplate, [rawPath], options);
|
||||
await Self.rawSql(sqlTemplate, [filePath], options);
|
||||
await Self.rawSql(`
|
||||
UPDATE edi.fileConfig
|
||||
SET updated = ?
|
||||
WHERE fileName = ?
|
||||
`, [lastUpdated, baseName], options);
|
||||
UPDATE edi.tableConfig
|
||||
SET updated = ?
|
||||
WHERE fileName = ?
|
||||
`, [new Date(), baseName], options);
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
|
|
|
@ -3,4 +3,5 @@ module.exports = Self => {
|
|||
require('../methods/chat/send')(Self);
|
||||
require('../methods/chat/sendCheckingPresence')(Self);
|
||||
require('../methods/chat/notifyIssues')(Self);
|
||||
require('../methods/chat/sendQueued')(Self);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,39 @@
|
|||
{
|
||||
"name": "Chat",
|
||||
"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": [{
|
||||
"property": "validations",
|
||||
"accessType": "EXECUTE",
|
||||
|
|
|
@ -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;
|
|
@ -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}');
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
DROP PROCEDURE IF EXISTS vn.ticket_doRefund;
|
||||
DROP PROCEDURE IF EXISTS `vn`.`ticket_doRefund`;
|
||||
|
||||
DELIMITER $$
|
||||
$$
|
|
@ -0,0 +1 @@
|
|||
RENAME TABLE `edi`.`fileConfig` to `edi`.`tableConfig`;
|
|
@ -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);
|
||||
|
|
@ -2558,7 +2558,13 @@ INSERT INTO `vn`.`supplierAgencyTerm` (`agencyFk`, `supplierFk`, `minimumPackage
|
|||
(2, 1, 60, 0.00, 0.00, NULL, 0, 5.00, 33),
|
||||
(3, 2, 0, 15.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
|
||||
|
|
|
@ -318,6 +318,8 @@ export default class SmartTable extends Component {
|
|||
for (let column of columns) {
|
||||
const field = column.getAttribute('field');
|
||||
const cell = document.createElement('td');
|
||||
cell.setAttribute('centered', '');
|
||||
|
||||
if (field) {
|
||||
let input;
|
||||
let options;
|
||||
|
@ -331,6 +333,15 @@ export default class SmartTable extends Component {
|
|||
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) {
|
||||
let props = ``;
|
||||
|
||||
|
@ -346,16 +357,29 @@ export default class SmartTable extends Component {
|
|||
on-change="$ctrl.searchByColumn('${field}')"
|
||||
clear-disabled="true"
|
||||
/>`)(this.$inputsScope);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (options && options.checkbox) {
|
||||
input = this.$compile(`
|
||||
<vn-textfield
|
||||
<vn-check
|
||||
class="dense"
|
||||
name="${field}"
|
||||
ng-model="searchProps['${field}']"
|
||||
ng-keydown="$ctrl.searchWithEvent($event, '${field}')"
|
||||
clear-disabled="true"
|
||||
on-change="$ctrl.searchByColumn('${field}')"
|
||||
triple-state="true"
|
||||
/>`)(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]);
|
||||
}
|
||||
searchRow.appendChild(cell);
|
||||
|
@ -372,13 +396,12 @@ export default class SmartTable extends Component {
|
|||
|
||||
searchByColumn(field) {
|
||||
const searchCriteria = this.$inputsScope.searchProps[field];
|
||||
const emptySearch = searchCriteria == '' || null;
|
||||
const emptySearch = searchCriteria === '' || searchCriteria == null;
|
||||
|
||||
const filters = this.filterSanitizer(field);
|
||||
|
||||
if (filters && filters.userFilter)
|
||||
this.model.userFilter = filters.userFilter;
|
||||
|
||||
if (!emptySearch)
|
||||
this.addFilter(field, this.$inputsScope.searchProps[field]);
|
||||
else this.model.refresh();
|
||||
|
|
|
@ -85,7 +85,6 @@
|
|||
}
|
||||
.icon-bucket:before {
|
||||
content: "\e97a";
|
||||
color: #000;
|
||||
}
|
||||
.icon-buscaman:before {
|
||||
content: "\e93b";
|
||||
|
@ -95,32 +94,26 @@
|
|||
}
|
||||
.icon-calc_volum .path1:before {
|
||||
content: "\e915";
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
.icon-calc_volum .path2:before {
|
||||
content: "\e916";
|
||||
margin-left: -1em;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
.icon-calc_volum .path3:before {
|
||||
content: "\e917";
|
||||
margin-left: -1em;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
.icon-calc_volum .path4:before {
|
||||
content: "\e918";
|
||||
margin-left: -1em;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
.icon-calc_volum .path5:before {
|
||||
content: "\e919";
|
||||
margin-left: -1em;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
.icon-calc_volum .path6:before {
|
||||
content: "\e91a";
|
||||
margin-left: -1em;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
.icon-calendar:before {
|
||||
content: "\e93d";
|
||||
|
|
|
@ -224,5 +224,7 @@
|
|||
"The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo",
|
||||
"date in the future": "Fecha en el futuro",
|
||||
"reference duplicated": "Referencia duplicada",
|
||||
"This ticket is already a refund": "Este ticket ya es un abono"
|
||||
"This ticket is already a refund": "Este ticket ya es un abono",
|
||||
"isWithoutNegatives": "isWithoutNegatives",
|
||||
"routeFk": "routeFk"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
<vn-crud-model vn-id="model"
|
||||
url="ClaimDms"
|
||||
filter="::$ctrl.filter"
|
||||
data="photos">
|
||||
</vn-crud-model>
|
||||
<vn-card class="summary">
|
||||
|
@ -106,8 +107,13 @@
|
|||
<section class="photo" ng-repeat="photo in photos">
|
||||
<section class="image" on-error-src
|
||||
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>
|
||||
<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>
|
||||
</vn-horizontal>
|
||||
</vn-auto>
|
||||
|
|
|
@ -6,6 +6,13 @@ class Controller extends Summary {
|
|||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
this.filter = {
|
||||
include: [
|
||||
{
|
||||
relation: 'dms'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
$onChanges() {
|
||||
|
|
|
@ -10,4 +10,19 @@ vn-claim-summary {
|
|||
vn-textarea *{
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,21 +1,39 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
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() => {
|
||||
const clientId = 1108;
|
||||
const salesPersonId = 18;
|
||||
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const expectedResult = 841.63;
|
||||
|
||||
const clientQuery = `UPDATE vn.client SET salesPersonFk = ${salesPersonId} WHERE id = ${clientId}; `;
|
||||
await models.Client.rawSql(clientQuery);
|
||||
const client = await models.Client.findById(clientId, null, options);
|
||||
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 [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() => {
|
||||
pending('task 3817');
|
||||
const clientId = 1107;
|
||||
const salesPersonId = 19;
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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()`;
|
||||
return await Self.rawSql(query);
|
||||
return Self.rawSql(query, null, myOptions);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -31,6 +31,7 @@ module.exports = Self => {
|
|||
require('../methods/client/createReceipt')(Self);
|
||||
require('../methods/client/updatePortfolio')(Self);
|
||||
require('../methods/client/checkDuplicated')(Self);
|
||||
require('../methods/client/extendedListFilter')(Self);
|
||||
|
||||
// Validations
|
||||
|
||||
|
|
|
@ -136,6 +136,9 @@
|
|||
"mysql": {
|
||||
"columnName": "businessTypeFk"
|
||||
}
|
||||
},
|
||||
"salesPersonFk": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -54,14 +54,7 @@
|
|||
show-field="bic"
|
||||
vn-acl="salesAssistant"
|
||||
disabled="$ctrl.ibanCountry == 'ES'">
|
||||
<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>
|
||||
<tpl-item>{{bic}} {{name}}</tpl-item>
|
||||
<append>
|
||||
<vn-icon-button
|
||||
vn-auto
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Mailing: Env. emails
|
||||
Sage tr. type: Tipo tr. sage
|
||||
Yes: Sí
|
|
@ -0,0 +1,6 @@
|
|||
@import "variables";
|
||||
|
||||
vn-chip.success,
|
||||
vn-chip.alert {
|
||||
color: $color-font-bg
|
||||
}
|
|
@ -47,3 +47,4 @@ import './consumption-search-panel';
|
|||
import './defaulter';
|
||||
import './notification';
|
||||
import './unpaid';
|
||||
import './extended-list';
|
||||
|
|
|
@ -33,6 +33,7 @@ Search client by id or name: Buscar clientes por identificador o nombre
|
|||
# Sections
|
||||
|
||||
Clients: Clientes
|
||||
Extended list: Listado extendido
|
||||
Defaulter: Morosos
|
||||
New client: Nuevo cliente
|
||||
Fiscal data: Datos fiscales
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"menus": {
|
||||
"main": [
|
||||
{"state": "client.index", "icon": "person"},
|
||||
{"state": "client.extendedList", "icon": "person"},
|
||||
{"state": "client.notification", "icon": "campaign"},
|
||||
{"state": "client.defaulter", "icon": "icon-defaulter"}
|
||||
],
|
||||
|
@ -381,6 +382,12 @@
|
|||
"component": "vn-client-unpaid",
|
||||
"acl": ["administrative"],
|
||||
"description": "Unpaid"
|
||||
},
|
||||
{
|
||||
"url": "/extended-list",
|
||||
"state": "client.extendedList",
|
||||
"component": "vn-client-extended-list",
|
||||
"description": "Extended list"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Client id: Id cliente
|
||||
Tax number: NIF/CIF
|
||||
Name: Nombre
|
||||
Social name: Razon social
|
||||
Social name: Razón social
|
||||
Town/City: Ciudad
|
||||
Postcode: Código postal
|
||||
Email: E-mail
|
||||
|
|
|
@ -53,7 +53,7 @@ export default class Controller extends Section {
|
|||
},
|
||||
{
|
||||
field: 'shippedDate',
|
||||
searchable: false
|
||||
datepicker: true
|
||||
},
|
||||
{
|
||||
field: 'theoreticalHour',
|
||||
|
|
|
@ -25,8 +25,13 @@
|
|||
label="Bank entity"
|
||||
ng-model="supplierAccount.bankEntityFk"
|
||||
url="BankEntities"
|
||||
show-field="name"
|
||||
fields="['name']"
|
||||
initial-data="supplierAccount.bankEntityFk"
|
||||
search-function="{or: [{bic: {like: $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
|
||||
value-field="id"
|
||||
show-field="bic"
|
||||
rule>
|
||||
<tpl-item>{{bic}} {{name}}</tpl-item>
|
||||
<append>
|
||||
<vn-icon-button
|
||||
vn-auto
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
"node": ">=14"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "^0.25.0",
|
||||
"bmp-js": "^0.1.0",
|
||||
"compression": "^1.7.3",
|
||||
|
@ -23,6 +22,7 @@
|
|||
"i18n": "^0.8.4",
|
||||
"image-type": "^4.1.0",
|
||||
"imap": "^0.8.19",
|
||||
"jszip": "^3.10.0",
|
||||
"ldapjs": "^2.2.0",
|
||||
"loopback": "^3.26.0",
|
||||
"loopback-boot": "3.3.1",
|
||||
|
|
Loading…
Reference in New Issue